@memberjunction/ng-dashboard-viewer 5.11.0 → 5.13.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.
@@ -1 +1 @@
1
- {"version":3,"file":"artifact-part.component.js","sourceRoot":"","sources":["../../../src/lib/parts/artifact-part.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ChangeDetectorRef, AfterViewInit, OnDestroy, Input } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseDashboardPart } from './base-dashboard-part';\nimport { PanelConfig } from '../models/dashboard-types';\nimport { NavigationRequest } from '@memberjunction/ng-artifacts';\nimport { UserInfo, Metadata, CompositeKey } from '@memberjunction/core';\nimport { Subject } from 'rxjs';\n\n/**\n * Runtime renderer for Artifact dashboard parts.\n * Displays artifacts using mj-artifact-viewer-panel including reports, charts, and AI-generated content.\n */\n@RegisterClass(BaseDashboardPart, 'ArtifactPanelRenderer')\n@Component({\n standalone: false,\n selector: 'mj-artifact-part',\n template: `\n <div class=\"artifact-part\" [class.loading]=\"IsLoading\" [class.error]=\"ErrorMessage\">\n <!-- Loading state -->\n @if (IsLoading) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading artifact...\"></mj-loading>\n </div>\n }\n \n <!-- Error state -->\n @if (ErrorMessage && !IsLoading) {\n <div class=\"error-state\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ ErrorMessage }}</span>\n </div>\n }\n \n <!-- No artifact configured -->\n @if (!IsLoading && !ErrorMessage && !hasArtifact) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-palette\"></i>\n <h4>No Artifact Selected</h4>\n <p>Click the configure button to select an artifact for this part.</p>\n </div>\n }\n \n <!-- Artifact Viewer Panel -->\n @if (!IsLoading && !ErrorMessage && hasArtifact && artifactId) {\n <mj-artifact-viewer-panel\n [artifactId]=\"artifactId\"\n [currentUser]=\"currentUser\"\n [environmentId]=\"environmentId\"\n [versionNumber]=\"versionNumber\"\n [showSaveToCollection]=\"false\"\n [showHeader]=\"showHeader\"\n [showTabs]=\"showTabs\"\n [showCloseButton]=\"showCloseButton\"\n [showMaximizeButton]=\"showMaximizeButton\"\n [viewContext]=\"null\"\n [canShare]=\"false\"\n [canEdit]=\"false\"\n [isMaximized]=\"false\"\n [refreshTrigger]=\"refreshTrigger\"\n (navigateToLink)=\"onNavigateToLink($event)\"\n (openEntityRecord)=\"onOpenEntityRecord($event)\"\n (navigationRequest)=\"onNavigationRequest($event)\">\n </mj-artifact-viewer-panel>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .artifact-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: #fff;\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #666;\n text-align: center;\n padding: 24px;\n }\n\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: #ccc;\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: #d32f2f;\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: #333;\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n mj-artifact-viewer-panel {\n flex: 1;\n min-height: 0;\n }\n `]\n})\nexport class ArtifactPartComponent extends BaseDashboardPart implements AfterViewInit, OnDestroy {\n /**\n * Current user - required by the artifact viewer panel.\n * Should be provided by the dashboard host or retrieved from a service.\n */\n @Input() CurrentUser: UserInfo | null = null;\n\n /**\n * Environment ID - required by the artifact viewer panel.\n * Should be provided by the dashboard host.\n */\n @Input() EnvironmentId: string = '';\n\n public hasArtifact = false;\n public artifactId: string | null = null;\n public versionNumber: number | undefined;\n public showHeader: boolean = false; // Default to false for dashboard embedding\n public showTabs: boolean = true;\n public showCloseButton: boolean = false; // Always false in dashboard context - close handled by dashboard\n public showMaximizeButton: boolean = false; // Always false in dashboard context - maximize handled by dashboard\n public refreshTrigger = new Subject<{ artifactId: string; versionNumber: number }>();\n\n // Expose for template\n public get currentUser(): UserInfo {\n // Use provided CurrentUser, or fall back to Metadata.CurrentUser\n // In client-side Angular context, Metadata.CurrentUser should always be available\n const user = this.CurrentUser || new Metadata().CurrentUser;\n if (!user) {\n throw new Error('No current user available - user must be logged in to view artifacts');\n }\n return user;\n }\n\n public get environmentId(): string {\n return this.EnvironmentId || '';\n }\n\n constructor(cdr: ChangeDetectorRef) {\n super(cdr);\n }\n\n ngAfterViewInit(): void {\n if (this.Panel) {\n this.loadContent();\n }\n }\n\n public async loadContent(): Promise<void> {\n const config = this.getConfig<PanelConfig>();\n const artifactId = config?.['artifactId'] as string | undefined;\n\n if (!artifactId) {\n this.hasArtifact = false;\n this.cdr.detectChanges();\n return;\n }\n\n this.setLoading(true);\n\n try {\n // Set artifact ID and version from config\n this.artifactId = artifactId;\n this.versionNumber = config?.['versionNumber'] as number | undefined;\n // Display options - showHeader defaults to false for clean dashboard embedding\n this.showHeader = (config?.['showHeader'] as boolean) ?? false;\n this.showTabs = (config?.['showTabs'] as boolean) ?? true;\n this.hasArtifact = true;\n\n this.setLoading(false);\n } catch (error) {\n this.setError(error instanceof Error ? error.message : 'Failed to load artifact');\n }\n }\n\n /**\n * Refresh the artifact display\n */\n public refresh(): void {\n if (this.artifactId && this.versionNumber) {\n this.refreshTrigger.next({\n artifactId: this.artifactId,\n versionNumber: this.versionNumber\n });\n }\n }\n\n /**\n * Handle navigation link events from artifact viewer (conversation/collection links)\n */\n public onNavigateToLink(event: { type: 'conversation' | 'collection'; id: string; artifactId?: string; versionNumber?: number; versionId?: string }): void {\n // Emit data change event for navigation link (for listeners)\n this.emitDataChanged({\n type: 'navigate-to-link',\n linkType: event.type,\n linkId: event.id,\n artifactId: event.artifactId,\n versionNumber: event.versionNumber,\n versionId: event.versionId\n });\n\n // TODO: Add navigation request methods for conversation/collection when needed\n // For now, these are emitted as data change events for parent components to handle\n }\n\n /**\n * Handle entity record navigation events from artifact viewer\n */\n public onOpenEntityRecord(event: { entityName: string; compositeKey: CompositeKey }): void {\n // Emit data change event for listeners\n this.emitDataChanged({\n type: 'open-entity-record',\n entityName: event.entityName,\n compositeKey: event.compositeKey\n });\n\n // Use proper navigation request to bubble up through the stack\n if (event.entityName && event.compositeKey) {\n this.RequestOpenEntityRecord(\n event.entityName,\n event.compositeKey.ToURLSegment(),\n 'view',\n false\n );\n }\n }\n\n /**\n * Handle general navigation request events from artifact viewer plugins\n */\n public onNavigationRequest(event: NavigationRequest): void {\n this.RequestOpenNavItem(\n event.navItemName,\n event.appName,\n event.queryParams,\n false\n );\n }\n\n protected override cleanup(): void {\n this.refreshTrigger.complete();\n this.artifactId = null;\n this.versionNumber = undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"artifact-part.component.js","sourceRoot":"","sources":["../../../src/lib/parts/artifact-part.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ChangeDetectorRef, AfterViewInit, OnDestroy, Input } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseDashboardPart } from './base-dashboard-part';\nimport { PanelConfig } from '../models/dashboard-types';\nimport { NavigationRequest } from '@memberjunction/ng-artifacts';\nimport { UserInfo, Metadata, CompositeKey } from '@memberjunction/core';\nimport { Subject } from 'rxjs';\n\n/**\n * Runtime renderer for Artifact dashboard parts.\n * Displays artifacts using mj-artifact-viewer-panel including reports, charts, and AI-generated content.\n */\n@RegisterClass(BaseDashboardPart, 'ArtifactPanelRenderer')\n@Component({\n standalone: false,\n selector: 'mj-artifact-part',\n template: `\n <div class=\"artifact-part\" [class.loading]=\"IsLoading\" [class.error]=\"ErrorMessage\">\n <!-- Loading state -->\n @if (IsLoading) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading artifact...\"></mj-loading>\n </div>\n }\n \n <!-- Error state -->\n @if (ErrorMessage && !IsLoading) {\n <div class=\"error-state\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ ErrorMessage }}</span>\n </div>\n }\n \n <!-- No artifact configured -->\n @if (!IsLoading && !ErrorMessage && !hasArtifact) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-palette\"></i>\n <h4>No Artifact Selected</h4>\n <p>Click the configure button to select an artifact for this part.</p>\n </div>\n }\n \n <!-- Artifact Viewer Panel -->\n @if (!IsLoading && !ErrorMessage && hasArtifact && artifactId) {\n <mj-artifact-viewer-panel\n [artifactId]=\"artifactId\"\n [currentUser]=\"currentUser\"\n [environmentId]=\"environmentId\"\n [versionNumber]=\"versionNumber\"\n [showSaveToCollection]=\"false\"\n [showHeader]=\"showHeader\"\n [showTabs]=\"showTabs\"\n [showCloseButton]=\"showCloseButton\"\n [showMaximizeButton]=\"showMaximizeButton\"\n [viewContext]=\"null\"\n [canShare]=\"false\"\n [canEdit]=\"false\"\n [isMaximized]=\"false\"\n [refreshTrigger]=\"refreshTrigger\"\n (navigateToLink)=\"onNavigateToLink($event)\"\n (openEntityRecord)=\"onOpenEntityRecord($event)\"\n (navigationRequest)=\"onNavigationRequest($event)\">\n </mj-artifact-viewer-panel>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .artifact-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: var(--mj-status-error);\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: var(--mj-text-primary);\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n mj-artifact-viewer-panel {\n flex: 1;\n min-height: 0;\n }\n `]\n})\nexport class ArtifactPartComponent extends BaseDashboardPart implements AfterViewInit, OnDestroy {\n /**\n * Current user - required by the artifact viewer panel.\n * Should be provided by the dashboard host or retrieved from a service.\n */\n @Input() CurrentUser: UserInfo | null = null;\n\n /**\n * Environment ID - required by the artifact viewer panel.\n * Should be provided by the dashboard host.\n */\n @Input() EnvironmentId: string = '';\n\n public hasArtifact = false;\n public artifactId: string | null = null;\n public versionNumber: number | undefined;\n public showHeader: boolean = false; // Default to false for dashboard embedding\n public showTabs: boolean = true;\n public showCloseButton: boolean = false; // Always false in dashboard context - close handled by dashboard\n public showMaximizeButton: boolean = false; // Always false in dashboard context - maximize handled by dashboard\n public refreshTrigger = new Subject<{ artifactId: string; versionNumber: number }>();\n\n // Expose for template\n public get currentUser(): UserInfo {\n // Use provided CurrentUser, or fall back to Metadata.CurrentUser\n // In client-side Angular context, Metadata.CurrentUser should always be available\n const user = this.CurrentUser || new Metadata().CurrentUser;\n if (!user) {\n throw new Error('No current user available - user must be logged in to view artifacts');\n }\n return user;\n }\n\n public get environmentId(): string {\n return this.EnvironmentId || '';\n }\n\n constructor(cdr: ChangeDetectorRef) {\n super(cdr);\n }\n\n ngAfterViewInit(): void {\n if (this.Panel) {\n this.loadContent();\n }\n }\n\n public async loadContent(): Promise<void> {\n const config = this.getConfig<PanelConfig>();\n const artifactId = config?.['artifactId'] as string | undefined;\n\n if (!artifactId) {\n this.hasArtifact = false;\n this.cdr.detectChanges();\n return;\n }\n\n this.setLoading(true);\n\n try {\n // Set artifact ID and version from config\n this.artifactId = artifactId;\n this.versionNumber = config?.['versionNumber'] as number | undefined;\n // Display options - showHeader defaults to false for clean dashboard embedding\n this.showHeader = (config?.['showHeader'] as boolean) ?? false;\n this.showTabs = (config?.['showTabs'] as boolean) ?? true;\n this.hasArtifact = true;\n\n this.setLoading(false);\n } catch (error) {\n this.setError(error instanceof Error ? error.message : 'Failed to load artifact');\n }\n }\n\n /**\n * Refresh the artifact display\n */\n public refresh(): void {\n if (this.artifactId && this.versionNumber) {\n this.refreshTrigger.next({\n artifactId: this.artifactId,\n versionNumber: this.versionNumber\n });\n }\n }\n\n /**\n * Handle navigation link events from artifact viewer (conversation/collection links)\n */\n public onNavigateToLink(event: { type: 'conversation' | 'collection'; id: string; artifactId?: string; versionNumber?: number; versionId?: string }): void {\n // Emit data change event for navigation link (for listeners)\n this.emitDataChanged({\n type: 'navigate-to-link',\n linkType: event.type,\n linkId: event.id,\n artifactId: event.artifactId,\n versionNumber: event.versionNumber,\n versionId: event.versionId\n });\n\n // TODO: Add navigation request methods for conversation/collection when needed\n // For now, these are emitted as data change events for parent components to handle\n }\n\n /**\n * Handle entity record navigation events from artifact viewer\n */\n public onOpenEntityRecord(event: { entityName: string; compositeKey: CompositeKey }): void {\n // Emit data change event for listeners\n this.emitDataChanged({\n type: 'open-entity-record',\n entityName: event.entityName,\n compositeKey: event.compositeKey\n });\n\n // Use proper navigation request to bubble up through the stack\n if (event.entityName && event.compositeKey) {\n this.RequestOpenEntityRecord(\n event.entityName,\n event.compositeKey.ToURLSegment(),\n 'view',\n false\n );\n }\n }\n\n /**\n * Handle general navigation request events from artifact viewer plugins\n */\n public onNavigationRequest(event: NavigationRequest): void {\n this.RequestOpenNavItem(\n event.navItemName,\n event.appName,\n event.queryParams,\n false\n );\n }\n\n protected override cleanup(): void {\n this.refreshTrigger.complete();\n this.artifactId = null;\n this.versionNumber = undefined;\n }\n}\n"]}
@@ -174,7 +174,7 @@ let QueryPartComponent = class QueryPartComponent extends BaseDashboardPart {
174
174
  i0.ɵɵconditional(!ctx.IsLoading && !ctx.ErrorMessage && !ctx.hasQuery ? 3 : -1);
175
175
  i0.ɵɵadvance();
176
176
  i0.ɵɵconditional(!ctx.IsLoading && !ctx.ErrorMessage && ctx.hasQuery && ctx.queryId ? 4 : -1);
177
- } }, dependencies: [i1.LoadingComponent, i2.QueryViewerComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .query-part[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: #fff;\n }\n\n .loading-state[_ngcontent-%COMP%], \n .error-state[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #666;\n text-align: center;\n padding: 24px;\n }\n\n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n color: #ccc;\n margin-bottom: 16px;\n }\n\n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #d32f2f;\n }\n\n .empty-state[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n color: #333;\n }\n\n .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n }\n\n .query-content[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n mj-query-viewer[_ngcontent-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n }"] });
177
+ } }, dependencies: [i1.LoadingComponent, i2.QueryViewerComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .query-part[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n }\n\n .loading-state[_ngcontent-%COMP%], \n .error-state[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n }\n\n .empty-state[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n color: var(--mj-text-primary);\n }\n\n .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n }\n\n .query-content[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n mj-query-viewer[_ngcontent-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n }"] });
178
178
  };
179
179
  QueryPartComponent = __decorate([
180
180
  RegisterClass(BaseDashboardPart, 'QueryPanelRenderer')
@@ -224,7 +224,7 @@ export { QueryPartComponent };
224
224
  </div>
225
225
  }
226
226
  </div>
227
- `, styles: ["\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .query-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: #fff;\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #666;\n text-align: center;\n padding: 24px;\n }\n\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: #ccc;\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: #d32f2f;\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: #333;\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n .query-content {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n mj-query-viewer {\n display: block;\n width: 100%;\n height: 100%;\n }\n "] }]
227
+ `, styles: ["\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .query-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: var(--mj-status-error);\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: var(--mj-text-primary);\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n .query-content {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n mj-query-viewer {\n display: block;\n width: 100%;\n height: 100%;\n }\n "] }]
228
228
  }], () => [{ type: i0.ChangeDetectorRef }], { queryViewer: [{
229
229
  type: ViewChild,
230
230
  args: ['queryViewer']
@@ -1 +1 @@
1
- {"version":3,"file":"query-part.component.js","sourceRoot":"","sources":["../../../src/lib/parts/query-part.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ChangeDetectorRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseDashboardPart } from './base-dashboard-part';\nimport { PanelConfig } from '../models/dashboard-types';\nimport { Metadata } from '@memberjunction/core';\nimport { MJQueryEntity } from '@memberjunction/core-entities';\nimport { QueryViewerComponent, QueryEntityLinkClickEvent } from '@memberjunction/ng-query-viewer';\n\n/**\n * Runtime renderer for Query dashboard parts.\n * Displays query results using mj-query-viewer with parameter controls and auto-refresh support.\n */\n@RegisterClass(BaseDashboardPart, 'QueryPanelRenderer')\n@Component({\n standalone: false,\n selector: 'mj-query-part',\n template: `\n <div class=\"query-part\" [class.loading]=\"IsLoading\" [class.error]=\"ErrorMessage\">\n <!-- Loading state -->\n @if (IsLoading) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading query...\"></mj-loading>\n </div>\n }\n \n <!-- Error state -->\n @if (ErrorMessage && !IsLoading) {\n <div class=\"error-state\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ ErrorMessage }}</span>\n </div>\n }\n \n <!-- No query configured -->\n @if (!IsLoading && !ErrorMessage && !hasQuery) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-flask\"></i>\n <h4>No Query Selected</h4>\n <p>Click the configure button to select a query for this part.</p>\n </div>\n }\n \n <!-- Query Viewer -->\n @if (!IsLoading && !ErrorMessage && hasQuery && queryId) {\n <div class=\"query-content\">\n <mj-query-viewer\n #queryViewer\n [QueryId]=\"queryId\"\n [AutoRun]=\"true\"\n [ShowToolbar]=\"showToolbar\"\n [PersistState]=\"true\"\n [PersistParameters]=\"true\"\n (EntityLinkClick)=\"onEntityLinkClick($event)\"\n (QueryError)=\"onQueryError($event)\">\n </mj-query-viewer>\n </div>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .query-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: #fff;\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #666;\n text-align: center;\n padding: 24px;\n }\n\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: #ccc;\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: #d32f2f;\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: #333;\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n .query-content {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n mj-query-viewer {\n display: block;\n width: 100%;\n height: 100%;\n }\n `]\n})\nexport class QueryPartComponent extends BaseDashboardPart implements AfterViewInit, OnDestroy {\n @ViewChild('queryViewer') queryViewer!: QueryViewerComponent;\n\n public hasQuery = false;\n public queryId: string | null = null;\n public showToolbar = true;\n\n private queryEntity: MJQueryEntity | null = null;\n private autoRefreshTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(cdr: ChangeDetectorRef) {\n super(cdr);\n }\n\n ngAfterViewInit(): void {\n if (this.Panel) {\n this.loadContent();\n }\n }\n\n public async loadContent(): Promise<void> {\n const config = this.getConfig<PanelConfig>();\n const queryId = config?.['queryId'] as string | undefined;\n const queryName = config?.['queryName'] as string | undefined;\n\n if (!queryId && !queryName) {\n this.hasQuery = false;\n this.cdr.detectChanges();\n return;\n }\n\n this.setLoading(true);\n this.stopAutoRefresh();\n\n try {\n const md = new Metadata();\n\n if (queryId) {\n // Load query by ID to verify it exists\n this.queryEntity = await md.GetEntityObject<MJQueryEntity>('MJ: Queries');\n const loaded = await this.queryEntity.Load(queryId);\n\n if (!loaded) {\n throw new Error('Query not found');\n }\n\n this.queryId = queryId;\n } else if (queryName) {\n // Query by name - find the query ID from metadata\n const queryInfo = md.Queries.find(q => q.Name === queryName);\n if (queryInfo) {\n this.queryId = queryInfo.ID;\n } else {\n throw new Error(`Query \"${queryName}\" not found`);\n }\n }\n\n this.hasQuery = true;\n this.showToolbar = (config?.['showParameterControls'] as boolean) !== false;\n\n // Set auto-refresh if configured\n const autoRefreshSeconds = (config?.['autoRefreshSeconds'] as number) || 0;\n if (autoRefreshSeconds > 0) {\n this.startAutoRefresh(autoRefreshSeconds);\n }\n\n this.setLoading(false);\n } catch (error) {\n this.setError(error instanceof Error ? error.message : 'Failed to load query');\n }\n }\n\n public onEntityLinkClick(event: QueryEntityLinkClickEvent): void {\n // Emit data change event with clicked entity info (for any listeners that need it)\n this.emitDataChanged({\n type: 'entity-link-click',\n entityName: event.entityName,\n recordId: event.recordId\n });\n\n // Request navigation to open the record\n if (event.entityName && event.recordId) {\n this.RequestOpenEntityRecord(\n event.entityName,\n event.recordId,\n 'view',\n false\n );\n }\n }\n\n public onQueryError(error: Error): void {\n console.error('[QueryPart] Query error:', error.message);\n }\n\n public refreshQuery(): void {\n if (this.queryViewer) {\n this.queryViewer.Refresh();\n }\n }\n\n private startAutoRefresh(seconds: number): void {\n this.stopAutoRefresh();\n if (seconds > 0) {\n this.autoRefreshTimer = setInterval(() => {\n this.refreshQuery();\n }, seconds * 1000);\n }\n }\n\n private stopAutoRefresh(): void {\n if (this.autoRefreshTimer) {\n clearInterval(this.autoRefreshTimer);\n this.autoRefreshTimer = null;\n }\n }\n\n protected override cleanup(): void {\n this.stopAutoRefresh();\n this.queryEntity = null;\n this.queryId = null;\n }\n}\n"]}
1
+ {"version":3,"file":"query-part.component.js","sourceRoot":"","sources":["../../../src/lib/parts/query-part.component.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,SAAS,EAAqB,SAAS,EAA4B,MAAM,eAAe,CAAC;AAClG,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;;;;;;IAgBpC,8BAA2B;IACzB,gCAAiD;IACnD,iBAAM;;;IAKN,8BAAyB;IACvB,uBAAgD;IAChD,4BAAM;IAAA,YAAkB;IAC1B,AAD0B,iBAAO,EAC3B;;;IADE,eAAkB;IAAlB,yCAAkB;;;IAM1B,8BAAyB;IACvB,uBAAiC;IACjC,0BAAI;IAAA,iCAAiB;IAAA,iBAAK;IAC1B,yBAAG;IAAA,2EAA2D;IAChE,AADgE,iBAAI,EAC9D;;;;IAMJ,AADF,8BAA2B,4BASa;IAApC,AADA,yNAAmB,gCAAyB,KAAC,kMAC/B,2BAAoB,KAAC;IAEvC,AADE,iBAAkB,EACd;;;IARF,cAAmB;IAInB,AADA,AADA,AADA,AADA,wCAAmB,iBACH,mCACW,sBACN,2BACK;;AA3C1C;;;GAGG;AA8GI,IAAM,kBAAkB,GAAxB,MAAM,kBAAmB,SAAQ,iBAAiB;IAC3B,WAAW,CAAwB;IAEtD,QAAQ,GAAG,KAAK,CAAC;IACjB,OAAO,GAAkB,IAAI,CAAC;IAC9B,WAAW,GAAG,IAAI,CAAC;IAElB,WAAW,GAAyB,IAAI,CAAC;IACzC,gBAAgB,GAA0C,IAAI,CAAC;IAEvE,YAAY,GAAsB;QAC9B,KAAK,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IAED,eAAe;QACX,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,WAAW,EAAE,CAAC;QACvB,CAAC;IACL,CAAC;IAEM,KAAK,CAAC,WAAW;QACpB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAe,CAAC;QAC7C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,SAAS,CAAuB,CAAC;QAC1D,MAAM,SAAS,GAAG,MAAM,EAAE,CAAC,WAAW,CAAuB,CAAC;QAE9D,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC;YACzB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;YACzB,OAAO;QACX,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,CAAC,eAAe,EAAE,CAAC;QAEvB,IAAI,CAAC;YACD,MAAM,EAAE,GAAG,IAAI,QAAQ,EAAE,CAAC;YAE1B,IAAI,OAAO,EAAE,CAAC;gBACV,uCAAuC;gBACvC,IAAI,CAAC,WAAW,GAAG,MAAM,EAAE,CAAC,eAAe,CAAgB,aAAa,CAAC,CAAC;gBAC1E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAEpD,IAAI,CAAC,MAAM,EAAE,CAAC;oBACV,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;gBACvC,CAAC;gBAED,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;YAC3B,CAAC;iBAAM,IAAI,SAAS,EAAE,CAAC;gBACnB,kDAAkD;gBAClD,MAAM,SAAS,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;gBAC7D,IAAI,SAAS,EAAE,CAAC;oBACZ,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC,EAAE,CAAC;gBAChC,CAAC;qBAAM,CAAC;oBACJ,MAAM,IAAI,KAAK,CAAC,UAAU,SAAS,aAAa,CAAC,CAAC;gBACtD,CAAC;YACL,CAAC;YAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC,WAAW,GAAI,MAAM,EAAE,CAAC,uBAAuB,CAAa,KAAK,KAAK,CAAC;YAE5E,iCAAiC;YACjC,MAAM,kBAAkB,GAAI,MAAM,EAAE,CAAC,oBAAoB,CAAY,IAAI,CAAC,CAAC;YAC3E,IAAI,kBAAkB,GAAG,CAAC,EAAE,CAAC;gBACzB,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;YAC9C,CAAC;YAED,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,QAAQ,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC;QACnF,CAAC;IACL,CAAC;IAEM,iBAAiB,CAAC,KAAgC;QACrD,mFAAmF;QACnF,IAAI,CAAC,eAAe,CAAC;YACjB,IAAI,EAAE,mBAAmB;YACzB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;SAC3B,CAAC,CAAC;QAEH,wCAAwC;QACxC,IAAI,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACrC,IAAI,CAAC,uBAAuB,CACxB,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,QAAQ,EACd,MAAM,EACN,KAAK,CACR,CAAC;QACN,CAAC;IACL,CAAC;IAEM,YAAY,CAAC,KAAY;QAC5B,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7D,CAAC;IAEM,YAAY;QACf,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACnB,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;QAC/B,CAAC;IACL,CAAC;IAEO,gBAAgB,CAAC,OAAe;QACpC,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YACd,IAAI,CAAC,gBAAgB,GAAG,WAAW,CAAC,GAAG,EAAE;gBACrC,IAAI,CAAC,YAAY,EAAE,CAAC;YACxB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC;QACvB,CAAC;IACL,CAAC;IAEO,eAAe;QACnB,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACrC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QACjC,CAAC;IACL,CAAC;IAEkB,OAAO;QACtB,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACxB,CAAC;4GAzHQ,kBAAkB;6DAAlB,kBAAkB;;;;;;YAxGvB,8BAAiF;YAE/E,oFAAiB;YAOjB,oFAAkC;YAQlC,oFAAgD;YAShD,oFAA0D;YAc5D,iBAAM;;YAxC8C,AAA5B,wCAA2B,2BAA6B;YAE9E,cAIC;YAJD,wCAIC;YAGD,cAKC;YALD,6DAKC;YAGD,cAMC;YAND,+EAMC;YAGD,cAaC;YAbD,6FAaC;;;AAiEE,kBAAkB;IA7G9B,aAAa,CAAC,iBAAiB,EAAE,oBAAoB,CAAC;GA6G1C,kBAAkB,CA0H9B;;iFA1HY,kBAAkB;cA5G9B,SAAS;6BACI,KAAK,YACL,eAAe,YACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SA0CL;;kBAgEJ,SAAS;mBAAC,aAAa;;kFADf,kBAAkB","sourcesContent":["import { Component, ChangeDetectorRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseDashboardPart } from './base-dashboard-part';\nimport { PanelConfig } from '../models/dashboard-types';\nimport { Metadata } from '@memberjunction/core';\nimport { MJQueryEntity } from '@memberjunction/core-entities';\nimport { QueryViewerComponent, QueryEntityLinkClickEvent } from '@memberjunction/ng-query-viewer';\n\n/**\n * Runtime renderer for Query dashboard parts.\n * Displays query results using mj-query-viewer with parameter controls and auto-refresh support.\n */\n@RegisterClass(BaseDashboardPart, 'QueryPanelRenderer')\n@Component({\n standalone: false,\n selector: 'mj-query-part',\n template: `\n <div class=\"query-part\" [class.loading]=\"IsLoading\" [class.error]=\"ErrorMessage\">\n <!-- Loading state -->\n @if (IsLoading) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading query...\"></mj-loading>\n </div>\n }\n \n <!-- Error state -->\n @if (ErrorMessage && !IsLoading) {\n <div class=\"error-state\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ ErrorMessage }}</span>\n </div>\n }\n \n <!-- No query configured -->\n @if (!IsLoading && !ErrorMessage && !hasQuery) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-flask\"></i>\n <h4>No Query Selected</h4>\n <p>Click the configure button to select a query for this part.</p>\n </div>\n }\n \n <!-- Query Viewer -->\n @if (!IsLoading && !ErrorMessage && hasQuery && queryId) {\n <div class=\"query-content\">\n <mj-query-viewer\n #queryViewer\n [QueryId]=\"queryId\"\n [AutoRun]=\"true\"\n [ShowToolbar]=\"showToolbar\"\n [PersistState]=\"true\"\n [PersistParameters]=\"true\"\n (EntityLinkClick)=\"onEntityLinkClick($event)\"\n (QueryError)=\"onQueryError($event)\">\n </mj-query-viewer>\n </div>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .query-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: var(--mj-status-error);\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: var(--mj-text-primary);\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n .query-content {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n }\n\n mj-query-viewer {\n display: block;\n width: 100%;\n height: 100%;\n }\n `]\n})\nexport class QueryPartComponent extends BaseDashboardPart implements AfterViewInit, OnDestroy {\n @ViewChild('queryViewer') queryViewer!: QueryViewerComponent;\n\n public hasQuery = false;\n public queryId: string | null = null;\n public showToolbar = true;\n\n private queryEntity: MJQueryEntity | null = null;\n private autoRefreshTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(cdr: ChangeDetectorRef) {\n super(cdr);\n }\n\n ngAfterViewInit(): void {\n if (this.Panel) {\n this.loadContent();\n }\n }\n\n public async loadContent(): Promise<void> {\n const config = this.getConfig<PanelConfig>();\n const queryId = config?.['queryId'] as string | undefined;\n const queryName = config?.['queryName'] as string | undefined;\n\n if (!queryId && !queryName) {\n this.hasQuery = false;\n this.cdr.detectChanges();\n return;\n }\n\n this.setLoading(true);\n this.stopAutoRefresh();\n\n try {\n const md = new Metadata();\n\n if (queryId) {\n // Load query by ID to verify it exists\n this.queryEntity = await md.GetEntityObject<MJQueryEntity>('MJ: Queries');\n const loaded = await this.queryEntity.Load(queryId);\n\n if (!loaded) {\n throw new Error('Query not found');\n }\n\n this.queryId = queryId;\n } else if (queryName) {\n // Query by name - find the query ID from metadata\n const queryInfo = md.Queries.find(q => q.Name === queryName);\n if (queryInfo) {\n this.queryId = queryInfo.ID;\n } else {\n throw new Error(`Query \"${queryName}\" not found`);\n }\n }\n\n this.hasQuery = true;\n this.showToolbar = (config?.['showParameterControls'] as boolean) !== false;\n\n // Set auto-refresh if configured\n const autoRefreshSeconds = (config?.['autoRefreshSeconds'] as number) || 0;\n if (autoRefreshSeconds > 0) {\n this.startAutoRefresh(autoRefreshSeconds);\n }\n\n this.setLoading(false);\n } catch (error) {\n this.setError(error instanceof Error ? error.message : 'Failed to load query');\n }\n }\n\n public onEntityLinkClick(event: QueryEntityLinkClickEvent): void {\n // Emit data change event with clicked entity info (for any listeners that need it)\n this.emitDataChanged({\n type: 'entity-link-click',\n entityName: event.entityName,\n recordId: event.recordId\n });\n\n // Request navigation to open the record\n if (event.entityName && event.recordId) {\n this.RequestOpenEntityRecord(\n event.entityName,\n event.recordId,\n 'view',\n false\n );\n }\n }\n\n public onQueryError(error: Error): void {\n console.error('[QueryPart] Query error:', error.message);\n }\n\n public refreshQuery(): void {\n if (this.queryViewer) {\n this.queryViewer.Refresh();\n }\n }\n\n private startAutoRefresh(seconds: number): void {\n this.stopAutoRefresh();\n if (seconds > 0) {\n this.autoRefreshTimer = setInterval(() => {\n this.refreshQuery();\n }, seconds * 1000);\n }\n }\n\n private stopAutoRefresh(): void {\n if (this.autoRefreshTimer) {\n clearInterval(this.autoRefreshTimer);\n this.autoRefreshTimer = null;\n }\n }\n\n protected override cleanup(): void {\n this.stopAutoRefresh();\n this.queryEntity = null;\n this.queryId = null;\n }\n}\n"]}
@@ -165,7 +165,7 @@ let ViewPartComponent = class ViewPartComponent extends BaseDashboardPart {
165
165
  i0.ɵɵconditional(!ctx.IsLoading && !ctx.ErrorMessage && !ctx.hasView ? 3 : -1);
166
166
  i0.ɵɵadvance();
167
167
  i0.ɵɵconditional(!ctx.IsLoading && !ctx.ErrorMessage && ctx.hasView && ctx.entityInfo ? 4 : -1);
168
- } }, dependencies: [i1.LoadingComponent, i2.EntityViewerComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .view-part[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: #fff;\n }\n\n .loading-state[_ngcontent-%COMP%], \n .error-state[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #666;\n text-align: center;\n padding: 24px;\n }\n\n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n color: #ccc;\n margin-bottom: 16px;\n }\n\n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #d32f2f;\n }\n\n .empty-state[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n color: #333;\n }\n\n .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n }\n\n mj-entity-viewer[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n }"] });
168
+ } }, dependencies: [i1.LoadingComponent, i2.EntityViewerComponent], styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .view-part[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n }\n\n .loading-state[_ngcontent-%COMP%], \n .error-state[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n }\n\n .empty-state[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n color: var(--mj-text-primary);\n }\n\n .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n }\n\n mj-entity-viewer[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n }"] });
169
169
  };
170
170
  ViewPartComponent = __decorate([
171
171
  RegisterClass(BaseDashboardPart, 'ViewPanelRenderer')
@@ -212,7 +212,7 @@ export { ViewPartComponent };
212
212
  </mj-entity-viewer>
213
213
  }
214
214
  </div>
215
- `, styles: ["\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .view-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: #fff;\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #666;\n text-align: center;\n padding: 24px;\n }\n\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: #ccc;\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: #d32f2f;\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: #333;\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n mj-entity-viewer {\n flex: 1;\n min-height: 0;\n }\n "] }]
215
+ `, styles: ["\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .view-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: var(--mj-status-error);\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: var(--mj-text-primary);\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n mj-entity-viewer {\n flex: 1;\n min-height: 0;\n }\n "] }]
216
216
  }], () => [{ type: i0.ChangeDetectorRef }], null); })();
217
217
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ViewPartComponent, { className: "ViewPartComponent", filePath: "src/lib/parts/view-part.component.ts", lineNumber: 112 }); })();
218
218
  //# sourceMappingURL=view-part.component.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"view-part.component.js","sourceRoot":"","sources":["../../../src/lib/parts/view-part.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ChangeDetectorRef, AfterViewInit, OnDestroy } from '@angular/core';\nimport { RegisterClass, UUIDsEqual } from '@memberjunction/global';\nimport { BaseDashboardPart } from './base-dashboard-part';\nimport { PanelConfig } from '../models/dashboard-types';\nimport { Metadata, EntityInfo } from '@memberjunction/core';\nimport { MJUserViewEntityExtended } from '@memberjunction/core-entities';\nimport { EntityViewMode, RecordSelectedEvent, RecordOpenedEvent } from '@memberjunction/ng-entity-viewer';\n\n/**\n * Runtime renderer for View dashboard parts.\n * Displays entity data using mj-entity-viewer with grid, cards, or timeline layout.\n */\n@RegisterClass(BaseDashboardPart, 'ViewPanelRenderer')\n@Component({\n standalone: false,\n selector: 'mj-view-part',\n template: `\n <div class=\"view-part\" [class.loading]=\"IsLoading\" [class.error]=\"ErrorMessage\">\n <!-- Loading state -->\n @if (IsLoading) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading view...\"></mj-loading>\n </div>\n }\n \n <!-- Error state -->\n @if (ErrorMessage && !IsLoading) {\n <div class=\"error-state\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ ErrorMessage }}</span>\n </div>\n }\n \n <!-- No view configured -->\n @if (!IsLoading && !ErrorMessage && !hasView) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-table\"></i>\n <h4>No View Selected</h4>\n <p>Click the configure button to select a view for this part.</p>\n </div>\n }\n \n <!-- Entity Viewer -->\n @if (!IsLoading && !ErrorMessage && hasView && entityInfo) {\n <mj-entity-viewer\n [entity]=\"entityInfo\"\n [viewEntity]=\"viewEntity\"\n [(viewMode)]=\"viewMode\"\n [gridSelectionMode]=\"selectionMode\"\n [showGridToolbar]=\"false\"\n (recordSelected)=\"onRecordSelected($event)\"\n (recordOpened)=\"onRecordOpened($event)\">\n </mj-entity-viewer>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .view-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: #fff;\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #666;\n text-align: center;\n padding: 24px;\n }\n\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: #ccc;\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: #d32f2f;\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: #333;\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n mj-entity-viewer {\n flex: 1;\n min-height: 0;\n }\n `]\n})\nexport class ViewPartComponent extends BaseDashboardPart implements AfterViewInit, OnDestroy {\n public hasView = false;\n public viewEntity: MJUserViewEntityExtended | null = null;\n public entityInfo: EntityInfo | null = null;\n public viewMode: EntityViewMode = 'grid';\n public selectionMode: 'single' | 'multiple' = 'single';\n\n constructor(cdr: ChangeDetectorRef) {\n super(cdr);\n }\n\n ngAfterViewInit(): void {\n if (this.Panel) {\n this.loadContent();\n }\n }\n\n public async loadContent(): Promise<void> {\n const config = this.getConfig<PanelConfig>();\n const viewId = config?.['viewId'] as string | undefined;\n const entityName = config?.['entityName'] as string | undefined;\n\n if (!viewId && !entityName) {\n this.hasView = false;\n this.cdr.detectChanges();\n return;\n }\n\n this.setLoading(true);\n\n try {\n const md = new Metadata();\n\n // Set view mode from config\n this.viewMode = (config?.['displayMode'] as EntityViewMode) || 'grid';\n this.selectionMode = config?.['selectionMode'] === 'multiple' ? 'multiple' : 'single';\n\n if (viewId) {\n // Load saved view by ID\n const viewEntity = await md.GetEntityObject<MJUserViewEntityExtended>('MJ: User Views');\n const loaded = await viewEntity.Load(viewId);\n this.viewEntity = viewEntity; // IMPORTANT - only set this.viewEntity AFTER we have it loaded in the above\n\n if (!loaded) {\n throw new Error('View not found');\n }\n\n // Get entity info from the view - prefer ViewEntityInfo if available (set by MJUserViewEntityExtended.Load)\n // Fall back to looking up by Entity name (virtual field) or EntityID\n if (viewEntity.ViewEntityInfo) {\n this.entityInfo = viewEntity.ViewEntityInfo;\n } else if (viewEntity.Entity) {\n this.entityInfo = md.Entities.find(e => e.Name === viewEntity!.Entity) || null;\n } else if (viewEntity.EntityID) {\n // Last resort: look up by EntityID\n this.entityInfo = md.Entities.find(e => UUIDsEqual(e.ID, viewEntity!.EntityID)) || null;\n }\n\n if (!this.entityInfo) {\n throw new Error(`Could not determine entity for view \"${this.viewEntity.Name}\" (ID: ${viewId})`);\n }\n } else if (entityName) {\n // Create dynamic view for entity (no saved view)\n this.entityInfo = md.Entities.find(e => e.Name === entityName) || null;\n\n if (!this.entityInfo) {\n throw new Error(`Entity \"${entityName}\" not found`);\n }\n\n // No viewEntity means the entity-viewer will show all records\n this.viewEntity = null;\n }\n\n this.hasView = true;\n this.setLoading(false);\n } catch (error) {\n this.setError(error instanceof Error ? error.message : 'Failed to load view');\n }\n }\n\n public onRecordSelected(event: RecordSelectedEvent): void {\n // Emit data change event with selected record\n this.emitDataChanged({\n type: 'record-selected',\n record: event.record,\n primaryKey: event.record?.PrimaryKey\n });\n }\n\n public onRecordOpened(event: RecordOpenedEvent): void {\n // Emit data change event for record open (for any listeners that need it)\n this.emitDataChanged({\n type: 'record-opened',\n record: event.record,\n primaryKey: event.record?.PrimaryKey\n });\n\n // Request navigation to open the record\n if (event.entity && event.compositeKey) {\n this.RequestOpenEntityRecord(\n event.entity.Name,\n event.compositeKey.ToURLSegment(),\n 'view',\n false\n );\n }\n }\n\n protected override cleanup(): void {\n // EntityViewer handles its own cleanup\n this.viewEntity = null;\n this.entityInfo = null;\n }\n}\n"]}
1
+ {"version":3,"file":"view-part.component.js","sourceRoot":"","sources":["../../../src/lib/parts/view-part.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ChangeDetectorRef, AfterViewInit, OnDestroy } from '@angular/core';\nimport { RegisterClass, UUIDsEqual } from '@memberjunction/global';\nimport { BaseDashboardPart } from './base-dashboard-part';\nimport { PanelConfig } from '../models/dashboard-types';\nimport { Metadata, EntityInfo } from '@memberjunction/core';\nimport { MJUserViewEntityExtended } from '@memberjunction/core-entities';\nimport { EntityViewMode, RecordSelectedEvent, RecordOpenedEvent } from '@memberjunction/ng-entity-viewer';\n\n/**\n * Runtime renderer for View dashboard parts.\n * Displays entity data using mj-entity-viewer with grid, cards, or timeline layout.\n */\n@RegisterClass(BaseDashboardPart, 'ViewPanelRenderer')\n@Component({\n standalone: false,\n selector: 'mj-view-part',\n template: `\n <div class=\"view-part\" [class.loading]=\"IsLoading\" [class.error]=\"ErrorMessage\">\n <!-- Loading state -->\n @if (IsLoading) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading view...\"></mj-loading>\n </div>\n }\n \n <!-- Error state -->\n @if (ErrorMessage && !IsLoading) {\n <div class=\"error-state\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ ErrorMessage }}</span>\n </div>\n }\n \n <!-- No view configured -->\n @if (!IsLoading && !ErrorMessage && !hasView) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-table\"></i>\n <h4>No View Selected</h4>\n <p>Click the configure button to select a view for this part.</p>\n </div>\n }\n \n <!-- Entity Viewer -->\n @if (!IsLoading && !ErrorMessage && hasView && entityInfo) {\n <mj-entity-viewer\n [entity]=\"entityInfo\"\n [viewEntity]=\"viewEntity\"\n [(viewMode)]=\"viewMode\"\n [gridSelectionMode]=\"selectionMode\"\n [showGridToolbar]=\"false\"\n (recordSelected)=\"onRecordSelected($event)\"\n (recordOpened)=\"onRecordOpened($event)\">\n </mj-entity-viewer>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .view-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: var(--mj-status-error);\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: var(--mj-text-primary);\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n mj-entity-viewer {\n flex: 1;\n min-height: 0;\n }\n `]\n})\nexport class ViewPartComponent extends BaseDashboardPart implements AfterViewInit, OnDestroy {\n public hasView = false;\n public viewEntity: MJUserViewEntityExtended | null = null;\n public entityInfo: EntityInfo | null = null;\n public viewMode: EntityViewMode = 'grid';\n public selectionMode: 'single' | 'multiple' = 'single';\n\n constructor(cdr: ChangeDetectorRef) {\n super(cdr);\n }\n\n ngAfterViewInit(): void {\n if (this.Panel) {\n this.loadContent();\n }\n }\n\n public async loadContent(): Promise<void> {\n const config = this.getConfig<PanelConfig>();\n const viewId = config?.['viewId'] as string | undefined;\n const entityName = config?.['entityName'] as string | undefined;\n\n if (!viewId && !entityName) {\n this.hasView = false;\n this.cdr.detectChanges();\n return;\n }\n\n this.setLoading(true);\n\n try {\n const md = new Metadata();\n\n // Set view mode from config\n this.viewMode = (config?.['displayMode'] as EntityViewMode) || 'grid';\n this.selectionMode = config?.['selectionMode'] === 'multiple' ? 'multiple' : 'single';\n\n if (viewId) {\n // Load saved view by ID\n const viewEntity = await md.GetEntityObject<MJUserViewEntityExtended>('MJ: User Views');\n const loaded = await viewEntity.Load(viewId);\n this.viewEntity = viewEntity; // IMPORTANT - only set this.viewEntity AFTER we have it loaded in the above\n\n if (!loaded) {\n throw new Error('View not found');\n }\n\n // Get entity info from the view - prefer ViewEntityInfo if available (set by MJUserViewEntityExtended.Load)\n // Fall back to looking up by Entity name (virtual field) or EntityID\n if (viewEntity.ViewEntityInfo) {\n this.entityInfo = viewEntity.ViewEntityInfo;\n } else if (viewEntity.Entity) {\n this.entityInfo = md.Entities.find(e => e.Name === viewEntity!.Entity) || null;\n } else if (viewEntity.EntityID) {\n // Last resort: look up by EntityID\n this.entityInfo = md.Entities.find(e => UUIDsEqual(e.ID, viewEntity!.EntityID)) || null;\n }\n\n if (!this.entityInfo) {\n throw new Error(`Could not determine entity for view \"${this.viewEntity.Name}\" (ID: ${viewId})`);\n }\n } else if (entityName) {\n // Create dynamic view for entity (no saved view)\n this.entityInfo = md.Entities.find(e => e.Name === entityName) || null;\n\n if (!this.entityInfo) {\n throw new Error(`Entity \"${entityName}\" not found`);\n }\n\n // No viewEntity means the entity-viewer will show all records\n this.viewEntity = null;\n }\n\n this.hasView = true;\n this.setLoading(false);\n } catch (error) {\n this.setError(error instanceof Error ? error.message : 'Failed to load view');\n }\n }\n\n public onRecordSelected(event: RecordSelectedEvent): void {\n // Emit data change event with selected record\n this.emitDataChanged({\n type: 'record-selected',\n record: event.record,\n primaryKey: event.record?.PrimaryKey\n });\n }\n\n public onRecordOpened(event: RecordOpenedEvent): void {\n // Emit data change event for record open (for any listeners that need it)\n this.emitDataChanged({\n type: 'record-opened',\n record: event.record,\n primaryKey: event.record?.PrimaryKey\n });\n\n // Request navigation to open the record\n if (event.entity && event.compositeKey) {\n this.RequestOpenEntityRecord(\n event.entity.Name,\n event.compositeKey.ToURLSegment(),\n 'view',\n false\n );\n }\n }\n\n protected override cleanup(): void {\n // EntityViewer handles its own cleanup\n this.viewEntity = null;\n this.entityInfo = null;\n }\n}\n"]}
@@ -263,7 +263,7 @@ let WebURLPartComponent = class WebURLPartComponent extends BaseDashboardPart {
263
263
  i0.ɵɵconditional(!ctx.IsLoading && !ctx.ErrorMessage && ctx.SafeUrl ? 4 : -1);
264
264
  i0.ɵɵadvance();
265
265
  i0.ɵɵconditional(!ctx.IsLoading && !ctx.ErrorMessage && ctx.SafeUrl && ctx.showFallbackLink ? 5 : -1);
266
- } }, styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .weburl-part[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: #fff;\n position: relative; \n\n }\n\n .loading-state[_ngcontent-%COMP%], \n .error-state[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #666;\n text-align: center;\n padding: 24px;\n }\n\n .loading-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n color: #ccc;\n margin-bottom: 16px;\n }\n\n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #d32f2f;\n }\n\n .error-state[_ngcontent-%COMP%] .open-link[_ngcontent-%COMP%] {\n margin-top: 16px;\n padding: 8px 16px;\n background: #5c6bc0;\n color: #fff;\n text-decoration: none;\n border-radius: 4px;\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n }\n\n .error-state[_ngcontent-%COMP%] .open-link[_ngcontent-%COMP%]:hover {\n background: #3f51b5;\n }\n\n .empty-state[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n color: #333;\n }\n\n .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n }\n\n .content-iframe[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n border: none;\n flex: 1;\n }\n\n .iframe-fallback[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 8px;\n background: #f5f5f5;\n border-top: 1px solid #e0e0e0;\n font-size: 12px;\n color: #666;\n }\n\n .iframe-fallback[_ngcontent-%COMP%] a[_ngcontent-%COMP%] {\n color: #5c6bc0;\n text-decoration: none;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .iframe-fallback[_ngcontent-%COMP%] a[_ngcontent-%COMP%]:hover {\n text-decoration: underline;\n }"] });
266
+ } }, styles: ["[_nghost-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .weburl-part[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n position: relative; \n\n }\n\n .loading-state[_ngcontent-%COMP%], \n .error-state[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .loading-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%], \n .empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .error-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n }\n\n .error-state[_ngcontent-%COMP%] .open-link[_ngcontent-%COMP%] {\n margin-top: 16px;\n padding: 8px 16px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n text-decoration: none;\n border-radius: 4px;\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n }\n\n .error-state[_ngcontent-%COMP%] .open-link[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary-hover);\n }\n\n .empty-state[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n color: var(--mj-text-primary);\n }\n\n .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n }\n\n .content-iframe[_ngcontent-%COMP%] {\n width: 100%;\n height: 100%;\n border: none;\n flex: 1;\n }\n\n .iframe-fallback[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 8px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-default);\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n\n .iframe-fallback[_ngcontent-%COMP%] a[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n text-decoration: none;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .iframe-fallback[_ngcontent-%COMP%] a[_ngcontent-%COMP%]:hover {\n text-decoration: underline;\n }"] });
267
267
  };
268
268
  WebURLPartComponent = __decorate([
269
269
  RegisterClass(BaseDashboardPart, 'WebURLPanelRenderer')
@@ -394,7 +394,7 @@ export { WebURLPartComponent };
394
394
  </div>
395
395
  }
396
396
  </div>
397
- `, styles: ["\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .weburl-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: #fff;\n position: relative; /* Required for overlay positioning */\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #666;\n text-align: center;\n padding: 24px;\n }\n\n .loading-state i,\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: #ccc;\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: #d32f2f;\n }\n\n .error-state .open-link {\n margin-top: 16px;\n padding: 8px 16px;\n background: #5c6bc0;\n color: #fff;\n text-decoration: none;\n border-radius: 4px;\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n }\n\n .error-state .open-link:hover {\n background: #3f51b5;\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: #333;\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n .content-iframe {\n width: 100%;\n height: 100%;\n border: none;\n flex: 1;\n }\n\n .iframe-fallback {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 8px;\n background: #f5f5f5;\n border-top: 1px solid #e0e0e0;\n font-size: 12px;\n color: #666;\n }\n\n .iframe-fallback a {\n color: #5c6bc0;\n text-decoration: none;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .iframe-fallback a:hover {\n text-decoration: underline;\n }\n "] }]
397
+ `, styles: ["\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .weburl-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n position: relative; /* Required for overlay positioning */\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .loading-state i,\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: var(--mj-status-error);\n }\n\n .error-state .open-link {\n margin-top: 16px;\n padding: 8px 16px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n text-decoration: none;\n border-radius: 4px;\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n }\n\n .error-state .open-link:hover {\n background: var(--mj-brand-primary-hover);\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: var(--mj-text-primary);\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n .content-iframe {\n width: 100%;\n height: 100%;\n border: none;\n flex: 1;\n }\n\n .iframe-fallback {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 8px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-default);\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n\n .iframe-fallback a {\n color: var(--mj-brand-primary);\n text-decoration: none;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .iframe-fallback a:hover {\n text-decoration: underline;\n }\n "] }]
398
398
  }], () => [{ type: i0.ChangeDetectorRef }, { type: i1.DomSanitizer }], { iframeRef: [{
399
399
  type: ViewChild,
400
400
  args: ['iframe']
@@ -1 +1 @@
1
- {"version":3,"file":"weburl-part.component.js","sourceRoot":"","sources":["../../../src/lib/parts/weburl-part.component.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,SAAS,EAAiC,SAAS,EAAiB,MAAM,eAAe,CAAC;AAEnG,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;;;;;IAe9C,8BAA2B;IACzB,uBAA2C;IAC3C,4BAAM;IAAA,0BAAU;IAClB,AADkB,iBAAO,EACnB;;;IASF,4BAAqD;IACnD,uBAA6C;IAC7C,oCACF;IAAA,iBAAI;;;IAHD,sDAAe;;;IAJtB,8BAAyB;IACvB,uBAAgD;IAChD,4BAAM;IAAA,YAAkB;IAAA,iBAAO;IAC/B,iGAAc;IAMhB,iBAAM;;;IAPE,eAAkB;IAAlB,yCAAkB;IACxB,cAKC;IALD,wCAKC;;;IAMH,8BAAyB;IACvB,wBAAiC;IACjC,0BAAI;IAAA,iCAAiB;IAAA,iBAAK;IAC1B,yBAAG;IAAA,sEAAsD;IAC3D,AAD2D,iBAAI,EACzD;;;;IAOJ,qCAQyB;IADvB,AADA,oMAAQ,qBAAc,KAAC,+LACd,4BAAqB,KAAC;IAEjC,iBAAS;;;IAJP,AAHA,8DAAe,mFAG6B;;;;IAQ9C,qCAOyB;IADvB,AADA,oMAAQ,qBAAc,KAAC,+LACd,4BAAqB,KAAC;IAEjC,iBAAS;;;IAJP,AAFA,8DAAe,mFAE6B;;;;IAQ9C,qCAQyB;IADvB,AADA,oMAAQ,qBAAc,KAAC,+LACd,4BAAqB,KAAC;IAEjC,iBAAS;;;IAJP,AAHA,8DAAe,mFAG6B;;;;IAQ9C,qCAOyB;IADvB,AADA,oMAAQ,qBAAc,KAAC,+LACd,4BAAqB,KAAC;IAEjC,iBAAS;;;IAJP,AAFA,8DAAe,mFAE6B;;;;IAQ9C,qCAQyB;IADvB,AADA,oMAAQ,qBAAc,KAAC,+LACd,4BAAqB,KAAC;IAEjC,iBAAS;;;IAJP,AAHA,8DAAe,mFAG6B;;;;IAQ9C,qCAOyB;IADvB,AADA,oMAAQ,qBAAc,KAAC,+LACd,4BAAqB,KAAC;IAEjC,iBAAS;;;IAJP,AAFA,8DAAe,mFAE6B;;;IApEhD,uGAAqD;IAarD,uGAAsD;IAYtD,uGAAmD;IAanD,uGAAoD;IAYpD,uGAAuD;IAavD,uGAAwD;;;IA/DxD,sFAWC;IAED,cAUC;IAVD,uFAUC;IAED,cAWC;IAXD,oFAWC;IAED,cAUC;IAVD,qFAUC;IAED,cAWC;IAXD,wFAWC;IAED,cAUC;IAVD,yFAUC;;;IAMC,AADF,8BAA6B,WACrB;IAAA,4CAA4B;IAAA,iBAAO;IACzC,6BAAmC;IACjC,uBAA6C;IAC7C,oCACF;IACF,AADE,iBAAI,EACA;;;IAJD,eAAe;IAAf,sDAAe;;AA5HhC;;;GAGG;AAqOI,IAAM,mBAAmB,GAAzB,MAAM,mBAAoB,SAAQ,iBAAiB;IACjC,SAAS,CAAiC;IAExD,OAAO,GAA2B,IAAI,CAAC;IACvC,MAAM,GAAW,EAAE,CAAC;IACpB,WAAW,GAAyC,UAAU,CAAC;IAC/D,eAAe,GAAY,IAAI,CAAC;IAChC,gBAAgB,GAAY,KAAK,CAAC,CAAC,uDAAuD;IAEzF,SAAS,CAAe;IACxB,gBAAgB,GAAyC,IAAI,CAAC;IAC9D,YAAY,GAAY,KAAK,CAAC;IAEtC,YAAY,GAAsB,EAAE,SAAuB;QACvD,KAAK,CAAC,GAAG,CAAC,CAAC;QACX,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/B,CAAC;IAED,eAAe;QACX,uCAAuC;QACvC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,WAAW,EAAE,CAAC;QACvB,CAAC;IACL,CAAC;IAEM,KAAK,CAAC,WAAW;QACpB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAe,CAAC;QAC7C,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,KAAK,CAAuB,CAAC;QAElD,6BAA6B;QAC7B,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACpC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QACjC,CAAC;QAED,cAAc;QACd,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC1B,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC;QAE9B,IAAI,CAAC,GAAG,EAAE,CAAC;YACP,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;YACzB,OAAO;QACX,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAEtB,IAAI,CAAC;YACD,kCAAkC;YAClC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;YAElB,8CAA8C;YAC9C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,8BAA8B,CAAC,GAAG,CAAC,CAAC;YAElE,+DAA+D;YAC/D,IAAI,CAAC,WAAW,GAAI,MAAM,EAAE,CAAC,aAAa,CAA0C,IAAI,UAAU,CAAC;YAEnG,4BAA4B;YAC5B,IAAI,CAAC,eAAe,GAAI,MAAM,EAAE,CAAC,iBAAiB,CAAa,IAAI,IAAI,CAAC;YAExE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAEvB,iEAAiE;YACjE,kFAAkF;YAClF,gGAAgG;YAChG,IAAI,CAAC,gBAAgB,GAAG,UAAU,CAAC,GAAG,EAAE;gBACpC,IAAI,CAAC,cAAc,EAAE,CAAC;YAC1B,CAAC,EAAE,IAAI,CAAC,CAAC;QAEb,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,QAAQ,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;QACjF,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,cAAc;QAClB,6EAA6E;QAC7E,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACrB,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;YAC7B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC;QACD,8EAA8E;IAClF,CAAC;IAED;;OAEG;IACI,YAAY;QACf,iCAAiC;QACjC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAEzB,4CAA4C;QAC5C,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACpC,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC;QACjC,CAAC;QAED,+DAA+D;QAC/D,4DAA4D;QAC5D,iFAAiF;QACjF,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;IAC7B,CAAC;IAED;;OAEG;IACI,aAAa,CAAC,MAAa;QAC9B,oEAAoE;QACpE,IAAI,CAAC,QAAQ,CAAC,mFAAmF,CAAC,CAAC;IACvG,CAAC;6GAjHQ,mBAAmB;6DAAnB,mBAAmB;;;;;;YA/NxB,8BAAkF;YAEhF,qFAAiB;YAQjB,qFAAoB;YAcpB,qFAA+C;YAS/C,2EAA8C;YA+E9C,qFAAkE;YASpE,iBAAM;;YAzH+C,AAA5B,wCAA2B,2BAA6B;YAE/E,cAKC;YALD,wCAKC;YAGD,cAWC;YAXD,2CAWC;YAGD,cAMC;YAND,8EAMC;YAGD,cA4EC;YA5ED,6EA4EC;YAGD,cAQC;YARD,qGAQC;;;AAuGE,mBAAmB;IApO/B,aAAa,CAAC,iBAAiB,EAAE,qBAAqB,CAAC;GAoO3C,mBAAmB,CAkH/B;;iFAlHY,mBAAmB;cAnO/B,SAAS;6BACI,KAAK,YACL,gBAAgB,YAChB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SA2HL;;kBAsGJ,SAAS;mBAAC,QAAQ;;kFADV,mBAAmB","sourcesContent":["import { Component, ChangeDetectorRef, ElementRef, ViewChild, AfterViewInit } from '@angular/core';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseDashboardPart } from './base-dashboard-part';\nimport { PanelConfig } from '../models/dashboard-types';\n\n/**\n * Runtime renderer for WebURL dashboard parts.\n * Displays web content in an iframe with configurable sandbox permissions.\n */\n@RegisterClass(BaseDashboardPart, 'WebURLPanelRenderer')\n@Component({\n standalone: false,\n selector: 'mj-weburl-part',\n template: `\n <div class=\"weburl-part\" [class.loading]=\"IsLoading\" [class.error]=\"ErrorMessage\">\n <!-- Loading state -->\n @if (IsLoading) {\n <div class=\"loading-state\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading...</span>\n </div>\n }\n \n <!-- Error state -->\n @if (ErrorMessage) {\n <div class=\"error-state\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ ErrorMessage }}</span>\n @if (rawUrl) {\n <a [href]=\"rawUrl\" target=\"_blank\" class=\"open-link\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n Open in new window\n </a>\n }\n </div>\n }\n \n <!-- No URL configured -->\n @if (!IsLoading && !ErrorMessage && !SafeUrl) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-globe\"></i>\n <h4>No URL Configured</h4>\n <p>Click the configure button to set a URL for this part.</p>\n </div>\n }\n \n <!-- Iframe container - sandbox and allowfullscreen must be static, so we use ng-container to switch -->\n @if (!IsLoading && !ErrorMessage && SafeUrl) {\n <!-- Standard sandbox + fullscreen enabled -->\n @if (sandboxMode === 'standard' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Standard sandbox + fullscreen disabled -->\n @if (sandboxMode === 'standard' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Strict sandbox + fullscreen enabled -->\n @if (sandboxMode === 'strict' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Strict sandbox + fullscreen disabled -->\n @if (sandboxMode === 'strict' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Permissive sandbox + fullscreen enabled -->\n @if (sandboxMode === 'permissive' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Permissive sandbox + fullscreen disabled -->\n @if (sandboxMode === 'permissive' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n }\n \n <!-- Fallback link shown below iframe if content might be blocked -->\n @if (!IsLoading && !ErrorMessage && SafeUrl && showFallbackLink) {\n <div class=\"iframe-fallback\">\n <span>If the content doesn't load:</span>\n <a [href]=\"rawUrl\" target=\"_blank\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n Open in new window\n </a>\n </div>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .weburl-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: #fff;\n position: relative; /* Required for overlay positioning */\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #666;\n text-align: center;\n padding: 24px;\n }\n\n .loading-state i,\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: #ccc;\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: #d32f2f;\n }\n\n .error-state .open-link {\n margin-top: 16px;\n padding: 8px 16px;\n background: #5c6bc0;\n color: #fff;\n text-decoration: none;\n border-radius: 4px;\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n }\n\n .error-state .open-link:hover {\n background: #3f51b5;\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: #333;\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n .content-iframe {\n width: 100%;\n height: 100%;\n border: none;\n flex: 1;\n }\n\n .iframe-fallback {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 8px;\n background: #f5f5f5;\n border-top: 1px solid #e0e0e0;\n font-size: 12px;\n color: #666;\n }\n\n .iframe-fallback a {\n color: #5c6bc0;\n text-decoration: none;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .iframe-fallback a:hover {\n text-decoration: underline;\n }\n `]\n})\nexport class WebURLPartComponent extends BaseDashboardPart implements AfterViewInit {\n @ViewChild('iframe') iframeRef!: ElementRef<HTMLIFrameElement>;\n\n public SafeUrl: SafeResourceUrl | null = null;\n public rawUrl: string = '';\n public sandboxMode: 'standard' | 'strict' | 'permissive' = 'standard';\n public allowFullscreen: boolean = true;\n public showFallbackLink: boolean = false; // Hidden by default, shown if content might be blocked\n\n private sanitizer: DomSanitizer;\n private loadCheckTimeout: ReturnType<typeof setTimeout> | null = null;\n private iframeLoaded: boolean = false;\n\n constructor(cdr: ChangeDetectorRef, sanitizer: DomSanitizer) {\n super(cdr);\n this.sanitizer = sanitizer;\n }\n\n ngAfterViewInit(): void {\n // Initial load if panel is already set\n if (this.Panel) {\n this.loadContent();\n }\n }\n\n public async loadContent(): Promise<void> {\n const config = this.getConfig<PanelConfig>();\n const url = config?.['url'] as string | undefined;\n\n // Clear any existing timeout\n if (this.loadCheckTimeout) {\n clearTimeout(this.loadCheckTimeout);\n this.loadCheckTimeout = null;\n }\n\n // Reset state\n this.iframeLoaded = false;\n this.showFallbackLink = false;\n\n if (!url) {\n this.SafeUrl = null;\n this.rawUrl = '';\n this.cdr.detectChanges();\n return;\n }\n\n this.setLoading(true);\n\n try {\n // Store raw URL for fallback link\n this.rawUrl = url;\n\n // Sanitize and set URL for iframe src binding\n this.SafeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);\n\n // Set sandbox mode (used by template to select correct iframe)\n this.sandboxMode = (config?.['sandboxMode'] as 'standard' | 'strict' | 'permissive') || 'standard';\n\n // Set fullscreen permission\n this.allowFullscreen = (config?.['allowFullscreen'] as boolean) ?? true;\n\n this.setLoading(false);\n\n // Set up a delayed check to detect if embedding might be blocked\n // If after 3 seconds the iframe hasn't successfully loaded content we can access,\n // show the blocked overlay. This is a heuristic since we can't detect X-Frame-Options directly.\n this.loadCheckTimeout = setTimeout(() => {\n this.checkIfBlocked();\n }, 3000);\n\n } catch (error) {\n this.setError(error instanceof Error ? error.message : 'Failed to load URL');\n }\n }\n\n /**\n * Check if the iframe content might be blocked.\n * Called after a timeout - if the iframe load event hasn't fired, show the fallback link.\n */\n private checkIfBlocked(): void {\n // If the load event hasn't fired after the timeout, content might be blocked\n if (!this.iframeLoaded) {\n this.showFallbackLink = true;\n this.cdr.detectChanges();\n }\n // If iframeLoaded is true, content loaded successfully - keep fallback hidden\n }\n\n /**\n * Handle iframe load event\n */\n public onIframeLoad(): void {\n // Mark that the load event fired\n this.iframeLoaded = true;\n\n // Clear the timeout since the iframe loaded\n if (this.loadCheckTimeout) {\n clearTimeout(this.loadCheckTimeout);\n this.loadCheckTimeout = null;\n }\n\n // The iframe loaded something - could still be blocked content\n // We can't reliably detect X-Frame-Options blocking from JS\n // The fallback link provides a way for users to open the site if it doesn't load\n this.cdr.detectChanges();\n }\n\n /**\n * Handle iframe error event\n */\n public onIframeError(_event: Event): void {\n // This may fire for some load failures, but not for X-Frame-Options\n this.setError('This site cannot be embedded. It may block iframe embedding for security reasons.');\n }\n}\n"]}
1
+ {"version":3,"file":"weburl-part.component.js","sourceRoot":"","sources":["../../../src/lib/parts/weburl-part.component.ts"],"names":[],"mappings":"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","sourcesContent":["import { Component, ChangeDetectorRef, ElementRef, ViewChild, AfterViewInit } from '@angular/core';\nimport { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\nimport { RegisterClass } from '@memberjunction/global';\nimport { BaseDashboardPart } from './base-dashboard-part';\nimport { PanelConfig } from '../models/dashboard-types';\n\n/**\n * Runtime renderer for WebURL dashboard parts.\n * Displays web content in an iframe with configurable sandbox permissions.\n */\n@RegisterClass(BaseDashboardPart, 'WebURLPanelRenderer')\n@Component({\n standalone: false,\n selector: 'mj-weburl-part',\n template: `\n <div class=\"weburl-part\" [class.loading]=\"IsLoading\" [class.error]=\"ErrorMessage\">\n <!-- Loading state -->\n @if (IsLoading) {\n <div class=\"loading-state\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading...</span>\n </div>\n }\n \n <!-- Error state -->\n @if (ErrorMessage) {\n <div class=\"error-state\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n <span>{{ ErrorMessage }}</span>\n @if (rawUrl) {\n <a [href]=\"rawUrl\" target=\"_blank\" class=\"open-link\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n Open in new window\n </a>\n }\n </div>\n }\n \n <!-- No URL configured -->\n @if (!IsLoading && !ErrorMessage && !SafeUrl) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-globe\"></i>\n <h4>No URL Configured</h4>\n <p>Click the configure button to set a URL for this part.</p>\n </div>\n }\n \n <!-- Iframe container - sandbox and allowfullscreen must be static, so we use ng-container to switch -->\n @if (!IsLoading && !ErrorMessage && SafeUrl) {\n <!-- Standard sandbox + fullscreen enabled -->\n @if (sandboxMode === 'standard' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Standard sandbox + fullscreen disabled -->\n @if (sandboxMode === 'standard' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Strict sandbox + fullscreen enabled -->\n @if (sandboxMode === 'strict' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Strict sandbox + fullscreen disabled -->\n @if (sandboxMode === 'strict' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Permissive sandbox + fullscreen enabled -->\n @if (sandboxMode === 'permissive' && allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation\"\n allowfullscreen\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n <!-- Permissive sandbox + fullscreen disabled -->\n @if (sandboxMode === 'permissive' && !allowFullscreen) {\n <iframe\n #iframe\n [src]=\"SafeUrl\"\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-top-navigation\"\n [title]=\"Panel?.title || 'Embedded content'\"\n (load)=\"onIframeLoad()\"\n (error)=\"onIframeError($event)\"\n class=\"content-iframe\">\n </iframe>\n }\n }\n \n <!-- Fallback link shown below iframe if content might be blocked -->\n @if (!IsLoading && !ErrorMessage && SafeUrl && showFallbackLink) {\n <div class=\"iframe-fallback\">\n <span>If the content doesn't load:</span>\n <a [href]=\"rawUrl\" target=\"_blank\">\n <i class=\"fa-solid fa-external-link-alt\"></i>\n Open in new window\n </a>\n </div>\n }\n </div>\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n .weburl-part {\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n position: relative; /* Required for overlay positioning */\n }\n\n .loading-state,\n .error-state,\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--mj-text-secondary);\n text-align: center;\n padding: 24px;\n }\n\n .loading-state i,\n .error-state i,\n .empty-state i {\n font-size: 48px;\n color: var(--mj-text-muted);\n margin-bottom: 16px;\n }\n\n .error-state i {\n color: var(--mj-status-error);\n }\n\n .error-state .open-link {\n margin-top: 16px;\n padding: 8px 16px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n text-decoration: none;\n border-radius: 4px;\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n }\n\n .error-state .open-link:hover {\n background: var(--mj-brand-primary-hover);\n }\n\n .empty-state h4 {\n margin: 0 0 8px 0;\n color: var(--mj-text-primary);\n }\n\n .empty-state p {\n margin: 0;\n font-size: 13px;\n }\n\n .content-iframe {\n width: 100%;\n height: 100%;\n border: none;\n flex: 1;\n }\n\n .iframe-fallback {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 8px;\n background: var(--mj-bg-surface-card);\n border-top: 1px solid var(--mj-border-default);\n font-size: 12px;\n color: var(--mj-text-secondary);\n }\n\n .iframe-fallback a {\n color: var(--mj-brand-primary);\n text-decoration: none;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .iframe-fallback a:hover {\n text-decoration: underline;\n }\n `]\n})\nexport class WebURLPartComponent extends BaseDashboardPart implements AfterViewInit {\n @ViewChild('iframe') iframeRef!: ElementRef<HTMLIFrameElement>;\n\n public SafeUrl: SafeResourceUrl | null = null;\n public rawUrl: string = '';\n public sandboxMode: 'standard' | 'strict' | 'permissive' = 'standard';\n public allowFullscreen: boolean = true;\n public showFallbackLink: boolean = false; // Hidden by default, shown if content might be blocked\n\n private sanitizer: DomSanitizer;\n private loadCheckTimeout: ReturnType<typeof setTimeout> | null = null;\n private iframeLoaded: boolean = false;\n\n constructor(cdr: ChangeDetectorRef, sanitizer: DomSanitizer) {\n super(cdr);\n this.sanitizer = sanitizer;\n }\n\n ngAfterViewInit(): void {\n // Initial load if panel is already set\n if (this.Panel) {\n this.loadContent();\n }\n }\n\n public async loadContent(): Promise<void> {\n const config = this.getConfig<PanelConfig>();\n const url = config?.['url'] as string | undefined;\n\n // Clear any existing timeout\n if (this.loadCheckTimeout) {\n clearTimeout(this.loadCheckTimeout);\n this.loadCheckTimeout = null;\n }\n\n // Reset state\n this.iframeLoaded = false;\n this.showFallbackLink = false;\n\n if (!url) {\n this.SafeUrl = null;\n this.rawUrl = '';\n this.cdr.detectChanges();\n return;\n }\n\n this.setLoading(true);\n\n try {\n // Store raw URL for fallback link\n this.rawUrl = url;\n\n // Sanitize and set URL for iframe src binding\n this.SafeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);\n\n // Set sandbox mode (used by template to select correct iframe)\n this.sandboxMode = (config?.['sandboxMode'] as 'standard' | 'strict' | 'permissive') || 'standard';\n\n // Set fullscreen permission\n this.allowFullscreen = (config?.['allowFullscreen'] as boolean) ?? true;\n\n this.setLoading(false);\n\n // Set up a delayed check to detect if embedding might be blocked\n // If after 3 seconds the iframe hasn't successfully loaded content we can access,\n // show the blocked overlay. This is a heuristic since we can't detect X-Frame-Options directly.\n this.loadCheckTimeout = setTimeout(() => {\n this.checkIfBlocked();\n }, 3000);\n\n } catch (error) {\n this.setError(error instanceof Error ? error.message : 'Failed to load URL');\n }\n }\n\n /**\n * Check if the iframe content might be blocked.\n * Called after a timeout - if the iframe load event hasn't fired, show the fallback link.\n */\n private checkIfBlocked(): void {\n // If the load event hasn't fired after the timeout, content might be blocked\n if (!this.iframeLoaded) {\n this.showFallbackLink = true;\n this.cdr.detectChanges();\n }\n // If iframeLoaded is true, content loaded successfully - keep fallback hidden\n }\n\n /**\n * Handle iframe load event\n */\n public onIframeLoad(): void {\n // Mark that the load event fired\n this.iframeLoaded = true;\n\n // Clear the timeout since the iframe loaded\n if (this.loadCheckTimeout) {\n clearTimeout(this.loadCheckTimeout);\n this.loadCheckTimeout = null;\n }\n\n // The iframe loaded something - could still be blocked content\n // We can't reliably detect X-Frame-Options blocking from JS\n // The fallback link provides a way for users to open the site if it doesn't load\n this.cdr.detectChanges();\n }\n\n /**\n * Handle iframe error event\n */\n public onIframeError(_event: Event): void {\n // This may fire for some load failures, but not for X-Frame-Options\n this.setError('This site cannot be embedded. It may block iframe embedding for security reasons.');\n }\n}\n"]}
@@ -192,7 +192,7 @@ export class GoldenLayoutWrapperService {
192
192
  // LAZY LOADING: Show loading placeholder initially
193
193
  // Actual content is created when panel is first shown (visible)
194
194
  wrapper.innerHTML = `
195
- <div class="panel-loading" style="display: flex; align-items: center; justify-content: center; height: 100%; color: #888;">
195
+ <div class="panel-loading" style="display: flex; align-items: center; justify-content: center; height: 100%; color: var(--mj-text-secondary);">
196
196
  <i class="fa-solid fa-spinner fa-spin" style="margin-right: 8px;"></i>
197
197
  Loading...
198
198
  </div>
@@ -1 +1 @@
1
- {"version":3,"file":"golden-layout-wrapper.service.js","sourceRoot":"","sources":["../../../src/lib/services/golden-layout-wrapper.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { Subject, BehaviorSubject } from 'rxjs';\nimport {\n LayoutConfig,\n ResolvedLayoutConfig,\n ComponentContainer,\n JsonValue,\n ComponentItemConfig,\n VirtualLayout\n} from 'golden-layout';\nimport {\n DashboardPanel,\n LayoutChangedEvent\n} from '../models/dashboard-types';\n\n// Golden Layout interfaces for VirtualLayout\ninterface GLVirtualLayout {\n rootItem: GLLayoutItem | null;\n on(event: string, callback: (item?: unknown) => void): void;\n destroy(): void;\n loadLayout(config: LayoutConfig): void;\n saveLayout(): ResolvedLayoutConfig;\n addItemAtLocation(config: ComponentItemConfig, location: Array<{ typeId: number }>): void;\n addComponent(componentType: string, componentState: Record<string, unknown>, title: string): void;\n setSize(width: number, height: number): void;\n}\n\ninterface GLLayoutItem {\n type: string;\n contentItems?: GLLayoutItem[];\n container?: ComponentContainer;\n addItem(config: ComponentItemConfig): void;\n}\n\n/**\n * Location specifier for adding panels\n */\nexport interface LayoutLocation {\n /** Target panel ID (add next to this panel) */\n targetPanelId?: string;\n /** Position relative to target */\n position?: 'left' | 'right' | 'top' | 'bottom' | 'tab';\n}\n\n/**\n * Panel component factory function type.\n * Receives the full DashboardPanel from GL's componentState (single source of truth).\n */\nexport type PanelComponentFactory = (panel: DashboardPanel, container: HTMLElement) => void;\n\n/**\n * Service that wraps Golden Layout for Angular integration.\n * Uses Golden Layout 2.6.0 VirtualLayout API.\n *\n * DESIGN: This service works with GL's native ResolvedLayoutConfig format.\n * Full DashboardPanel objects are stored in componentState - no conversion needed.\n */\n@Injectable()\nexport class GoldenLayoutWrapperService {\n private _initialized = false;\n private _layout: GLVirtualLayout | null = null;\n private _containerElement: HTMLElement | null = null;\n private _componentFactory: PanelComponentFactory | null = null;\n private _containerMap = new Map<string, ComponentContainer>();\n private _isEditing = false;\n /** Tracks which panels have been lazily loaded (content created) */\n private _loadedPanels = new Set<string>();\n\n /** Emitted when layout configuration changes */\n public onLayoutChanged = new Subject<LayoutChangedEvent>();\n\n /** Emitted when a panel is closed */\n public onPanelClosed = new Subject<string>();\n\n /** Emitted when a panel is maximized/restored */\n public onPanelMaximized = new Subject<{ panelId: string; maximized: boolean }>();\n\n /** Emitted when a panel is selected (in a stack) */\n public onPanelSelected = new Subject<string>();\n\n /** Current panels in layout */\n public panels$ = new BehaviorSubject<string[]>([]);\n\n /** Whether layout editing (drag/drop/resize/close) is enabled */\n public get isEditing(): boolean {\n return this._isEditing;\n }\n\n /**\n * Initialize Golden Layout in the specified container.\n * @param container The HTML element to render Golden Layout in\n * @param savedLayout Previously saved layout (from saveLayout()) or null for empty\n * @param componentFactory Factory function to create panel content\n * @param isEditing Whether editing (drag/drop/resize/close) is enabled\n */\n public initialize(\n container: HTMLElement,\n savedLayout: ResolvedLayoutConfig | null,\n componentFactory: PanelComponentFactory,\n isEditing = false\n ): void {\n this._componentFactory = componentFactory;\n this._containerElement = container;\n this._isEditing = isEditing;\n\n // Create VirtualLayout instance with bind/unbind callbacks\n // Cast through unknown: GLVirtualLayout is a subset interface used throughout this service;\n // the structural mismatch with golden-layout's VirtualLayout is intentional.\n this._layout = new VirtualLayout(\n container,\n this.bindComponentEventListener.bind(this) as VirtualLayout.BindComponentEventHandler,\n this.unbindComponentEventListener.bind(this) as VirtualLayout.UnbindComponentEventHandler\n ) as unknown as GLVirtualLayout;\n\n // Enable automatic resize when container size changes\n (this._layout as unknown as { resizeWithContainerAutomatically: boolean }).resizeWithContainerAutomatically = true;\n\n // Subscribe to events\n this._layout.on('stateChanged', () => {\n this.emitLayoutChanged('resize');\n });\n\n // Build the LayoutConfig to load\n const glConfig = this.buildLayoutConfig(savedLayout);\n\n // Load the layout configuration\n this._layout.loadLayout(glConfig);\n\n // Set the size of Golden Layout to match the container\n const rect = container.getBoundingClientRect();\n this._layout.setSize(rect.width, rect.height);\n\n this._initialized = true;\n this.updatePanelsList();\n }\n\n /**\n * Build LayoutConfig from saved ResolvedLayoutConfig.\n * Uses GL's LayoutConfig.fromResolved() for proper conversion,\n * then applies current edit mode settings.\n */\n private buildLayoutConfig(savedLayout: ResolvedLayoutConfig | null): LayoutConfig {\n // Base settings - reorderEnabled is always true so users can click tabs and\n // rearrange in view mode (changes won't be saved). Close button only in edit mode.\n const baseSettings: LayoutConfig = {\n settings: {\n reorderEnabled: this._isEditing\n },\n header: {\n show: 'top',\n popout: false,\n maximise: false,\n close: this._isEditing ? 'tab' : false\n },\n root: undefined\n };\n\n // If we have a saved layout, convert it properly using GL's conversion\n if (savedLayout && savedLayout.root) {\n try {\n // Validate the saved layout has required structure\n if (!this.isValidLayoutConfig(savedLayout)) {\n console.warn('[GoldenLayoutWrapper] Invalid saved layout structure, using default');\n return baseSettings;\n }\n\n // Use GL's built-in conversion from ResolvedLayoutConfig to LayoutConfig\n const convertedConfig = LayoutConfig.fromResolved(savedLayout) as LayoutConfig;\n\n // Merge our base settings with the converted config\n return {\n ...convertedConfig,\n settings: {\n ...convertedConfig.settings,\n ...baseSettings.settings\n },\n header: {\n ...convertedConfig.header,\n ...baseSettings.header\n }\n };\n } catch (err) {\n console.error('[GoldenLayoutWrapper] Error converting saved layout, using default:', err);\n // Fall through to return base settings\n }\n }\n\n return baseSettings;\n }\n\n /**\n * Validate that a saved layout config has the required structure for GL conversion.\n * GL's fromResolved() can crash if certain properties are undefined.\n */\n private isValidLayoutConfig(config: ResolvedLayoutConfig): boolean {\n if (!config || !config.root) {\n return false;\n }\n\n // Check that root has a type\n if (!config.root.type) {\n return false;\n }\n\n // Recursively validate content items have required properties\n return this.validateLayoutItem(config.root);\n }\n\n /**\n * Recursively validate a layout item and its children.\n */\n private validateLayoutItem(item: unknown): boolean {\n if (!item || typeof item !== 'object') {\n return false;\n }\n\n const layoutItem = item as Record<string, unknown>;\n\n // Must have a type\n if (!layoutItem['type']) {\n return false;\n }\n\n // If it has content, validate each child\n const content = layoutItem['content'] as unknown[] | undefined;\n if (content && Array.isArray(content)) {\n for (const child of content) {\n if (!this.validateLayoutItem(child)) {\n return false;\n }\n }\n }\n\n return true;\n }\n\n /**\n * Update layout size to match container.\n */\n public updateSize(): void {\n if (this._layout && this._containerElement) {\n const rect = this._containerElement.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n this._layout.setSize(rect.width, rect.height);\n }\n }\n }\n\n /**\n * Bind component event listener (called by Golden Layout VirtualLayout).\n * The componentState contains the full DashboardPanel object.\n */\n private bindComponentEventListener(\n container: ComponentContainer,\n _itemConfig: { componentState: Record<string, unknown> }\n ): { component: HTMLElement; virtual: boolean } {\n // componentState IS the DashboardPanel\n const panel = container.state as unknown as DashboardPanel;\n\n // Create a wrapper element for our panel content\n const wrapper = document.createElement('div');\n wrapper.className = 'dashboard-panel-content';\n wrapper.style.width = '100%';\n wrapper.style.height = '100%';\n wrapper.style.overflow = 'auto';\n\n if (!panel?.id) {\n wrapper.innerHTML = '<div class=\"panel-error\">No panel ID provided</div>';\n } else {\n // Store reference to container\n this._containerMap.set(panel.id, container);\n\n // Set the tab title (plain text - GL escapes HTML)\n container.setTitle(panel.title);\n\n // Add icon to the tab element via DOM manipulation\n // This must happen after GL has created the tab element\n // Always add an icon - use panel's icon or a default fallback (cube = generic widget)\n // IMPORTANT: We append to tabElement, NOT modify .lm_title, to preserve GL's drag/drop\n const iconClass = panel.icon || 'fa-solid fa-cube';\n this.addIconToTab(container, iconClass);\n\n // LAZY LOADING: Show loading placeholder initially\n // Actual content is created when panel is first shown (visible)\n wrapper.innerHTML = `\n <div class=\"panel-loading\" style=\"display: flex; align-items: center; justify-content: center; height: 100%; color: #888;\">\n <i class=\"fa-solid fa-spinner fa-spin\" style=\"margin-right: 8px;\"></i>\n Loading...\n </div>\n `;\n\n // Listen for close events\n container.on('beforeComponentRelease', () => {\n this._containerMap.delete(panel.id);\n this._loadedPanels.delete(panel.id);\n this.onPanelClosed.next(panel.id);\n this.updatePanelsList();\n });\n\n // Listen for show/focus events - LAZY LOAD on first show\n container.on('show', () => {\n // Check if this is first time panel is shown\n if (!this._loadedPanels.has(panel.id)) {\n // First show - create actual content\n wrapper.innerHTML = ''; // Clear loading placeholder\n if (this._componentFactory) {\n this._componentFactory(panel, wrapper);\n }\n this._loadedPanels.add(panel.id);\n }\n this.onPanelSelected.next(panel.id);\n });\n\n this.updatePanelsList();\n }\n\n // Append the wrapper to container.element (.lm_content)\n container.element.appendChild(wrapper);\n\n // Return the bindable component object\n return {\n component: wrapper,\n virtual: false\n };\n }\n\n /**\n * Unbind component event listener (called by Golden Layout VirtualLayout)\n */\n private unbindComponentEventListener(_container: ComponentContainer): void {\n // Cleanup handled in beforeComponentRelease\n }\n\n /**\n * Check if Golden Layout is initialized\n */\n public isInitialized(): boolean {\n return this._initialized;\n }\n\n /**\n * Destroy Golden Layout and clean up\n */\n public destroy(): void {\n if (this._layout) {\n this._layout.destroy();\n this._layout = null;\n }\n\n this._containerMap.clear();\n this._loadedPanels.clear();\n this._initialized = false;\n this._componentFactory = null;\n this._containerElement = null;\n }\n\n /**\n * Add a panel to the layout.\n * The full DashboardPanel is stored in componentState.\n */\n public addPanel(panel: DashboardPanel, _location?: LayoutLocation): void {\n if (!this._layout) {\n return;\n }\n\n // Try to add to existing stack first, otherwise create new\n const existingStack = this.findFirstStack();\n if (existingStack) {\n const componentConfig: ComponentItemConfig = {\n type: 'component',\n componentType: 'dashboard-panel',\n componentState: panel as unknown as JsonValue,\n title: panel.title,\n isClosable: true\n };\n existingStack.addItem(componentConfig);\n } else {\n // Use addComponent which will create a stack\n this._layout.addComponent(\n 'dashboard-panel',\n panel as unknown as Record<string, unknown>,\n panel.title\n );\n }\n\n this.emitLayoutChanged('resize');\n }\n\n /**\n * Find first stack in layout\n */\n private findFirstStack(): GLLayoutItem | null {\n if (!this._layout || !this._layout.rootItem) return null;\n\n const findStack = (item: GLLayoutItem): GLLayoutItem | null => {\n if (item.type === 'stack') {\n return item;\n }\n if (item.contentItems) {\n for (const child of item.contentItems) {\n const found = findStack(child);\n if (found) return found;\n }\n }\n return null;\n };\n\n return findStack(this._layout.rootItem);\n }\n\n /**\n * Remove a panel from the layout\n */\n public removePanel(panelId: string): void {\n const container = this._containerMap.get(panelId);\n if (container) {\n container.close();\n }\n }\n\n /**\n * Split a panel horizontally (add panel to the right)\n */\n public splitHorizontal(panelId: string, newPanel: DashboardPanel): void {\n this.addPanel(newPanel, { targetPanelId: panelId, position: 'right' });\n }\n\n /**\n * Split a panel vertically (add panel below)\n */\n public splitVertical(panelId: string, newPanel: DashboardPanel): void {\n this.addPanel(newPanel, { targetPanelId: panelId, position: 'bottom' });\n }\n\n /**\n * Add a panel as a tab to an existing stack\n */\n public addToStack(panelId: string, newPanel: DashboardPanel): void {\n this.addPanel(newPanel, { targetPanelId: panelId, position: 'tab' });\n }\n\n /**\n * Maximize a panel\n */\n public maximizePanel(panelId: string): void {\n this.onPanelMaximized.next({ panelId, maximized: true });\n }\n\n /**\n * Restore a maximized panel\n */\n public restorePanel(panelId: string): void {\n this.onPanelMaximized.next({ panelId, maximized: false });\n }\n\n /**\n * Focus a panel by ID\n */\n public focusPanel(panelId: string): void {\n const container = this._containerMap.get(panelId);\n if (container) {\n container.focus();\n }\n }\n\n /**\n * Get the current layout configuration.\n * Returns GL's native ResolvedLayoutConfig - no conversion.\n */\n public getLayoutConfig(): ResolvedLayoutConfig | null {\n if (!this._layout) return null;\n\n try {\n return this._layout.saveLayout();\n } catch (error) {\n console.error('[GLWrapper] Failed to save layout:', error);\n return null;\n }\n }\n\n /**\n * Get the container element for a panel\n */\n public getPanelContainer(panelId: string): HTMLElement | null {\n const container = this._containerMap.get(panelId);\n return container?.element || null;\n }\n\n /**\n * Get all panel IDs in the current layout\n */\n public getPanelIds(): string[] {\n return Array.from(this._containerMap.keys());\n }\n\n /**\n * Set editing mode.\n * Note: Golden Layout 2 doesn't support changing settings after initialization.\n * The layout must be reinitialized to apply new settings.\n */\n public setEditingMode(isEditing: boolean): void {\n this._isEditing = isEditing;\n }\n\n private updatePanelsList(): void {\n this.panels$.next(Array.from(this._containerMap.keys()));\n }\n\n private emitLayoutChanged(changeType: LayoutChangedEvent['changeType']): void {\n const layoutConfig = this.getLayoutConfig();\n if (layoutConfig) {\n this.onLayoutChanged.next({\n layout: layoutConfig,\n changeType\n });\n }\n }\n\n /**\n * Add an icon to a tab element via DOM manipulation.\n * IMPORTANT: We append to tabElement, NOT modify .lm_title, to preserve GL's drag/drop.\n * This follows the same pattern as the shell's GoldenLayoutManager.\n */\n private addIconToTab(container: ComponentContainer, iconClass: string): void {\n // Normalize the icon class - ensure it has the Font Awesome prefix\n let normalizedIconClass = iconClass.trim();\n if (!normalizedIconClass.includes('fa-') && !normalizedIconClass.includes('fa ')) {\n // If no FA prefix, assume it's a Font Awesome icon name and add fa-solid prefix\n normalizedIconClass = `fa-solid fa-${normalizedIconClass}`;\n } else if (!normalizedIconClass.startsWith('fa-') && !normalizedIconClass.startsWith('fa ')) {\n // Has fa- in it but not at the start - likely just the icon name like \"globe\"\n // Check if it's missing the style prefix (fa-solid, fa-regular, etc.)\n if (!normalizedIconClass.match(/^fa[srldb]?\\s/) && !normalizedIconClass.match(/^fa-(solid|regular|light|duotone|brands)\\s/)) {\n normalizedIconClass = `fa-solid ${normalizedIconClass}`;\n }\n }\n\n // Try immediately, then retry with delay if tab not ready\n const tryAddIcon = (): boolean => {\n const tab = container.tab;\n if (!tab) return false;\n\n const tabElement = tab.element as HTMLElement;\n if (!tabElement) return false;\n\n // Check if icon already added to tab element\n if (tabElement.querySelector('.panel-icon')) return true;\n\n // Create icon element and insert at the beginning of tabElement\n // This preserves GL's .lm_title structure for drag/drop\n // Icon is positioned absolutely via CSS in the left padding area\n const iconEl = document.createElement('i');\n iconEl.className = `${normalizedIconClass} panel-icon`;\n\n // Insert icon as first child of tab element\n tabElement.insertBefore(iconEl, tabElement.firstChild);\n return true;\n };\n\n // Try immediately\n if (tryAddIcon()) return;\n\n // Tab may not be created yet - try again after a delay\n setTimeout(() => {\n if (tryAddIcon()) return;\n\n // Still not ready - try one more time with longer delay\n setTimeout(() => {\n tryAddIcon();\n }, 100);\n }, 50);\n }\n}\n"]}
1
+ {"version":3,"file":"golden-layout-wrapper.service.js","sourceRoot":"","sources":["../../../src/lib/services/golden-layout-wrapper.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { Subject, BehaviorSubject } from 'rxjs';\nimport {\n LayoutConfig,\n ResolvedLayoutConfig,\n ComponentContainer,\n JsonValue,\n ComponentItemConfig,\n VirtualLayout\n} from 'golden-layout';\nimport {\n DashboardPanel,\n LayoutChangedEvent\n} from '../models/dashboard-types';\n\n// Golden Layout interfaces for VirtualLayout\ninterface GLVirtualLayout {\n rootItem: GLLayoutItem | null;\n on(event: string, callback: (item?: unknown) => void): void;\n destroy(): void;\n loadLayout(config: LayoutConfig): void;\n saveLayout(): ResolvedLayoutConfig;\n addItemAtLocation(config: ComponentItemConfig, location: Array<{ typeId: number }>): void;\n addComponent(componentType: string, componentState: Record<string, unknown>, title: string): void;\n setSize(width: number, height: number): void;\n}\n\ninterface GLLayoutItem {\n type: string;\n contentItems?: GLLayoutItem[];\n container?: ComponentContainer;\n addItem(config: ComponentItemConfig): void;\n}\n\n/**\n * Location specifier for adding panels\n */\nexport interface LayoutLocation {\n /** Target panel ID (add next to this panel) */\n targetPanelId?: string;\n /** Position relative to target */\n position?: 'left' | 'right' | 'top' | 'bottom' | 'tab';\n}\n\n/**\n * Panel component factory function type.\n * Receives the full DashboardPanel from GL's componentState (single source of truth).\n */\nexport type PanelComponentFactory = (panel: DashboardPanel, container: HTMLElement) => void;\n\n/**\n * Service that wraps Golden Layout for Angular integration.\n * Uses Golden Layout 2.6.0 VirtualLayout API.\n *\n * DESIGN: This service works with GL's native ResolvedLayoutConfig format.\n * Full DashboardPanel objects are stored in componentState - no conversion needed.\n */\n@Injectable()\nexport class GoldenLayoutWrapperService {\n private _initialized = false;\n private _layout: GLVirtualLayout | null = null;\n private _containerElement: HTMLElement | null = null;\n private _componentFactory: PanelComponentFactory | null = null;\n private _containerMap = new Map<string, ComponentContainer>();\n private _isEditing = false;\n /** Tracks which panels have been lazily loaded (content created) */\n private _loadedPanels = new Set<string>();\n\n /** Emitted when layout configuration changes */\n public onLayoutChanged = new Subject<LayoutChangedEvent>();\n\n /** Emitted when a panel is closed */\n public onPanelClosed = new Subject<string>();\n\n /** Emitted when a panel is maximized/restored */\n public onPanelMaximized = new Subject<{ panelId: string; maximized: boolean }>();\n\n /** Emitted when a panel is selected (in a stack) */\n public onPanelSelected = new Subject<string>();\n\n /** Current panels in layout */\n public panels$ = new BehaviorSubject<string[]>([]);\n\n /** Whether layout editing (drag/drop/resize/close) is enabled */\n public get isEditing(): boolean {\n return this._isEditing;\n }\n\n /**\n * Initialize Golden Layout in the specified container.\n * @param container The HTML element to render Golden Layout in\n * @param savedLayout Previously saved layout (from saveLayout()) or null for empty\n * @param componentFactory Factory function to create panel content\n * @param isEditing Whether editing (drag/drop/resize/close) is enabled\n */\n public initialize(\n container: HTMLElement,\n savedLayout: ResolvedLayoutConfig | null,\n componentFactory: PanelComponentFactory,\n isEditing = false\n ): void {\n this._componentFactory = componentFactory;\n this._containerElement = container;\n this._isEditing = isEditing;\n\n // Create VirtualLayout instance with bind/unbind callbacks\n // Cast through unknown: GLVirtualLayout is a subset interface used throughout this service;\n // the structural mismatch with golden-layout's VirtualLayout is intentional.\n this._layout = new VirtualLayout(\n container,\n this.bindComponentEventListener.bind(this) as VirtualLayout.BindComponentEventHandler,\n this.unbindComponentEventListener.bind(this) as VirtualLayout.UnbindComponentEventHandler\n ) as unknown as GLVirtualLayout;\n\n // Enable automatic resize when container size changes\n (this._layout as unknown as { resizeWithContainerAutomatically: boolean }).resizeWithContainerAutomatically = true;\n\n // Subscribe to events\n this._layout.on('stateChanged', () => {\n this.emitLayoutChanged('resize');\n });\n\n // Build the LayoutConfig to load\n const glConfig = this.buildLayoutConfig(savedLayout);\n\n // Load the layout configuration\n this._layout.loadLayout(glConfig);\n\n // Set the size of Golden Layout to match the container\n const rect = container.getBoundingClientRect();\n this._layout.setSize(rect.width, rect.height);\n\n this._initialized = true;\n this.updatePanelsList();\n }\n\n /**\n * Build LayoutConfig from saved ResolvedLayoutConfig.\n * Uses GL's LayoutConfig.fromResolved() for proper conversion,\n * then applies current edit mode settings.\n */\n private buildLayoutConfig(savedLayout: ResolvedLayoutConfig | null): LayoutConfig {\n // Base settings - reorderEnabled is always true so users can click tabs and\n // rearrange in view mode (changes won't be saved). Close button only in edit mode.\n const baseSettings: LayoutConfig = {\n settings: {\n reorderEnabled: this._isEditing\n },\n header: {\n show: 'top',\n popout: false,\n maximise: false,\n close: this._isEditing ? 'tab' : false\n },\n root: undefined\n };\n\n // If we have a saved layout, convert it properly using GL's conversion\n if (savedLayout && savedLayout.root) {\n try {\n // Validate the saved layout has required structure\n if (!this.isValidLayoutConfig(savedLayout)) {\n console.warn('[GoldenLayoutWrapper] Invalid saved layout structure, using default');\n return baseSettings;\n }\n\n // Use GL's built-in conversion from ResolvedLayoutConfig to LayoutConfig\n const convertedConfig = LayoutConfig.fromResolved(savedLayout) as LayoutConfig;\n\n // Merge our base settings with the converted config\n return {\n ...convertedConfig,\n settings: {\n ...convertedConfig.settings,\n ...baseSettings.settings\n },\n header: {\n ...convertedConfig.header,\n ...baseSettings.header\n }\n };\n } catch (err) {\n console.error('[GoldenLayoutWrapper] Error converting saved layout, using default:', err);\n // Fall through to return base settings\n }\n }\n\n return baseSettings;\n }\n\n /**\n * Validate that a saved layout config has the required structure for GL conversion.\n * GL's fromResolved() can crash if certain properties are undefined.\n */\n private isValidLayoutConfig(config: ResolvedLayoutConfig): boolean {\n if (!config || !config.root) {\n return false;\n }\n\n // Check that root has a type\n if (!config.root.type) {\n return false;\n }\n\n // Recursively validate content items have required properties\n return this.validateLayoutItem(config.root);\n }\n\n /**\n * Recursively validate a layout item and its children.\n */\n private validateLayoutItem(item: unknown): boolean {\n if (!item || typeof item !== 'object') {\n return false;\n }\n\n const layoutItem = item as Record<string, unknown>;\n\n // Must have a type\n if (!layoutItem['type']) {\n return false;\n }\n\n // If it has content, validate each child\n const content = layoutItem['content'] as unknown[] | undefined;\n if (content && Array.isArray(content)) {\n for (const child of content) {\n if (!this.validateLayoutItem(child)) {\n return false;\n }\n }\n }\n\n return true;\n }\n\n /**\n * Update layout size to match container.\n */\n public updateSize(): void {\n if (this._layout && this._containerElement) {\n const rect = this._containerElement.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n this._layout.setSize(rect.width, rect.height);\n }\n }\n }\n\n /**\n * Bind component event listener (called by Golden Layout VirtualLayout).\n * The componentState contains the full DashboardPanel object.\n */\n private bindComponentEventListener(\n container: ComponentContainer,\n _itemConfig: { componentState: Record<string, unknown> }\n ): { component: HTMLElement; virtual: boolean } {\n // componentState IS the DashboardPanel\n const panel = container.state as unknown as DashboardPanel;\n\n // Create a wrapper element for our panel content\n const wrapper = document.createElement('div');\n wrapper.className = 'dashboard-panel-content';\n wrapper.style.width = '100%';\n wrapper.style.height = '100%';\n wrapper.style.overflow = 'auto';\n\n if (!panel?.id) {\n wrapper.innerHTML = '<div class=\"panel-error\">No panel ID provided</div>';\n } else {\n // Store reference to container\n this._containerMap.set(panel.id, container);\n\n // Set the tab title (plain text - GL escapes HTML)\n container.setTitle(panel.title);\n\n // Add icon to the tab element via DOM manipulation\n // This must happen after GL has created the tab element\n // Always add an icon - use panel's icon or a default fallback (cube = generic widget)\n // IMPORTANT: We append to tabElement, NOT modify .lm_title, to preserve GL's drag/drop\n const iconClass = panel.icon || 'fa-solid fa-cube';\n this.addIconToTab(container, iconClass);\n\n // LAZY LOADING: Show loading placeholder initially\n // Actual content is created when panel is first shown (visible)\n wrapper.innerHTML = `\n <div class=\"panel-loading\" style=\"display: flex; align-items: center; justify-content: center; height: 100%; color: var(--mj-text-secondary);\">\n <i class=\"fa-solid fa-spinner fa-spin\" style=\"margin-right: 8px;\"></i>\n Loading...\n </div>\n `;\n\n // Listen for close events\n container.on('beforeComponentRelease', () => {\n this._containerMap.delete(panel.id);\n this._loadedPanels.delete(panel.id);\n this.onPanelClosed.next(panel.id);\n this.updatePanelsList();\n });\n\n // Listen for show/focus events - LAZY LOAD on first show\n container.on('show', () => {\n // Check if this is first time panel is shown\n if (!this._loadedPanels.has(panel.id)) {\n // First show - create actual content\n wrapper.innerHTML = ''; // Clear loading placeholder\n if (this._componentFactory) {\n this._componentFactory(panel, wrapper);\n }\n this._loadedPanels.add(panel.id);\n }\n this.onPanelSelected.next(panel.id);\n });\n\n this.updatePanelsList();\n }\n\n // Append the wrapper to container.element (.lm_content)\n container.element.appendChild(wrapper);\n\n // Return the bindable component object\n return {\n component: wrapper,\n virtual: false\n };\n }\n\n /**\n * Unbind component event listener (called by Golden Layout VirtualLayout)\n */\n private unbindComponentEventListener(_container: ComponentContainer): void {\n // Cleanup handled in beforeComponentRelease\n }\n\n /**\n * Check if Golden Layout is initialized\n */\n public isInitialized(): boolean {\n return this._initialized;\n }\n\n /**\n * Destroy Golden Layout and clean up\n */\n public destroy(): void {\n if (this._layout) {\n this._layout.destroy();\n this._layout = null;\n }\n\n this._containerMap.clear();\n this._loadedPanels.clear();\n this._initialized = false;\n this._componentFactory = null;\n this._containerElement = null;\n }\n\n /**\n * Add a panel to the layout.\n * The full DashboardPanel is stored in componentState.\n */\n public addPanel(panel: DashboardPanel, _location?: LayoutLocation): void {\n if (!this._layout) {\n return;\n }\n\n // Try to add to existing stack first, otherwise create new\n const existingStack = this.findFirstStack();\n if (existingStack) {\n const componentConfig: ComponentItemConfig = {\n type: 'component',\n componentType: 'dashboard-panel',\n componentState: panel as unknown as JsonValue,\n title: panel.title,\n isClosable: true\n };\n existingStack.addItem(componentConfig);\n } else {\n // Use addComponent which will create a stack\n this._layout.addComponent(\n 'dashboard-panel',\n panel as unknown as Record<string, unknown>,\n panel.title\n );\n }\n\n this.emitLayoutChanged('resize');\n }\n\n /**\n * Find first stack in layout\n */\n private findFirstStack(): GLLayoutItem | null {\n if (!this._layout || !this._layout.rootItem) return null;\n\n const findStack = (item: GLLayoutItem): GLLayoutItem | null => {\n if (item.type === 'stack') {\n return item;\n }\n if (item.contentItems) {\n for (const child of item.contentItems) {\n const found = findStack(child);\n if (found) return found;\n }\n }\n return null;\n };\n\n return findStack(this._layout.rootItem);\n }\n\n /**\n * Remove a panel from the layout\n */\n public removePanel(panelId: string): void {\n const container = this._containerMap.get(panelId);\n if (container) {\n container.close();\n }\n }\n\n /**\n * Split a panel horizontally (add panel to the right)\n */\n public splitHorizontal(panelId: string, newPanel: DashboardPanel): void {\n this.addPanel(newPanel, { targetPanelId: panelId, position: 'right' });\n }\n\n /**\n * Split a panel vertically (add panel below)\n */\n public splitVertical(panelId: string, newPanel: DashboardPanel): void {\n this.addPanel(newPanel, { targetPanelId: panelId, position: 'bottom' });\n }\n\n /**\n * Add a panel as a tab to an existing stack\n */\n public addToStack(panelId: string, newPanel: DashboardPanel): void {\n this.addPanel(newPanel, { targetPanelId: panelId, position: 'tab' });\n }\n\n /**\n * Maximize a panel\n */\n public maximizePanel(panelId: string): void {\n this.onPanelMaximized.next({ panelId, maximized: true });\n }\n\n /**\n * Restore a maximized panel\n */\n public restorePanel(panelId: string): void {\n this.onPanelMaximized.next({ panelId, maximized: false });\n }\n\n /**\n * Focus a panel by ID\n */\n public focusPanel(panelId: string): void {\n const container = this._containerMap.get(panelId);\n if (container) {\n container.focus();\n }\n }\n\n /**\n * Get the current layout configuration.\n * Returns GL's native ResolvedLayoutConfig - no conversion.\n */\n public getLayoutConfig(): ResolvedLayoutConfig | null {\n if (!this._layout) return null;\n\n try {\n return this._layout.saveLayout();\n } catch (error) {\n console.error('[GLWrapper] Failed to save layout:', error);\n return null;\n }\n }\n\n /**\n * Get the container element for a panel\n */\n public getPanelContainer(panelId: string): HTMLElement | null {\n const container = this._containerMap.get(panelId);\n return container?.element || null;\n }\n\n /**\n * Get all panel IDs in the current layout\n */\n public getPanelIds(): string[] {\n return Array.from(this._containerMap.keys());\n }\n\n /**\n * Set editing mode.\n * Note: Golden Layout 2 doesn't support changing settings after initialization.\n * The layout must be reinitialized to apply new settings.\n */\n public setEditingMode(isEditing: boolean): void {\n this._isEditing = isEditing;\n }\n\n private updatePanelsList(): void {\n this.panels$.next(Array.from(this._containerMap.keys()));\n }\n\n private emitLayoutChanged(changeType: LayoutChangedEvent['changeType']): void {\n const layoutConfig = this.getLayoutConfig();\n if (layoutConfig) {\n this.onLayoutChanged.next({\n layout: layoutConfig,\n changeType\n });\n }\n }\n\n /**\n * Add an icon to a tab element via DOM manipulation.\n * IMPORTANT: We append to tabElement, NOT modify .lm_title, to preserve GL's drag/drop.\n * This follows the same pattern as the shell's GoldenLayoutManager.\n */\n private addIconToTab(container: ComponentContainer, iconClass: string): void {\n // Normalize the icon class - ensure it has the Font Awesome prefix\n let normalizedIconClass = iconClass.trim();\n if (!normalizedIconClass.includes('fa-') && !normalizedIconClass.includes('fa ')) {\n // If no FA prefix, assume it's a Font Awesome icon name and add fa-solid prefix\n normalizedIconClass = `fa-solid fa-${normalizedIconClass}`;\n } else if (!normalizedIconClass.startsWith('fa-') && !normalizedIconClass.startsWith('fa ')) {\n // Has fa- in it but not at the start - likely just the icon name like \"globe\"\n // Check if it's missing the style prefix (fa-solid, fa-regular, etc.)\n if (!normalizedIconClass.match(/^fa[srldb]?\\s/) && !normalizedIconClass.match(/^fa-(solid|regular|light|duotone|brands)\\s/)) {\n normalizedIconClass = `fa-solid ${normalizedIconClass}`;\n }\n }\n\n // Try immediately, then retry with delay if tab not ready\n const tryAddIcon = (): boolean => {\n const tab = container.tab;\n if (!tab) return false;\n\n const tabElement = tab.element as HTMLElement;\n if (!tabElement) return false;\n\n // Check if icon already added to tab element\n if (tabElement.querySelector('.panel-icon')) return true;\n\n // Create icon element and insert at the beginning of tabElement\n // This preserves GL's .lm_title structure for drag/drop\n // Icon is positioned absolutely via CSS in the left padding area\n const iconEl = document.createElement('i');\n iconEl.className = `${normalizedIconClass} panel-icon`;\n\n // Insert icon as first child of tab element\n tabElement.insertBefore(iconEl, tabElement.firstChild);\n return true;\n };\n\n // Try immediately\n if (tryAddIcon()) return;\n\n // Tab may not be created yet - try again after a delay\n setTimeout(() => {\n if (tryAddIcon()) return;\n\n // Still not ready - try one more time with longer delay\n setTimeout(() => {\n tryAddIcon();\n }, 100);\n }, 50);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-dashboard-viewer",
3
- "version": "5.11.0",
3
+ "version": "5.13.0",
4
4
  "description": "MemberJunction: Angular components for metadata-driven dashboards with Golden Layout panels, supporting views, queries, artifacts, and custom content",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -37,14 +37,14 @@
37
37
  "golden-layout": "^2.6.0"
38
38
  },
39
39
  "dependencies": {
40
- "@memberjunction/core-entities": "5.11.0",
41
- "@memberjunction/global": "5.11.0",
42
- "@memberjunction/core": "5.11.0",
43
- "@memberjunction/ng-shared-generic": "5.11.0",
44
- "@memberjunction/ng-entity-viewer": "5.11.0",
45
- "@memberjunction/ng-artifacts": "5.11.0",
46
- "@memberjunction/ng-query-viewer": "5.11.0",
47
- "@memberjunction/ng-trees": "5.11.0",
40
+ "@memberjunction/core-entities": "5.13.0",
41
+ "@memberjunction/global": "5.13.0",
42
+ "@memberjunction/core": "5.13.0",
43
+ "@memberjunction/ng-shared-generic": "5.13.0",
44
+ "@memberjunction/ng-entity-viewer": "5.13.0",
45
+ "@memberjunction/ng-artifacts": "5.13.0",
46
+ "@memberjunction/ng-query-viewer": "5.13.0",
47
+ "@memberjunction/ng-trees": "5.13.0",
48
48
  "rxjs": "^7.8.2",
49
49
  "tslib": "^2.8.1"
50
50
  },