@dragonworks/ngx-dashboard 20.0.6 → 20.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/dragonworks-ngx-dashboard.mjs +2250 -0
- package/fesm2022/dragonworks-ngx-dashboard.mjs.map +1 -0
- package/index.d.ts +727 -0
- package/package.json +45 -34
- package/ng-package.json +0 -7
- package/src/lib/__tests__/dashboard-component-widget-state-integration.spec.ts +0 -537
- package/src/lib/cell/__tests__/cell-resize.component.spec.ts +0 -442
- package/src/lib/cell/__tests__/cell.component.spec.ts +0 -541
- package/src/lib/cell/cell-context-menu.component.ts +0 -138
- package/src/lib/cell/cell-context-menu.service.ts +0 -36
- package/src/lib/cell/cell.component.html +0 -37
- package/src/lib/cell/cell.component.scss +0 -198
- package/src/lib/cell/cell.component.ts +0 -375
- package/src/lib/dashboard/dashboard.component.html +0 -18
- package/src/lib/dashboard/dashboard.component.scss +0 -17
- package/src/lib/dashboard/dashboard.component.ts +0 -187
- package/src/lib/dashboard-editor/dashboard-editor.component.html +0 -57
- package/src/lib/dashboard-editor/dashboard-editor.component.scss +0 -87
- package/src/lib/dashboard-editor/dashboard-editor.component.ts +0 -219
- package/src/lib/dashboard-viewer/__tests__/dashboard-viewer.component.spec.ts +0 -258
- package/src/lib/dashboard-viewer/dashboard-viewer.component.html +0 -20
- package/src/lib/dashboard-viewer/dashboard-viewer.component.scss +0 -50
- package/src/lib/dashboard-viewer/dashboard-viewer.component.ts +0 -70
- package/src/lib/drop-zone/__tests__/drop-zone.component.spec.ts +0 -465
- package/src/lib/drop-zone/drop-zone.component.html +0 -20
- package/src/lib/drop-zone/drop-zone.component.scss +0 -67
- package/src/lib/drop-zone/drop-zone.component.ts +0 -122
- package/src/lib/internal-widgets/unknown-widget/unknown-widget.component.ts +0 -72
- package/src/lib/models/cell-data.ts +0 -13
- package/src/lib/models/cell-dialog.ts +0 -7
- package/src/lib/models/cell-id.ts +0 -85
- package/src/lib/models/cell-position.ts +0 -15
- package/src/lib/models/dashboard-data.dto.ts +0 -44
- package/src/lib/models/dashboard-data.utils.ts +0 -49
- package/src/lib/models/drag-data.ts +0 -6
- package/src/lib/models/index.ts +0 -11
- package/src/lib/models/reserved-space.ts +0 -24
- package/src/lib/models/widget-factory.ts +0 -33
- package/src/lib/models/widget-id.ts +0 -70
- package/src/lib/models/widget.ts +0 -21
- package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.component.ts +0 -127
- package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.provider.ts +0 -15
- package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.tokens.ts +0 -20
- package/src/lib/providers/cell-settings-dialog/default-cell-settings-dialog.provider.ts +0 -32
- package/src/lib/providers/cell-settings-dialog/index.ts +0 -3
- package/src/lib/providers/index.ts +0 -1
- package/src/lib/services/__tests__/dashboard-bridge.service.spec.ts +0 -220
- package/src/lib/services/__tests__/dashboard-viewport.service.spec.ts +0 -362
- package/src/lib/services/dashboard-bridge.service.ts +0 -155
- package/src/lib/services/dashboard-viewport.service.ts +0 -148
- package/src/lib/services/dashboard.service.ts +0 -62
- package/src/lib/store/__tests__/dashboard-store-collision-detection.spec.ts +0 -756
- package/src/lib/store/__tests__/dashboard-store-computed-properties.spec.ts +0 -974
- package/src/lib/store/__tests__/dashboard-store-drag-drop.spec.ts +0 -279
- package/src/lib/store/__tests__/dashboard-store-export-import.spec.ts +0 -780
- package/src/lib/store/__tests__/dashboard-store-grid-config.spec.ts +0 -128
- package/src/lib/store/__tests__/dashboard-store-query-methods.spec.ts +0 -229
- package/src/lib/store/__tests__/dashboard-store-resize-operations.spec.ts +0 -652
- package/src/lib/store/__tests__/dashboard-store-widget-management.spec.ts +0 -461
- package/src/lib/store/__tests__/dashboard-store-widget-state-preservation.spec.ts +0 -369
- package/src/lib/store/dashboard-store.ts +0 -239
- package/src/lib/store/features/drag-drop.feature.ts +0 -140
- package/src/lib/store/features/grid-config.feature.ts +0 -43
- package/src/lib/store/features/resize.feature.ts +0 -140
- package/src/lib/store/features/utils/collision.utils.ts +0 -89
- package/src/lib/store/features/utils/grid-query-internal.utils.ts +0 -37
- package/src/lib/store/features/utils/resize.utils.ts +0 -165
- package/src/lib/store/features/widget-management.feature.ts +0 -158
- package/src/lib/styles/_dashboard-grid-vars.scss +0 -11
- package/src/lib/widget-list/__tests__/widget-list-bridge-integration.spec.ts +0 -137
- package/src/lib/widget-list/widget-list.component.html +0 -22
- package/src/lib/widget-list/widget-list.component.scss +0 -154
- package/src/lib/widget-list/widget-list.component.ts +0 -106
- package/src/public-api.ts +0 -21
- package/src/test-setup.ts +0 -10
- package/tsconfig.lib.json +0 -15
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -14
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
// dashboard.component.ts
|
|
2
|
-
//
|
|
3
|
-
// A performant, modular, and fully reactive Angular dashboard container that orchestrates between
|
|
4
|
-
// editing and viewing modes — with clean component separation and no external dependencies.
|
|
5
|
-
|
|
6
|
-
import { CommonModule } from '@angular/common';
|
|
7
|
-
import {
|
|
8
|
-
ChangeDetectionStrategy,
|
|
9
|
-
Component,
|
|
10
|
-
effect,
|
|
11
|
-
inject,
|
|
12
|
-
input,
|
|
13
|
-
viewChild,
|
|
14
|
-
DestroyRef,
|
|
15
|
-
OnChanges,
|
|
16
|
-
SimpleChanges,
|
|
17
|
-
untracked,
|
|
18
|
-
computed,
|
|
19
|
-
} from '@angular/core';
|
|
20
|
-
import { DashboardViewerComponent } from '../dashboard-viewer/dashboard-viewer.component';
|
|
21
|
-
import { DashboardEditorComponent } from '../dashboard-editor/dashboard-editor.component';
|
|
22
|
-
import { DashboardStore } from '../store/dashboard-store';
|
|
23
|
-
import { DashboardDataDto } from '../models/dashboard-data.dto';
|
|
24
|
-
import { DashboardBridgeService } from '../services/dashboard-bridge.service';
|
|
25
|
-
import { DashboardViewportService } from '../services/dashboard-viewport.service';
|
|
26
|
-
import { ReservedSpace } from '../models/reserved-space';
|
|
27
|
-
|
|
28
|
-
@Component({
|
|
29
|
-
selector: 'ngx-dashboard',
|
|
30
|
-
standalone: true,
|
|
31
|
-
imports: [CommonModule, DashboardViewerComponent, DashboardEditorComponent],
|
|
32
|
-
providers: [DashboardStore, DashboardViewportService],
|
|
33
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
34
|
-
templateUrl: './dashboard.component.html',
|
|
35
|
-
styleUrl: './dashboard.component.scss',
|
|
36
|
-
host: {
|
|
37
|
-
'[style.--rows]': 'store.rows()',
|
|
38
|
-
'[style.--columns]': 'store.columns()',
|
|
39
|
-
'[style.--gutter-size]': 'store.gutterSize()',
|
|
40
|
-
'[style.--gutters]': 'store.columns() + 1',
|
|
41
|
-
'[class.is-edit-mode]': 'editMode()',
|
|
42
|
-
'[style.max-width.px]': 'viewport.constraints().maxWidth',
|
|
43
|
-
'[style.max-height.px]': 'viewport.constraints().maxHeight',
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
export class DashboardComponent implements OnChanges {
|
|
47
|
-
#store = inject(DashboardStore);
|
|
48
|
-
#bridge = inject(DashboardBridgeService);
|
|
49
|
-
#viewport = inject(DashboardViewportService);
|
|
50
|
-
#destroyRef = inject(DestroyRef);
|
|
51
|
-
|
|
52
|
-
// Public accessors for template
|
|
53
|
-
protected readonly store = this.#store;
|
|
54
|
-
protected readonly viewport = this.#viewport;
|
|
55
|
-
|
|
56
|
-
// Component inputs
|
|
57
|
-
dashboardData = input.required<DashboardDataDto>();
|
|
58
|
-
editMode = input<boolean>(false);
|
|
59
|
-
reservedSpace = input<ReservedSpace>();
|
|
60
|
-
|
|
61
|
-
// Store signals - shared by both child components
|
|
62
|
-
cells = this.#store.cells;
|
|
63
|
-
|
|
64
|
-
// ViewChild references for export/import functionality
|
|
65
|
-
private dashboardEditor = viewChild(DashboardEditorComponent);
|
|
66
|
-
private dashboardViewer = viewChild(DashboardViewerComponent);
|
|
67
|
-
|
|
68
|
-
// Track if we're in the middle of preserving states
|
|
69
|
-
#isPreservingStates = false;
|
|
70
|
-
// Track if component has been initialized
|
|
71
|
-
#isInitialized = false;
|
|
72
|
-
|
|
73
|
-
constructor() {
|
|
74
|
-
// Cleanup registration when component is destroyed
|
|
75
|
-
this.#destroyRef.onDestroy(() => {
|
|
76
|
-
this.#bridge.unregisterDashboard(this.#store);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// Initialize from dashboardData
|
|
80
|
-
effect(() => {
|
|
81
|
-
const data = this.dashboardData();
|
|
82
|
-
if (data) {
|
|
83
|
-
this.#store.initializeFromDto(data);
|
|
84
|
-
// Register with bridge service after dashboard ID is set
|
|
85
|
-
this.#bridge.updateDashboardRegistration(this.#store);
|
|
86
|
-
this.#isInitialized = true;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Sync edit mode with store (without triggering state preservation)
|
|
91
|
-
effect(() => {
|
|
92
|
-
const editMode = this.editMode();
|
|
93
|
-
untracked(() => {
|
|
94
|
-
this.#store.setEditMode(editMode);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Sync reserved space input with viewport service
|
|
99
|
-
effect(() => {
|
|
100
|
-
const reserved = this.reservedSpace();
|
|
101
|
-
if (reserved) {
|
|
102
|
-
this.#viewport.setReservedSpace(reserved);
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
ngOnChanges(changes: SimpleChanges): void {
|
|
108
|
-
// Handle edit mode changes after initialization
|
|
109
|
-
if (
|
|
110
|
-
changes['editMode'] &&
|
|
111
|
-
!changes['editMode'].firstChange &&
|
|
112
|
-
this.#isInitialized
|
|
113
|
-
) {
|
|
114
|
-
const previousValue = changes['editMode'].previousValue;
|
|
115
|
-
const currentValue = changes['editMode'].currentValue;
|
|
116
|
-
|
|
117
|
-
if (previousValue !== currentValue) {
|
|
118
|
-
// Preserve widget states before the mode switch
|
|
119
|
-
this.#preserveWidgetStatesBeforeModeSwitch(previousValue);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Public export/import methods
|
|
125
|
-
exportDashboard(): DashboardDataDto {
|
|
126
|
-
// Delegate to the active child component
|
|
127
|
-
if (this.editMode()) {
|
|
128
|
-
const editor = this.dashboardEditor();
|
|
129
|
-
return editor ? editor.exportDashboard() : this.#store.exportDashboard();
|
|
130
|
-
} else {
|
|
131
|
-
const viewer = this.dashboardViewer();
|
|
132
|
-
return viewer ? viewer.exportDashboard() : this.#store.exportDashboard();
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
loadDashboard(data: DashboardDataDto): void {
|
|
137
|
-
this.#store.loadDashboard(data);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
getCurrentDashboardData(): DashboardDataDto {
|
|
141
|
-
return this.exportDashboard();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
clearDashboard(): void {
|
|
145
|
-
this.#store.clearDashboard();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Preserve widget states before switching modes by collecting live states
|
|
150
|
-
* from the currently active component and updating the store.
|
|
151
|
-
*/
|
|
152
|
-
#preserveWidgetStatesBeforeModeSwitch(previousEditMode: boolean): void {
|
|
153
|
-
// Prevent re-entrant calls
|
|
154
|
-
if (this.#isPreservingStates) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
this.#isPreservingStates = true;
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
let currentWidgetStates: Map<string, unknown> | null = null;
|
|
162
|
-
|
|
163
|
-
if (previousEditMode) {
|
|
164
|
-
// Previously in edit mode, collect states from editor
|
|
165
|
-
const editor = this.dashboardEditor();
|
|
166
|
-
if (editor) {
|
|
167
|
-
currentWidgetStates = editor.getCurrentWidgetStates();
|
|
168
|
-
}
|
|
169
|
-
} else {
|
|
170
|
-
// Previously in view mode, collect states from viewer
|
|
171
|
-
const viewer = this.dashboardViewer();
|
|
172
|
-
if (viewer) {
|
|
173
|
-
currentWidgetStates = viewer.getCurrentWidgetStates();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Update the store with the live widget states using untracked to avoid triggering effects
|
|
178
|
-
if (currentWidgetStates && currentWidgetStates.size > 0) {
|
|
179
|
-
untracked(() => {
|
|
180
|
-
this.#store.updateAllWidgetStates(currentWidgetStates);
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
} finally {
|
|
184
|
-
this.#isPreservingStates = false;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
<!-- dashboard-editor.component.html -->
|
|
2
|
-
<div class="grid-container">
|
|
3
|
-
<!-- Bottom grid with drop zones -->
|
|
4
|
-
<div class="grid" id="bottom-grid" #bottomGrid>
|
|
5
|
-
@for (position of dropzonePositions(); track position.id) {
|
|
6
|
-
<lib-drop-zone
|
|
7
|
-
class="drop-zone"
|
|
8
|
-
[row]="position.row"
|
|
9
|
-
[col]="position.col"
|
|
10
|
-
[index]="position.index"
|
|
11
|
-
[highlight]="highlightMap().has(createCellId(position.row, position.col))"
|
|
12
|
-
[highlightInvalid]="
|
|
13
|
-
invalidHighlightMap().has(createCellId(position.row, position.col))
|
|
14
|
-
"
|
|
15
|
-
[highlightResize]="
|
|
16
|
-
resizePreviewMap().has(createCellId(position.row, position.col))
|
|
17
|
-
"
|
|
18
|
-
[editMode]="true"
|
|
19
|
-
(dragEnter)="onDragEnter($event)"
|
|
20
|
-
(dragExit)="onDragExit()"
|
|
21
|
-
(dragOver)="onDragOver($event)"
|
|
22
|
-
(dragDrop)="onDragDrop($event)"
|
|
23
|
-
></lib-drop-zone>
|
|
24
|
-
}
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<!-- Top grid with interactive cells -->
|
|
28
|
-
<div class="grid" id="top-grid">
|
|
29
|
-
@for (cell of cells(); track cell.widgetId) {
|
|
30
|
-
<lib-cell
|
|
31
|
-
class="grid-cell"
|
|
32
|
-
[widgetId]="cell.widgetId"
|
|
33
|
-
[cellId]="cell.cellId"
|
|
34
|
-
[isEditMode]="true"
|
|
35
|
-
[draggable]="true"
|
|
36
|
-
[row]="cell.row"
|
|
37
|
-
[column]="cell.col"
|
|
38
|
-
[rowSpan]="cell.rowSpan"
|
|
39
|
-
[colSpan]="cell.colSpan"
|
|
40
|
-
[flat]="cell.flat"
|
|
41
|
-
[widgetFactory]="cell.widgetFactory"
|
|
42
|
-
[widgetState]="cell.widgetState"
|
|
43
|
-
(dragStart)="onCellDragStart($event)"
|
|
44
|
-
(dragEnd)="dragEnd()"
|
|
45
|
-
(delete)="onCellDelete($event)"
|
|
46
|
-
(settings)="onCellSettings($event)"
|
|
47
|
-
(resizeStart)="onCellResizeStart($event)"
|
|
48
|
-
(resizeMove)="onCellResizeMove($event)"
|
|
49
|
-
(resizeEnd)="onCellResizeEnd($event)"
|
|
50
|
-
>
|
|
51
|
-
</lib-cell>
|
|
52
|
-
}
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<!-- Context menu -->
|
|
57
|
-
<lib-cell-context-menu></lib-cell-context-menu>
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/* dashboard-editor.component.scss */
|
|
2
|
-
@use "../styles/dashboard-grid-vars" as *;
|
|
3
|
-
|
|
4
|
-
:host {
|
|
5
|
-
@include dashboard-grid-vars;
|
|
6
|
-
|
|
7
|
-
display: block;
|
|
8
|
-
container-type: inline-size;
|
|
9
|
-
box-sizing: border-box;
|
|
10
|
-
aspect-ratio: var(--columns) / var(--rows);
|
|
11
|
-
width: 100%;
|
|
12
|
-
height: auto;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/* ── Grid-line overlay (visual only) ──────────────────────────────── */
|
|
16
|
-
:host .grid {
|
|
17
|
-
/* Three layers:
|
|
18
|
-
1) vertical hair-lines (repeat)
|
|
19
|
-
2) horizontal centred hair-lines (repeat)
|
|
20
|
-
3) *single* horizontal hair-line flush with the bottom edge (no-repeat) */
|
|
21
|
-
background-image: linear-gradient(
|
|
22
|
-
to right,
|
|
23
|
-
rgba(100 100 100 / 0.12) 1px,
|
|
24
|
-
transparent 1px
|
|
25
|
-
),
|
|
26
|
-
linear-gradient(to bottom, rgba(100 100 100 / 0.12) 1px, transparent 1px),
|
|
27
|
-
linear-gradient(to bottom, rgba(100 100 100 / 0.12) 1px, transparent 1px);
|
|
28
|
-
|
|
29
|
-
background-size: var(--tile-size) var(--tile-size),
|
|
30
|
-
/* vertical lines tile */ var(--tile-size) var(--tile-size),
|
|
31
|
-
/* horizontal centred lines tile */ 100% 1px; /* bottom edge only */
|
|
32
|
-
|
|
33
|
-
background-position: var(--tile-offset) var(--tile-offset),
|
|
34
|
-
/* vertical-line grid */ var(--tile-offset) var(--tile-offset),
|
|
35
|
-
/* horizontal centred */ bottom; /* bottom hair-line */
|
|
36
|
-
|
|
37
|
-
background-repeat: repeat, repeat, no-repeat;
|
|
38
|
-
|
|
39
|
-
// background-color: rgba(245 245 245 / 0.5);
|
|
40
|
-
//border: 1px solid rgba(245 245 245 / 0.1);
|
|
41
|
-
}
|
|
42
|
-
/* ── Structural wrappers ─────────────────────────────────────────── */
|
|
43
|
-
.grid-container {
|
|
44
|
-
position: relative;
|
|
45
|
-
width: 100%;
|
|
46
|
-
height: 100%;
|
|
47
|
-
// background-color: var(--grid-background-color);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/* Real CSS Grid that holds widgets */
|
|
51
|
-
.grid {
|
|
52
|
-
display: grid;
|
|
53
|
-
gap: var(--gutter-size);
|
|
54
|
-
padding: var(--gutter-size);
|
|
55
|
-
|
|
56
|
-
position: absolute;
|
|
57
|
-
inset: 0; /* top/right/bottom/left: 0 */
|
|
58
|
-
|
|
59
|
-
width: 100%;
|
|
60
|
-
height: 100%;
|
|
61
|
-
box-sizing: border-box;
|
|
62
|
-
|
|
63
|
-
align-items: stretch;
|
|
64
|
-
justify-items: stretch;
|
|
65
|
-
|
|
66
|
-
grid-template-columns: repeat(var(--columns), var(--cell-size));
|
|
67
|
-
grid-template-rows: repeat(var(--rows), var(--cell-size));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/* ── Layering & DnD helpers ──────────────────────────────────────── */
|
|
71
|
-
#bottom-grid {
|
|
72
|
-
z-index: 1;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
#top-grid {
|
|
76
|
-
z-index: 2;
|
|
77
|
-
pointer-events: none; /* overlay shouldn’t block interactions */
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.grid-cell {
|
|
81
|
-
pointer-events: auto; /* re-enable for actual content */
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.grid-cell.is-dragging {
|
|
85
|
-
pointer-events: none; /* don’t block drop zones while lifted */
|
|
86
|
-
opacity: 0.5;
|
|
87
|
-
}
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
// dashboard-editor.component.ts
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
|
-
import {
|
|
4
|
-
ChangeDetectionStrategy,
|
|
5
|
-
Component,
|
|
6
|
-
computed,
|
|
7
|
-
DestroyRef,
|
|
8
|
-
effect,
|
|
9
|
-
ElementRef,
|
|
10
|
-
inject,
|
|
11
|
-
input,
|
|
12
|
-
output,
|
|
13
|
-
viewChild,
|
|
14
|
-
viewChildren,
|
|
15
|
-
afterNextRender,
|
|
16
|
-
} from '@angular/core';
|
|
17
|
-
import { CellComponent } from '../cell/cell.component';
|
|
18
|
-
import { CellContextMenuComponent } from '../cell/cell-context-menu.component';
|
|
19
|
-
import { CellContextMenuService } from '../cell/cell-context-menu.service';
|
|
20
|
-
import { DropZoneComponent } from '../drop-zone/drop-zone.component';
|
|
21
|
-
import { CellId, CellIdUtils, WidgetId, WidgetIdUtils, DragData, CellData } from '../models';
|
|
22
|
-
import { DashboardStore } from '../store/dashboard-store';
|
|
23
|
-
|
|
24
|
-
@Component({
|
|
25
|
-
selector: 'ngx-dashboard-editor',
|
|
26
|
-
standalone: true,
|
|
27
|
-
imports: [
|
|
28
|
-
CellComponent,
|
|
29
|
-
CommonModule,
|
|
30
|
-
DropZoneComponent,
|
|
31
|
-
CellContextMenuComponent,
|
|
32
|
-
],
|
|
33
|
-
providers: [
|
|
34
|
-
CellContextMenuService,
|
|
35
|
-
],
|
|
36
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
37
|
-
templateUrl: './dashboard-editor.component.html',
|
|
38
|
-
styleUrl: './dashboard-editor.component.scss',
|
|
39
|
-
host: {
|
|
40
|
-
'[style.--rows]': 'rows()',
|
|
41
|
-
'[style.--columns]': 'columns()',
|
|
42
|
-
'[style.--gutter-size]': 'gutterSize()',
|
|
43
|
-
'[style.--gutters]': 'gutters()',
|
|
44
|
-
'[class.is-edit-mode]': 'true', // Always in edit mode
|
|
45
|
-
},
|
|
46
|
-
})
|
|
47
|
-
export class DashboardEditorComponent {
|
|
48
|
-
bottomGridRef = viewChild.required<ElementRef<HTMLDivElement>>('bottomGrid');
|
|
49
|
-
dropZones = viewChildren(DropZoneComponent);
|
|
50
|
-
cellComponents = viewChildren(CellComponent);
|
|
51
|
-
|
|
52
|
-
#store = inject(DashboardStore);
|
|
53
|
-
#destroyRef = inject(DestroyRef);
|
|
54
|
-
#resizeObserver?: ResizeObserver;
|
|
55
|
-
|
|
56
|
-
rows = input.required<number>();
|
|
57
|
-
columns = input.required<number>();
|
|
58
|
-
gutterSize = input<string>('1em');
|
|
59
|
-
gutters = computed(() => this.columns() + 1);
|
|
60
|
-
|
|
61
|
-
// store signals
|
|
62
|
-
cells = this.#store.cells;
|
|
63
|
-
highlightedZones = this.#store.highlightedZones;
|
|
64
|
-
highlightMap = this.#store.highlightMap;
|
|
65
|
-
invalidHighlightMap = this.#store.invalidHighlightMap;
|
|
66
|
-
hoveredDropZone = this.#store.hoveredDropZone;
|
|
67
|
-
resizePreviewMap = this.#store.resizePreviewMap;
|
|
68
|
-
|
|
69
|
-
// Generate all possible cell positions for the grid
|
|
70
|
-
dropzonePositions = computed(() => {
|
|
71
|
-
const positions = [];
|
|
72
|
-
for (let row = 1; row <= this.rows(); row++) {
|
|
73
|
-
for (let col = 1; col <= this.columns(); col++) {
|
|
74
|
-
positions.push({
|
|
75
|
-
row,
|
|
76
|
-
col,
|
|
77
|
-
id: `dropzone-${row}-${col}`,
|
|
78
|
-
index: (row - 1) * this.columns() + col,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return positions;
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Helper method for template
|
|
86
|
-
createCellId(row: number, col: number): CellId {
|
|
87
|
-
return CellIdUtils.create(row, col);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
constructor() {
|
|
91
|
-
// Sync grid configuration with store when inputs change
|
|
92
|
-
effect(() => {
|
|
93
|
-
this.#store.setGridConfig({
|
|
94
|
-
rows: this.rows(),
|
|
95
|
-
columns: this.columns(),
|
|
96
|
-
gutterSize: this.gutterSize(),
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Observe grid size after rendering
|
|
101
|
-
afterNextRender(() => {
|
|
102
|
-
this.#observeGridSize();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// Always set edit mode to true
|
|
106
|
-
effect(() => {
|
|
107
|
-
this.#store.setEditMode(true);
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
#observeGridSize(): void {
|
|
112
|
-
const gridEl = this.bottomGridRef()?.nativeElement;
|
|
113
|
-
if (!gridEl || this.#resizeObserver) return;
|
|
114
|
-
|
|
115
|
-
this.#resizeObserver = new ResizeObserver(() => {
|
|
116
|
-
const dropZonesList = this.dropZones();
|
|
117
|
-
const firstDropZone = dropZonesList[0];
|
|
118
|
-
if (!firstDropZone) return;
|
|
119
|
-
const el: HTMLElement = firstDropZone.nativeElement;
|
|
120
|
-
if (!el) return;
|
|
121
|
-
const rect = el.getBoundingClientRect();
|
|
122
|
-
|
|
123
|
-
const width = rect.width;
|
|
124
|
-
const height = rect.height;
|
|
125
|
-
|
|
126
|
-
this.#store.setGridCellDimensions(width, height);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
this.#resizeObserver.observe(gridEl);
|
|
130
|
-
|
|
131
|
-
// Register cleanup with DestroyRef for automatic memory management
|
|
132
|
-
this.#destroyRef.onDestroy(() => {
|
|
133
|
-
this.#resizeObserver?.disconnect();
|
|
134
|
-
this.#resizeObserver = undefined;
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Pure delegation methods - no business logic in component
|
|
139
|
-
addWidget = (cellData: CellData) => this.#store.addWidget(cellData);
|
|
140
|
-
|
|
141
|
-
updateCellPosition = (id: WidgetId, row: number, column: number) =>
|
|
142
|
-
this.#store.updateWidgetPosition(id, row, column);
|
|
143
|
-
|
|
144
|
-
updateCellSpan = (id: WidgetId, colSpan: number, rowSpan: number) =>
|
|
145
|
-
this.#store.updateWidgetSpan(id, rowSpan, colSpan);
|
|
146
|
-
|
|
147
|
-
updateCellSettings = (id: WidgetId, flat: boolean) =>
|
|
148
|
-
this.#store.updateCellSettings(id, flat);
|
|
149
|
-
|
|
150
|
-
// Pure delegation - drag and drop event handlers
|
|
151
|
-
onDragOver = (event: { row: number; col: number }) =>
|
|
152
|
-
this.#store.setHoveredDropZone(event);
|
|
153
|
-
|
|
154
|
-
onDragEnter = (event: { row: number; col: number }) => this.onDragOver(event);
|
|
155
|
-
|
|
156
|
-
onDragExit = () => this.#store.setHoveredDropZone(null);
|
|
157
|
-
|
|
158
|
-
dragEnd = () => this.#store.endDrag();
|
|
159
|
-
|
|
160
|
-
// Pure delegation - cell event handlers
|
|
161
|
-
onCellDelete = (id: WidgetId) => this.#store.removeWidget(id);
|
|
162
|
-
|
|
163
|
-
onCellSettings = (event: { id: WidgetId; flat: boolean }) =>
|
|
164
|
-
this.updateCellSettings(event.id, event.flat);
|
|
165
|
-
|
|
166
|
-
onCellResize = (event: { id: WidgetId; rowSpan: number; colSpan: number }) =>
|
|
167
|
-
this.updateCellSpan(event.id, event.colSpan, event.rowSpan);
|
|
168
|
-
|
|
169
|
-
// Handle drag events from cell component
|
|
170
|
-
onCellDragStart = (dragData: DragData) => this.#store.startDrag(dragData);
|
|
171
|
-
|
|
172
|
-
// Handle resize events from cell component
|
|
173
|
-
onCellResizeStart = (event: {
|
|
174
|
-
cellId: CellId;
|
|
175
|
-
direction: 'horizontal' | 'vertical';
|
|
176
|
-
}) => this.#store.startResize(event.cellId);
|
|
177
|
-
|
|
178
|
-
onCellResizeMove = (event: {
|
|
179
|
-
cellId: CellId;
|
|
180
|
-
direction: 'horizontal' | 'vertical';
|
|
181
|
-
delta: number;
|
|
182
|
-
}) => this.#store.updateResizePreview(event.direction, event.delta);
|
|
183
|
-
|
|
184
|
-
onCellResizeEnd = (event: { cellId: CellId; apply: boolean }) =>
|
|
185
|
-
this.#store.endResize(event.apply);
|
|
186
|
-
|
|
187
|
-
// Handle drop events by delegating to store's business logic
|
|
188
|
-
onDragDrop(event: { data: DragData; target: { row: number; col: number } }) {
|
|
189
|
-
this.#store.handleDrop(event.data, event.target);
|
|
190
|
-
// Note: Store handles all validation and error handling internally
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Get current widget states from all cell components.
|
|
195
|
-
* Used during dashboard export to get live widget states.
|
|
196
|
-
*/
|
|
197
|
-
getCurrentWidgetStates(): Map<string, unknown> {
|
|
198
|
-
const stateMap = new Map<string, unknown>();
|
|
199
|
-
|
|
200
|
-
const cells = this.cellComponents();
|
|
201
|
-
for (const cell of cells) {
|
|
202
|
-
const cellId = cell.cellId();
|
|
203
|
-
const currentState = cell.getCurrentWidgetState();
|
|
204
|
-
if (currentState !== undefined) {
|
|
205
|
-
stateMap.set(CellIdUtils.toString(cellId), currentState);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return stateMap;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Export dashboard with live widget states from current component instances.
|
|
214
|
-
* This ensures the most up-to-date widget states are captured.
|
|
215
|
-
*/
|
|
216
|
-
exportDashboard() {
|
|
217
|
-
return this.#store.exportDashboard(() => this.getCurrentWidgetStates());
|
|
218
|
-
}
|
|
219
|
-
}
|