@memberjunction/ng-dashboards 6.2.0-edge.3 → 6.2.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts +4 -3
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts.map +1 -1
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js +8 -5
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js.map +1 -1
- package/dist/AI/components/duplicates/compare-value.component.d.ts +67 -0
- package/dist/AI/components/duplicates/compare-value.component.d.ts.map +1 -0
- package/dist/AI/components/duplicates/compare-value.component.js +168 -0
- package/dist/AI/components/duplicates/compare-value.component.js.map +1 -0
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +49 -3
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +287 -169
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts +77 -0
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts.map +1 -0
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.js +116 -0
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.js.map +1 -0
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts +1 -0
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts.map +1 -1
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js +16 -4
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js.map +1 -1
- package/dist/AI/components/tags/tags-resource.component.d.ts +2 -0
- package/dist/AI/components/tags/tags-resource.component.d.ts.map +1 -1
- package/dist/AI/components/tags/tags-resource.component.js +12 -6
- package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
- package/dist/AI/components/vectors/vector-index-auto.d.ts +29 -0
- package/dist/AI/components/vectors/vector-index-auto.d.ts.map +1 -0
- package/dist/AI/components/vectors/vector-index-auto.js +59 -0
- package/dist/AI/components/vectors/vector-index-auto.js.map +1 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts +9 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
- package/dist/AI/components/vectors/vector-management-resource.component.js +65 -20
- package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
- package/dist/Actions/components/actions-overview.component.d.ts.map +1 -1
- package/dist/Actions/components/actions-overview.component.js +7 -3
- package/dist/Actions/components/actions-overview.component.js.map +1 -1
- package/dist/Actions/components/categories-list-view.component.d.ts.map +1 -1
- package/dist/Actions/components/categories-list-view.component.js +3 -2
- package/dist/Actions/components/categories-list-view.component.js.map +1 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.d.ts.map +1 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.js +1 -0
- package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts +0 -3
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js +5 -14
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js.map +1 -1
- package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/artifact-load-dialog.component.js +1 -0
- package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
- package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/artifact-selection-dialog.component.js +1 -0
- package/dist/ComponentStudio/components/artifact-selection-dialog.component.js.map +1 -1
- package/dist/Credentials/components/credentials-list-resource.component.d.ts +2 -0
- package/dist/Credentials/components/credentials-list-resource.component.d.ts.map +1 -1
- package/dist/Credentials/components/credentials-list-resource.component.js +12 -10
- package/dist/Credentials/components/credentials-list-resource.component.js.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts +14 -43
- package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-agent-context.js +6 -29
- package/dist/DashboardBrowser/dashboard-browser-agent-context.js.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts +174 -321
- package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js +522 -1195
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js.map +1 -1
- package/dist/DashboardBrowser/dashboard-library-filter.d.ts +116 -0
- package/dist/DashboardBrowser/dashboard-library-filter.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboard-library-filter.js +277 -0
- package/dist/DashboardBrowser/dashboard-library-filter.js.map +1 -0
- package/dist/DashboardBrowser/dashboard-share-dialog.component.d.ts +1 -1
- package/dist/DashboardBrowser/dashboard-share-dialog.component.js.map +1 -1
- package/dist/DashboardBrowser/dashboards-agent-tools.d.ts +26 -0
- package/dist/DashboardBrowser/dashboards-agent-tools.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboards-agent-tools.js +32 -0
- package/dist/DashboardBrowser/dashboards-agent-tools.js.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts +58 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.js +391 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.js.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts +37 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.js +68 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.js.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.component.d.ts +295 -0
- package/dist/DashboardEditor/dashboard-editor.component.d.ts.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.component.js +1022 -0
- package/dist/DashboardEditor/dashboard-editor.component.js.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.types.d.ts +23 -0
- package/dist/DashboardEditor/dashboard-editor.types.d.ts.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.types.js +3 -0
- package/dist/DashboardEditor/dashboard-editor.types.js.map +1 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.d.ts +11 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.d.ts.map +1 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.js +17 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.js.map +1 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +26 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +302 -219
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/DataExplorer/services/explorer-state.service.d.ts +1 -1
- package/dist/DataExplorer/services/explorer-state.service.js +2 -5
- package/dist/DataExplorer/services/explorer-state.service.js.map +1 -1
- package/dist/FormBuilder/form-builder-resource.component.d.ts +0 -14
- package/dist/FormBuilder/form-builder-resource.component.d.ts.map +1 -1
- package/dist/FormBuilder/form-builder-resource.component.js +7 -29
- package/dist/FormBuilder/form-builder-resource.component.js.map +1 -1
- package/dist/Home/home-agent-context.d.ts +29 -10
- package/dist/Home/home-agent-context.d.ts.map +1 -1
- package/dist/Home/home-agent-context.js +50 -24
- package/dist/Home/home-agent-context.js.map +1 -1
- package/dist/Home/home-dashboard-switcher.component.d.ts +95 -0
- package/dist/Home/home-dashboard-switcher.component.d.ts.map +1 -0
- package/dist/Home/home-dashboard-switcher.component.js +442 -0
- package/dist/Home/home-dashboard-switcher.component.js.map +1 -0
- package/dist/Home/home-dashboard.component.d.ts +330 -17
- package/dist/Home/home-dashboard.component.d.ts.map +1 -1
- package/dist/Home/home-dashboard.component.js +1702 -1014
- package/dist/Home/home-dashboard.component.js.map +1 -1
- package/dist/Home/home-menus.d.ts +38 -0
- package/dist/Home/home-menus.d.ts.map +1 -0
- package/dist/Home/home-menus.js +53 -0
- package/dist/Home/home-menus.js.map +1 -0
- package/dist/Home/home-pinned-dashboards.d.ts +33 -0
- package/dist/Home/home-pinned-dashboards.d.ts.map +1 -0
- package/dist/Home/home-pinned-dashboards.js +76 -0
- package/dist/Home/home-pinned-dashboards.js.map +1 -0
- package/dist/PredictiveStudio/components/ps-production.component.d.ts +5 -1
- package/dist/PredictiveStudio/components/ps-production.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/components/ps-production.component.js +64 -41
- package/dist/PredictiveStudio/components/ps-production.component.js.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.js +3 -2
- package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
- package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js +2 -2
- package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js.map +1 -1
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts +1 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts.map +1 -1
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js +7 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js.map +1 -1
- package/dist/Testing/components/testing-explorer.component.d.ts.map +1 -1
- package/dist/Testing/components/testing-explorer.component.js +7 -7
- package/dist/Testing/components/testing-explorer.component.js.map +1 -1
- package/dist/ai-dashboards.module.d.ts +21 -20
- package/dist/ai-dashboards.module.d.ts.map +1 -1
- package/dist/ai-dashboards.module.js +4 -1
- package/dist/ai-dashboards.module.js.map +1 -1
- package/dist/core-dashboards.module.d.ts +69 -62
- package/dist/core-dashboards.module.d.ts.map +1 -1
- package/dist/core-dashboards.module.js +22 -2
- package/dist/core-dashboards.module.js.map +1 -1
- package/dist/data-explorer-dashboards.module.d.ts +1 -1
- package/dist/data-explorer-dashboards.module.d.ts.map +1 -1
- package/dist/data-explorer-dashboards.module.js +3 -1
- package/dist/data-explorer-dashboards.module.js.map +1 -1
- package/dist/public-api.d.ts +2 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +3 -0
- package/dist/public-api.js.map +1 -1
- package/dist/shared/dashboard-library-changes.d.ts +23 -0
- package/dist/shared/dashboard-library-changes.d.ts.map +1 -0
- package/dist/shared/dashboard-library-changes.js +26 -0
- package/dist/shared/dashboard-library-changes.js.map +1 -0
- package/dist/shared/dashboard-owner-names.d.ts +17 -0
- package/dist/shared/dashboard-owner-names.d.ts.map +1 -0
- package/dist/shared/dashboard-owner-names.js +65 -0
- package/dist/shared/dashboard-owner-names.js.map +1 -0
- package/dist/shared/dashboard-recents.d.ts +34 -0
- package/dist/shared/dashboard-recents.d.ts.map +1 -0
- package/dist/shared/dashboard-recents.js +87 -0
- package/dist/shared/dashboard-recents.js.map +1 -0
- package/dist/shared/dashboards-app-install.d.ts +59 -0
- package/dist/shared/dashboards-app-install.d.ts.map +1 -0
- package/dist/shared/dashboards-app-install.js +97 -0
- package/dist/shared/dashboards-app-install.js.map +1 -0
- package/dist/shared/dashboards-app.helpers.d.ts +42 -0
- package/dist/shared/dashboards-app.helpers.d.ts.map +1 -0
- package/dist/shared/dashboards-app.helpers.js +143 -0
- package/dist/shared/dashboards-app.helpers.js.map +1 -0
- package/package.json +69 -68
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Pure logic for the Library page of the Dashboards app: the order of its dashboards,
|
|
3
|
+
* the list each rail item shows, the empty state of each list, the rail sections and their counts,
|
|
4
|
+
* the rail item ids, and the `lib` / `category` query params.
|
|
5
|
+
*
|
|
6
|
+
* Every Library filter and Uncategorized is a flat list; only a category opens as a folder. Counts
|
|
7
|
+
* follow the same rules as `mj-dashboard-browser`, so a rail badge equals the number of dashboard
|
|
8
|
+
* cards the browser shows when that item is clicked: only Config dashboards count, and a category
|
|
9
|
+
* or Uncategorized shows the dashboards whose effective category it is.
|
|
10
|
+
*/
|
|
11
|
+
import type { MJDashboardCategoryEntity, MJDashboardEntity } from '@memberjunction/core-entities';
|
|
12
|
+
import type { MJLeftNavSection } from '@memberjunction/ng-ui-components';
|
|
13
|
+
/** The Library filters, in rail order. */
|
|
14
|
+
export declare const DASHBOARD_LIBRARY_FILTERS: readonly ["all", "mine", "shared", "favorites", "recent"];
|
|
15
|
+
/** A Library filter in the Library rail. */
|
|
16
|
+
export type DashboardLibraryFilter = (typeof DASHBOARD_LIBRARY_FILTERS)[number];
|
|
17
|
+
/** True when `value` is one of the Library filters (exact, case-sensitive). */
|
|
18
|
+
export declare function IsDashboardLibraryFilter(value: unknown): value is DashboardLibraryFilter;
|
|
19
|
+
/** What the Library filters need to know about the current user. */
|
|
20
|
+
export interface LibraryFilterContext {
|
|
21
|
+
CurrentUserId: string;
|
|
22
|
+
FavoriteIds: string[];
|
|
23
|
+
/** Recently opened dashboard ids, most recent first. */
|
|
24
|
+
RecentIds: string[];
|
|
25
|
+
}
|
|
26
|
+
/** Where the Library is: a Library filter, or (with the All filter) a category or Uncategorized. */
|
|
27
|
+
export interface BrowseLocation {
|
|
28
|
+
Filter: DashboardLibraryFilter;
|
|
29
|
+
/** The selected category, UNCATEGORIZED_CATEGORY_ID for Uncategorized, or null for none. */
|
|
30
|
+
CategoryId: string | null;
|
|
31
|
+
}
|
|
32
|
+
/** The `category` value of the Uncategorized list: the dashboards in no category. */
|
|
33
|
+
export declare const UNCATEGORIZED_CATEGORY_ID = "uncategorized";
|
|
34
|
+
/** Category counts for the rail, keyed by the category's own id. */
|
|
35
|
+
export interface CategoryCounts {
|
|
36
|
+
ByCategory: Map<string, number>;
|
|
37
|
+
Uncategorized: number;
|
|
38
|
+
}
|
|
39
|
+
/** The dashboards a Library filter keeps, in the order given. `recent` follows the recents order. */
|
|
40
|
+
export declare function FilterDashboardsForLibrary(dashboards: MJDashboardEntity[], filter: DashboardLibraryFilter, ctx: LibraryFilterContext): MJDashboardEntity[];
|
|
41
|
+
/** True for the dashboards `mj-dashboard-browser` shows: Config dashboards. */
|
|
42
|
+
export declare function IsBrowsableDashboard(dashboard: MJDashboardEntity): boolean;
|
|
43
|
+
/** For each Library filter, the number of dashboards the browser shows for it. */
|
|
44
|
+
export declare function CountLibraryDashboards(dashboards: MJDashboardEntity[], ctx: LibraryFilterContext): Record<DashboardLibraryFilter, number>;
|
|
45
|
+
/**
|
|
46
|
+
* The category a dashboard shows under. A dashboard in `effectiveCategoryMap` (a shared one) uses
|
|
47
|
+
* the mapped value, where null means the root; any other dashboard uses its own CategoryID.
|
|
48
|
+
*/
|
|
49
|
+
export declare function EffectiveCategoryId(dashboard: MJDashboardEntity, effectiveCategoryMap: Map<string, string | null>): string | null;
|
|
50
|
+
/**
|
|
51
|
+
* The Library's default order: favorites first, then the dashboards the user opened, the most recent
|
|
52
|
+
* open first, then the rest. Within each group a dashboard opened more recently comes first;
|
|
53
|
+
* dashboards never opened follow, the most recently updated first, then by name. Returns a new array.
|
|
54
|
+
*/
|
|
55
|
+
export declare function OrderLibraryDashboards(dashboards: readonly MJDashboardEntity[], ctx: LibraryFilterContext): MJDashboardEntity[];
|
|
56
|
+
/** True when a location shows one category's folder; every other location is a flat list. */
|
|
57
|
+
export declare function IsCategoryFolderLocation(location: BrowseLocation): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The dashboards the Library gives the browser for a location, in display order (see
|
|
60
|
+
* OrderLibraryDashboards; Recently opened keeps the order of the last opens). Uncategorized keeps the
|
|
61
|
+
* dashboards in no category. A category folder gets every dashboard: the browser shows the folder's own.
|
|
62
|
+
*/
|
|
63
|
+
export declare function VisibleLibraryDashboards(dashboards: readonly MJDashboardEntity[], location: BrowseLocation, ctx: LibraryFilterContext, effectiveCategoryMap: Map<string, string | null>): MJDashboardEntity[];
|
|
64
|
+
/** What an empty flat Library list shows. */
|
|
65
|
+
export interface LibraryEmptyStateText {
|
|
66
|
+
Icon: string;
|
|
67
|
+
Title: string;
|
|
68
|
+
Message: string;
|
|
69
|
+
/** True for the whole library: the browser shows its first-run welcome instead. */
|
|
70
|
+
Welcome: boolean;
|
|
71
|
+
}
|
|
72
|
+
/** The empty state of a flat Library list. */
|
|
73
|
+
export declare function LibraryEmptyState(location: BrowseLocation): LibraryEmptyStateText;
|
|
74
|
+
/**
|
|
75
|
+
* Counts the browsable dashboards directly in each category, and those at the root. A dashboard
|
|
76
|
+
* whose category is not in `categories` is not counted, as the browser cannot reach it.
|
|
77
|
+
*/
|
|
78
|
+
export declare function CountDashboardsByCategory(dashboards: MJDashboardEntity[], categories: MJDashboardCategoryEntity[], effectiveCategoryMap: Map<string, string | null>): CategoryCounts;
|
|
79
|
+
/**
|
|
80
|
+
* The rail's two sections: Dashboards (one item per Library filter) and Categories (the category
|
|
81
|
+
* tree, then Uncategorized). A category whose parent is not in `categories` shows at the top level.
|
|
82
|
+
*/
|
|
83
|
+
export declare function BuildLibraryRailSections(counts: Record<DashboardLibraryFilter, number>, categories: MJDashboardCategoryEntity[], categoryCounts: Map<string, number>, uncategorizedCount: number): MJLeftNavSection[];
|
|
84
|
+
/** The Library filter named by a `lib` query param; `all` for anything else. */
|
|
85
|
+
export declare function ParseLibraryFilter(value: string | null | undefined): DashboardLibraryFilter;
|
|
86
|
+
/**
|
|
87
|
+
* The location named by the `lib` and `category` query params, or null when they name none.
|
|
88
|
+
* A category wins over a Library filter, since a category is only shown under All.
|
|
89
|
+
*/
|
|
90
|
+
export declare function ResolveBrowseLocation(params: Record<string, string>): BrowseLocation | null;
|
|
91
|
+
/** True when two locations have the same Library filter and the same category (or both none). */
|
|
92
|
+
export declare function SameBrowseLocation(a: BrowseLocation, b: BrowseLocation): boolean;
|
|
93
|
+
/** The `lib` and `category` query params for a location. All and the root write null. */
|
|
94
|
+
export declare function LocationQueryParams(location: BrowseLocation): {
|
|
95
|
+
lib: DashboardLibraryFilter | null;
|
|
96
|
+
category: string | null;
|
|
97
|
+
};
|
|
98
|
+
/** The rail item id for a Library filter. */
|
|
99
|
+
export declare function LibraryRailItemId(filter: DashboardLibraryFilter): string;
|
|
100
|
+
/** The rail item id for a category. */
|
|
101
|
+
export declare function CategoryRailItemId(categoryId: string): string;
|
|
102
|
+
/** The location a rail item opens, or null for an id the rail never builds. */
|
|
103
|
+
export declare function ParseRailItemId(id: string): BrowseLocation | null;
|
|
104
|
+
/**
|
|
105
|
+
* The rail item to mark active: Uncategorized, or the selected category, written with the
|
|
106
|
+
* category's own id so the rail's exact id match finds it, else the Library filter.
|
|
107
|
+
*/
|
|
108
|
+
export declare function RailActiveItemId(location: BrowseLocation, categories: MJDashboardCategoryEntity[]): string;
|
|
109
|
+
/** `expandedIds` with `id` added when missing, or removed when present. */
|
|
110
|
+
export declare function ToggleExpandedId(expandedIds: string[], id: string): string[];
|
|
111
|
+
/**
|
|
112
|
+
* `expandedIds` with every folder above `categoryId` expanded, so the rail shows that category.
|
|
113
|
+
* Returns `expandedIds` itself when nothing changes.
|
|
114
|
+
*/
|
|
115
|
+
export declare function WithExpandedAncestors(expandedIds: string[], categoryId: string | null, categories: MJDashboardCategoryEntity[]): string[];
|
|
116
|
+
//# sourceMappingURL=dashboard-library-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-library-filter.d.ts","sourceRoot":"","sources":["../../src/DashboardBrowser/dashboard-library-filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { FilterByUUIDs, IndexByUUID, NormalizeUUID, UUIDsEqual } from '@memberjunction/global';
|
|
2
|
+
/** The Library filters, in rail order. */
|
|
3
|
+
export const DASHBOARD_LIBRARY_FILTERS = ['all', 'mine', 'shared', 'favorites', 'recent'];
|
|
4
|
+
/** True when `value` is one of the Library filters (exact, case-sensitive). */
|
|
5
|
+
export function IsDashboardLibraryFilter(value) {
|
|
6
|
+
return typeof value === 'string' && DASHBOARD_LIBRARY_FILTERS.includes(value);
|
|
7
|
+
}
|
|
8
|
+
/** The `category` value of the Uncategorized list: the dashboards in no category. */
|
|
9
|
+
export const UNCATEGORIZED_CATEGORY_ID = 'uncategorized';
|
|
10
|
+
const LIBRARY_RAIL_PREFIX = 'lib:';
|
|
11
|
+
const CATEGORY_RAIL_PREFIX = 'cat:';
|
|
12
|
+
const UNCATEGORIZED_RAIL_ID = 'cat:unc';
|
|
13
|
+
const LIBRARY_ITEMS = [
|
|
14
|
+
['all', 'All dashboards', 'fa-solid fa-layer-group'],
|
|
15
|
+
['mine', 'My dashboards', 'fa-solid fa-user'],
|
|
16
|
+
['shared', 'Shared with me', 'fa-solid fa-inbox'],
|
|
17
|
+
['favorites', 'Favorites', 'fa-solid fa-star'],
|
|
18
|
+
['recent', 'Recently opened', 'fa-solid fa-clock-rotate-left'],
|
|
19
|
+
];
|
|
20
|
+
/** The dashboards a Library filter keeps, in the order given. `recent` follows the recents order. */
|
|
21
|
+
export function FilterDashboardsForLibrary(dashboards, filter, ctx) {
|
|
22
|
+
switch (filter) {
|
|
23
|
+
case 'mine':
|
|
24
|
+
return dashboards.filter(d => UUIDsEqual(d.UserID, ctx.CurrentUserId));
|
|
25
|
+
case 'shared':
|
|
26
|
+
return dashboards.filter(d => !UUIDsEqual(d.UserID, ctx.CurrentUserId));
|
|
27
|
+
case 'favorites':
|
|
28
|
+
return FilterByUUIDs(dashboards, ctx.FavoriteIds);
|
|
29
|
+
case 'recent':
|
|
30
|
+
return pickInOrder(dashboards, ctx.RecentIds);
|
|
31
|
+
default:
|
|
32
|
+
return dashboards;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** The dashboards named by `ids`, in that order, each once. Unknown ids are skipped. */
|
|
36
|
+
function pickInOrder(dashboards, ids) {
|
|
37
|
+
const byID = IndexByUUID(dashboards);
|
|
38
|
+
const picked = [];
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
for (const id of ids) {
|
|
41
|
+
const hit = byID.Get(id);
|
|
42
|
+
if (hit && !seen.has(hit)) {
|
|
43
|
+
seen.add(hit);
|
|
44
|
+
picked.push(hit);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return picked;
|
|
48
|
+
}
|
|
49
|
+
/** True for the dashboards `mj-dashboard-browser` shows: Config dashboards. */
|
|
50
|
+
export function IsBrowsableDashboard(dashboard) {
|
|
51
|
+
return dashboard.Type === 'Config';
|
|
52
|
+
}
|
|
53
|
+
/** For each Library filter, the number of dashboards the browser shows for it. */
|
|
54
|
+
export function CountLibraryDashboards(dashboards, ctx) {
|
|
55
|
+
const browsable = dashboards.filter(IsBrowsableDashboard);
|
|
56
|
+
const count = (filter) => FilterDashboardsForLibrary(browsable, filter, ctx).length;
|
|
57
|
+
return { all: count('all'), mine: count('mine'), shared: count('shared'), favorites: count('favorites'), recent: count('recent') };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The category a dashboard shows under. A dashboard in `effectiveCategoryMap` (a shared one) uses
|
|
61
|
+
* the mapped value, where null means the root; any other dashboard uses its own CategoryID.
|
|
62
|
+
*/
|
|
63
|
+
export function EffectiveCategoryId(dashboard, effectiveCategoryMap) {
|
|
64
|
+
if (effectiveCategoryMap.has(dashboard.ID)) {
|
|
65
|
+
return effectiveCategoryMap.get(dashboard.ID) || null;
|
|
66
|
+
}
|
|
67
|
+
return dashboard.CategoryID || null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The Library's default order: favorites first, then the dashboards the user opened, the most recent
|
|
71
|
+
* open first, then the rest. Within each group a dashboard opened more recently comes first;
|
|
72
|
+
* dashboards never opened follow, the most recently updated first, then by name. Returns a new array.
|
|
73
|
+
*/
|
|
74
|
+
export function OrderLibraryDashboards(dashboards, ctx) {
|
|
75
|
+
const favorites = new Set(ctx.FavoriteIds.map(id => NormalizeUUID(id)));
|
|
76
|
+
const ranks = recentRanks(ctx.RecentIds);
|
|
77
|
+
return dashboards
|
|
78
|
+
.map(dashboard => ({ dashboard, key: libraryOrderKey(dashboard, favorites, ranks) }))
|
|
79
|
+
.sort((a, b) => compareLibraryOrderKeys(a.key, b.key))
|
|
80
|
+
.map(entry => entry.dashboard);
|
|
81
|
+
}
|
|
82
|
+
function libraryOrderKey(dashboard, favorites, ranks) {
|
|
83
|
+
const id = NormalizeUUID(dashboard.ID);
|
|
84
|
+
return {
|
|
85
|
+
Favorite: favorites.has(id),
|
|
86
|
+
RecentRank: ranks.get(id) ?? Number.POSITIVE_INFINITY,
|
|
87
|
+
UpdatedAt: timeOf(dashboard.__mj_UpdatedAt),
|
|
88
|
+
Name: dashboard.Name ?? '',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function compareLibraryOrderKeys(a, b) {
|
|
92
|
+
if (a.Favorite !== b.Favorite)
|
|
93
|
+
return a.Favorite ? -1 : 1;
|
|
94
|
+
if (a.RecentRank !== b.RecentRank)
|
|
95
|
+
return a.RecentRank < b.RecentRank ? -1 : 1;
|
|
96
|
+
if (a.UpdatedAt !== b.UpdatedAt)
|
|
97
|
+
return b.UpdatedAt - a.UpdatedAt;
|
|
98
|
+
return a.Name.localeCompare(b.Name);
|
|
99
|
+
}
|
|
100
|
+
/** Each recent id's first position, keyed by the normalized id. */
|
|
101
|
+
function recentRanks(recentIds) {
|
|
102
|
+
const ranks = new Map();
|
|
103
|
+
recentIds.forEach((id, index) => {
|
|
104
|
+
const key = NormalizeUUID(id);
|
|
105
|
+
if (!ranks.has(key))
|
|
106
|
+
ranks.set(key, index);
|
|
107
|
+
});
|
|
108
|
+
return ranks;
|
|
109
|
+
}
|
|
110
|
+
/** Milliseconds since the epoch, or 0 for a missing or invalid date. */
|
|
111
|
+
function timeOf(value) {
|
|
112
|
+
const time = value ? new Date(value).getTime() : Number.NaN;
|
|
113
|
+
return Number.isNaN(time) ? 0 : time;
|
|
114
|
+
}
|
|
115
|
+
/** True when a location shows one category's folder; every other location is a flat list. */
|
|
116
|
+
export function IsCategoryFolderLocation(location) {
|
|
117
|
+
return !!location.CategoryId && location.CategoryId !== UNCATEGORIZED_CATEGORY_ID;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The dashboards the Library gives the browser for a location, in display order (see
|
|
121
|
+
* OrderLibraryDashboards; Recently opened keeps the order of the last opens). Uncategorized keeps the
|
|
122
|
+
* dashboards in no category. A category folder gets every dashboard: the browser shows the folder's own.
|
|
123
|
+
*/
|
|
124
|
+
export function VisibleLibraryDashboards(dashboards, location, ctx, effectiveCategoryMap) {
|
|
125
|
+
const ordered = OrderLibraryDashboards(dashboards, ctx);
|
|
126
|
+
if (location.CategoryId === UNCATEGORIZED_CATEGORY_ID) {
|
|
127
|
+
return ordered.filter(d => !EffectiveCategoryId(d, effectiveCategoryMap));
|
|
128
|
+
}
|
|
129
|
+
return FilterDashboardsForLibrary(ordered, location.Filter, ctx);
|
|
130
|
+
}
|
|
131
|
+
const LIBRARY_EMPTY_STATES = {
|
|
132
|
+
all: { Icon: 'fa-solid fa-gauge-high', Title: 'Welcome to Dashboards', Message: '', Welcome: true },
|
|
133
|
+
mine: { Icon: 'fa-solid fa-user', Title: 'You have no dashboards yet', Message: 'Create one with New.', Welcome: false },
|
|
134
|
+
shared: { Icon: 'fa-solid fa-inbox', Title: 'Nothing shared with you yet', Message: 'Dashboards other people share with you appear here.', Welcome: false },
|
|
135
|
+
favorites: { Icon: 'fa-solid fa-star', Title: 'No favorites yet', Message: 'Star a dashboard to keep it here.', Welcome: false },
|
|
136
|
+
recent: { Icon: 'fa-solid fa-clock-rotate-left', Title: 'Nothing opened yet', Message: 'Open a dashboard and it appears here.', Welcome: false },
|
|
137
|
+
};
|
|
138
|
+
const UNCATEGORIZED_EMPTY_STATE = {
|
|
139
|
+
Icon: 'fa-solid fa-folder-open',
|
|
140
|
+
Title: 'No uncategorized dashboards',
|
|
141
|
+
Message: 'Every dashboard is in a category.',
|
|
142
|
+
Welcome: false,
|
|
143
|
+
};
|
|
144
|
+
/** The empty state of a flat Library list. */
|
|
145
|
+
export function LibraryEmptyState(location) {
|
|
146
|
+
return location.CategoryId === UNCATEGORIZED_CATEGORY_ID ? UNCATEGORIZED_EMPTY_STATE : LIBRARY_EMPTY_STATES[location.Filter];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Counts the browsable dashboards directly in each category, and those at the root. A dashboard
|
|
150
|
+
* whose category is not in `categories` is not counted, as the browser cannot reach it.
|
|
151
|
+
*/
|
|
152
|
+
export function CountDashboardsByCategory(dashboards, categories, effectiveCategoryMap) {
|
|
153
|
+
const byCategory = new Map();
|
|
154
|
+
const categoriesByID = IndexByUUID(categories);
|
|
155
|
+
let uncategorized = 0;
|
|
156
|
+
for (const dashboard of dashboards.filter(IsBrowsableDashboard)) {
|
|
157
|
+
const categoryId = EffectiveCategoryId(dashboard, effectiveCategoryMap);
|
|
158
|
+
if (!categoryId) {
|
|
159
|
+
uncategorized++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const category = categoriesByID.Get(categoryId);
|
|
163
|
+
if (category) {
|
|
164
|
+
byCategory.set(category.ID, (byCategory.get(category.ID) ?? 0) + 1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return { ByCategory: byCategory, Uncategorized: uncategorized };
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The rail's two sections: Dashboards (one item per Library filter) and Categories (the category
|
|
171
|
+
* tree, then Uncategorized). A category whose parent is not in `categories` shows at the top level.
|
|
172
|
+
*/
|
|
173
|
+
export function BuildLibraryRailSections(counts, categories, categoryCounts, uncategorizedCount) {
|
|
174
|
+
const library = LIBRARY_ITEMS.map(([key, label, icon]) => ({ id: LibraryRailItemId(key), label, icon, badge: counts[key] }));
|
|
175
|
+
const toItem = (c) => {
|
|
176
|
+
const children = categories.filter(k => k.ParentID != null && UUIDsEqual(k.ParentID, c.ID)).map(toItem);
|
|
177
|
+
return { id: CategoryRailItemId(c.ID), label: c.Name, icon: 'fa-solid fa-folder', badge: categoryCounts.get(c.ID) ?? 0, ...(children.length ? { children } : {}) };
|
|
178
|
+
};
|
|
179
|
+
const roots = categories.filter(c => isTopLevel(c, categories)).map(toItem);
|
|
180
|
+
roots.push({ id: UNCATEGORIZED_RAIL_ID, label: 'Uncategorized', icon: 'fa-solid fa-folder-open', badge: uncategorizedCount });
|
|
181
|
+
return [
|
|
182
|
+
{ label: 'Dashboards', items: library },
|
|
183
|
+
{ label: 'Categories', items: roots },
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
/** True when a category has no parent, or its parent is not in `categories`. */
|
|
187
|
+
function isTopLevel(category, categories) {
|
|
188
|
+
return category.ParentID == null || !categories.some(c => UUIDsEqual(c.ID, category.ParentID));
|
|
189
|
+
}
|
|
190
|
+
/** The Library filter named by a `lib` query param; `all` for anything else. */
|
|
191
|
+
export function ParseLibraryFilter(value) {
|
|
192
|
+
return IsDashboardLibraryFilter(value) ? value : 'all';
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The location named by the `lib` and `category` query params, or null when they name none.
|
|
196
|
+
* A category wins over a Library filter, since a category is only shown under All.
|
|
197
|
+
*/
|
|
198
|
+
export function ResolveBrowseLocation(params) {
|
|
199
|
+
if (params['category']) {
|
|
200
|
+
return { Filter: 'all', CategoryId: params['category'] };
|
|
201
|
+
}
|
|
202
|
+
if (params['lib']) {
|
|
203
|
+
return { Filter: ParseLibraryFilter(params['lib']), CategoryId: null };
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
/** True when two locations have the same Library filter and the same category (or both none). */
|
|
208
|
+
export function SameBrowseLocation(a, b) {
|
|
209
|
+
return a.Filter === b.Filter && UUIDsEqual(a.CategoryId, b.CategoryId);
|
|
210
|
+
}
|
|
211
|
+
/** The `lib` and `category` query params for a location. All and the root write null. */
|
|
212
|
+
export function LocationQueryParams(location) {
|
|
213
|
+
return {
|
|
214
|
+
lib: location.Filter === 'all' ? null : location.Filter,
|
|
215
|
+
category: location.CategoryId || null,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/** The rail item id for a Library filter. */
|
|
219
|
+
export function LibraryRailItemId(filter) {
|
|
220
|
+
return `${LIBRARY_RAIL_PREFIX}${filter}`;
|
|
221
|
+
}
|
|
222
|
+
/** The rail item id for a category. */
|
|
223
|
+
export function CategoryRailItemId(categoryId) {
|
|
224
|
+
return `${CATEGORY_RAIL_PREFIX}${categoryId}`;
|
|
225
|
+
}
|
|
226
|
+
/** The location a rail item opens, or null for an id the rail never builds. */
|
|
227
|
+
export function ParseRailItemId(id) {
|
|
228
|
+
if (id === UNCATEGORIZED_RAIL_ID) {
|
|
229
|
+
return { Filter: 'all', CategoryId: UNCATEGORIZED_CATEGORY_ID };
|
|
230
|
+
}
|
|
231
|
+
if (id.startsWith(CATEGORY_RAIL_PREFIX)) {
|
|
232
|
+
return { Filter: 'all', CategoryId: id.slice(CATEGORY_RAIL_PREFIX.length) };
|
|
233
|
+
}
|
|
234
|
+
if (id.startsWith(LIBRARY_RAIL_PREFIX)) {
|
|
235
|
+
const filter = id.slice(LIBRARY_RAIL_PREFIX.length);
|
|
236
|
+
return IsDashboardLibraryFilter(filter) ? { Filter: filter, CategoryId: null } : null;
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The rail item to mark active: Uncategorized, or the selected category, written with the
|
|
242
|
+
* category's own id so the rail's exact id match finds it, else the Library filter.
|
|
243
|
+
*/
|
|
244
|
+
export function RailActiveItemId(location, categories) {
|
|
245
|
+
if (location.CategoryId === UNCATEGORIZED_CATEGORY_ID) {
|
|
246
|
+
return UNCATEGORIZED_RAIL_ID;
|
|
247
|
+
}
|
|
248
|
+
if (!location.CategoryId) {
|
|
249
|
+
return LibraryRailItemId(location.Filter);
|
|
250
|
+
}
|
|
251
|
+
const category = categories.find(c => UUIDsEqual(c.ID, location.CategoryId));
|
|
252
|
+
return CategoryRailItemId(category?.ID ?? location.CategoryId);
|
|
253
|
+
}
|
|
254
|
+
/** `expandedIds` with `id` added when missing, or removed when present. */
|
|
255
|
+
export function ToggleExpandedId(expandedIds, id) {
|
|
256
|
+
return expandedIds.includes(id) ? expandedIds.filter(x => x !== id) : [...expandedIds, id];
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* `expandedIds` with every folder above `categoryId` expanded, so the rail shows that category.
|
|
260
|
+
* Returns `expandedIds` itself when nothing changes.
|
|
261
|
+
*/
|
|
262
|
+
export function WithExpandedAncestors(expandedIds, categoryId, categories) {
|
|
263
|
+
const missing = [];
|
|
264
|
+
const seen = new Set();
|
|
265
|
+
let current = categoryId ? categories.find(c => UUIDsEqual(c.ID, categoryId)) : undefined;
|
|
266
|
+
while (current?.ParentID && !seen.has(current)) {
|
|
267
|
+
seen.add(current);
|
|
268
|
+
const parentId = current.ParentID;
|
|
269
|
+
const parent = categories.find(c => UUIDsEqual(c.ID, parentId));
|
|
270
|
+
if (parent && !expandedIds.includes(CategoryRailItemId(parent.ID))) {
|
|
271
|
+
missing.unshift(CategoryRailItemId(parent.ID));
|
|
272
|
+
}
|
|
273
|
+
current = parent;
|
|
274
|
+
}
|
|
275
|
+
return missing.length ? [...expandedIds, ...missing] : expandedIds;
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=dashboard-library-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-library-filter.js","sourceRoot":"","sources":["../../src/DashboardBrowser/dashboard-library-filter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Pure logic for the Library page of the Dashboards app: the order of its dashboards,\n * the list each rail item shows, the empty state of each list, the rail sections and their counts,\n * the rail item ids, and the `lib` / `category` query params.\n *\n * Every Library filter and Uncategorized is a flat list; only a category opens as a folder. Counts\n * follow the same rules as `mj-dashboard-browser`, so a rail badge equals the number of dashboard\n * cards the browser shows when that item is clicked: only Config dashboards count, and a category\n * or Uncategorized shows the dashboards whose effective category it is.\n */\nimport type { MJDashboardCategoryEntity, MJDashboardEntity } from '@memberjunction/core-entities';\nimport { FilterByUUIDs, IndexByUUID, NormalizeUUID, UUIDsEqual } from '@memberjunction/global';\nimport type { MJLeftNavItem, MJLeftNavSection } from '@memberjunction/ng-ui-components';\n\n/** The Library filters, in rail order. */\nexport const DASHBOARD_LIBRARY_FILTERS = ['all', 'mine', 'shared', 'favorites', 'recent'] as const;\n\n/** A Library filter in the Library rail. */\nexport type DashboardLibraryFilter = (typeof DASHBOARD_LIBRARY_FILTERS)[number];\n\n/** True when `value` is one of the Library filters (exact, case-sensitive). */\nexport function IsDashboardLibraryFilter(value: unknown): value is DashboardLibraryFilter {\n return typeof value === 'string' && (DASHBOARD_LIBRARY_FILTERS as readonly string[]).includes(value);\n}\n\n/** What the Library filters need to know about the current user. */\nexport interface LibraryFilterContext {\n CurrentUserId: string;\n FavoriteIds: string[];\n /** Recently opened dashboard ids, most recent first. */\n RecentIds: string[];\n}\n\n/** Where the Library is: a Library filter, or (with the All filter) a category or Uncategorized. */\nexport interface BrowseLocation {\n Filter: DashboardLibraryFilter;\n /** The selected category, UNCATEGORIZED_CATEGORY_ID for Uncategorized, or null for none. */\n CategoryId: string | null;\n}\n\n/** The `category` value of the Uncategorized list: the dashboards in no category. */\nexport const UNCATEGORIZED_CATEGORY_ID = 'uncategorized';\n\n/** Category counts for the rail, keyed by the category's own id. */\nexport interface CategoryCounts {\n ByCategory: Map<string, number>;\n Uncategorized: number;\n}\n\nconst LIBRARY_RAIL_PREFIX = 'lib:';\nconst CATEGORY_RAIL_PREFIX = 'cat:';\nconst UNCATEGORIZED_RAIL_ID = 'cat:unc';\n\nconst LIBRARY_ITEMS: Array<[DashboardLibraryFilter, string, string]> = [\n ['all', 'All dashboards', 'fa-solid fa-layer-group'],\n ['mine', 'My dashboards', 'fa-solid fa-user'],\n ['shared', 'Shared with me', 'fa-solid fa-inbox'],\n ['favorites', 'Favorites', 'fa-solid fa-star'],\n ['recent', 'Recently opened', 'fa-solid fa-clock-rotate-left'],\n];\n\n/** The dashboards a Library filter keeps, in the order given. `recent` follows the recents order. */\nexport function FilterDashboardsForLibrary(\n dashboards: MJDashboardEntity[],\n filter: DashboardLibraryFilter,\n ctx: LibraryFilterContext\n): MJDashboardEntity[] {\n switch (filter) {\n case 'mine':\n return dashboards.filter(d => UUIDsEqual(d.UserID, ctx.CurrentUserId));\n case 'shared':\n return dashboards.filter(d => !UUIDsEqual(d.UserID, ctx.CurrentUserId));\n case 'favorites':\n return FilterByUUIDs(dashboards, ctx.FavoriteIds);\n case 'recent':\n return pickInOrder(dashboards, ctx.RecentIds);\n default:\n return dashboards;\n }\n}\n\n/** The dashboards named by `ids`, in that order, each once. Unknown ids are skipped. */\nfunction pickInOrder(dashboards: MJDashboardEntity[], ids: string[]): MJDashboardEntity[] {\n const byID = IndexByUUID(dashboards);\n const picked: MJDashboardEntity[] = [];\n const seen = new Set<MJDashboardEntity>();\n for (const id of ids) {\n const hit = byID.Get(id);\n if (hit && !seen.has(hit)) {\n seen.add(hit);\n picked.push(hit);\n }\n }\n return picked;\n}\n\n/** True for the dashboards `mj-dashboard-browser` shows: Config dashboards. */\nexport function IsBrowsableDashboard(dashboard: MJDashboardEntity): boolean {\n return dashboard.Type === 'Config';\n}\n\n/** For each Library filter, the number of dashboards the browser shows for it. */\nexport function CountLibraryDashboards(\n dashboards: MJDashboardEntity[],\n ctx: LibraryFilterContext\n): Record<DashboardLibraryFilter, number> {\n const browsable = dashboards.filter(IsBrowsableDashboard);\n const count = (filter: DashboardLibraryFilter): number => FilterDashboardsForLibrary(browsable, filter, ctx).length;\n return { all: count('all'), mine: count('mine'), shared: count('shared'), favorites: count('favorites'), recent: count('recent') };\n}\n\n/**\n * The category a dashboard shows under. A dashboard in `effectiveCategoryMap` (a shared one) uses\n * the mapped value, where null means the root; any other dashboard uses its own CategoryID.\n */\nexport function EffectiveCategoryId(dashboard: MJDashboardEntity, effectiveCategoryMap: Map<string, string | null>): string | null {\n if (effectiveCategoryMap.has(dashboard.ID)) {\n return effectiveCategoryMap.get(dashboard.ID) || null;\n }\n return dashboard.CategoryID || null;\n}\n\n/**\n * The Library's default order: favorites first, then the dashboards the user opened, the most recent\n * open first, then the rest. Within each group a dashboard opened more recently comes first;\n * dashboards never opened follow, the most recently updated first, then by name. Returns a new array.\n */\nexport function OrderLibraryDashboards(dashboards: readonly MJDashboardEntity[], ctx: LibraryFilterContext): MJDashboardEntity[] {\n const favorites = new Set(ctx.FavoriteIds.map(id => NormalizeUUID(id)));\n const ranks = recentRanks(ctx.RecentIds);\n return dashboards\n .map(dashboard => ({ dashboard, key: libraryOrderKey(dashboard, favorites, ranks) }))\n .sort((a, b) => compareLibraryOrderKeys(a.key, b.key))\n .map(entry => entry.dashboard);\n}\n\n/** What the Library sorts a dashboard by. */\ninterface LibraryOrderKey {\n Favorite: boolean;\n /** Position in the recents (0 = opened last); Infinity when never opened. */\n RecentRank: number;\n /** Last update in ms since the epoch; 0 when unknown. */\n UpdatedAt: number;\n Name: string;\n}\n\nfunction libraryOrderKey(dashboard: MJDashboardEntity, favorites: ReadonlySet<string>, ranks: ReadonlyMap<string, number>): LibraryOrderKey {\n const id = NormalizeUUID(dashboard.ID);\n return {\n Favorite: favorites.has(id),\n RecentRank: ranks.get(id) ?? Number.POSITIVE_INFINITY,\n UpdatedAt: timeOf(dashboard.__mj_UpdatedAt),\n Name: dashboard.Name ?? '',\n };\n}\n\nfunction compareLibraryOrderKeys(a: LibraryOrderKey, b: LibraryOrderKey): number {\n if (a.Favorite !== b.Favorite) return a.Favorite ? -1 : 1;\n if (a.RecentRank !== b.RecentRank) return a.RecentRank < b.RecentRank ? -1 : 1;\n if (a.UpdatedAt !== b.UpdatedAt) return b.UpdatedAt - a.UpdatedAt;\n return a.Name.localeCompare(b.Name);\n}\n\n/** Each recent id's first position, keyed by the normalized id. */\nfunction recentRanks(recentIds: readonly string[]): Map<string, number> {\n const ranks = new Map<string, number>();\n recentIds.forEach((id, index) => {\n const key = NormalizeUUID(id);\n if (!ranks.has(key)) ranks.set(key, index);\n });\n return ranks;\n}\n\n/** Milliseconds since the epoch, or 0 for a missing or invalid date. */\nfunction timeOf(value: Date | null | undefined): number {\n const time = value ? new Date(value).getTime() : Number.NaN;\n return Number.isNaN(time) ? 0 : time;\n}\n\n/** True when a location shows one category's folder; every other location is a flat list. */\nexport function IsCategoryFolderLocation(location: BrowseLocation): boolean {\n return !!location.CategoryId && location.CategoryId !== UNCATEGORIZED_CATEGORY_ID;\n}\n\n/**\n * The dashboards the Library gives the browser for a location, in display order (see\n * OrderLibraryDashboards; Recently opened keeps the order of the last opens). Uncategorized keeps the\n * dashboards in no category. A category folder gets every dashboard: the browser shows the folder's own.\n */\nexport function VisibleLibraryDashboards(\n dashboards: readonly MJDashboardEntity[],\n location: BrowseLocation,\n ctx: LibraryFilterContext,\n effectiveCategoryMap: Map<string, string | null>\n): MJDashboardEntity[] {\n const ordered = OrderLibraryDashboards(dashboards, ctx);\n if (location.CategoryId === UNCATEGORIZED_CATEGORY_ID) {\n return ordered.filter(d => !EffectiveCategoryId(d, effectiveCategoryMap));\n }\n return FilterDashboardsForLibrary(ordered, location.Filter, ctx);\n}\n\n/** What an empty flat Library list shows. */\nexport interface LibraryEmptyStateText {\n Icon: string;\n Title: string;\n Message: string;\n /** True for the whole library: the browser shows its first-run welcome instead. */\n Welcome: boolean;\n}\n\nconst LIBRARY_EMPTY_STATES: Record<DashboardLibraryFilter, LibraryEmptyStateText> = {\n all: { Icon: 'fa-solid fa-gauge-high', Title: 'Welcome to Dashboards', Message: '', Welcome: true },\n mine: { Icon: 'fa-solid fa-user', Title: 'You have no dashboards yet', Message: 'Create one with New.', Welcome: false },\n shared: { Icon: 'fa-solid fa-inbox', Title: 'Nothing shared with you yet', Message: 'Dashboards other people share with you appear here.', Welcome: false },\n favorites: { Icon: 'fa-solid fa-star', Title: 'No favorites yet', Message: 'Star a dashboard to keep it here.', Welcome: false },\n recent: { Icon: 'fa-solid fa-clock-rotate-left', Title: 'Nothing opened yet', Message: 'Open a dashboard and it appears here.', Welcome: false },\n};\n\nconst UNCATEGORIZED_EMPTY_STATE: LibraryEmptyStateText = {\n Icon: 'fa-solid fa-folder-open',\n Title: 'No uncategorized dashboards',\n Message: 'Every dashboard is in a category.',\n Welcome: false,\n};\n\n/** The empty state of a flat Library list. */\nexport function LibraryEmptyState(location: BrowseLocation): LibraryEmptyStateText {\n return location.CategoryId === UNCATEGORIZED_CATEGORY_ID ? UNCATEGORIZED_EMPTY_STATE : LIBRARY_EMPTY_STATES[location.Filter];\n}\n\n/**\n * Counts the browsable dashboards directly in each category, and those at the root. A dashboard\n * whose category is not in `categories` is not counted, as the browser cannot reach it.\n */\nexport function CountDashboardsByCategory(\n dashboards: MJDashboardEntity[],\n categories: MJDashboardCategoryEntity[],\n effectiveCategoryMap: Map<string, string | null>\n): CategoryCounts {\n const byCategory = new Map<string, number>();\n const categoriesByID = IndexByUUID(categories);\n let uncategorized = 0;\n for (const dashboard of dashboards.filter(IsBrowsableDashboard)) {\n const categoryId = EffectiveCategoryId(dashboard, effectiveCategoryMap);\n if (!categoryId) {\n uncategorized++;\n continue;\n }\n const category = categoriesByID.Get(categoryId);\n if (category) {\n byCategory.set(category.ID, (byCategory.get(category.ID) ?? 0) + 1);\n }\n }\n return { ByCategory: byCategory, Uncategorized: uncategorized };\n}\n\n/**\n * The rail's two sections: Dashboards (one item per Library filter) and Categories (the category\n * tree, then Uncategorized). A category whose parent is not in `categories` shows at the top level.\n */\nexport function BuildLibraryRailSections(\n counts: Record<DashboardLibraryFilter, number>,\n categories: MJDashboardCategoryEntity[],\n categoryCounts: Map<string, number>,\n uncategorizedCount: number\n): MJLeftNavSection[] {\n const library: MJLeftNavItem[] = LIBRARY_ITEMS.map(([key, label, icon]) => ({ id: LibraryRailItemId(key), label, icon, badge: counts[key] }));\n const toItem = (c: MJDashboardCategoryEntity): MJLeftNavItem => {\n const children = categories.filter(k => k.ParentID != null && UUIDsEqual(k.ParentID, c.ID)).map(toItem);\n return { id: CategoryRailItemId(c.ID), label: c.Name, icon: 'fa-solid fa-folder', badge: categoryCounts.get(c.ID) ?? 0, ...(children.length ? { children } : {}) };\n };\n const roots = categories.filter(c => isTopLevel(c, categories)).map(toItem);\n roots.push({ id: UNCATEGORIZED_RAIL_ID, label: 'Uncategorized', icon: 'fa-solid fa-folder-open', badge: uncategorizedCount });\n return [\n { label: 'Dashboards', items: library },\n { label: 'Categories', items: roots },\n ];\n}\n\n/** True when a category has no parent, or its parent is not in `categories`. */\nfunction isTopLevel(category: MJDashboardCategoryEntity, categories: MJDashboardCategoryEntity[]): boolean {\n return category.ParentID == null || !categories.some(c => UUIDsEqual(c.ID, category.ParentID));\n}\n\n/** The Library filter named by a `lib` query param; `all` for anything else. */\nexport function ParseLibraryFilter(value: string | null | undefined): DashboardLibraryFilter {\n return IsDashboardLibraryFilter(value) ? value : 'all';\n}\n\n/**\n * The location named by the `lib` and `category` query params, or null when they name none.\n * A category wins over a Library filter, since a category is only shown under All.\n */\nexport function ResolveBrowseLocation(params: Record<string, string>): BrowseLocation | null {\n if (params['category']) {\n return { Filter: 'all', CategoryId: params['category'] };\n }\n if (params['lib']) {\n return { Filter: ParseLibraryFilter(params['lib']), CategoryId: null };\n }\n return null;\n}\n\n/** True when two locations have the same Library filter and the same category (or both none). */\nexport function SameBrowseLocation(a: BrowseLocation, b: BrowseLocation): boolean {\n return a.Filter === b.Filter && UUIDsEqual(a.CategoryId, b.CategoryId);\n}\n\n/** The `lib` and `category` query params for a location. All and the root write null. */\nexport function LocationQueryParams(location: BrowseLocation): { lib: DashboardLibraryFilter | null; category: string | null } {\n return {\n lib: location.Filter === 'all' ? null : location.Filter,\n category: location.CategoryId || null,\n };\n}\n\n/** The rail item id for a Library filter. */\nexport function LibraryRailItemId(filter: DashboardLibraryFilter): string {\n return `${LIBRARY_RAIL_PREFIX}${filter}`;\n}\n\n/** The rail item id for a category. */\nexport function CategoryRailItemId(categoryId: string): string {\n return `${CATEGORY_RAIL_PREFIX}${categoryId}`;\n}\n\n/** The location a rail item opens, or null for an id the rail never builds. */\nexport function ParseRailItemId(id: string): BrowseLocation | null {\n if (id === UNCATEGORIZED_RAIL_ID) {\n return { Filter: 'all', CategoryId: UNCATEGORIZED_CATEGORY_ID };\n }\n if (id.startsWith(CATEGORY_RAIL_PREFIX)) {\n return { Filter: 'all', CategoryId: id.slice(CATEGORY_RAIL_PREFIX.length) };\n }\n if (id.startsWith(LIBRARY_RAIL_PREFIX)) {\n const filter = id.slice(LIBRARY_RAIL_PREFIX.length);\n return IsDashboardLibraryFilter(filter) ? { Filter: filter, CategoryId: null } : null;\n }\n return null;\n}\n\n/**\n * The rail item to mark active: Uncategorized, or the selected category, written with the\n * category's own id so the rail's exact id match finds it, else the Library filter.\n */\nexport function RailActiveItemId(location: BrowseLocation, categories: MJDashboardCategoryEntity[]): string {\n if (location.CategoryId === UNCATEGORIZED_CATEGORY_ID) {\n return UNCATEGORIZED_RAIL_ID;\n }\n if (!location.CategoryId) {\n return LibraryRailItemId(location.Filter);\n }\n const category = categories.find(c => UUIDsEqual(c.ID, location.CategoryId));\n return CategoryRailItemId(category?.ID ?? location.CategoryId);\n}\n\n/** `expandedIds` with `id` added when missing, or removed when present. */\nexport function ToggleExpandedId(expandedIds: string[], id: string): string[] {\n return expandedIds.includes(id) ? expandedIds.filter(x => x !== id) : [...expandedIds, id];\n}\n\n/**\n * `expandedIds` with every folder above `categoryId` expanded, so the rail shows that category.\n * Returns `expandedIds` itself when nothing changes.\n */\nexport function WithExpandedAncestors(\n expandedIds: string[],\n categoryId: string | null,\n categories: MJDashboardCategoryEntity[]\n): string[] {\n const missing: string[] = [];\n const seen = new Set<MJDashboardCategoryEntity>();\n let current = categoryId ? categories.find(c => UUIDsEqual(c.ID, categoryId)) : undefined;\n while (current?.ParentID && !seen.has(current)) {\n seen.add(current);\n const parentId = current.ParentID;\n const parent = categories.find(c => UUIDsEqual(c.ID, parentId));\n if (parent && !expandedIds.includes(CategoryRailItemId(parent.ID))) {\n missing.unshift(CategoryRailItemId(parent.ID));\n }\n current = parent;\n }\n return missing.length ? [...expandedIds, ...missing] : expandedIds;\n}\n"]}
|
|
@@ -6,7 +6,7 @@ import { DashboardShareAdapter } from './dashboard-share-adapter';
|
|
|
6
6
|
import * as i0 from "@angular/core";
|
|
7
7
|
/**
|
|
8
8
|
* Public re-export kept for backward compatibility with existing call sites
|
|
9
|
-
* (
|
|
9
|
+
* (the `DashboardResource` wrapper in `@memberjunction/ng-explorer-core`).
|
|
10
10
|
* The new abstraction lives in `@memberjunction/ng-resource-permissions`.
|
|
11
11
|
*/
|
|
12
12
|
export type ShareDialogResult = ResourceShareDialogResult & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard-share-dialog.component.js","sourceRoot":"","sources":["../../src/DashboardBrowser/dashboard-share-dialog.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAMvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;;;AASlE;;;;;;GAMG;AAaH,MAAM,OAAO,6BAA8B,SAAQ,oBAAoB;IAC1D,OAAO,GAAG,KAAK,CAAC;IACzB,IACI,SAAS,CAAC,KAA+B;QACzC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,KAAK;YAChB,CAAC,CAAC;gBACI,UAAU,EAAE,KAAK,CAAC,EAAE;gBACpB,YAAY,EAAE,KAAK,CAAC,IAAI,IAAI,UAAU;gBACtC,WAAW,EAAE,KAAK,CAAC,MAAM;gBACzB,gBAAgB,EAAE,KAAK,CAAC,IAAI,IAAI,KAAK;aACxC;YACH,CAAC,CAAC,IAAI,CAAC;IACf,CAAC;IACD,IAAI,SAAS;QACT,OAAO,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IACS,MAAM,GAAG,IAAI,YAAY,EAAqB,CAAC;IAElD,OAAO,GAAgC,IAAI,CAAC;IAC5C,OAAO,GAAG,IAAI,qBAAqB,EAAE,CAAC;IAE7C,uCAAuC;IACvC,IAAW,OAAO;QACd,OAAO,IAAI,CAAC,OAAO,CAAC;IACxB,CAAC;IACD,uCAAuC;IACvC,IAAW,OAAO,CAAC,KAAK;QACpB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACzB,CAAC;IAEO,UAAU,GAA6B,IAAI,CAAC;IAEpD;QACI,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC;IAC/C,CAAC;IAED,QAAQ,CAAC,MAAiC;QACtC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YACb,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,SAAS,EAAE,MAAM,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;SACvF,CAAC,CAAC;IACP,CAAC;IAED,wCAAwC;IACxC,QAAQ,CAAC,MAAiC;QACtC,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;uHAhDQ,6BAA6B;6DAA7B,6BAA6B;YARlC,mDAIgC;YAA5B,oIAAU,oBAAgB,IAAC;YAC/B,iBAA2B;;YAFvB,AADA,AADA,qCAAmB,wBACA,wBACA;;;iFAKlB,6BAA6B;cAZzC,SAAS;eAAC;gBACP,UAAU,EAAE,KAAK;gBACjB,QAAQ,EAAE,2BAA2B;gBACrC,QAAQ,EAAE;;;;;;;KAOT;aACJ;;kBAEI,KAAK;;kBACL,KAAK;;kBAeL,MAAM;;kFAjBE,6BAA6B","sourcesContent":["import { Component, Input, Output, EventEmitter } from '@angular/core';\nimport { MJDashboardEntity } from '@memberjunction/core-entities';\nimport {\n ResourceShareContext,\n ResourceShareDialogResult\n} from '@memberjunction/ng-resource-permissions';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { DashboardShareAdapter } from './dashboard-share-adapter';\n\n/**\n * Public re-export kept for backward compatibility with existing call sites\n * (
|
|
1
|
+
{"version":3,"file":"dashboard-share-dialog.component.js","sourceRoot":"","sources":["../../src/DashboardBrowser/dashboard-share-dialog.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAMvE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;;;AASlE;;;;;;GAMG;AAaH,MAAM,OAAO,6BAA8B,SAAQ,oBAAoB;IAC1D,OAAO,GAAG,KAAK,CAAC;IACzB,IACI,SAAS,CAAC,KAA+B;QACzC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,KAAK;YAChB,CAAC,CAAC;gBACI,UAAU,EAAE,KAAK,CAAC,EAAE;gBACpB,YAAY,EAAE,KAAK,CAAC,IAAI,IAAI,UAAU;gBACtC,WAAW,EAAE,KAAK,CAAC,MAAM;gBACzB,gBAAgB,EAAE,KAAK,CAAC,IAAI,IAAI,KAAK;aACxC;YACH,CAAC,CAAC,IAAI,CAAC;IACf,CAAC;IACD,IAAI,SAAS;QACT,OAAO,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IACS,MAAM,GAAG,IAAI,YAAY,EAAqB,CAAC;IAElD,OAAO,GAAgC,IAAI,CAAC;IAC5C,OAAO,GAAG,IAAI,qBAAqB,EAAE,CAAC;IAE7C,uCAAuC;IACvC,IAAW,OAAO;QACd,OAAO,IAAI,CAAC,OAAO,CAAC;IACxB,CAAC;IACD,uCAAuC;IACvC,IAAW,OAAO,CAAC,KAAK;QACpB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACzB,CAAC;IAEO,UAAU,GAA6B,IAAI,CAAC;IAEpD;QACI,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC;IAC/C,CAAC;IAED,QAAQ,CAAC,MAAiC;QACtC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YACb,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,SAAS,EAAE,MAAM,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;SACvF,CAAC,CAAC;IACP,CAAC;IAED,wCAAwC;IACxC,QAAQ,CAAC,MAAiC;QACtC,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;uHAhDQ,6BAA6B;6DAA7B,6BAA6B;YARlC,mDAIgC;YAA5B,oIAAU,oBAAgB,IAAC;YAC/B,iBAA2B;;YAFvB,AADA,AADA,qCAAmB,wBACA,wBACA;;;iFAKlB,6BAA6B;cAZzC,SAAS;eAAC;gBACP,UAAU,EAAE,KAAK;gBACjB,QAAQ,EAAE,2BAA2B;gBACrC,QAAQ,EAAE;;;;;;;KAOT;aACJ;;kBAEI,KAAK;;kBACL,KAAK;;kBAeL,MAAM;;kFAjBE,6BAA6B","sourcesContent":["import { Component, Input, Output, EventEmitter } from '@angular/core';\nimport { MJDashboardEntity } from '@memberjunction/core-entities';\nimport {\n ResourceShareContext,\n ResourceShareDialogResult\n} from '@memberjunction/ng-resource-permissions';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\nimport { DashboardShareAdapter } from './dashboard-share-adapter';\n\n/**\n * Public re-export kept for backward compatibility with existing call sites\n * (the `DashboardResource` wrapper in `@memberjunction/ng-explorer-core`).\n * The new abstraction lives in `@memberjunction/ng-resource-permissions`.\n */\nexport type ShareDialogResult = ResourceShareDialogResult & { Dashboard?: MJDashboardEntity };\n\n/**\n * Thin wrapper that maps a `MJDashboardEntity` + the Dashboard-specific\n * permission model onto the generic `mj-resource-share-dialog`. Existing call\n * sites (`<mj-dashboard-share-dialog [Dashboard]=... [Visible]=... (Result)=... />`)\n * continue to work unchanged — this component just constructs the context +\n * adapter and delegates.\n */\n@Component({\n standalone: false,\n selector: 'mj-dashboard-share-dialog',\n template: `\n <mj-resource-share-dialog\n [Visible]=\"Visible\"\n [Context]=\"context\"\n [Adapter]=\"adapter\"\n (Result)=\"onResult($event)\">\n </mj-resource-share-dialog>\n `\n})\nexport class DashboardShareDialogComponent extends BaseAngularComponent {\n @Input() Visible = false;\n @Input()\n set Dashboard(value: MJDashboardEntity | null) {\n this._dashboard = value;\n this.context = value\n ? {\n ResourceID: value.ID,\n ResourceName: value.Name ?? 'Untitled',\n OwnerUserID: value.UserID,\n OwnerDisplayName: value.User ?? 'You'\n }\n : null;\n }\n get Dashboard(): MJDashboardEntity | null {\n return this._dashboard;\n }\n @Output() Result = new EventEmitter<ShareDialogResult>();\n\n public context: ResourceShareContext | null = null;\n public Adapter = new DashboardShareAdapter();\n\n /** @deprecated Use {@link Adapter}. */\n public get adapter() {\n return this.Adapter;\n }\n /** @deprecated Use {@link Adapter}. */\n public set adapter(value) {\n this.Adapter = value;\n }\n\n private _dashboard: MJDashboardEntity | null = null;\n\n constructor() {\n super();\n this.Adapter.Provider = this.ProviderToUse;\n }\n\n OnResult(result: ResourceShareDialogResult): void {\n this.Result.emit({\n Action: result.Action,\n Dashboard: result.Action === 'save' && this._dashboard ? this._dashboard : undefined\n });\n }\n\n /** @deprecated Use {@link OnResult}. */\n onResult(result: ResourceShareDialogResult): void {\n return this.OnResult(result);\n }\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type AgentToolResult } from '../shared/agent-tool-validation';
|
|
2
|
+
/** An agent client tool, in the shape `NavigationService.SetAgentClientTools` takes. */
|
|
3
|
+
export interface DashboardsAgentClientTool {
|
|
4
|
+
Name: string;
|
|
5
|
+
Description: string;
|
|
6
|
+
ParameterSchema: Record<string, unknown>;
|
|
7
|
+
Handler: (params: Record<string, unknown>) => Promise<unknown>;
|
|
8
|
+
}
|
|
9
|
+
/** The item an agent named, or the failure result to return to the agent. */
|
|
10
|
+
export type AgentLookup<T> = {
|
|
11
|
+
ok: true;
|
|
12
|
+
value: T;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
result: AgentToolResult;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Finds the item an agent named: an exact ID match first, then an exact name match that ignores
|
|
19
|
+
* case. `kind` names the item in messages, for example "dashboard". When nothing matches, the
|
|
20
|
+
* failure lists up to 25 available names.
|
|
21
|
+
*/
|
|
22
|
+
export declare function ResolveByIdOrName<T extends {
|
|
23
|
+
ID: string;
|
|
24
|
+
Name: string;
|
|
25
|
+
}>(items: readonly T[], raw: unknown, kind: string): AgentLookup<T>;
|
|
26
|
+
//# sourceMappingURL=dashboards-agent-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboards-agent-tools.d.ts","sourceRoot":"","sources":["../../src/DashboardBrowser/dashboards-agent-tools.ts"],"names":[],"mappings":"AAYA,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAEtF,wFAAwF;AACxF,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAChE;AAED,6EAA6E;AAC7E,MAAM,MAAM,WAAW,CAAC,CAAC,IACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACtB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,CAAC;AAE3C;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAYzI"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Agent tool parts for the Categories page of the Dashboards app: the tool shape and
|
|
3
|
+
* the id-or-name lookup its handlers use. Handlers never throw; a bad parameter returns a failure
|
|
4
|
+
* result the agent can act on.
|
|
5
|
+
*
|
|
6
|
+
* 🔒 SAFETY BOUNDARY: the Categories page gives the AI agent ONLY read-only / navigational tools, as
|
|
7
|
+
* the Library does. Mutating operations — create / delete / save / share / move a dashboard or a
|
|
8
|
+
* category — are intentionally NOT exposed; the user performs them from the UI. Do NOT add a
|
|
9
|
+
* mutating tool builder here without revisiting this boundary (the page's agent section lists its
|
|
10
|
+
* tools).
|
|
11
|
+
*/
|
|
12
|
+
import { UUIDsEqual } from '@memberjunction/global';
|
|
13
|
+
import { BoundNameList } from '../shared/agent-tool-validation';
|
|
14
|
+
/**
|
|
15
|
+
* Finds the item an agent named: an exact ID match first, then an exact name match that ignores
|
|
16
|
+
* case. `kind` names the item in messages, for example "dashboard". When nothing matches, the
|
|
17
|
+
* failure lists up to 25 available names.
|
|
18
|
+
*/
|
|
19
|
+
export function ResolveByIdOrName(items, raw, kind) {
|
|
20
|
+
const value = typeof raw === 'string' ? raw.trim() : '';
|
|
21
|
+
if (!value) {
|
|
22
|
+
return { ok: false, result: { Success: false, ErrorMessage: `A ${kind} ID or name is required.` } };
|
|
23
|
+
}
|
|
24
|
+
const lowered = value.toLowerCase();
|
|
25
|
+
const match = items.find(i => UUIDsEqual(i.ID, value)) ?? items.find(i => (i.Name ?? '').toLowerCase() === lowered);
|
|
26
|
+
if (match) {
|
|
27
|
+
return { ok: true, value: match };
|
|
28
|
+
}
|
|
29
|
+
const available = BoundNameList(items.map(i => i.Name)).join(', ') || '(none)';
|
|
30
|
+
return { ok: false, result: { Success: false, ErrorMessage: `No ${kind} named or identified by "${value}". Available: ${available}.` } };
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=dashboards-agent-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboards-agent-tools.js","sourceRoot":"","sources":["../../src/DashboardBrowser/dashboards-agent-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAwB,MAAM,iCAAiC,CAAC;AAetF;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAyC,KAAmB,EAAE,GAAY,EAAE,IAAY;IACvH,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,IAAI,0BAA0B,EAAE,EAAE,CAAC;IACtG,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACpC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC,CAAC;IACpH,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACpC,CAAC;IACD,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC;IAC/E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,IAAI,4BAA4B,KAAK,iBAAiB,SAAS,GAAG,EAAE,EAAE,CAAC;AAC3I,CAAC","sourcesContent":["/**\n * @fileoverview Agent tool parts for the Categories page of the Dashboards app: the tool shape and\n * the id-or-name lookup its handlers use. Handlers never throw; a bad parameter returns a failure\n * result the agent can act on.\n *\n * 🔒 SAFETY BOUNDARY: the Categories page gives the AI agent ONLY read-only / navigational tools, as\n * the Library does. Mutating operations — create / delete / save / share / move a dashboard or a\n * category — are intentionally NOT exposed; the user performs them from the UI. Do NOT add a\n * mutating tool builder here without revisiting this boundary (the page's agent section lists its\n * tools).\n */\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { BoundNameList, type AgentToolResult } from '../shared/agent-tool-validation';\n\n/** An agent client tool, in the shape `NavigationService.SetAgentClientTools` takes. */\nexport interface DashboardsAgentClientTool {\n Name: string;\n Description: string;\n ParameterSchema: Record<string, unknown>;\n Handler: (params: Record<string, unknown>) => Promise<unknown>;\n}\n\n/** The item an agent named, or the failure result to return to the agent. */\nexport type AgentLookup<T> =\n | { ok: true; value: T } // case-violation-ok-legacy-back-compat: matches the { ok, value } / { ok, result } result of ValidateStringParam (agent-tool-validation.ts)\n | { ok: false; result: AgentToolResult }; // case-violation-ok-legacy-back-compat: matches the { ok, value } / { ok, result } result of ValidateStringParam (agent-tool-validation.ts)\n\n/**\n * Finds the item an agent named: an exact ID match first, then an exact name match that ignores\n * case. `kind` names the item in messages, for example \"dashboard\". When nothing matches, the\n * failure lists up to 25 available names.\n */\nexport function ResolveByIdOrName<T extends { ID: string; Name: string }>(items: readonly T[], raw: unknown, kind: string): AgentLookup<T> {\n const value = typeof raw === 'string' ? raw.trim() : '';\n if (!value) {\n return { ok: false, result: { Success: false, ErrorMessage: `A ${kind} ID or name is required.` } };\n }\n const lowered = value.toLowerCase();\n const match = items.find(i => UUIDsEqual(i.ID, value)) ?? items.find(i => (i.Name ?? '').toLowerCase() === lowered);\n if (match) {\n return { ok: true, value: match };\n }\n const available = BoundNameList(items.map(i => i.Name)).join(', ') || '(none)';\n return { ok: false, result: { Success: false, ErrorMessage: `No ${kind} named or identified by \"${value}\". Available: ${available}.` } };\n}\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ChangeDetectorRef, OnDestroy, OnInit } from '@angular/core';
|
|
2
|
+
import { MJDashboardCategoryEntity, ResourceData } from '@memberjunction/core-entities';
|
|
3
|
+
import { BaseResourceComponent } from '@memberjunction/ng-shared';
|
|
4
|
+
import { CategoryRow } from './dashboards-categories.helpers';
|
|
5
|
+
import * as i0 from "@angular/core";
|
|
6
|
+
/**
|
|
7
|
+
* Categories page of the Dashboards app: the user's category tree with dashboard and sub-category
|
|
8
|
+
* counts, a New category box, Delete, after a confirm, for an empty category the user owns, and Open,
|
|
9
|
+
* which shows the category on the Library page. The agent tools are read-only; see the SAFETY
|
|
10
|
+
* BOUNDARY in the agent section below.
|
|
11
|
+
*/
|
|
12
|
+
export declare class DashboardsCategoriesResourceComponent extends BaseResourceComponent implements OnInit, OnDestroy {
|
|
13
|
+
private cdr;
|
|
14
|
+
private appManager;
|
|
15
|
+
private notifications;
|
|
16
|
+
private confirmService;
|
|
17
|
+
/** True until the categories have loaded. */
|
|
18
|
+
IsLoading: boolean;
|
|
19
|
+
/** True while a category is being created or deleted. */
|
|
20
|
+
IsSaving: boolean;
|
|
21
|
+
/** The category tree, one row per category, depth first. */
|
|
22
|
+
Rows: CategoryRow[];
|
|
23
|
+
/** The name typed in the New category box. */
|
|
24
|
+
NewName: string;
|
|
25
|
+
/** The longest category name the box accepts. */
|
|
26
|
+
readonly MaxNameLength = 100;
|
|
27
|
+
constructor(cdr: ChangeDetectorRef);
|
|
28
|
+
ngOnInit(): void;
|
|
29
|
+
ngOnDestroy(): void;
|
|
30
|
+
GetResourceDisplayName(_data: ResourceData): Promise<string>;
|
|
31
|
+
GetResourceIconClass(_data: ResourceData): Promise<string>;
|
|
32
|
+
/** True when the New category box holds a name and nothing is being saved. */
|
|
33
|
+
get CanCreate(): boolean;
|
|
34
|
+
/** The row's summary line, for example "3 dashboards · 1 sub-category". */
|
|
35
|
+
Summary(row: CategoryRow): string;
|
|
36
|
+
/** Creates a top-level category owned by the user from the New category box, then reloads the tree. */
|
|
37
|
+
Create(): Promise<void>;
|
|
38
|
+
/** Deletes an empty category the user owns, after the user confirms. */
|
|
39
|
+
Delete(category: MJDashboardCategoryEntity): Promise<void>;
|
|
40
|
+
/** Shows the category on the Library page of the Dashboards app. */
|
|
41
|
+
OpenInLibrary(category: MJDashboardCategoryEntity): Promise<void>;
|
|
42
|
+
private load;
|
|
43
|
+
/**
|
|
44
|
+
* Reloads the dashboard cache from the server, rebuilds the rows and reports them to the agent.
|
|
45
|
+
* Returns false when the reload fails.
|
|
46
|
+
*/
|
|
47
|
+
private reload;
|
|
48
|
+
private onCacheChanged;
|
|
49
|
+
private rebuildRows;
|
|
50
|
+
private openInLibrary;
|
|
51
|
+
private setSaving;
|
|
52
|
+
private emitAgentContext;
|
|
53
|
+
private buildAgentTools;
|
|
54
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DashboardsCategoriesResourceComponent, never>;
|
|
55
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<DashboardsCategoriesResourceComponent, "mj-dashboards-categories-resource", never, {}, {}, never, never, false, never>;
|
|
56
|
+
}
|
|
57
|
+
export declare function LoadDashboardsCategoriesResource(): void;
|
|
58
|
+
//# sourceMappingURL=dashboards-categories-resource.component.d.ts.map
|