@dragonworks/ngx-dashboard 20.0.4 → 20.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/ng-package.json +7 -0
  2. package/package.json +34 -45
  3. package/src/lib/__tests__/dashboard-component-widget-state-integration.spec.ts +537 -0
  4. package/src/lib/cell/__tests__/cell-resize.component.spec.ts +442 -0
  5. package/src/lib/cell/__tests__/cell.component.spec.ts +541 -0
  6. package/src/lib/cell/cell-context-menu.component.ts +138 -0
  7. package/src/lib/cell/cell-context-menu.service.ts +36 -0
  8. package/src/lib/cell/cell.component.html +37 -0
  9. package/src/lib/cell/cell.component.scss +198 -0
  10. package/src/lib/cell/cell.component.ts +375 -0
  11. package/src/lib/dashboard/dashboard.component.html +18 -0
  12. package/src/lib/dashboard/dashboard.component.scss +17 -0
  13. package/src/lib/dashboard/dashboard.component.ts +187 -0
  14. package/src/lib/dashboard-editor/dashboard-editor.component.html +57 -0
  15. package/src/lib/dashboard-editor/dashboard-editor.component.scss +87 -0
  16. package/src/lib/dashboard-editor/dashboard-editor.component.ts +219 -0
  17. package/src/lib/dashboard-viewer/__tests__/dashboard-viewer.component.spec.ts +258 -0
  18. package/src/lib/dashboard-viewer/dashboard-viewer.component.html +20 -0
  19. package/src/lib/dashboard-viewer/dashboard-viewer.component.scss +50 -0
  20. package/src/lib/dashboard-viewer/dashboard-viewer.component.ts +70 -0
  21. package/src/lib/drop-zone/__tests__/drop-zone.component.spec.ts +465 -0
  22. package/src/lib/drop-zone/drop-zone.component.html +20 -0
  23. package/src/lib/drop-zone/drop-zone.component.scss +67 -0
  24. package/src/lib/drop-zone/drop-zone.component.ts +122 -0
  25. package/src/lib/internal-widgets/unknown-widget/unknown-widget.component.ts +72 -0
  26. package/src/lib/models/cell-data.ts +13 -0
  27. package/src/lib/models/cell-dialog.ts +7 -0
  28. package/src/lib/models/cell-id.ts +85 -0
  29. package/src/lib/models/cell-position.ts +15 -0
  30. package/src/lib/models/dashboard-data.dto.ts +44 -0
  31. package/src/lib/models/dashboard-data.utils.ts +49 -0
  32. package/src/lib/models/drag-data.ts +6 -0
  33. package/src/lib/models/index.ts +11 -0
  34. package/src/lib/models/reserved-space.ts +24 -0
  35. package/src/lib/models/widget-factory.ts +33 -0
  36. package/src/lib/models/widget-id.ts +70 -0
  37. package/src/lib/models/widget.ts +21 -0
  38. package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.component.ts +127 -0
  39. package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.provider.ts +15 -0
  40. package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.tokens.ts +20 -0
  41. package/src/lib/providers/cell-settings-dialog/default-cell-settings-dialog.provider.ts +32 -0
  42. package/src/lib/providers/cell-settings-dialog/index.ts +3 -0
  43. package/src/lib/providers/index.ts +1 -0
  44. package/src/lib/services/__tests__/dashboard-bridge.service.spec.ts +220 -0
  45. package/src/lib/services/__tests__/dashboard-viewport.service.spec.ts +362 -0
  46. package/src/lib/services/dashboard-bridge.service.ts +155 -0
  47. package/src/lib/services/dashboard-viewport.service.ts +148 -0
  48. package/src/lib/services/dashboard.service.ts +62 -0
  49. package/src/lib/store/__tests__/dashboard-store-collision-detection.spec.ts +756 -0
  50. package/src/lib/store/__tests__/dashboard-store-computed-properties.spec.ts +974 -0
  51. package/src/lib/store/__tests__/dashboard-store-drag-drop.spec.ts +279 -0
  52. package/src/lib/store/__tests__/dashboard-store-export-import.spec.ts +780 -0
  53. package/src/lib/store/__tests__/dashboard-store-grid-config.spec.ts +128 -0
  54. package/src/lib/store/__tests__/dashboard-store-query-methods.spec.ts +229 -0
  55. package/src/lib/store/__tests__/dashboard-store-resize-operations.spec.ts +652 -0
  56. package/src/lib/store/__tests__/dashboard-store-widget-management.spec.ts +461 -0
  57. package/src/lib/store/__tests__/dashboard-store-widget-state-preservation.spec.ts +369 -0
  58. package/src/lib/store/dashboard-store.ts +239 -0
  59. package/src/lib/store/features/drag-drop.feature.ts +140 -0
  60. package/src/lib/store/features/grid-config.feature.ts +43 -0
  61. package/src/lib/store/features/resize.feature.ts +140 -0
  62. package/src/lib/store/features/utils/collision.utils.ts +89 -0
  63. package/src/lib/store/features/utils/grid-query-internal.utils.ts +37 -0
  64. package/src/lib/store/features/utils/resize.utils.ts +165 -0
  65. package/src/lib/store/features/widget-management.feature.ts +158 -0
  66. package/src/lib/styles/_dashboard-grid-vars.scss +11 -0
  67. package/src/lib/widget-list/__tests__/widget-list-bridge-integration.spec.ts +137 -0
  68. package/src/lib/widget-list/widget-list.component.html +22 -0
  69. package/src/lib/widget-list/widget-list.component.scss +154 -0
  70. package/src/lib/widget-list/widget-list.component.ts +106 -0
  71. package/src/public-api.ts +21 -0
  72. package/src/test-setup.ts +10 -0
  73. package/tsconfig.lib.json +15 -0
  74. package/tsconfig.lib.prod.json +11 -0
  75. package/tsconfig.spec.json +14 -0
  76. package/fesm2022/dragonworks-ngx-dashboard.mjs +0 -2192
  77. package/fesm2022/dragonworks-ngx-dashboard.mjs.map +0 -1
  78. package/index.d.ts +0 -678
@@ -0,0 +1,155 @@
1
+ // dashboard-bridge.service.ts
2
+ import { Injectable, computed, signal } from '@angular/core';
3
+ import { DashboardStore } from '../store/dashboard-store';
4
+ import { DragData } from '../models';
5
+
6
+ interface DashboardInstance {
7
+ store: InstanceType<typeof DashboardStore>;
8
+ dimensions: () => { width: number; height: number };
9
+ }
10
+
11
+ /**
12
+ * Internal bridge service that coordinates between component-scoped DashboardStore instances
13
+ * and standalone components like WidgetListComponent.
14
+ *
15
+ * This service is NOT part of the public API and should remain internal to the library.
16
+ */
17
+ @Injectable({ providedIn: 'root' })
18
+ export class DashboardBridgeService {
19
+ // Map of registered dashboard instances with their reactive dimensions
20
+ private readonly dashboards = signal(new Map<string, DashboardInstance>());
21
+
22
+ /**
23
+ * Register a dashboard store instance using its dashboard ID
24
+ */
25
+ registerDashboard(store: InstanceType<typeof DashboardStore>): void {
26
+ const dashboardId = store.dashboardId();
27
+
28
+ // If dashboard ID is not set yet, we'll register later when it's available
29
+ if (!dashboardId) {
30
+ return;
31
+ }
32
+
33
+ this.dashboards.update(dashboards => {
34
+ const newMap = new Map(dashboards);
35
+ newMap.set(dashboardId, {
36
+ store,
37
+ dimensions: store.gridCellDimensions
38
+ });
39
+ return newMap;
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Unregister a dashboard store instance
45
+ */
46
+ unregisterDashboard(store: InstanceType<typeof DashboardStore>): void {
47
+ const dashboardId = store.dashboardId();
48
+ if (!dashboardId) {
49
+ return;
50
+ }
51
+
52
+ this.dashboards.update(dashboards => {
53
+ const newMap = new Map(dashboards);
54
+ newMap.delete(dashboardId);
55
+ return newMap;
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Get cell dimensions for a specific dashboard instance
61
+ */
62
+ getDashboardDimensions(dashboardId: string): { width: number; height: number } {
63
+ const dashboard = this.dashboards().get(dashboardId);
64
+ return dashboard?.dimensions() || { width: 100, height: 100 };
65
+ }
66
+
67
+ /**
68
+ * Get all available dashboard dimensions (for widget lists to choose from)
69
+ * Returns the first available dashboard's dimensions as fallback
70
+ */
71
+ readonly availableDimensions = computed(() => {
72
+ const dashboardEntries = Array.from(this.dashboards().values());
73
+ if (dashboardEntries.length === 0) {
74
+ return { width: 100, height: 100 }; // fallback
75
+ }
76
+
77
+ // Return dimensions from first available dashboard with fallback for undefined
78
+ return dashboardEntries[0].dimensions() || { width: 100, height: 100 };
79
+ });
80
+
81
+ /**
82
+ * Start drag operation on the first available dashboard
83
+ * (Widget lists need some dashboard to coordinate with during drag)
84
+ */
85
+ startDrag(dragData: DragData): void {
86
+ const dashboardEntries = Array.from(this.dashboards().values());
87
+ if (dashboardEntries.length > 0) {
88
+ dashboardEntries[0].store.startDrag(dragData);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * End drag operation on all dashboards
94
+ * (Safer to notify all in case drag state got distributed)
95
+ */
96
+ endDrag(): void {
97
+ for (const dashboard of this.dashboards().values()) {
98
+ dashboard.store.endDrag();
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Get all registered dashboard IDs
104
+ */
105
+ readonly registeredDashboards = computed(() => Array.from(this.dashboards().keys()));
106
+
107
+ /**
108
+ * Get the number of registered dashboards
109
+ */
110
+ readonly dashboardCount = computed(() => this.dashboards().size);
111
+
112
+ /**
113
+ * Check if any dashboards are registered
114
+ */
115
+ readonly hasDashboards = computed(() => this.dashboards().size > 0);
116
+
117
+ /**
118
+ * Update registration for a dashboard store when its ID becomes available
119
+ */
120
+ updateDashboardRegistration(store: InstanceType<typeof DashboardStore>): void {
121
+ this.registerDashboard(store);
122
+ }
123
+
124
+ /**
125
+ * Get grid configuration for a specific dashboard
126
+ */
127
+ getDashboardGridConfig(dashboardId: string): { rows: number; columns: number; gutterSize: string } | null {
128
+ const dashboard = this.dashboards().get(dashboardId);
129
+ if (!dashboard) {
130
+ return null;
131
+ }
132
+
133
+ const store = dashboard.store;
134
+ return {
135
+ rows: store.rows(),
136
+ columns: store.columns(),
137
+ gutterSize: store.gutterSize()
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Get the store instance for a specific dashboard ID
143
+ */
144
+ getDashboardStore(dashboardId: string): InstanceType<typeof DashboardStore> | null {
145
+ const dashboard = this.dashboards().get(dashboardId);
146
+ return dashboard?.store || null;
147
+ }
148
+
149
+ /**
150
+ * Get all registered dashboards (for viewport service integration)
151
+ */
152
+ getAllDashboards(): Map<string, DashboardInstance> {
153
+ return this.dashboards();
154
+ }
155
+ }
@@ -0,0 +1,148 @@
1
+ import { Injectable, computed, signal, inject, DestroyRef, PLATFORM_ID } from '@angular/core';
2
+ import { isPlatformBrowser } from '@angular/common';
3
+ import { DashboardStore } from '../store/dashboard-store';
4
+ import { ReservedSpace, DEFAULT_RESERVED_SPACE } from '../models/reserved-space';
5
+
6
+ export interface ViewportSize {
7
+ width: number;
8
+ height: number;
9
+ }
10
+
11
+ export interface DashboardConstraints {
12
+ maxWidth: number;
13
+ maxHeight: number;
14
+ constrainedBy: 'width' | 'height' | 'none';
15
+ }
16
+
17
+ /**
18
+ * Internal component-scoped service that provides viewport-aware constraints for a single dashboard.
19
+ * Each dashboard component gets its own instance of this service.
20
+ *
21
+ * This service is NOT part of the public API and should remain internal to the library.
22
+ */
23
+ @Injectable()
24
+ export class DashboardViewportService {
25
+ private readonly platformId = inject(PLATFORM_ID);
26
+ private readonly destroyRef = inject(DestroyRef);
27
+ private readonly store = inject(DashboardStore);
28
+
29
+ private readonly viewportSize = signal<ViewportSize>({ width: 0, height: 0 });
30
+ private readonly reservedSpace = signal<ReservedSpace>(DEFAULT_RESERVED_SPACE);
31
+ private resizeObserver: ResizeObserver | null = null;
32
+
33
+ constructor() {
34
+ if (isPlatformBrowser(this.platformId)) {
35
+ this.initializeViewportTracking();
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Initialize viewport size tracking using ResizeObserver on the window
41
+ */
42
+ private initializeViewportTracking(): void {
43
+ // Use ResizeObserver on document.documentElement for accurate viewport tracking
44
+ this.resizeObserver = new ResizeObserver((entries) => {
45
+ if (entries.length > 0) {
46
+ const entry = entries[0];
47
+ const { inlineSize, blockSize } = entry.contentBoxSize[0];
48
+ this.viewportSize.set({
49
+ width: inlineSize,
50
+ height: blockSize
51
+ });
52
+ }
53
+ });
54
+
55
+ this.resizeObserver.observe(document.documentElement);
56
+
57
+ // Initial size
58
+ this.viewportSize.set({
59
+ width: window.innerWidth,
60
+ height: window.innerHeight
61
+ });
62
+
63
+ // Cleanup on destroy
64
+ this.destroyRef.onDestroy(() => {
65
+ this.resizeObserver?.disconnect();
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Set reserved space that should be excluded from dashboard calculations
71
+ * (e.g., toolbar height, widget list width, padding)
72
+ */
73
+ setReservedSpace(space: ReservedSpace): void {
74
+ this.reservedSpace.set(space);
75
+ }
76
+
77
+ /**
78
+ * Get current viewport size
79
+ */
80
+ readonly currentViewportSize = this.viewportSize.asReadonly();
81
+
82
+ /**
83
+ * Get current reserved space
84
+ */
85
+ readonly currentReservedSpace = this.reservedSpace.asReadonly();
86
+
87
+ /**
88
+ * Calculate available space for dashboard after accounting for reserved areas
89
+ */
90
+ readonly availableSpace = computed((): ViewportSize => {
91
+ const viewport = this.viewportSize();
92
+ const reserved = this.reservedSpace();
93
+
94
+ return {
95
+ width: Math.max(0, viewport.width - reserved.left - reserved.right),
96
+ height: Math.max(0, viewport.height - reserved.top - reserved.bottom)
97
+ };
98
+ });
99
+
100
+ /**
101
+ * Calculate dashboard constraints for this dashboard instance
102
+ */
103
+ readonly constraints = computed((): DashboardConstraints => {
104
+ const availableSize = this.availableSpace();
105
+
106
+ // Get grid configuration from our component's store
107
+ const rows = this.store.rows();
108
+ const columns = this.store.columns();
109
+
110
+ if (rows === 0 || columns === 0) {
111
+ return {
112
+ maxWidth: availableSize.width,
113
+ maxHeight: availableSize.height,
114
+ constrainedBy: 'none'
115
+ };
116
+ }
117
+
118
+ // Calculate aspect ratio
119
+ const aspectRatio = columns / rows;
120
+
121
+ // Calculate maximum size that fits within available space
122
+ const maxWidthFromHeight = availableSize.height * aspectRatio;
123
+ const maxHeightFromWidth = availableSize.width / aspectRatio;
124
+
125
+ let maxWidth: number;
126
+ let maxHeight: number;
127
+ let constrainedBy: 'width' | 'height';
128
+
129
+ if (maxWidthFromHeight <= availableSize.width) {
130
+ // Height is the limiting factor
131
+ maxWidth = maxWidthFromHeight;
132
+ maxHeight = availableSize.height;
133
+ constrainedBy = 'height';
134
+ } else {
135
+ // Width is the limiting factor
136
+ maxWidth = availableSize.width;
137
+ maxHeight = maxHeightFromWidth;
138
+ constrainedBy = 'width';
139
+ }
140
+
141
+ return {
142
+ maxWidth: Math.max(0, maxWidth),
143
+ maxHeight: Math.max(0, maxHeight),
144
+ constrainedBy
145
+ };
146
+ });
147
+
148
+ }
@@ -0,0 +1,62 @@
1
+ // dashboard.service.ts
2
+ import { Injectable, signal } from '@angular/core';
3
+ import {
4
+ createFactoryFromComponent,
5
+ WidgetComponentClass,
6
+ WidgetFactory,
7
+ } from '../models';
8
+ import { UnknownWidgetComponent } from '../internal-widgets/unknown-widget/unknown-widget.component';
9
+
10
+ @Injectable({
11
+ providedIn: 'root',
12
+ })
13
+ export class DashboardService {
14
+ readonly #widgetTypes = signal<WidgetComponentClass[]>([]);
15
+ readonly #widgetFactoryMap = new Map<string, WidgetFactory>();
16
+ readonly #unknownWidgetFactory = createFactoryFromComponent(UnknownWidgetComponent);
17
+ readonly widgetTypes = this.#widgetTypes.asReadonly(); // make the widget list available as a readonly signal
18
+
19
+ registerWidgetType(widget: WidgetComponentClass) {
20
+ if (
21
+ this.#widgetTypes().some(
22
+ (w) => w.metadata.widgetTypeid === widget.metadata.widgetTypeid
23
+ )
24
+ ) {
25
+ throw new Error(
26
+ `Widget type '${widget.metadata.widgetTypeid}' is already registered`
27
+ );
28
+ }
29
+
30
+ this.#widgetFactoryMap.set(
31
+ widget.metadata.widgetTypeid,
32
+ createFactoryFromComponent(widget)
33
+ );
34
+
35
+ this.#widgetTypes.set([...this.#widgetTypes(), widget]);
36
+ }
37
+
38
+ getFactory(widgetTypeid: string): WidgetFactory {
39
+ const factory = this.#widgetFactoryMap.get(widgetTypeid);
40
+
41
+ if (factory) {
42
+ return factory;
43
+ }
44
+
45
+ // Return fallback factory for unknown widget types
46
+ console.warn(
47
+ `Unknown widget type: ${widgetTypeid}, using fallback error widget`
48
+ );
49
+
50
+ // Create a custom factory that preserves the original widget type ID in state
51
+ return {
52
+ ...this.#unknownWidgetFactory,
53
+ createInstance: (container, state) => {
54
+ const ref = this.#unknownWidgetFactory.createInstance(container, {
55
+ originalWidgetTypeid: widgetTypeid,
56
+ ...((state as any) || {}),
57
+ });
58
+ return ref;
59
+ },
60
+ };
61
+ }
62
+ }