@memberjunction/ng-explorer-core 5.50.0 → 6.1.0-edge.0
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/LICENSE +7 -0
- package/dist/app-routing.module.d.ts.map +1 -1
- package/dist/app-routing.module.js +5 -5
- package/dist/app-routing.module.js.map +1 -1
- package/dist/generated/lazy-feature-config.d.ts +11 -2
- package/dist/generated/lazy-feature-config.d.ts.map +1 -1
- package/dist/generated/lazy-feature-config.js +79 -23
- package/dist/generated/lazy-feature-config.js.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.d.ts.map +1 -1
- package/dist/lib/resource-wrappers/dashboard-resource.component.js +28 -0
- package/dist/lib/resource-wrappers/dashboard-resource.component.js.map +1 -1
- package/dist/lib/services/lazy-module-registry.d.ts +48 -8
- package/dist/lib/services/lazy-module-registry.d.ts.map +1 -1
- package/dist/lib/services/lazy-module-registry.js +103 -23
- package/dist/lib/services/lazy-module-registry.js.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts +2 -1
- package/dist/lib/shell/components/header/app-nav.component.d.ts.map +1 -1
- package/dist/lib/shell/components/header/app-nav.component.js +58 -42
- package/dist/lib/shell/components/header/app-nav.component.js.map +1 -1
- package/dist/lib/shell/components/record-open/record-bar.component.d.ts +29 -0
- package/dist/lib/shell/components/record-open/record-bar.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-bar.component.js +72 -0
- package/dist/lib/shell/components/record-open/record-bar.component.js.map +1 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts +26 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js +140 -0
- package/dist/lib/shell/components/record-open/record-origin-crumb.component.js.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.d.ts +43 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.js +102 -0
- package/dist/lib/shell/components/record-open/record-switcher-sheet.component.js.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.d.ts +21 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.js +30 -0
- package/dist/lib/shell/components/record-open/record-switcher.service.js.map +1 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.d.ts +63 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.d.ts.map +1 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.js +143 -0
- package/dist/lib/shell/components/record-open/records-hub-pill.component.js.map +1 -0
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts +228 -2
- package/dist/lib/shell/components/tabs/tab-container.component.d.ts.map +1 -1
- package/dist/lib/shell/components/tabs/tab-container.component.js +1073 -113
- package/dist/lib/shell/components/tabs/tab-container.component.js.map +1 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.d.ts.map +1 -1
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js +2 -0
- package/dist/lib/shell/services/sharing-center-dialog-host.component.js.map +1 -1
- package/dist/lib/shell/shell.component.d.ts +41 -4
- package/dist/lib/shell/shell.component.d.ts.map +1 -1
- package/dist/lib/shell/shell.component.js +400 -240
- package/dist/lib/shell/shell.component.js.map +1 -1
- package/dist/lib/shell/shell.module.d.ts +4 -1
- package/dist/lib/shell/shell.module.d.ts.map +1 -1
- package/dist/lib/shell/shell.module.js +14 -3
- package/dist/lib/shell/shell.module.js.map +1 -1
- package/package.json +62 -59
|
@@ -397,6 +397,18 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
397
397
|
instance.LoadCompleteEvent = () => {
|
|
398
398
|
this.NotifyLoadComplete();
|
|
399
399
|
};
|
|
400
|
+
// Surface the dashboard's own load failures — but only for the INITIAL load. Wrap the
|
|
401
|
+
// completion hook to learn when the first load has settled, so a later Refresh() that
|
|
402
|
+
// fails does NOT replace an already-rendered Data Explorer with a sticky error card.
|
|
403
|
+
let initialLoadSettled = false;
|
|
404
|
+
const onComplete = instance.LoadCompleteEvent;
|
|
405
|
+
instance.LoadCompleteEvent = () => { initialLoadSettled = true; onComplete?.(); };
|
|
406
|
+
instance.Error.subscribe((err) => {
|
|
407
|
+
if (initialLoadSettled)
|
|
408
|
+
return; // post-mount refresh failure — already logged by BaseDashboard.runGuardedLoad
|
|
409
|
+
this.setError('The Data Explorer could not be loaded.', err);
|
|
410
|
+
this.cdr.markForCheck();
|
|
411
|
+
});
|
|
400
412
|
// Initialize dashboard (no database config needed for DataExplorer)
|
|
401
413
|
const config = {
|
|
402
414
|
dashboard: null, // No database record
|
|
@@ -434,6 +446,22 @@ let DashboardResource = class DashboardResource extends BaseResourceComponent {
|
|
|
434
446
|
instance.LoadCompleteEvent = () => {
|
|
435
447
|
this.NotifyLoadComplete();
|
|
436
448
|
};
|
|
449
|
+
// Surface the dashboard's own load failures in the host's error card — but only for the
|
|
450
|
+
// INITIAL load. BaseDashboard guarantees the loading screen is released even when
|
|
451
|
+
// initDashboard()/loadData() throws (it emits Error, then NotifyLoadComplete in a
|
|
452
|
+
// finally). Wrap the completion hook to learn when the first load has settled, so a later
|
|
453
|
+
// Refresh() failure keeps the rendered dashboard instead of blanking it to a sticky error
|
|
454
|
+
// card. Wired BEFORE the first await below, so an Error during the instance's own
|
|
455
|
+
// ngOnInit isn't missed.
|
|
456
|
+
let initialLoadSettled = false;
|
|
457
|
+
const onComplete = instance.LoadCompleteEvent;
|
|
458
|
+
instance.LoadCompleteEvent = () => { initialLoadSettled = true; onComplete?.(); };
|
|
459
|
+
instance.Error.subscribe((err) => {
|
|
460
|
+
if (initialLoadSettled)
|
|
461
|
+
return; // post-mount refresh failure — already logged by BaseDashboard.runGuardedLoad
|
|
462
|
+
this.setError(`The dashboard "${dashboard.Name}" could not be loaded.`, err);
|
|
463
|
+
this.cdr.markForCheck();
|
|
464
|
+
});
|
|
437
465
|
// Initialize with dashboard data
|
|
438
466
|
const userStateEntity = await this.loadDashboardUserState(dashboard.ID);
|
|
439
467
|
const config = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard-resource.component.js","sourceRoot":"","sources":["../../../src/lib/resource-wrappers/dashboard-resource.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ViewContainerRef, ComponentRef, ViewChild, ElementRef, ChangeDetectorRef, inject } from '@angular/core';\nimport { BaseResourceComponent, NavigationService, BaseDashboard, DashboardConfig } from '@memberjunction/ng-shared';\nimport { ResourceData, MJDashboardEntity, DashboardEngine, MJDashboardUserStateEntity, MJDashboardCategoryEntity, MJDashboardPartTypeEntity, DashboardUserPermissions } from '@memberjunction/core-entities';\nimport { RegisterClass, MJGlobal, SafeJSONParse , UUIDsEqual } from '@memberjunction/global';\nimport { Metadata, CompositeKey, RunView, LogError } from '@memberjunction/core';\nimport type { DataExplorerFilter } from '@memberjunction/ng-dashboards/data-explorer-dashboards.module';\nimport type { ShareDialogResult } from '@memberjunction/ng-dashboards/core-dashboards.module';\nimport { DashboardViewerComponent, DashboardNavRequestEvent, PanelInteractionEvent, AddPanelResult, DashboardPanel } from '@memberjunction/ng-dashboard-viewer';\n/**\n * Dashboard Resource Wrapper - displays a single dashboard in a tab\n * Extends BaseResourceComponent to work with the resource type system\n * Dynamically routes between code-based and config-based dashboards based on dashboard type\n */\n@RegisterClass(BaseResourceComponent, 'DashboardResource')\n@Component({\n standalone: false,\n selector: 'mj-dashboard-resource',\n template: `\n <div class=\"dashboard-resource-wrapper\">\n <!-- Error State -->\n @if (errorMessage) {\n <div class=\"error-state\">\n <div class=\"error-icon\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n </div>\n <h2 class=\"error-title\">Unable to Load Dashboard</h2>\n <p class=\"error-message\">{{ errorMessage }}</p>\n @if (errorDetails) {\n <details class=\"error-details\">\n <summary>Technical Details</summary>\n <pre>{{ errorDetails }}</pre>\n </details>\n }\n </div>\n }\n\n <!-- View Mode Toolbar -->\n @if (configDashboard && !isEditMode && !errorMessage) {\n <div class=\"viewer-toolbar\">\n <div class=\"toolbar-left\">\n <span class=\"dashboard-title\">\n <i class=\"fa-solid fa-chart-line\"></i>\n {{ configDashboard.Name }}\n </span>\n @if (!dashboardPermissions.IsOwner && dashboardPermissions.PermissionSource !== 'none') {\n <span class=\"shared-indicator\" title=\"Shared with you\">\n <i class=\"fa-solid fa-share-nodes\"></i>\n </span>\n }\n </div>\n <div class=\"toolbar-actions\">\n @if (dashboardPermissions.CanShare) {\n <button\n class=\"btn-icon\"\n title=\"Share Dashboard\"\n (click)=\"openShareDialog()\">\n <i class=\"fa-solid fa-share-nodes\"></i>\n </button>\n }\n @if (dashboardPermissions.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\n <!-- Edit Mode Toolbar -->\n @if (configDashboard && isEditMode && !errorMessage) {\n <div class=\"viewer-header editing\">\n <div class=\"header-left\">\n <button class=\"btn-add-part\" (click)=\"openAddPartDialog()\">\n <i class=\"fa-solid fa-plus\"></i>\n Add Part\n </button>\n <div class=\"header-separator\"></div>\n <div class=\"dashboard-info-edit\">\n <input\n type=\"text\"\n class=\"dashboard-name-input\"\n [(ngModel)]=\"editingName\"\n placeholder=\"Dashboard name\">\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 <button class=\"btn-primary\" (click)=\"saveDashboard()\">\n <i class=\"fa-solid fa-save\"></i>\n Save\n </button>\n <button class=\"btn-cancel\" (click)=\"cancelEdit()\">\n Cancel\n </button>\n </div>\n </div>\n }\n\n <!-- Dashboard Content Container -->\n <div #container class=\"dashboard-resource-container\"></div>\n\n <!-- Share Dashboard Dialog -->\n @if (configDashboard) {\n <mj-dashboard-share-dialog\n [Visible]=\"showShareDialog\"\n [Dashboard]=\"configDashboard\"\n (Result)=\"onShareDialogResult($event)\">\n </mj-dashboard-share-dialog>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .dashboard-resource-wrapper {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n }\n .dashboard-resource-container {\n flex: 1;\n overflow: hidden;\n min-height: 0;\n }\n\n /* View Mode Toolbar */\n .viewer-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 24px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n gap: 16px;\n }\n .viewer-toolbar .toolbar-left {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n .viewer-toolbar .dashboard-title {\n font-size: 16px;\n font-weight: 500;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .viewer-toolbar .dashboard-title i {\n color: var(--mj-brand-primary);\n }\n .shared-indicator {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 11px;\n }\n .viewer-toolbar .toolbar-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n /* Edit Mode Header */\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-card);\n border-bottom: 1px solid var(--mj-border-default);\n transition: background 0.2s, border-color 0.2s;\n }\n .viewer-header.editing {\n background: linear-gradient(135deg, color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface)) 0%, color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-bg-surface)) 100%);\n border-bottom: 2px solid var(--mj-brand-primary);\n }\n .viewer-header .header-left {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n }\n .viewer-header .header-right {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n /* Add Part button */\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 .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 .btn-add-part i { font-size: 12px; }\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 /* Buttons */\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 .btn-primary:hover { background: var(--mj-brand-primary-hover); }\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-card);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.2s;\n }\n .btn-icon:hover { background: var(--mj-bg-surface-sunken); }\n\n .btn-cancel {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 18px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s;\n }\n .btn-cancel:hover {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-border-default);\n color: var(--mj-text-primary);\n }\n\n /* Dashboard info inputs */\n .dashboard-info-edit {\n display: flex;\n align-items: center;\n gap: 16px;\n flex: 1;\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: rgba(255, 255, 255, 0.7);\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 .dashboard-name-input:hover { background: rgba(255, 255, 255, 0.9); }\n .dashboard-name-input:focus {\n background: var(--mj-bg-surface-card);\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 .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: rgba(255, 255, 255, 0.5);\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 .dashboard-description-input:hover { background: rgba(255, 255, 255, 0.8); }\n .dashboard-description-input:focus {\n background: var(--mj-bg-surface-card);\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 .dashboard-description-input::placeholder {\n color: var(--mj-text-muted);\n font-style: normal;\n }\n\n /* Error state */\n .error-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 40px;\n text-align: center;\n color: var(--mj-text-secondary);\n }\n .error-icon {\n font-size: 64px;\n color: var(--mj-status-error);\n margin-bottom: 24px;\n opacity: 0.8;\n }\n .error-title {\n font-size: 24px;\n font-weight: 500;\n margin: 0 0 12px 0;\n color: var(--mj-text-primary);\n }\n .error-message {\n font-size: 16px;\n color: var(--mj-text-muted);\n margin: 0 0 24px 0;\n max-width: 500px;\n line-height: 1.5;\n }\n .error-details {\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 12px 16px;\n max-width: 600px;\n text-align: left;\n font-size: 13px;\n }\n .error-details summary {\n cursor: pointer;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin-bottom: 8px;\n }\n .error-details pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--mj-status-error-text);\n font-family: 'Consolas', 'Monaco', monospace;\n font-size: 12px;\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .viewer-header {\n flex-direction: column;\n gap: 12px;\n align-items: stretch;\n }\n .viewer-header .header-left { flex-wrap: wrap; }\n .dashboard-info-edit {\n flex-direction: column;\n align-items: stretch;\n }\n .dashboard-name-input,\n .dashboard-description-input { max-width: none; }\n }\n `]\n})\nexport class DashboardResource extends BaseResourceComponent {\n private componentRef: ComponentRef<unknown> | null = null;\n private dataLoaded = false;\n @ViewChild('container', { static: true }) containerElement!: ElementRef<HTMLDivElement>;\n\n /** Error message to display when dashboard fails to load */\n public errorMessage: string | null = null;\n /** Technical error details (shown in expandable section) */\n public errorDetails: string | null = null;\n\n /** Cached dashboard categories for breadcrumb navigation */\n private categories: MJDashboardCategoryEntity[] = [];\n\n /** Reference to the dashboard viewer component (for config-based dashboards) */\n private viewerInstance: DashboardViewerComponent | null = null;\n\n /** The config-based dashboard entity (null for code-based dashboards) */\n public configDashboard: MJDashboardEntity | null = null;\n\n /** Whether we're in edit mode */\n public isEditMode = false;\n\n /** Editing fields */\n public editingName = '';\n public editingDescription = '';\n\n /** Current user's permissions for this dashboard */\n public dashboardPermissions: DashboardUserPermissions = {\n DashboardID: '',\n CanRead: true,\n CanEdit: true,\n CanDelete: true,\n CanShare: true,\n IsOwner: true,\n PermissionSource: 'owner'\n };\n\n /** Whether the share dialog is visible */\n public showShareDialog = false;\n\n /**\n * Sets the error state with a user-friendly message and optional technical details\n */\n private setError(message: string, error?: unknown): void {\n this.errorMessage = message;\n if (error instanceof Error) {\n this.errorDetails = error.message;\n if (error.stack) {\n this.errorDetails += '\\n\\nStack trace:\\n' + error.stack;\n }\n } else if (error) {\n this.errorDetails = String(error);\n }\n }\n\n /**\n * Clears any previous error state\n */\n private clearError(): void {\n this.errorMessage = null;\n this.errorDetails = null;\n }\n\n constructor(\n private viewContainer: ViewContainerRef,\n private cdr: ChangeDetectorRef\n ) {\n super();\n }\n\n override set Data(value: ResourceData) {\n const previousRecordId = super.Data?.ResourceRecordID;\n super.Data = value;\n\n const newRecordId = value?.ResourceRecordID;\n\n // Load on first set, or when the dashboard has changed\n if (!this.dataLoaded || newRecordId !== previousRecordId) {\n this.dataLoaded = true;\n // Destroy previous component before loading new one\n if (this.componentRef) {\n this.componentRef.destroy();\n this.componentRef = null;\n }\n this.clearError();\n this.configDashboard = null;\n this.viewerInstance = null;\n this.loadDashboard();\n }\n }\n\n // Need to override the getter too in TS otherwise the override to the setter alone above would break things\n override get Data(): ResourceData {\n return super.Data;\n }\n\n ngOnDestroy(): void {\n super.ngOnDestroy();\n if (this.componentRef) {\n this.componentRef.destroy();\n }\n }\n\n // ========================================\n // Edit Mode Methods\n // ========================================\n\n /**\n * Toggle between view and edit mode\n */\n public toggleEditMode(): void {\n if (this.isEditMode) {\n this.cancelEdit();\n } else {\n this.enterEditMode();\n }\n }\n\n /**\n * Enter edit mode\n */\n private enterEditMode(): void {\n if (!this.configDashboard) return;\n\n this.isEditMode = true;\n this.editingName = this.configDashboard.Name;\n this.editingDescription = this.configDashboard.Description || '';\n\n // Tell the viewer to enter edit mode\n if (this.viewerInstance) {\n this.viewerInstance.isEditing = true;\n }\n\n this.cdr.detectChanges();\n }\n\n /**\n * Cancel edit mode and discard changes\n */\n public cancelEdit(): void {\n this.isEditMode = false;\n\n // Tell the viewer to exit edit mode\n if (this.viewerInstance) {\n this.viewerInstance.isEditing = false;\n }\n\n this.cdr.detectChanges();\n }\n\n /**\n * Save dashboard changes\n */\n public async saveDashboard(): Promise<void> {\n if (!this.configDashboard || !this.viewerInstance) return;\n\n try {\n // Update dashboard name and description\n this.configDashboard.Name = this.editingName;\n this.configDashboard.Description = this.editingDescription;\n\n // Save via the viewer (which handles layout saving)\n await this.viewerInstance.save();\n\n // Exit edit mode\n this.isEditMode = false;\n this.viewerInstance.isEditing = false;\n\n this.cdr.detectChanges();\n } catch (error) {\n console.error('Error saving dashboard:', error);\n }\n }\n\n /**\n * Open the add panel dialog\n */\n public openAddPartDialog(): void {\n if (this.viewerInstance) {\n // Trigger the viewer's add panel flow\n this.viewerInstance.onAddPanelClick();\n }\n }\n\n /**\n * Open the share dialog for this dashboard\n */\n public openShareDialog(): void {\n this.showShareDialog = true;\n this.cdr.detectChanges();\n }\n\n /**\n * Close the share dialog\n */\n public closeShareDialog(): void {\n this.showShareDialog = false;\n this.cdr.detectChanges();\n }\n\n /**\n * Handle share dialog result\n */\n public onShareDialogResult(result: ShareDialogResult): void {\n this.showShareDialog = false;\n\n if (result.Action === 'save' && this.configDashboard) {\n // Recompute permissions after sharing changes\n const md = this.ProviderToUse;\n this.dashboardPermissions = DashboardEngine.Instance.GetDashboardPermissions(\n this.configDashboard.ID,\n md.CurrentUser.ID\n );\n }\n\n this.cdr.detectChanges();\n }\n\n /**\n * Load the appropriate dashboard component based on dashboard type\n * Routes between code-based dashboards (registered classes) and config-based dashboards\n */\n private async loadDashboard(): Promise<void> {\n // Clear any previous error state\n this.clearError();\n\n const data = this.Data;\n\n if (!data?.ResourceRecordID) {\n this.NotifyLoadStarted();\n this.NotifyLoadComplete();\n return;\n }\n\n this.NotifyLoadStarted();\n\n try {\n // Check if this is a special dashboard type (not a database record)\n const config = data.Configuration || {};\n\n if (config['dashboardType'] === 'DataExplorer' || data.ResourceRecordID === 'DataExplorer') {\n // Special case: Data Explorer dashboard with optional entity filter\n await this.loadDataExplorer(\n config['entityFilter'],\n config['appName'] as string | undefined,\n config['appIcon'] as string | undefined\n );\n return;\n }\n\n await DashboardEngine.Instance.Config(false); // make sure it is configured, if already configured does nothing\n const dashboard = DashboardEngine.Instance.Dashboards.find(d => UUIDsEqual(d.ID, data.ResourceRecordID));\n if (!dashboard) {\n throw new Error(`Dashboard with ID ${data.ResourceRecordID} not found.`);\n }\n\n // Determine which dashboard component to load based on dashboard type\n if (dashboard.Type === 'Code') {\n // CODE-BASED DASHBOARD: Use registered class via DriverClass\n await this.loadCodeBasedDashboard(dashboard);\n } else {\n // CONFIG-BASED DASHBOARD: Use the generic metadata-driven renderer\n await this.loadConfigBasedDashboard(dashboard);\n }\n } catch (error) {\n console.error('Error loading dashboard:', error);\n this.setError('The dashboard could not be loaded. This may be due to a missing component or configuration issue.', error);\n this.NotifyLoadComplete();\n }\n }\n\n /**\n * Load the Data Explorer dashboard component with optional entity filter and context info\n * @param entityFilter Optional filter to constrain which entities are shown\n * @param contextName Optional name to display in the header (e.g., \"CRM\", \"Association Demo\")\n * @param contextIcon Optional Font Awesome icon class for the header\n */\n private async loadDataExplorer(\n entityFilter?: DataExplorerFilter,\n contextName?: string,\n contextIcon?: string\n ): Promise<void> {\n try {\n // Lazy-load the Data Explorer component to keep it out of the initial bundle\n const { DataExplorerDashboardComponent } = await import('@memberjunction/ng-dashboards/data-explorer-dashboards.module');\n this.containerElement.nativeElement.innerHTML = '';\n const componentRef = this.viewContainer.createComponent(DataExplorerDashboardComponent);\n this.componentRef = componentRef;\n const instance = componentRef.instance;\n\n // Set the entity filter - ngOnInit will use this when it runs\n if (entityFilter) {\n instance.entityFilter = entityFilter;\n }\n\n // Set context name and icon for customized header display\n if (contextName) {\n instance.contextName = contextName;\n }\n if (contextIcon) {\n instance.contextIcon = contextIcon;\n }\n\n // Manually append the component's native element inside the div\n const nativeElement = (componentRef.hostView as any).rootNodes[0];\n nativeElement.style.width = '100%';\n nativeElement.style.height = '100%';\n this.containerElement.nativeElement.appendChild(nativeElement);\n\n // Handle open entity record events\n instance.OpenEntityRecord.subscribe((eventData: { EntityName: string; RecordPKey: CompositeKey }) => {\n if (eventData && eventData.EntityName && eventData.RecordPKey) {\n this.navigationService.OpenEntityRecord(eventData.EntityName, eventData.RecordPKey);\n }\n });\n\n // Setup LoadCompleteEvent to know when the dashboard is ready\n instance.LoadCompleteEvent = () => {\n this.NotifyLoadComplete();\n };\n\n // Initialize dashboard (no database config needed for DataExplorer)\n const config: DashboardConfig = {\n dashboard: null as unknown as MJDashboardEntity, // No database record\n userState: {}\n };\n instance.Config = config;\n instance.Refresh();\n\n // Trigger change detection to ensure the component updates\n componentRef.changeDetectorRef.detectChanges();\n } catch (error) {\n console.error('Error loading Data Explorer:', error);\n this.setError('The Data Explorer could not be loaded.', error);\n this.NotifyLoadComplete();\n }\n }\n\n /**\n * Load a code-based dashboard by looking up the registered class\n */\n private async loadCodeBasedDashboard(dashboard: MJDashboardEntity): Promise<void> {\n try {\n if (!dashboard.DriverClass) {\n throw new Error(`Dashboard '${dashboard.Name}' is marked as Code type but has no DriverClass specified`);\n }\n\n // Look up the registered class using the DriverClass name (with lazy loading fallback via ClassFactory)\n const classReg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(\n BaseDashboard,\n dashboard.DriverClass\n );\n\n if (!classReg?.SubClass) {\n throw new Error(`Dashboard class '${dashboard.DriverClass}' is not registered. Please check the class registration.`);\n }\n\n // Create the component instance\n this.containerElement.nativeElement.innerHTML = '';\n this.componentRef = this.viewContainer.createComponent<BaseDashboard>(classReg.SubClass);\n const instance = this.componentRef.instance as BaseDashboard;\n\n // Setup LoadCompleteEvent() to know when the dashboard is ready\n instance.LoadCompleteEvent = () => {\n this.NotifyLoadComplete();\n };\n\n // Initialize with dashboard data\n const userStateEntity = await this.loadDashboardUserState(dashboard.ID);\n const config: DashboardConfig = {\n dashboard,\n userState: userStateEntity.UserState ? SafeJSONParse(userStateEntity.UserState) : {}\n };\n\n instance.Config = config;\n\n // Manually append the component's native element inside the div\n const nativeElement = (this.componentRef.hostView as any).rootNodes[0];\n nativeElement.style.width = '100%';\n nativeElement.style.height = '100%';\n this.containerElement.nativeElement.appendChild(nativeElement);\n\n // handle open entity record events in MJ Explorer with routing\n instance.OpenEntityRecord.subscribe((data: { EntityName: string; RecordPKey: CompositeKey }) => {\n // check to see if the data has entityname/pkey\n if (data && data.EntityName && data.RecordPKey) {\n // Use NavigationService to open entity record in new tab\n this.navigationService.OpenEntityRecord(data.EntityName, data.RecordPKey);\n } else {\n console.warn('DashboardResource - invalid data, missing EntityName or RecordPKey:', data);\n }\n });\n\n instance.UserStateChanged.subscribe(async (userState: any) => {\n if (!userState) {\n // if the user state is null, we need to remove it from the user state\n userState = {};\n }\n // save the user state to the dashboard user state entity\n userStateEntity.UserState = JSON.stringify(userState);\n if (!await userStateEntity.Save()) {\n LogError('Error saving user state', null, userStateEntity.LatestResult?.CompleteMessage);\n }\n });\n\n instance.Refresh();\n } catch (error) {\n console.error('Error loading code-based dashboard:', error);\n this.setError(`The dashboard \"${dashboard.Name}\" could not be loaded. The dashboard class may not be registered or may have failed to initialize.`, error);\n this.NotifyLoadComplete();\n }\n }\n\n protected async loadDashboardUserState(dashboardId: string): Promise<MJDashboardUserStateEntity> {\n // handle user state changes for the dashboard\n const md = this.ProviderToUse;\n const stateResult = DashboardEngine.Instance.DashboardUserStates.filter(dus => UUIDsEqual(dus.DashboardID, dashboardId) && UUIDsEqual(dus.UserID, md.CurrentUser.ID));\n let stateObject: MJDashboardUserStateEntity;\n if (stateResult && stateResult.length > 0) {\n stateObject = stateResult[0];\n }\n else {\n stateObject = await md.GetEntityObject<MJDashboardUserStateEntity>('MJ: Dashboard User States');\n stateObject.DashboardID = dashboardId;\n stateObject.UserID = md.CurrentUser.ID;\n // don't save becuase we don't care about the state until something changes\n }\n return stateObject;\n }\n\n /**\n * Load a config-based dashboard using the new DashboardViewerComponent (Golden Layout)\n */\n private async loadConfigBasedDashboard(dashboard: MJDashboardEntity): Promise<void> {\n try {\n this.containerElement.nativeElement.innerHTML = '';\n const componentRef = this.viewContainer.createComponent(DashboardViewerComponent);\n this.componentRef = componentRef;\n const instance = componentRef.instance;\n\n // Store references for external toolbar control\n this.viewerInstance = instance;\n this.configDashboard = dashboard;\n\n // Compute user permissions for this dashboard\n const md = this.ProviderToUse;\n this.dashboardPermissions = DashboardEngine.Instance.GetDashboardPermissions(\n dashboard.ID,\n md.CurrentUser.ID\n );\n\n // Manually append the component's native element inside the div\n const nativeElement = (this.componentRef.hostView as any).rootNodes[0];\n nativeElement.style.width = '100%';\n nativeElement.style.height = '100%';\n this.containerElement.nativeElement.appendChild(nativeElement);\n\n // Load categories for breadcrumb navigation (if not already loaded)\n if (this.categories.length === 0) {\n this.categories = DashboardEngine.Instance.DashboardCategories;\n }\n\n // Configure the viewer before assigning the dashboard. The dashboard input\n // starts the layout lifecycle synchronously, so event handlers need to be\n // wired first.\n instance.showToolbar = false; // We provide external toolbar\n instance.showBreadcrumb = false; // Already in its own tab, no breadcrumb needed\n instance.showOpenInTabButton = false; // Already in its own tab\n instance.showEditButton = false; // External toolbar handles edit\n instance.Categories = this.categories;\n\n // Wire up navigation events - handle navigation requests from the dashboard\n instance.navigationRequested.subscribe((event: DashboardNavRequestEvent) => {\n this.handleNavigationRequest(event);\n });\n\n // Wire up \"Open in Tab\" button click\n instance.openInTab.subscribe((event: { dashboardId: string; dashboardName: string }) => {\n this.navigationService.OpenDashboard(event.dashboardId, event.dashboardName);\n });\n\n // Wire up dashboard saved event\n instance.dashboardSaved.subscribe((savedDashboard: MJDashboardEntity) => {\n this.ResourceRecordSaved(savedDashboard);\n });\n\n // Wire up error events\n instance.error.subscribe((errorEvent: { message: string; error?: Error }) => {\n console.error('Dashboard error:', errorEvent.message, errorEvent.error);\n });\n\n // Set the dashboard entity directly on the viewer and wait for Golden Layout\n // to initialize against a real container before clearing the resource loader.\n instance.dashboard = dashboard;\n await instance.waitForLayoutReady();\n this.NotifyLoadComplete();\n this.cdr.detectChanges();\n\n } catch (error) {\n console.error('Error loading config-based dashboard:', error);\n this.setError(`The dashboard \"${dashboard.Name}\" could not be loaded. There may be an issue with the dashboard configuration.`, error);\n this.NotifyLoadComplete();\n }\n }\n\n /**\n * Handle navigation requests from the dashboard viewer\n */\n private handleNavigationRequest(event: DashboardNavRequestEvent): void {\n const request = event.request;\n\n switch (request.type) {\n case 'OpenEntityRecord': {\n const entityRequest = request as { type: 'OpenEntityRecord'; entityName: string; recordId: string };\n // `recordId` is documented as URL-segment format (see OpenEntityRecordNavRequest in\n // @memberjunction/ng-dashboard-viewer). E.g. a single-PK record is `\"ID|11055\"`; a\n // composite-PK record is `\"Field1|Value1||Field2|Value2\"`. Senders in\n // dashboard-viewer (artifact-part, view-part) intentionally call\n // `compositeKey.ToURLSegment()` to produce this shape.\n //\n // Wrapping that string verbatim into `{ FieldName: 'ID', Value: <segment> }` makes\n // ToURLSegment serialize it a second time as `ID|<segment>` and produces a\n // malformed `Field|Field|Value` URL the host parser silently mis-reads (manifesting\n // downstream as `BaseEntity.Load(... Key: ID=ID)` and `Primary Key value is not a\n // valid number`). Parse the segment with `LoadFromURLSegment` against the entity's\n // PK metadata instead, so single-PK and composite-PK both round-trip correctly.\n const md = this.ProviderToUse;\n const entityInfo = md.EntityByName(entityRequest.entityName);\n const pkey = new CompositeKey();\n if (entityInfo) {\n pkey.LoadFromURLSegment(entityInfo, entityRequest.recordId);\n } else {\n // Last-resort fallback when entity metadata isn't resolvable — preserves\n // pre-fix behavior so we don't NRE on an unknown entity name.\n pkey.KeyValuePairs = [{ FieldName: 'ID', Value: entityRequest.recordId }];\n }\n this.navigationService.OpenEntityRecord(entityRequest.entityName, pkey);\n break;\n }\n case 'OpenDashboard': {\n const dashRequest = request as { type: 'OpenDashboard'; dashboardId: string };\n // Load dashboard name from engine cache\n const targetDashboard = DashboardEngine.Instance.Dashboards.find(d => UUIDsEqual(d.ID, dashRequest.dashboardId));\n const name = targetDashboard?.Name || 'Dashboard';\n this.navigationService.OpenDashboard(dashRequest.dashboardId, name);\n break;\n }\n case 'OpenQuery': {\n const queryRequest = request as { type: 'OpenQuery'; queryId: string };\n this.navigationService.OpenQuery(queryRequest.queryId, 'Query');\n break;\n }\n default:\n console.warn('Unhandled navigation request type:', request.type);\n }\n }\n\n /**\n * Get the display name for a dashboard resource\n * Loads the actual dashboard name from the database if available\n */\n override async GetResourceDisplayName(data: ResourceData): Promise<string> {\n try {\n // Try to load dashboard metadata if we have the record ID\n if (data.ResourceRecordID && data.ResourceRecordID.length > 0) {\n const md = this.ProviderToUse;\n const compositeKey = new CompositeKey([{ FieldName: 'ID', Value: data.ResourceRecordID }]);\n const name = await md.GetEntityRecordName('Dashboards', compositeKey);\n if (name) {\n return name;\n }\n }\n } catch (error) {\n // Silently fail and use fallback\n }\n\n // Fallback: use provided name or generic label\n return data.Name || 'Dashboard';\n }\n\n /**\n * Get the icon class for dashboard resources\n */\n async GetResourceIconClass(data: ResourceData): Promise<string> {\n return 'fa-solid fa-table-columns';\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"dashboard-resource.component.js","sourceRoot":"","sources":["../../../src/lib/resource-wrappers/dashboard-resource.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ViewContainerRef, ComponentRef, ViewChild, ElementRef, ChangeDetectorRef, inject } from '@angular/core';\nimport { BaseResourceComponent, NavigationService, BaseDashboard, DashboardConfig } from '@memberjunction/ng-shared';\nimport { ResourceData, MJDashboardEntity, DashboardEngine, MJDashboardUserStateEntity, MJDashboardCategoryEntity, MJDashboardPartTypeEntity, DashboardUserPermissions } from '@memberjunction/core-entities';\nimport { RegisterClass, MJGlobal, SafeJSONParse , UUIDsEqual } from '@memberjunction/global';\nimport { Metadata, CompositeKey, RunView, LogError } from '@memberjunction/core';\nimport type { DataExplorerFilter } from '@memberjunction/ng-dashboards/data-explorer-dashboards.module';\nimport type { ShareDialogResult } from '@memberjunction/ng-dashboards/core-dashboards.module';\nimport { DashboardViewerComponent, DashboardNavRequestEvent, PanelInteractionEvent, AddPanelResult, DashboardPanel } from '@memberjunction/ng-dashboard-viewer';\n/**\n * Dashboard Resource Wrapper - displays a single dashboard in a tab\n * Extends BaseResourceComponent to work with the resource type system\n * Dynamically routes between code-based and config-based dashboards based on dashboard type\n */\n@RegisterClass(BaseResourceComponent, 'DashboardResource')\n@Component({\n standalone: false,\n selector: 'mj-dashboard-resource',\n template: `\n <div class=\"dashboard-resource-wrapper\">\n <!-- Error State -->\n @if (errorMessage) {\n <div class=\"error-state\">\n <div class=\"error-icon\">\n <i class=\"fa-solid fa-triangle-exclamation\"></i>\n </div>\n <h2 class=\"error-title\">Unable to Load Dashboard</h2>\n <p class=\"error-message\">{{ errorMessage }}</p>\n @if (errorDetails) {\n <details class=\"error-details\">\n <summary>Technical Details</summary>\n <pre>{{ errorDetails }}</pre>\n </details>\n }\n </div>\n }\n\n <!-- View Mode Toolbar -->\n @if (configDashboard && !isEditMode && !errorMessage) {\n <div class=\"viewer-toolbar\">\n <div class=\"toolbar-left\">\n <span class=\"dashboard-title\">\n <i class=\"fa-solid fa-chart-line\"></i>\n {{ configDashboard.Name }}\n </span>\n @if (!dashboardPermissions.IsOwner && dashboardPermissions.PermissionSource !== 'none') {\n <span class=\"shared-indicator\" title=\"Shared with you\">\n <i class=\"fa-solid fa-share-nodes\"></i>\n </span>\n }\n </div>\n <div class=\"toolbar-actions\">\n @if (dashboardPermissions.CanShare) {\n <button\n class=\"btn-icon\"\n title=\"Share Dashboard\"\n (click)=\"openShareDialog()\">\n <i class=\"fa-solid fa-share-nodes\"></i>\n </button>\n }\n @if (dashboardPermissions.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\n <!-- Edit Mode Toolbar -->\n @if (configDashboard && isEditMode && !errorMessage) {\n <div class=\"viewer-header editing\">\n <div class=\"header-left\">\n <button class=\"btn-add-part\" (click)=\"openAddPartDialog()\">\n <i class=\"fa-solid fa-plus\"></i>\n Add Part\n </button>\n <div class=\"header-separator\"></div>\n <div class=\"dashboard-info-edit\">\n <input\n type=\"text\"\n class=\"dashboard-name-input\"\n [(ngModel)]=\"editingName\"\n placeholder=\"Dashboard name\">\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 <button class=\"btn-primary\" (click)=\"saveDashboard()\">\n <i class=\"fa-solid fa-save\"></i>\n Save\n </button>\n <button class=\"btn-cancel\" (click)=\"cancelEdit()\">\n Cancel\n </button>\n </div>\n </div>\n }\n\n <!-- Dashboard Content Container -->\n <div #container class=\"dashboard-resource-container\"></div>\n\n <!-- Share Dashboard Dialog -->\n @if (configDashboard) {\n <mj-dashboard-share-dialog\n [Visible]=\"showShareDialog\"\n [Dashboard]=\"configDashboard\"\n (Result)=\"onShareDialogResult($event)\">\n </mj-dashboard-share-dialog>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n }\n .dashboard-resource-wrapper {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n }\n .dashboard-resource-container {\n flex: 1;\n overflow: hidden;\n min-height: 0;\n }\n\n /* View Mode Toolbar */\n .viewer-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 24px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n gap: 16px;\n }\n .viewer-toolbar .toolbar-left {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n .viewer-toolbar .dashboard-title {\n font-size: 16px;\n font-weight: 500;\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .viewer-toolbar .dashboard-title i {\n color: var(--mj-brand-primary);\n }\n .shared-indicator {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 11px;\n }\n .viewer-toolbar .toolbar-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n /* Edit Mode Header */\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-card);\n border-bottom: 1px solid var(--mj-border-default);\n transition: background 0.2s, border-color 0.2s;\n }\n .viewer-header.editing {\n background: linear-gradient(135deg, color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface)) 0%, color-mix(in srgb, var(--mj-brand-primary) 25%, var(--mj-bg-surface)) 100%);\n border-bottom: 2px solid var(--mj-brand-primary);\n }\n .viewer-header .header-left {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n }\n .viewer-header .header-right {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n /* Add Part button */\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 .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 .btn-add-part i { font-size: 12px; }\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 /* Buttons */\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 .btn-primary:hover { background: var(--mj-brand-primary-hover); }\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-card);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.2s;\n }\n .btn-icon:hover { background: var(--mj-bg-surface-sunken); }\n\n .btn-cancel {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 18px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s;\n }\n .btn-cancel:hover {\n background: var(--mj-bg-surface-sunken);\n border-color: var(--mj-border-default);\n color: var(--mj-text-primary);\n }\n\n /* Dashboard info inputs */\n .dashboard-info-edit {\n display: flex;\n align-items: center;\n gap: 16px;\n flex: 1;\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: rgba(255, 255, 255, 0.7);\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 .dashboard-name-input:hover { background: rgba(255, 255, 255, 0.9); }\n .dashboard-name-input:focus {\n background: var(--mj-bg-surface-card);\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 .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: rgba(255, 255, 255, 0.5);\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 .dashboard-description-input:hover { background: rgba(255, 255, 255, 0.8); }\n .dashboard-description-input:focus {\n background: var(--mj-bg-surface-card);\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 .dashboard-description-input::placeholder {\n color: var(--mj-text-muted);\n font-style: normal;\n }\n\n /* Error state */\n .error-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 40px;\n text-align: center;\n color: var(--mj-text-secondary);\n }\n .error-icon {\n font-size: 64px;\n color: var(--mj-status-error);\n margin-bottom: 24px;\n opacity: 0.8;\n }\n .error-title {\n font-size: 24px;\n font-weight: 500;\n margin: 0 0 12px 0;\n color: var(--mj-text-primary);\n }\n .error-message {\n font-size: 16px;\n color: var(--mj-text-muted);\n margin: 0 0 24px 0;\n max-width: 500px;\n line-height: 1.5;\n }\n .error-details {\n background: var(--mj-bg-surface-sunken);\n border-radius: 8px;\n padding: 12px 16px;\n max-width: 600px;\n text-align: left;\n font-size: 13px;\n }\n .error-details summary {\n cursor: pointer;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin-bottom: 8px;\n }\n .error-details pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n color: var(--mj-status-error-text);\n font-family: 'Consolas', 'Monaco', monospace;\n font-size: 12px;\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .viewer-header {\n flex-direction: column;\n gap: 12px;\n align-items: stretch;\n }\n .viewer-header .header-left { flex-wrap: wrap; }\n .dashboard-info-edit {\n flex-direction: column;\n align-items: stretch;\n }\n .dashboard-name-input,\n .dashboard-description-input { max-width: none; }\n }\n `]\n})\nexport class DashboardResource extends BaseResourceComponent {\n private componentRef: ComponentRef<unknown> | null = null;\n private dataLoaded = false;\n @ViewChild('container', { static: true }) containerElement!: ElementRef<HTMLDivElement>;\n\n /** Error message to display when dashboard fails to load */\n public errorMessage: string | null = null;\n /** Technical error details (shown in expandable section) */\n public errorDetails: string | null = null;\n\n /** Cached dashboard categories for breadcrumb navigation */\n private categories: MJDashboardCategoryEntity[] = [];\n\n /** Reference to the dashboard viewer component (for config-based dashboards) */\n private viewerInstance: DashboardViewerComponent | null = null;\n\n /** The config-based dashboard entity (null for code-based dashboards) */\n public configDashboard: MJDashboardEntity | null = null;\n\n /** Whether we're in edit mode */\n public isEditMode = false;\n\n /** Editing fields */\n public editingName = '';\n public editingDescription = '';\n\n /** Current user's permissions for this dashboard */\n public dashboardPermissions: DashboardUserPermissions = {\n DashboardID: '',\n CanRead: true,\n CanEdit: true,\n CanDelete: true,\n CanShare: true,\n IsOwner: true,\n PermissionSource: 'owner'\n };\n\n /** Whether the share dialog is visible */\n public showShareDialog = false;\n\n /**\n * Sets the error state with a user-friendly message and optional technical details\n */\n private setError(message: string, error?: unknown): void {\n this.errorMessage = message;\n if (error instanceof Error) {\n this.errorDetails = error.message;\n if (error.stack) {\n this.errorDetails += '\\n\\nStack trace:\\n' + error.stack;\n }\n } else if (error) {\n this.errorDetails = String(error);\n }\n }\n\n /**\n * Clears any previous error state\n */\n private clearError(): void {\n this.errorMessage = null;\n this.errorDetails = null;\n }\n\n constructor(\n private viewContainer: ViewContainerRef,\n private cdr: ChangeDetectorRef\n ) {\n super();\n }\n\n override set Data(value: ResourceData) {\n const previousRecordId = super.Data?.ResourceRecordID;\n super.Data = value;\n\n const newRecordId = value?.ResourceRecordID;\n\n // Load on first set, or when the dashboard has changed\n if (!this.dataLoaded || newRecordId !== previousRecordId) {\n this.dataLoaded = true;\n // Destroy previous component before loading new one\n if (this.componentRef) {\n this.componentRef.destroy();\n this.componentRef = null;\n }\n this.clearError();\n this.configDashboard = null;\n this.viewerInstance = null;\n this.loadDashboard();\n }\n }\n\n // Need to override the getter too in TS otherwise the override to the setter alone above would break things\n override get Data(): ResourceData {\n return super.Data;\n }\n\n ngOnDestroy(): void {\n super.ngOnDestroy();\n if (this.componentRef) {\n this.componentRef.destroy();\n }\n }\n\n // ========================================\n // Edit Mode Methods\n // ========================================\n\n /**\n * Toggle between view and edit mode\n */\n public toggleEditMode(): void {\n if (this.isEditMode) {\n this.cancelEdit();\n } else {\n this.enterEditMode();\n }\n }\n\n /**\n * Enter edit mode\n */\n private enterEditMode(): void {\n if (!this.configDashboard) return;\n\n this.isEditMode = true;\n this.editingName = this.configDashboard.Name;\n this.editingDescription = this.configDashboard.Description || '';\n\n // Tell the viewer to enter edit mode\n if (this.viewerInstance) {\n this.viewerInstance.isEditing = true;\n }\n\n this.cdr.detectChanges();\n }\n\n /**\n * Cancel edit mode and discard changes\n */\n public cancelEdit(): void {\n this.isEditMode = false;\n\n // Tell the viewer to exit edit mode\n if (this.viewerInstance) {\n this.viewerInstance.isEditing = false;\n }\n\n this.cdr.detectChanges();\n }\n\n /**\n * Save dashboard changes\n */\n public async saveDashboard(): Promise<void> {\n if (!this.configDashboard || !this.viewerInstance) return;\n\n try {\n // Update dashboard name and description\n this.configDashboard.Name = this.editingName;\n this.configDashboard.Description = this.editingDescription;\n\n // Save via the viewer (which handles layout saving)\n await this.viewerInstance.save();\n\n // Exit edit mode\n this.isEditMode = false;\n this.viewerInstance.isEditing = false;\n\n this.cdr.detectChanges();\n } catch (error) {\n console.error('Error saving dashboard:', error);\n }\n }\n\n /**\n * Open the add panel dialog\n */\n public openAddPartDialog(): void {\n if (this.viewerInstance) {\n // Trigger the viewer's add panel flow\n this.viewerInstance.onAddPanelClick();\n }\n }\n\n /**\n * Open the share dialog for this dashboard\n */\n public openShareDialog(): void {\n this.showShareDialog = true;\n this.cdr.detectChanges();\n }\n\n /**\n * Close the share dialog\n */\n public closeShareDialog(): void {\n this.showShareDialog = false;\n this.cdr.detectChanges();\n }\n\n /**\n * Handle share dialog result\n */\n public onShareDialogResult(result: ShareDialogResult): void {\n this.showShareDialog = false;\n\n if (result.Action === 'save' && this.configDashboard) {\n // Recompute permissions after sharing changes\n const md = this.ProviderToUse;\n this.dashboardPermissions = DashboardEngine.Instance.GetDashboardPermissions(\n this.configDashboard.ID,\n md.CurrentUser.ID\n );\n }\n\n this.cdr.detectChanges();\n }\n\n /**\n * Load the appropriate dashboard component based on dashboard type\n * Routes between code-based dashboards (registered classes) and config-based dashboards\n */\n private async loadDashboard(): Promise<void> {\n // Clear any previous error state\n this.clearError();\n\n const data = this.Data;\n\n if (!data?.ResourceRecordID) {\n this.NotifyLoadStarted();\n this.NotifyLoadComplete();\n return;\n }\n\n this.NotifyLoadStarted();\n\n try {\n // Check if this is a special dashboard type (not a database record)\n const config = data.Configuration || {};\n\n if (config['dashboardType'] === 'DataExplorer' || data.ResourceRecordID === 'DataExplorer') {\n // Special case: Data Explorer dashboard with optional entity filter\n await this.loadDataExplorer(\n config['entityFilter'],\n config['appName'] as string | undefined,\n config['appIcon'] as string | undefined\n );\n return;\n }\n\n await DashboardEngine.Instance.Config(false); // make sure it is configured, if already configured does nothing\n const dashboard = DashboardEngine.Instance.Dashboards.find(d => UUIDsEqual(d.ID, data.ResourceRecordID));\n if (!dashboard) {\n throw new Error(`Dashboard with ID ${data.ResourceRecordID} not found.`);\n }\n\n // Determine which dashboard component to load based on dashboard type\n if (dashboard.Type === 'Code') {\n // CODE-BASED DASHBOARD: Use registered class via DriverClass\n await this.loadCodeBasedDashboard(dashboard);\n } else {\n // CONFIG-BASED DASHBOARD: Use the generic metadata-driven renderer\n await this.loadConfigBasedDashboard(dashboard);\n }\n } catch (error) {\n console.error('Error loading dashboard:', error);\n this.setError('The dashboard could not be loaded. This may be due to a missing component or configuration issue.', error);\n this.NotifyLoadComplete();\n }\n }\n\n /**\n * Load the Data Explorer dashboard component with optional entity filter and context info\n * @param entityFilter Optional filter to constrain which entities are shown\n * @param contextName Optional name to display in the header (e.g., \"CRM\", \"Association Demo\")\n * @param contextIcon Optional Font Awesome icon class for the header\n */\n private async loadDataExplorer(\n entityFilter?: DataExplorerFilter,\n contextName?: string,\n contextIcon?: string\n ): Promise<void> {\n try {\n // Lazy-load the Data Explorer component to keep it out of the initial bundle\n const { DataExplorerDashboardComponent } = await import('@memberjunction/ng-dashboards/data-explorer-dashboards.module');\n this.containerElement.nativeElement.innerHTML = '';\n const componentRef = this.viewContainer.createComponent(DataExplorerDashboardComponent);\n this.componentRef = componentRef;\n const instance = componentRef.instance;\n\n // Set the entity filter - ngOnInit will use this when it runs\n if (entityFilter) {\n instance.entityFilter = entityFilter;\n }\n\n // Set context name and icon for customized header display\n if (contextName) {\n instance.contextName = contextName;\n }\n if (contextIcon) {\n instance.contextIcon = contextIcon;\n }\n\n // Manually append the component's native element inside the div\n const nativeElement = (componentRef.hostView as any).rootNodes[0];\n nativeElement.style.width = '100%';\n nativeElement.style.height = '100%';\n this.containerElement.nativeElement.appendChild(nativeElement);\n\n // Handle open entity record events\n instance.OpenEntityRecord.subscribe((eventData: { EntityName: string; RecordPKey: CompositeKey }) => {\n if (eventData && eventData.EntityName && eventData.RecordPKey) {\n this.navigationService.OpenEntityRecord(eventData.EntityName, eventData.RecordPKey);\n }\n });\n\n // Setup LoadCompleteEvent to know when the dashboard is ready\n instance.LoadCompleteEvent = () => {\n this.NotifyLoadComplete();\n };\n\n // Surface the dashboard's own load failures — but only for the INITIAL load. Wrap the\n // completion hook to learn when the first load has settled, so a later Refresh() that\n // fails does NOT replace an already-rendered Data Explorer with a sticky error card.\n let initialLoadSettled = false;\n const onComplete = instance.LoadCompleteEvent;\n instance.LoadCompleteEvent = () => { initialLoadSettled = true; onComplete?.(); };\n instance.Error.subscribe((err: Error) => {\n if (initialLoadSettled) return; // post-mount refresh failure — already logged by BaseDashboard.runGuardedLoad\n this.setError('The Data Explorer could not be loaded.', err);\n this.cdr.markForCheck();\n });\n\n // Initialize dashboard (no database config needed for DataExplorer)\n const config: DashboardConfig = {\n dashboard: null as unknown as MJDashboardEntity, // No database record\n userState: {}\n };\n instance.Config = config;\n instance.Refresh();\n\n // Trigger change detection to ensure the component updates\n componentRef.changeDetectorRef.detectChanges();\n } catch (error) {\n console.error('Error loading Data Explorer:', error);\n this.setError('The Data Explorer could not be loaded.', error);\n this.NotifyLoadComplete();\n }\n }\n\n /**\n * Load a code-based dashboard by looking up the registered class\n */\n private async loadCodeBasedDashboard(dashboard: MJDashboardEntity): Promise<void> {\n try {\n if (!dashboard.DriverClass) {\n throw new Error(`Dashboard '${dashboard.Name}' is marked as Code type but has no DriverClass specified`);\n }\n\n // Look up the registered class using the DriverClass name (with lazy loading fallback via ClassFactory)\n const classReg = await MJGlobal.Instance.ClassFactory.GetRegistrationAsync(\n BaseDashboard,\n dashboard.DriverClass\n );\n\n if (!classReg?.SubClass) {\n throw new Error(`Dashboard class '${dashboard.DriverClass}' is not registered. Please check the class registration.`);\n }\n\n // Create the component instance\n this.containerElement.nativeElement.innerHTML = '';\n this.componentRef = this.viewContainer.createComponent<BaseDashboard>(classReg.SubClass);\n const instance = this.componentRef.instance as BaseDashboard;\n\n // Setup LoadCompleteEvent() to know when the dashboard is ready\n instance.LoadCompleteEvent = () => {\n this.NotifyLoadComplete();\n };\n\n // Surface the dashboard's own load failures in the host's error card — but only for the\n // INITIAL load. BaseDashboard guarantees the loading screen is released even when\n // initDashboard()/loadData() throws (it emits Error, then NotifyLoadComplete in a\n // finally). Wrap the completion hook to learn when the first load has settled, so a later\n // Refresh() failure keeps the rendered dashboard instead of blanking it to a sticky error\n // card. Wired BEFORE the first await below, so an Error during the instance's own\n // ngOnInit isn't missed.\n let initialLoadSettled = false;\n const onComplete = instance.LoadCompleteEvent;\n instance.LoadCompleteEvent = () => { initialLoadSettled = true; onComplete?.(); };\n instance.Error.subscribe((err: Error) => {\n if (initialLoadSettled) return; // post-mount refresh failure — already logged by BaseDashboard.runGuardedLoad\n this.setError(`The dashboard \"${dashboard.Name}\" could not be loaded.`, err);\n this.cdr.markForCheck();\n });\n\n // Initialize with dashboard data\n const userStateEntity = await this.loadDashboardUserState(dashboard.ID);\n const config: DashboardConfig = {\n dashboard,\n userState: userStateEntity.UserState ? SafeJSONParse(userStateEntity.UserState) : {}\n };\n\n instance.Config = config;\n\n // Manually append the component's native element inside the div\n const nativeElement = (this.componentRef.hostView as any).rootNodes[0];\n nativeElement.style.width = '100%';\n nativeElement.style.height = '100%';\n this.containerElement.nativeElement.appendChild(nativeElement);\n\n // handle open entity record events in MJ Explorer with routing\n instance.OpenEntityRecord.subscribe((data: { EntityName: string; RecordPKey: CompositeKey }) => {\n // check to see if the data has entityname/pkey\n if (data && data.EntityName && data.RecordPKey) {\n // Use NavigationService to open entity record in new tab\n this.navigationService.OpenEntityRecord(data.EntityName, data.RecordPKey);\n } else {\n console.warn('DashboardResource - invalid data, missing EntityName or RecordPKey:', data);\n }\n });\n\n instance.UserStateChanged.subscribe(async (userState: any) => {\n if (!userState) {\n // if the user state is null, we need to remove it from the user state\n userState = {};\n }\n // save the user state to the dashboard user state entity\n userStateEntity.UserState = JSON.stringify(userState);\n if (!await userStateEntity.Save()) {\n LogError('Error saving user state', null, userStateEntity.LatestResult?.CompleteMessage);\n }\n });\n\n instance.Refresh();\n } catch (error) {\n console.error('Error loading code-based dashboard:', error);\n this.setError(`The dashboard \"${dashboard.Name}\" could not be loaded. The dashboard class may not be registered or may have failed to initialize.`, error);\n this.NotifyLoadComplete();\n }\n }\n\n protected async loadDashboardUserState(dashboardId: string): Promise<MJDashboardUserStateEntity> {\n // handle user state changes for the dashboard\n const md = this.ProviderToUse;\n const stateResult = DashboardEngine.Instance.DashboardUserStates.filter(dus => UUIDsEqual(dus.DashboardID, dashboardId) && UUIDsEqual(dus.UserID, md.CurrentUser.ID));\n let stateObject: MJDashboardUserStateEntity;\n if (stateResult && stateResult.length > 0) {\n stateObject = stateResult[0];\n }\n else {\n stateObject = await md.GetEntityObject<MJDashboardUserStateEntity>('MJ: Dashboard User States');\n stateObject.DashboardID = dashboardId;\n stateObject.UserID = md.CurrentUser.ID;\n // don't save becuase we don't care about the state until something changes\n }\n return stateObject;\n }\n\n /**\n * Load a config-based dashboard using the new DashboardViewerComponent (Golden Layout)\n */\n private async loadConfigBasedDashboard(dashboard: MJDashboardEntity): Promise<void> {\n try {\n this.containerElement.nativeElement.innerHTML = '';\n const componentRef = this.viewContainer.createComponent(DashboardViewerComponent);\n this.componentRef = componentRef;\n const instance = componentRef.instance;\n\n // Store references for external toolbar control\n this.viewerInstance = instance;\n this.configDashboard = dashboard;\n\n // Compute user permissions for this dashboard\n const md = this.ProviderToUse;\n this.dashboardPermissions = DashboardEngine.Instance.GetDashboardPermissions(\n dashboard.ID,\n md.CurrentUser.ID\n );\n\n // Manually append the component's native element inside the div\n const nativeElement = (this.componentRef.hostView as any).rootNodes[0];\n nativeElement.style.width = '100%';\n nativeElement.style.height = '100%';\n this.containerElement.nativeElement.appendChild(nativeElement);\n\n // Load categories for breadcrumb navigation (if not already loaded)\n if (this.categories.length === 0) {\n this.categories = DashboardEngine.Instance.DashboardCategories;\n }\n\n // Configure the viewer before assigning the dashboard. The dashboard input\n // starts the layout lifecycle synchronously, so event handlers need to be\n // wired first.\n instance.showToolbar = false; // We provide external toolbar\n instance.showBreadcrumb = false; // Already in its own tab, no breadcrumb needed\n instance.showOpenInTabButton = false; // Already in its own tab\n instance.showEditButton = false; // External toolbar handles edit\n instance.Categories = this.categories;\n\n // Wire up navigation events - handle navigation requests from the dashboard\n instance.navigationRequested.subscribe((event: DashboardNavRequestEvent) => {\n this.handleNavigationRequest(event);\n });\n\n // Wire up \"Open in Tab\" button click\n instance.openInTab.subscribe((event: { dashboardId: string; dashboardName: string }) => {\n this.navigationService.OpenDashboard(event.dashboardId, event.dashboardName);\n });\n\n // Wire up dashboard saved event\n instance.dashboardSaved.subscribe((savedDashboard: MJDashboardEntity) => {\n this.ResourceRecordSaved(savedDashboard);\n });\n\n // Wire up error events\n instance.error.subscribe((errorEvent: { message: string; error?: Error }) => {\n console.error('Dashboard error:', errorEvent.message, errorEvent.error);\n });\n\n // Set the dashboard entity directly on the viewer and wait for Golden Layout\n // to initialize against a real container before clearing the resource loader.\n instance.dashboard = dashboard;\n await instance.waitForLayoutReady();\n this.NotifyLoadComplete();\n this.cdr.detectChanges();\n\n } catch (error) {\n console.error('Error loading config-based dashboard:', error);\n this.setError(`The dashboard \"${dashboard.Name}\" could not be loaded. There may be an issue with the dashboard configuration.`, error);\n this.NotifyLoadComplete();\n }\n }\n\n /**\n * Handle navigation requests from the dashboard viewer\n */\n private handleNavigationRequest(event: DashboardNavRequestEvent): void {\n const request = event.request;\n\n switch (request.type) {\n case 'OpenEntityRecord': {\n const entityRequest = request as { type: 'OpenEntityRecord'; entityName: string; recordId: string };\n // `recordId` is documented as URL-segment format (see OpenEntityRecordNavRequest in\n // @memberjunction/ng-dashboard-viewer). E.g. a single-PK record is `\"ID|11055\"`; a\n // composite-PK record is `\"Field1|Value1||Field2|Value2\"`. Senders in\n // dashboard-viewer (artifact-part, view-part) intentionally call\n // `compositeKey.ToURLSegment()` to produce this shape.\n //\n // Wrapping that string verbatim into `{ FieldName: 'ID', Value: <segment> }` makes\n // ToURLSegment serialize it a second time as `ID|<segment>` and produces a\n // malformed `Field|Field|Value` URL the host parser silently mis-reads (manifesting\n // downstream as `BaseEntity.Load(... Key: ID=ID)` and `Primary Key value is not a\n // valid number`). Parse the segment with `LoadFromURLSegment` against the entity's\n // PK metadata instead, so single-PK and composite-PK both round-trip correctly.\n const md = this.ProviderToUse;\n const entityInfo = md.EntityByName(entityRequest.entityName);\n const pkey = new CompositeKey();\n if (entityInfo) {\n pkey.LoadFromURLSegment(entityInfo, entityRequest.recordId);\n } else {\n // Last-resort fallback when entity metadata isn't resolvable — preserves\n // pre-fix behavior so we don't NRE on an unknown entity name.\n pkey.KeyValuePairs = [{ FieldName: 'ID', Value: entityRequest.recordId }];\n }\n this.navigationService.OpenEntityRecord(entityRequest.entityName, pkey);\n break;\n }\n case 'OpenDashboard': {\n const dashRequest = request as { type: 'OpenDashboard'; dashboardId: string };\n // Load dashboard name from engine cache\n const targetDashboard = DashboardEngine.Instance.Dashboards.find(d => UUIDsEqual(d.ID, dashRequest.dashboardId));\n const name = targetDashboard?.Name || 'Dashboard';\n this.navigationService.OpenDashboard(dashRequest.dashboardId, name);\n break;\n }\n case 'OpenQuery': {\n const queryRequest = request as { type: 'OpenQuery'; queryId: string };\n this.navigationService.OpenQuery(queryRequest.queryId, 'Query');\n break;\n }\n default:\n console.warn('Unhandled navigation request type:', request.type);\n }\n }\n\n /**\n * Get the display name for a dashboard resource\n * Loads the actual dashboard name from the database if available\n */\n override async GetResourceDisplayName(data: ResourceData): Promise<string> {\n try {\n // Try to load dashboard metadata if we have the record ID\n if (data.ResourceRecordID && data.ResourceRecordID.length > 0) {\n const md = this.ProviderToUse;\n const compositeKey = new CompositeKey([{ FieldName: 'ID', Value: data.ResourceRecordID }]);\n const name = await md.GetEntityRecordName('Dashboards', compositeKey);\n if (name) {\n return name;\n }\n }\n } catch (error) {\n // Silently fail and use fallback\n }\n\n // Fallback: use provided name or generic label\n return data.Name || 'Dashboard';\n }\n\n /**\n * Get the icon class for dashboard resources\n */\n async GetResourceIconClass(data: ResourceData): Promise<string> {\n return 'fa-solid fa-table-columns';\n }\n}\n"]}
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* One lazy-loadable chunk: a stable identity plus the dynamic import that loads it.
|
|
4
|
+
*
|
|
5
|
+
* Many compound keys map to the same chunk, so `chunkId` is what the registry dedupes
|
|
6
|
+
* loads by — it must uniquely identify the chunk. The generated LAZY_FEATURE_CONFIG
|
|
7
|
+
* uses the dynamic import specifier (e.g. '@memberjunction/ng-dashboards/ai-dashboards.module').
|
|
8
|
+
*/
|
|
9
|
+
export interface LazyFeatureChunk {
|
|
10
|
+
/** Stable, unique chunk identity. Also the label dev tools show for the chunk. */
|
|
11
|
+
readonly chunkId: string;
|
|
12
|
+
/** Imports the chunk, which runs the `@RegisterClass` decorators inside it. */
|
|
13
|
+
readonly load: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Registry that maps compound keys (BaseClassName::Key) to the chunk that provides them.
|
|
4
17
|
*
|
|
5
18
|
* Wired to ClassFactory via `WireToClassFactory()` so that any `GetRegistrationAsync()`
|
|
6
19
|
* or `CreateInstanceAsync()` call that misses synchronously will automatically trigger
|
|
@@ -10,18 +23,35 @@ import * as i0 from "@angular/core";
|
|
|
10
23
|
*/
|
|
11
24
|
export declare class LazyModuleRegistry {
|
|
12
25
|
private registry;
|
|
26
|
+
/**
|
|
27
|
+
* Secondary index: subclass key → (compound key → chunk), ignoring the base-class half.
|
|
28
|
+
*
|
|
29
|
+
* Needed because the base-class half of a compound key is NOT stable at runtime. ClassFactory
|
|
30
|
+
* builds the lookup from `baseClass.name`, but the emitted identifier varies by build mode —
|
|
31
|
+
* for the same `BaseResourceComponent` we have observed `_BaseResourceComponent` (unminified,
|
|
32
|
+
* Angular's named class expression) and `BaseResourceComponent2` (minified, esbuild's collision
|
|
33
|
+
* rename) — while the generated LAZY_FEATURE_CONFIG keys use the TypeScript source name. Any
|
|
34
|
+
* mismatch made the primary lookup miss, so NO chunk was ever imported and every lazily
|
|
35
|
+
* provided class silently failed to resolve.
|
|
36
|
+
*
|
|
37
|
+
* The inner map is keyed by compound key (not a flat Set of chunks) so that re-registering a
|
|
38
|
+
* compound key replaces its contribution here too, matching `registry`'s replace semantics.
|
|
39
|
+
*/
|
|
40
|
+
private chunksBySubclassKey;
|
|
41
|
+
/** chunkIds that have finished loading. */
|
|
13
42
|
private loadedChunks;
|
|
43
|
+
/** In-flight loads, keyed by chunkId, so concurrent callers share one import. */
|
|
14
44
|
private pendingLoads;
|
|
15
45
|
/**
|
|
16
|
-
* Register a single compound key with
|
|
46
|
+
* Register a single compound key with the chunk that provides it.
|
|
17
47
|
* @param compoundKey Format: "BaseClassName::Key" (e.g., "BaseResourceComponent::HomeDashboard")
|
|
18
|
-
* @param
|
|
48
|
+
* @param chunk The chunk descriptor (stable id + dynamic import)
|
|
19
49
|
*/
|
|
20
|
-
Register(compoundKey: string,
|
|
50
|
+
Register(compoundKey: string, chunk: LazyFeatureChunk): void;
|
|
21
51
|
/**
|
|
22
52
|
* Register multiple compound keys at once (from the generated LAZY_FEATURE_CONFIG).
|
|
23
53
|
*/
|
|
24
|
-
RegisterBulk(mappings: Record<string,
|
|
54
|
+
RegisterBulk(mappings: Record<string, LazyFeatureChunk>): void;
|
|
25
55
|
/**
|
|
26
56
|
* Wires this registry to ClassFactory as a lazy loader.
|
|
27
57
|
* After calling this, any `ClassFactory.GetRegistrationAsync()` or `CreateInstanceAsync()`
|
|
@@ -31,8 +61,8 @@ export declare class LazyModuleRegistry {
|
|
|
31
61
|
WireToClassFactory(): void;
|
|
32
62
|
/**
|
|
33
63
|
* Read-only snapshot of the registry state — for diagnostic tools.
|
|
34
|
-
* Groups compound keys by
|
|
35
|
-
*
|
|
64
|
+
* Groups compound keys by `chunkId` so inspectors can show "X chunks, Y loaded"
|
|
65
|
+
* plus the keys covered by each.
|
|
36
66
|
*/
|
|
37
67
|
GetSnapshot(): {
|
|
38
68
|
registered: string[];
|
|
@@ -42,7 +72,8 @@ export declare class LazyModuleRegistry {
|
|
|
42
72
|
loaded: boolean;
|
|
43
73
|
keys: string[];
|
|
44
74
|
}>;
|
|
45
|
-
|
|
75
|
+
/** How many chunks have finished loading. The total is `chunks.length`. */
|
|
76
|
+
loadedChunkCount: number;
|
|
46
77
|
};
|
|
47
78
|
/**
|
|
48
79
|
* Programmatically trigger a lazy chunk load by compound key. Returns true
|
|
@@ -57,6 +88,15 @@ export declare class LazyModuleRegistry {
|
|
|
57
88
|
* @param compoundKey Format: "BaseClassName::Key" (e.g., "BaseResourceComponent::HomeDashboard")
|
|
58
89
|
*/
|
|
59
90
|
Load(compoundKey: string): Promise<boolean>;
|
|
91
|
+
/**
|
|
92
|
+
* The distinct chunks (by `chunkId`) registered under a subclass key, in registration order.
|
|
93
|
+
*/
|
|
94
|
+
private candidateChunksFor;
|
|
95
|
+
/**
|
|
96
|
+
* Imports a chunk once, sharing in-flight loads and remembering completed ones.
|
|
97
|
+
* Resolves when the chunk is loaded; rejects if the import fails.
|
|
98
|
+
*/
|
|
99
|
+
private loadChunk;
|
|
60
100
|
static ɵfac: i0.ɵɵFactoryDeclaration<LazyModuleRegistry, never>;
|
|
61
101
|
static ɵprov: i0.ɵɵInjectableDeclaration<LazyModuleRegistry>;
|
|
62
102
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-module-registry.d.ts","sourceRoot":"","sources":["../../../src/lib/services/lazy-module-registry.ts"],"names":[],"mappings":";AAGA;;;;;;;;GAQG;AACH,qBACa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"lazy-module-registry.d.ts","sourceRoot":"","sources":["../../../src/lib/services/lazy-module-registry.ts"],"names":[],"mappings":";AAGA;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAQD;;;;;;;;GAQG;AACH,qBACa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAuC;IACvD;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,mBAAmB,CAAoD;IAC/E,2CAA2C;IAC3C,OAAO,CAAC,YAAY,CAAqB;IACzC,iFAAiF;IACjF,OAAO,CAAC,YAAY,CAAoC;IAExD;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAQ5D;;OAEG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,IAAI;IAM9D;;;;;OAKG;IACH,kBAAkB,IAAI,IAAI;IAU1B;;;;OAIG;IACH,WAAW,IAAI;QACb,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,MAAM,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,OAAO,CAAC;YAAC,IAAI,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAC;QACpE,2EAA2E;QAC3E,gBAAgB,EAAE,MAAM,CAAC;KAC1B;IA6BD;;;OAGG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAItD;;;;;;OAMG;IACG,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgCjD;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;;OAGG;YACW,SAAS;yCArKZ,kBAAkB;6CAAlB,kBAAkB;CAwM9B"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { Injectable } from '@angular/core';
|
|
2
2
|
import { MJGlobal } from '@memberjunction/global';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
|
+
/** Extracts the subclass-key half of a `"BaseClassName::Key"` compound key. */
|
|
5
|
+
function subclassKeyOf(compoundKey) {
|
|
6
|
+
const separator = compoundKey.indexOf('::');
|
|
7
|
+
return separator < 0 ? compoundKey : compoundKey.slice(separator + 2);
|
|
8
|
+
}
|
|
4
9
|
/**
|
|
5
|
-
* Registry that maps compound keys (BaseClassName::Key) to
|
|
10
|
+
* Registry that maps compound keys (BaseClassName::Key) to the chunk that provides them.
|
|
6
11
|
*
|
|
7
12
|
* Wired to ClassFactory via `WireToClassFactory()` so that any `GetRegistrationAsync()`
|
|
8
13
|
* or `CreateInstanceAsync()` call that misses synchronously will automatically trigger
|
|
@@ -12,22 +17,43 @@ import * as i0 from "@angular/core";
|
|
|
12
17
|
*/
|
|
13
18
|
export class LazyModuleRegistry {
|
|
14
19
|
registry = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* Secondary index: subclass key → (compound key → chunk), ignoring the base-class half.
|
|
22
|
+
*
|
|
23
|
+
* Needed because the base-class half of a compound key is NOT stable at runtime. ClassFactory
|
|
24
|
+
* builds the lookup from `baseClass.name`, but the emitted identifier varies by build mode —
|
|
25
|
+
* for the same `BaseResourceComponent` we have observed `_BaseResourceComponent` (unminified,
|
|
26
|
+
* Angular's named class expression) and `BaseResourceComponent2` (minified, esbuild's collision
|
|
27
|
+
* rename) — while the generated LAZY_FEATURE_CONFIG keys use the TypeScript source name. Any
|
|
28
|
+
* mismatch made the primary lookup miss, so NO chunk was ever imported and every lazily
|
|
29
|
+
* provided class silently failed to resolve.
|
|
30
|
+
*
|
|
31
|
+
* The inner map is keyed by compound key (not a flat Set of chunks) so that re-registering a
|
|
32
|
+
* compound key replaces its contribution here too, matching `registry`'s replace semantics.
|
|
33
|
+
*/
|
|
34
|
+
chunksBySubclassKey = new Map();
|
|
35
|
+
/** chunkIds that have finished loading. */
|
|
15
36
|
loadedChunks = new Set();
|
|
37
|
+
/** In-flight loads, keyed by chunkId, so concurrent callers share one import. */
|
|
16
38
|
pendingLoads = new Map();
|
|
17
39
|
/**
|
|
18
|
-
* Register a single compound key with
|
|
40
|
+
* Register a single compound key with the chunk that provides it.
|
|
19
41
|
* @param compoundKey Format: "BaseClassName::Key" (e.g., "BaseResourceComponent::HomeDashboard")
|
|
20
|
-
* @param
|
|
42
|
+
* @param chunk The chunk descriptor (stable id + dynamic import)
|
|
21
43
|
*/
|
|
22
|
-
Register(compoundKey,
|
|
23
|
-
this.registry.set(compoundKey,
|
|
44
|
+
Register(compoundKey, chunk) {
|
|
45
|
+
this.registry.set(compoundKey, chunk);
|
|
46
|
+
const subclassKey = subclassKeyOf(compoundKey);
|
|
47
|
+
const byCompoundKey = this.chunksBySubclassKey.get(subclassKey) ?? new Map();
|
|
48
|
+
byCompoundKey.set(compoundKey, chunk);
|
|
49
|
+
this.chunksBySubclassKey.set(subclassKey, byCompoundKey);
|
|
24
50
|
}
|
|
25
51
|
/**
|
|
26
52
|
* Register multiple compound keys at once (from the generated LAZY_FEATURE_CONFIG).
|
|
27
53
|
*/
|
|
28
54
|
RegisterBulk(mappings) {
|
|
29
|
-
for (const [compoundKey,
|
|
30
|
-
this.
|
|
55
|
+
for (const [compoundKey, chunk] of Object.entries(mappings)) {
|
|
56
|
+
this.Register(compoundKey, chunk);
|
|
31
57
|
}
|
|
32
58
|
}
|
|
33
59
|
/**
|
|
@@ -45,16 +71,15 @@ export class LazyModuleRegistry {
|
|
|
45
71
|
}
|
|
46
72
|
/**
|
|
47
73
|
* Read-only snapshot of the registry state — for diagnostic tools.
|
|
48
|
-
* Groups compound keys by
|
|
49
|
-
*
|
|
74
|
+
* Groups compound keys by `chunkId` so inspectors can show "X chunks, Y loaded"
|
|
75
|
+
* plus the keys covered by each.
|
|
50
76
|
*/
|
|
51
77
|
GetSnapshot() {
|
|
52
78
|
const byChunk = new Map();
|
|
53
|
-
for (const [compoundKey,
|
|
54
|
-
const
|
|
55
|
-
const arr = byChunk.get(id) ?? [];
|
|
79
|
+
for (const [compoundKey, chunk] of this.registry.entries()) {
|
|
80
|
+
const arr = byChunk.get(chunk.chunkId) ?? [];
|
|
56
81
|
arr.push(compoundKey);
|
|
57
|
-
byChunk.set(
|
|
82
|
+
byChunk.set(chunk.chunkId, arr);
|
|
58
83
|
}
|
|
59
84
|
const chunks = Array.from(byChunk.entries())
|
|
60
85
|
.map(([chunkId, keys]) => ({
|
|
@@ -72,7 +97,7 @@ export class LazyModuleRegistry {
|
|
|
72
97
|
registered: Array.from(this.registry.keys()).sort(),
|
|
73
98
|
loaded: loaded.sort(),
|
|
74
99
|
chunks,
|
|
75
|
-
|
|
100
|
+
loadedChunkCount: this.loadedChunks.size
|
|
76
101
|
};
|
|
77
102
|
}
|
|
78
103
|
/**
|
|
@@ -90,27 +115,82 @@ export class LazyModuleRegistry {
|
|
|
90
115
|
* @param compoundKey Format: "BaseClassName::Key" (e.g., "BaseResourceComponent::HomeDashboard")
|
|
91
116
|
*/
|
|
92
117
|
async Load(compoundKey) {
|
|
93
|
-
|
|
94
|
-
|
|
118
|
+
// Primary: exact compound key. Correct and precise when the runtime base-class name
|
|
119
|
+
// happens to match the generated config.
|
|
120
|
+
const exact = this.registry.get(compoundKey);
|
|
121
|
+
if (exact) {
|
|
122
|
+
await this.loadChunk(exact);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
// Fallback: match on the subclass key alone, because the base-class half is not stable
|
|
126
|
+
// across build modes (see `chunksBySubclassKey`). Subclass keys are unique per chunk in
|
|
127
|
+
// practice; if several chunks ever claim one, load them all so resolution can't silently
|
|
128
|
+
// pick wrong — ClassFactory still decides the winner by base class afterwards.
|
|
129
|
+
const candidates = this.candidateChunksFor(subclassKeyOf(compoundKey));
|
|
130
|
+
if (candidates.length === 0)
|
|
95
131
|
return false;
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
const
|
|
132
|
+
// One candidate failing must not skip the rest — a later one may hold the class. Only
|
|
133
|
+
// surface a failure if NOTHING loaded, so a partial failure can't mask a success.
|
|
134
|
+
const errors = [];
|
|
135
|
+
let loaded = false;
|
|
136
|
+
for (const chunk of candidates) {
|
|
137
|
+
try {
|
|
138
|
+
await this.loadChunk(chunk);
|
|
139
|
+
loaded = true;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
errors.push(error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!loaded)
|
|
146
|
+
throw errors[0];
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The distinct chunks (by `chunkId`) registered under a subclass key, in registration order.
|
|
151
|
+
*/
|
|
152
|
+
candidateChunksFor(subclassKey) {
|
|
153
|
+
const byCompoundKey = this.chunksBySubclassKey.get(subclassKey);
|
|
154
|
+
if (!byCompoundKey)
|
|
155
|
+
return [];
|
|
156
|
+
const distinct = new Map();
|
|
157
|
+
for (const chunk of byCompoundKey.values()) {
|
|
158
|
+
distinct.set(chunk.chunkId, chunk);
|
|
159
|
+
}
|
|
160
|
+
return Array.from(distinct.values());
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Imports a chunk once, sharing in-flight loads and remembering completed ones.
|
|
164
|
+
* Resolves when the chunk is loaded; rejects if the import fails.
|
|
165
|
+
*/
|
|
166
|
+
async loadChunk(chunk) {
|
|
167
|
+
// Dedupe on the chunk's declared id — multiple compound keys share one chunk.
|
|
168
|
+
// This MUST be a value that differs between chunks; deriving it from the loader
|
|
169
|
+
// function (e.g. Function.toString()) collapses every chunk into one, so the first
|
|
170
|
+
// chunk loaded makes all the others look already-loaded and their classes never register.
|
|
171
|
+
const chunkKey = chunk.chunkId;
|
|
99
172
|
if (this.loadedChunks.has(chunkKey))
|
|
100
|
-
return
|
|
173
|
+
return;
|
|
101
174
|
// Deduplicate concurrent loads
|
|
102
175
|
const pending = this.pendingLoads.get(chunkKey);
|
|
103
176
|
if (pending) {
|
|
104
177
|
await pending;
|
|
105
|
-
return
|
|
178
|
+
return;
|
|
106
179
|
}
|
|
107
|
-
const loadPromise =
|
|
180
|
+
const loadPromise = chunk.load().then(() => {
|
|
108
181
|
this.loadedChunks.add(chunkKey);
|
|
109
182
|
this.pendingLoads.delete(chunkKey);
|
|
183
|
+
}, (error) => {
|
|
184
|
+
// Drop the failed attempt so a later navigation retries. Chunk fetches fail
|
|
185
|
+
// transiently in normal use — a dev-server rebuild renames every lazy chunk, so a
|
|
186
|
+
// page loaded before the rebuild 404s once. Left in `pendingLoads`, the rejected
|
|
187
|
+
// promise would be re-awaited (and re-thrown) forever, killing the chunk for the
|
|
188
|
+
// rest of the page session. The rejection still propagates to this caller.
|
|
189
|
+
this.pendingLoads.delete(chunkKey);
|
|
190
|
+
throw error;
|
|
110
191
|
});
|
|
111
192
|
this.pendingLoads.set(chunkKey, loadPromise);
|
|
112
193
|
await loadPromise;
|
|
113
|
-
return true;
|
|
114
194
|
}
|
|
115
195
|
static ɵfac = function LazyModuleRegistry_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || LazyModuleRegistry)(); };
|
|
116
196
|
static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: LazyModuleRegistry, factory: LazyModuleRegistry.ɵfac, providedIn: 'root' });
|