@ogidor/dashboard 1.0.3 → 1.0.5
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/README.md +283 -48
- package/app/app.module.d.ts +20 -0
- package/app/custom-grid.component.d.ts +93 -0
- package/app/dashboard-state.service.d.ts +70 -0
- package/app/dashboard.component.d.ts +48 -0
- package/app/models.d.ts +53 -0
- package/app/widget-renderer.component.d.ts +13 -0
- package/esm2020/app/app.module.mjs +57 -0
- package/esm2020/app/custom-grid.component.mjs +509 -0
- package/esm2020/app/dashboard-state.service.mjs +288 -0
- package/esm2020/app/dashboard.component.mjs +299 -0
- package/esm2020/app/models.mjs +2 -0
- package/esm2020/app/widget-renderer.component.mjs +83 -0
- package/esm2020/public-api.mjs +10 -0
- package/fesm2015/ogidor-dashboard.mjs +1233 -0
- package/fesm2015/ogidor-dashboard.mjs.map +1 -0
- package/fesm2020/ogidor-dashboard.mjs +1229 -0
- package/fesm2020/ogidor-dashboard.mjs.map +1 -0
- package/package.json +30 -44
- package/{dist-lib/public-api.d.ts → public-api.d.ts} +3 -2
- package/dist-lib/README.md +0 -103
- package/dist-lib/app/app.module.d.ts +0 -22
- package/dist-lib/app/dashboard-state.service.d.ts +0 -49
- package/dist-lib/app/dashboard.component.d.ts +0 -80
- package/dist-lib/app/models.d.ts +0 -73
- package/dist-lib/app/widget-renderer.component.d.ts +0 -40
- package/dist-lib/esm2020/app/app.module.mjs +0 -65
- package/dist-lib/esm2020/app/dashboard-state.service.mjs +0 -218
- package/dist-lib/esm2020/app/dashboard.component.mjs +0 -703
- package/dist-lib/esm2020/app/models.mjs +0 -2
- package/dist-lib/esm2020/app/widget-renderer.component.mjs +0 -163
- package/dist-lib/esm2020/public-api.mjs +0 -9
- package/dist-lib/fesm2015/ogidor-dashboard.mjs +0 -1154
- package/dist-lib/fesm2015/ogidor-dashboard.mjs.map +0 -1
- package/dist-lib/fesm2020/ogidor-dashboard.mjs +0 -1145
- package/dist-lib/fesm2020/ogidor-dashboard.mjs.map +0 -1
- package/dist-lib/package.json +0 -51
- /package/{dist-lib/esm2020 → esm2020}/ogidor-dashboard.mjs +0 -0
- /package/{dist-lib/index.d.ts → index.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,103 +1,338 @@
|
|
|
1
1
|
# @ogidor/dashboard
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A lightweight, content-agnostic drag-and-drop dashboard library for Angular 15.
|
|
4
|
+
Drop it into any project, feed it your own card content, and get a fully functional multi-workspace grid with persistent layout, pop-out windows, and live cross-tab sync — out of the box.
|
|
5
|
+
|
|
6
|
+
---
|
|
4
7
|
|
|
5
8
|
## Features
|
|
6
9
|
|
|
7
|
-
- **Drag & Drop
|
|
8
|
-
- **
|
|
9
|
-
- **Multi-Workspace
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
10
|
+
- **Drag & Drop Grid** — smooth cursor-following drag with animated collision resolution and auto-compaction
|
|
11
|
+
- **Resize** — live resize with a bottom-right handle; other cards shift out of the way in real time
|
|
12
|
+
- **Multi-Workspace** — unlimited tabbed workspaces, each with its own independent widget layout
|
|
13
|
+
- **Independent Pop-out Layouts** — each window keeps its own card positions; drag cards in Window B and Window A is completely unaffected
|
|
14
|
+
- **Structural Sync** — adding, removing, or editing a widget in any window immediately appears in all other open windows
|
|
15
|
+
- **Persistent Layout** — layout is saved to `localStorage` and restored on reload
|
|
16
|
+
- **Content-Agnostic Cards** — you own the card body; render any Angular component, chart, or HTML inside
|
|
17
|
+
- **Themeable** — override colors and fonts via a theme input or CSS custom properties
|
|
18
|
+
|
|
19
|
+
---
|
|
12
20
|
|
|
13
21
|
## Installation
|
|
14
22
|
|
|
15
23
|
```bash
|
|
16
|
-
npm install @ogidor/dashboard
|
|
24
|
+
npm install @ogidor/dashboard line-awesome
|
|
17
25
|
```
|
|
18
26
|
|
|
19
|
-
|
|
27
|
+
Add the Line Awesome icon font to your global styles (`angular.json` or `styles.scss`):
|
|
20
28
|
|
|
21
29
|
```scss
|
|
22
30
|
@import "line-awesome/dist/line-awesome/css/line-awesome.css";
|
|
23
31
|
```
|
|
24
32
|
|
|
25
|
-
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
26
36
|
|
|
27
|
-
Import the
|
|
37
|
+
### 1. Import the module
|
|
28
38
|
|
|
29
39
|
```typescript
|
|
40
|
+
import { NgModule } from '@angular/core';
|
|
41
|
+
import { BrowserModule } from '@angular/platform-browser';
|
|
30
42
|
import { DashboardModule } from '@ogidor/dashboard';
|
|
31
43
|
|
|
32
44
|
@NgModule({
|
|
33
|
-
imports: [
|
|
34
|
-
|
|
35
|
-
// ...
|
|
36
|
-
]
|
|
45
|
+
imports: [BrowserModule, DashboardModule],
|
|
46
|
+
bootstrap: [AppComponent],
|
|
37
47
|
})
|
|
38
|
-
export class AppModule {
|
|
48
|
+
export class AppModule {}
|
|
39
49
|
```
|
|
40
50
|
|
|
41
|
-
Use the component
|
|
51
|
+
### 2. Use the component
|
|
42
52
|
|
|
43
53
|
```html
|
|
44
|
-
<app-dashboard
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
<app-dashboard
|
|
55
|
+
(addWidgetRequested)="onAddWidget()"
|
|
56
|
+
(editWidgetRequested)="onEditWidget($event)"
|
|
57
|
+
>
|
|
58
|
+
<!-- Optional: custom card body rendered inside every widget -->
|
|
59
|
+
<ng-template gridCell let-widget="widget">
|
|
60
|
+
<div style="padding: 8px; color: white;">
|
|
61
|
+
{{ widget.data | json }}
|
|
62
|
+
</div>
|
|
63
|
+
</ng-template>
|
|
64
|
+
</app-dashboard>
|
|
48
65
|
```
|
|
49
66
|
|
|
67
|
+
### 3. Add widgets programmatically
|
|
68
|
+
|
|
69
|
+
Inject `DashboardStateService` anywhere in your app:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { DashboardStateService, Widget } from '@ogidor/dashboard';
|
|
73
|
+
|
|
74
|
+
@Component({ ... })
|
|
75
|
+
export class AppComponent {
|
|
76
|
+
constructor(private dashboard: DashboardStateService) {}
|
|
77
|
+
|
|
78
|
+
onAddWidget() {
|
|
79
|
+
this.dashboard.addWidget({
|
|
80
|
+
title: 'Sales Overview',
|
|
81
|
+
cols: 6,
|
|
82
|
+
rows: 4,
|
|
83
|
+
cardColor: '#1a1a2e', // optional — overrides the theme card color
|
|
84
|
+
data: { metric: 'revenue', period: 'Q1' }, // any shape you need
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
onEditWidget(widget: Widget) {
|
|
89
|
+
// open your own dialog, then:
|
|
90
|
+
this.dashboard.updateWidget(widget.id, {
|
|
91
|
+
title: 'Updated Title',
|
|
92
|
+
cardColor: '#0f3460',
|
|
93
|
+
data: { metric: 'users' },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
50
101
|
## API Reference
|
|
51
102
|
|
|
52
|
-
###
|
|
103
|
+
### `<app-dashboard>`
|
|
104
|
+
|
|
105
|
+
#### Inputs
|
|
106
|
+
|
|
107
|
+
| Input | Type | Default | Description |
|
|
108
|
+
|---|---|---|---|
|
|
109
|
+
| `initialLayout` | `string` | — | JSON string from a previous `serializeLayout()` call. Restores a full layout on load. |
|
|
110
|
+
| `theme` | `DashboardTheme` | See below | Override default colors and font. |
|
|
111
|
+
|
|
112
|
+
#### Outputs
|
|
113
|
+
|
|
114
|
+
| Output | Payload | Description |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `addWidgetRequested` | `void` | Fires when the user clicks **Add Widget**. Open your own dialog here. |
|
|
117
|
+
| `editWidgetRequested` | `Widget` | Fires when the user clicks the edit icon on a card. Receives the full widget object. |
|
|
118
|
+
|
|
119
|
+
#### Content children
|
|
53
120
|
|
|
54
|
-
|
|
|
121
|
+
| Selector | Description |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `<ng-template gridCell let-widget="widget">` | Optional template rendered inside every card body. The `widget` context variable gives you the full `Widget` object including your `data`. |
|
|
124
|
+
|
|
125
|
+
#### Methods (via `@ViewChild`)
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
@ViewChild(DashboardComponent) dash!: DashboardComponent;
|
|
129
|
+
|
|
130
|
+
// Serialize the current layout to a JSON string (e.g. to save to a backend)
|
|
131
|
+
const json = this.dash.serializeLayout();
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### `DashboardStateService`
|
|
137
|
+
|
|
138
|
+
The service is provided at root level. Inject it directly for full programmatic control.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
constructor(private state: DashboardStateService) {}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Methods
|
|
145
|
+
|
|
146
|
+
| Method | Signature | Description |
|
|
55
147
|
|---|---|---|
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
148
|
+
| `addWidget` | `(widget: Partial<Widget> & { title: string }) => Widget` | Adds a widget to the active workspace. Returns the created widget with its generated `id`. |
|
|
149
|
+
| `updateWidget` | `(id: string, patch: Partial<Widget>) => void` | Update any widget field except `id`, `x`, `y`, `cols`, `rows`. |
|
|
150
|
+
| `removeWidget` | `(id: string) => void` | Remove a widget from the active workspace. |
|
|
151
|
+
| `updateWidgetPosition` | `(pageId, widgetId, x, y, cols, rows) => void` | Manually reposition/resize a widget. |
|
|
152
|
+
| `addPage` | `(name: string) => void` | Add a new workspace tab. |
|
|
153
|
+
| `removePage` | `(id: string) => void` | Remove a workspace tab (minimum 1 kept). |
|
|
154
|
+
| `setActivePage` | `(id: string) => void` | Switch to a workspace by id. |
|
|
155
|
+
| `popOutPage` | `(id: string) => void` | Open a workspace in its own browser window. |
|
|
156
|
+
| `loadLayout` | `(config: DashboardConfig) => void` | Load a full layout object (parsed from `serializeLayout`). |
|
|
157
|
+
| `serializeLayout` | `() => string` | Serialize the full layout to a JSON string. |
|
|
58
158
|
|
|
59
|
-
|
|
159
|
+
#### Observables
|
|
160
|
+
|
|
161
|
+
| Observable | Type | Description |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| `pages$` | `Observable<Page[]>` | Emits the full page list on every change. |
|
|
164
|
+
| `activePageId$` | `Observable<string>` | Emits the active workspace id on every change. |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### `Widget`
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
export interface Widget {
|
|
172
|
+
id: string; // auto-generated — do not set manually
|
|
173
|
+
x: number; // grid column (0-based)
|
|
174
|
+
y: number; // grid row (0-based)
|
|
175
|
+
cols: number; // column span (default: 4)
|
|
176
|
+
rows: number; // row span (default: 3)
|
|
177
|
+
title: string; // displayed in the card header
|
|
178
|
+
cardColor?: string; // optional background color for this card
|
|
179
|
+
data?: any; // your custom payload — stored & synced, never inspected
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### `DashboardTheme`
|
|
186
|
+
|
|
187
|
+
All fields are optional. Any omitted field falls back to its default.
|
|
60
188
|
|
|
61
189
|
```typescript
|
|
62
190
|
export interface DashboardTheme {
|
|
63
|
-
backgroundColor?: string;
|
|
64
|
-
panelColor?:
|
|
65
|
-
widgetCardColor?: string;
|
|
66
|
-
|
|
67
|
-
accentColor?:
|
|
68
|
-
dangerColor?:
|
|
69
|
-
|
|
70
|
-
fontFamily?: string;
|
|
191
|
+
backgroundColor?: string; // wrapper background default: #000000
|
|
192
|
+
panelColor?: string; // main panel background default: #1c1c1e
|
|
193
|
+
widgetCardColor?: string; // card background default: #2c2c2e
|
|
194
|
+
foreColor?: string; // text / labels default: #8e8e93
|
|
195
|
+
accentColor?: string; // buttons, active states default: #0a84ff
|
|
196
|
+
dangerColor?: string; // remove / close actions default: #ff453a
|
|
197
|
+
fontFamily?: string; // applied globally default: system-ui stack
|
|
71
198
|
}
|
|
72
199
|
```
|
|
73
200
|
|
|
74
|
-
|
|
201
|
+
---
|
|
75
202
|
|
|
76
|
-
|
|
203
|
+
## CSS Custom Properties
|
|
204
|
+
|
|
205
|
+
You can also theme the dashboard entirely via CSS in your global stylesheet:
|
|
77
206
|
|
|
78
207
|
```css
|
|
79
|
-
|
|
80
|
-
--dash-bg:
|
|
81
|
-
--dash-panel-bg:
|
|
82
|
-
--dash-card-bg:
|
|
83
|
-
--dash-fore-color:
|
|
84
|
-
--dash-accent-color: #
|
|
85
|
-
--dash-danger-color: #
|
|
86
|
-
--dash-font-family:
|
|
208
|
+
app-dashboard {
|
|
209
|
+
--dash-bg: #0d0d0d;
|
|
210
|
+
--dash-panel-bg: #161616;
|
|
211
|
+
--dash-card-bg: #1f1f1f;
|
|
212
|
+
--dash-fore-color: #a0a0a0;
|
|
213
|
+
--dash-accent-color: #6c63ff;
|
|
214
|
+
--dash-danger-color: #e84545;
|
|
215
|
+
--dash-font-family: 'Inter', sans-serif;
|
|
87
216
|
}
|
|
88
217
|
```
|
|
89
218
|
|
|
90
|
-
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Pop-out Windows
|
|
222
|
+
|
|
223
|
+
Every workspace tab has a **pop-out** button (the arrow icon, visible on hover). Clicking it opens that workspace in a separate browser window.
|
|
224
|
+
|
|
225
|
+
The popped-out window:
|
|
226
|
+
- shows only that one workspace — no tabs, no "Add Widget" bar, just the grid
|
|
227
|
+
- has its **own independent card layout** — dragging and resizing cards there does not affect any other window
|
|
228
|
+
- **does** receive structural changes from other windows in real time
|
|
229
|
+
|
|
230
|
+
### What syncs and what doesn't
|
|
91
231
|
|
|
92
|
-
|
|
232
|
+
The library separates two kinds of state:
|
|
233
|
+
|
|
234
|
+
| State | Synced across windows? | Stored where |
|
|
235
|
+
|---|---|---|
|
|
236
|
+
| Page list (add / remove / rename workspace) | ✅ Yes | `ogidor_shared` in localStorage |
|
|
237
|
+
| Widget list (add / remove a card) | ✅ Yes | `ogidor_shared` in localStorage |
|
|
238
|
+
| Widget metadata (title, card color, data) | ✅ Yes | `ogidor_shared` in localStorage |
|
|
239
|
+
| Card positions and sizes (drag / resize) | ❌ No — local only | `ogidor_positions_<windowId>` |
|
|
240
|
+
|
|
241
|
+
**Example:** you have your main dashboard open in Tab A, and you pop out "Workspace 2" into Window B.
|
|
242
|
+
|
|
243
|
+
- You drag a card in **Window B** → it stays where you put it in B; Tab A is completely unaffected
|
|
244
|
+
- You add a widget from **Tab A** → the new card appears in **Window B** at its default position
|
|
245
|
+
- You delete a widget in **Window B** → it disappears from **Tab A** immediately
|
|
246
|
+
- Each window remembers its own layout between page reloads
|
|
247
|
+
|
|
248
|
+
To pop out a workspace programmatically:
|
|
93
249
|
|
|
94
250
|
```typescript
|
|
95
|
-
|
|
251
|
+
this.state.popOutPage(pageId);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Persisting Layouts
|
|
257
|
+
|
|
258
|
+
The library automatically persists state to `localStorage` on every change using two keys:
|
|
96
259
|
|
|
97
|
-
|
|
98
|
-
|
|
260
|
+
| Key | Contains |
|
|
261
|
+
|---|---|
|
|
262
|
+
| `ogidor_shared` | Page list and widget metadata (titles, colors, data). Shared across all windows. |
|
|
263
|
+
| `ogidor_positions_main` | Card positions/sizes for the main window. |
|
|
264
|
+
| `ogidor_positions_<pageId>` | Card positions/sizes for each pop-out window, keyed by the workspace id. |
|
|
265
|
+
|
|
266
|
+
To save the full current layout to a backend and restore it later:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// Save (includes this window's positions)
|
|
270
|
+
const json = this.dash.serializeLayout();
|
|
271
|
+
await myApi.saveLayout(json);
|
|
272
|
+
|
|
273
|
+
// Restore
|
|
274
|
+
const json = await myApi.loadLayout();
|
|
275
|
+
this.dash.stateService.loadLayout(JSON.parse(json));
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Example: Full Integration
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// app.component.ts
|
|
284
|
+
@Component({
|
|
285
|
+
selector: 'app-root',
|
|
286
|
+
template: `
|
|
287
|
+
<app-dashboard
|
|
288
|
+
[theme]="theme"
|
|
289
|
+
(addWidgetRequested)="openAddDialog()"
|
|
290
|
+
(editWidgetRequested)="openEditDialog($event)"
|
|
291
|
+
>
|
|
292
|
+
<ng-template gridCell let-widget="widget">
|
|
293
|
+
<my-chart [config]="widget.data"></my-chart>
|
|
294
|
+
</ng-template>
|
|
295
|
+
</app-dashboard>
|
|
296
|
+
|
|
297
|
+
<!-- Your own add/edit dialogs -->
|
|
298
|
+
<my-add-widget-dialog
|
|
299
|
+
*ngIf="addOpen"
|
|
300
|
+
(confirmed)="onAddConfirmed($event)"
|
|
301
|
+
(cancelled)="addOpen = false"
|
|
302
|
+
></my-add-widget-dialog>
|
|
303
|
+
|
|
304
|
+
<my-edit-widget-dialog
|
|
305
|
+
*ngIf="editTarget"
|
|
306
|
+
[widget]="editTarget"
|
|
307
|
+
(confirmed)="onEditConfirmed($event)"
|
|
308
|
+
(cancelled)="editTarget = null"
|
|
309
|
+
></my-edit-widget-dialog>
|
|
310
|
+
`,
|
|
311
|
+
})
|
|
312
|
+
export class AppComponent {
|
|
313
|
+
theme: DashboardTheme = { accentColor: '#6c63ff' };
|
|
314
|
+
addOpen = false;
|
|
315
|
+
editTarget: Widget | null = null;
|
|
316
|
+
|
|
317
|
+
constructor(private state: DashboardStateService) {}
|
|
318
|
+
|
|
319
|
+
openAddDialog() { this.addOpen = true; }
|
|
320
|
+
openEditDialog(w: Widget) { this.editTarget = w; }
|
|
321
|
+
|
|
322
|
+
onAddConfirmed(payload: { title: string; data: any; cols: number; rows: number }) {
|
|
323
|
+
this.state.addWidget(payload);
|
|
324
|
+
this.addOpen = false;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
onEditConfirmed(patch: Partial<Widget>) {
|
|
328
|
+
this.state.updateWidget(this.editTarget!.id, patch);
|
|
329
|
+
this.editTarget = null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
99
332
|
```
|
|
100
333
|
|
|
334
|
+
---
|
|
335
|
+
|
|
101
336
|
## License
|
|
102
337
|
|
|
103
338
|
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
import * as i1 from "./dashboard.component";
|
|
3
|
+
import * as i2 from "./widget-renderer.component";
|
|
4
|
+
import * as i3 from "./custom-grid.component";
|
|
5
|
+
import * as i4 from "@angular/common";
|
|
6
|
+
import * as i5 from "@angular/platform-browser";
|
|
7
|
+
export declare class DashboardModule {
|
|
8
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DashboardModule, never>;
|
|
9
|
+
static ɵmod: i0.ɵɵNgModuleDeclaration<DashboardModule, [typeof i1.DashboardComponent, typeof i2.WidgetRendererComponent, typeof i3.CustomGridComponent, typeof i3.GridCellDirective], [typeof i4.CommonModule], [typeof i1.DashboardComponent, typeof i2.WidgetRendererComponent, typeof i3.CustomGridComponent, typeof i3.GridCellDirective]>;
|
|
10
|
+
static ɵinj: i0.ɵɵInjectorDeclaration<DashboardModule>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Root module for local demo / testing purposes only.
|
|
14
|
+
* Library consumers import DashboardModule, not AppModule.
|
|
15
|
+
*/
|
|
16
|
+
export declare class AppModule {
|
|
17
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<AppModule, never>;
|
|
18
|
+
static ɵmod: i0.ɵɵNgModuleDeclaration<AppModule, never, [typeof i5.BrowserModule, typeof DashboardModule], never>;
|
|
19
|
+
static ɵinj: i0.ɵɵInjectorDeclaration<AppModule>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { EventEmitter, ElementRef, OnDestroy, OnInit, NgZone, ChangeDetectorRef, TemplateRef, OnChanges, SimpleChanges } from '@angular/core';
|
|
2
|
+
import { Widget } from './models';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* Directive applied to the template that should be stamped inside each grid cell.
|
|
6
|
+
* Usage:
|
|
7
|
+
* <app-grid [widgets]="...">
|
|
8
|
+
* <ng-template gridCell let-widget="widget">
|
|
9
|
+
* <app-widget-renderer [widget]="widget" ...></app-widget-renderer>
|
|
10
|
+
* </ng-template>
|
|
11
|
+
* </app-grid>
|
|
12
|
+
*/
|
|
13
|
+
export declare class GridCellDirective {
|
|
14
|
+
templateRef: TemplateRef<{
|
|
15
|
+
widget: Widget;
|
|
16
|
+
}>;
|
|
17
|
+
constructor(templateRef: TemplateRef<{
|
|
18
|
+
widget: Widget;
|
|
19
|
+
}>);
|
|
20
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<GridCellDirective, never>;
|
|
21
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<GridCellDirective, "[gridCell]", never, {}, {}, never, never, false, never>;
|
|
22
|
+
}
|
|
23
|
+
export declare class CustomGridComponent implements OnInit, OnDestroy, OnChanges {
|
|
24
|
+
private zone;
|
|
25
|
+
private cdr;
|
|
26
|
+
widgets: Widget[];
|
|
27
|
+
columns: number;
|
|
28
|
+
gap: number;
|
|
29
|
+
rowHeight: number;
|
|
30
|
+
minItemCols: number;
|
|
31
|
+
minItemRows: number;
|
|
32
|
+
itemChanged: EventEmitter<Widget>;
|
|
33
|
+
layoutChanged: EventEmitter<Widget[]>;
|
|
34
|
+
gridContainer: ElementRef<HTMLDivElement>;
|
|
35
|
+
/** The ng-template decorated with gridCell from the parent */
|
|
36
|
+
cellTemplate: TemplateRef<{
|
|
37
|
+
widget: Widget;
|
|
38
|
+
}>;
|
|
39
|
+
placeholder: {
|
|
40
|
+
left: number;
|
|
41
|
+
top: number;
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
} | null;
|
|
45
|
+
containerHeight: number;
|
|
46
|
+
dragging: {
|
|
47
|
+
id: string;
|
|
48
|
+
offsetX: number;
|
|
49
|
+
offsetY: number;
|
|
50
|
+
currentPixelX: number;
|
|
51
|
+
currentPixelY: number;
|
|
52
|
+
} | null;
|
|
53
|
+
resizing: {
|
|
54
|
+
id: string;
|
|
55
|
+
startMouseX: number;
|
|
56
|
+
startMouseY: number;
|
|
57
|
+
startCols: number;
|
|
58
|
+
startRows: number;
|
|
59
|
+
currentPixelW: number;
|
|
60
|
+
currentPixelH: number;
|
|
61
|
+
} | null;
|
|
62
|
+
private previewWidgets;
|
|
63
|
+
private boundMouseMove;
|
|
64
|
+
private boundMouseUp;
|
|
65
|
+
constructor(zone: NgZone, cdr: ChangeDetectorRef);
|
|
66
|
+
ngOnInit(): void;
|
|
67
|
+
ngOnChanges(_changes: SimpleChanges): void;
|
|
68
|
+
ngOnDestroy(): void;
|
|
69
|
+
trackByFn(_index: number, item: Widget): string;
|
|
70
|
+
private get cellWidth();
|
|
71
|
+
private colToPixel;
|
|
72
|
+
private rowToPixel;
|
|
73
|
+
private pixelToCol;
|
|
74
|
+
private pixelToRow;
|
|
75
|
+
private colsToPixelWidth;
|
|
76
|
+
private rowsToPixelHeight;
|
|
77
|
+
getItemLeft(w: Widget): number;
|
|
78
|
+
getItemTop(w: Widget): number;
|
|
79
|
+
getItemWidth(w: Widget): number;
|
|
80
|
+
getItemHeight(w: Widget): number;
|
|
81
|
+
onDragStart(event: MouseEvent, widget: Widget): void;
|
|
82
|
+
onResizeStart(event: MouseEvent, widget: Widget): void;
|
|
83
|
+
private onMouseMove;
|
|
84
|
+
private onMouseUp;
|
|
85
|
+
private handleDragMove;
|
|
86
|
+
private finalizeDrag;
|
|
87
|
+
private handleResizeMove;
|
|
88
|
+
private finalizeResize;
|
|
89
|
+
private compactAndApply;
|
|
90
|
+
private updateContainerHeight;
|
|
91
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<CustomGridComponent, never>;
|
|
92
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<CustomGridComponent, "app-grid", never, { "widgets": "widgets"; "columns": "columns"; "gap": "gap"; "rowHeight": "rowHeight"; "minItemCols": "minItemCols"; "minItemRows": "minItemRows"; }, { "itemChanged": "itemChanged"; "layoutChanged": "layoutChanged"; }, ["cellTemplate"], never, false, never>;
|
|
93
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { NgZone, OnDestroy } from '@angular/core';
|
|
2
|
+
import { Page, Widget } from './models';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export declare class DashboardStateService implements OnDestroy {
|
|
5
|
+
private zone;
|
|
6
|
+
/**
|
|
7
|
+
* Shared key — stores page list + widget metadata (no positions).
|
|
8
|
+
* Read and written by every window.
|
|
9
|
+
*/
|
|
10
|
+
private readonly SHARED_KEY;
|
|
11
|
+
/**
|
|
12
|
+
* Per-window positions key — stores {pageId:widgetId → {x,y,cols,rows}}.
|
|
13
|
+
* Pop-out windows get a key scoped to their pageId so they never overwrite
|
|
14
|
+
* the main window or each other.
|
|
15
|
+
* main window → ogidor_positions_main
|
|
16
|
+
* pop-out #abc → ogidor_positions_abc
|
|
17
|
+
*/
|
|
18
|
+
private readonly positionsKey;
|
|
19
|
+
private readonly CHANNEL_NAME;
|
|
20
|
+
private channel;
|
|
21
|
+
private readonly initialPages;
|
|
22
|
+
private pagesSubject;
|
|
23
|
+
private activePageIdSubject;
|
|
24
|
+
pages$: import("rxjs").Observable<Page[]>;
|
|
25
|
+
activePageId$: import("rxjs").Observable<string>;
|
|
26
|
+
constructor(zone: NgZone);
|
|
27
|
+
ngOnDestroy(): void;
|
|
28
|
+
getActivePage(): Page | undefined;
|
|
29
|
+
setActivePage(id: string): void;
|
|
30
|
+
addPage(name: string): void;
|
|
31
|
+
removePage(id: string): void;
|
|
32
|
+
addWidget(widget: Partial<Widget> & {
|
|
33
|
+
title: string;
|
|
34
|
+
}): Widget;
|
|
35
|
+
updateWidget(widgetId: string, patch: Partial<Omit<Widget, 'id' | 'x' | 'y' | 'cols' | 'rows'>>): void;
|
|
36
|
+
removeWidget(widgetId: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Update a widget's grid position/size.
|
|
39
|
+
* Saved only for this window — never broadcast to others.
|
|
40
|
+
*/
|
|
41
|
+
updateWidgetPosition(pageId: string, widgetId: string, x: number, y: number, cols: number, rows: number): void;
|
|
42
|
+
serializeLayout(): string;
|
|
43
|
+
loadLayout(config: any): void;
|
|
44
|
+
popOutPage(pageId: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Save page list + widget metadata (titles, cardColor, data) — no positions.
|
|
47
|
+
*/
|
|
48
|
+
private _saveShared;
|
|
49
|
+
/**
|
|
50
|
+
* Save this window's grid positions (x, y, cols, rows) per widget.
|
|
51
|
+
*/
|
|
52
|
+
private _savePositions;
|
|
53
|
+
/**
|
|
54
|
+
* Overlay the positions saved for THIS window on top of the current pages.
|
|
55
|
+
*/
|
|
56
|
+
private _restorePositions;
|
|
57
|
+
/**
|
|
58
|
+
* Apply a shared config object (page list + metadata) without touching
|
|
59
|
+
* this window's saved positions.
|
|
60
|
+
*/
|
|
61
|
+
private _applyShared;
|
|
62
|
+
/**
|
|
63
|
+
* Handle a structural sync event arriving from another window.
|
|
64
|
+
* Position changes are never sent so we never receive them here.
|
|
65
|
+
*/
|
|
66
|
+
private _onSyncEvent;
|
|
67
|
+
private _broadcast;
|
|
68
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DashboardStateService, never>;
|
|
69
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<DashboardStateService>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { OnInit, OnDestroy, OnChanges, SimpleChanges, EventEmitter, TemplateRef } from '@angular/core';
|
|
2
|
+
import { DashboardStateService } from './dashboard-state.service';
|
|
3
|
+
import { Page, Widget, DashboardTheme } from './models';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
export declare class DashboardComponent implements OnInit, OnDestroy, OnChanges {
|
|
6
|
+
stateService: DashboardStateService;
|
|
7
|
+
initialLayout?: string;
|
|
8
|
+
theme?: DashboardTheme;
|
|
9
|
+
/**
|
|
10
|
+
* Emits when the user clicks "Add Widget".
|
|
11
|
+
* The consumer should open their own dialog and call `stateService.addWidget(...)`.
|
|
12
|
+
*/
|
|
13
|
+
addWidgetRequested: EventEmitter<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Emits the Widget when the user clicks the edit (pen) icon on a card.
|
|
16
|
+
* The consumer should open their own edit UI and call `stateService.updateWidget(...)`.
|
|
17
|
+
*/
|
|
18
|
+
editWidgetRequested: EventEmitter<Widget>;
|
|
19
|
+
/**
|
|
20
|
+
* Optional: consumer can provide an `<ng-template widgetCell let-widget="widget">` child
|
|
21
|
+
* to render custom content inside every card body.
|
|
22
|
+
*/
|
|
23
|
+
cellTemplate?: TemplateRef<{
|
|
24
|
+
widget: Widget;
|
|
25
|
+
}>;
|
|
26
|
+
resolvedTheme: Required<DashboardTheme>;
|
|
27
|
+
wrapperStyles: Record<string, string>;
|
|
28
|
+
pages: Page[];
|
|
29
|
+
activePageId: string;
|
|
30
|
+
activePage?: Page;
|
|
31
|
+
isPoppedOut: boolean;
|
|
32
|
+
private subs;
|
|
33
|
+
constructor(stateService: DashboardStateService);
|
|
34
|
+
ngOnChanges(changes: SimpleChanges): void;
|
|
35
|
+
ngOnInit(): void;
|
|
36
|
+
ngOnDestroy(): void;
|
|
37
|
+
onItemChanged(widget: Widget): void;
|
|
38
|
+
onRemoveWidget(widgetId: string): void;
|
|
39
|
+
onSelectPage(id: string): void;
|
|
40
|
+
onAddPage(): void;
|
|
41
|
+
onRemovePage(event: Event, id: string): void;
|
|
42
|
+
onPopOut(event: Event, pageId: string): void;
|
|
43
|
+
serializeLayout(): string;
|
|
44
|
+
private applyTheme;
|
|
45
|
+
private updateActivePage;
|
|
46
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DashboardComponent, never>;
|
|
47
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<DashboardComponent, "app-dashboard", never, { "initialLayout": "initialLayout"; "theme": "theme"; }, { "addWidgetRequested": "addWidgetRequested"; "editWidgetRequested": "editWidgetRequested"; }, ["cellTemplate"], never, false, never>;
|
|
48
|
+
}
|
package/app/models.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single widget on the grid.
|
|
3
|
+
* The lib is content-agnostic — `data` can hold anything the consumer needs.
|
|
4
|
+
*/
|
|
5
|
+
export interface Widget {
|
|
6
|
+
id: string;
|
|
7
|
+
/** Grid column (0-based) */
|
|
8
|
+
x: number;
|
|
9
|
+
/** Grid row (0-based) */
|
|
10
|
+
y: number;
|
|
11
|
+
/** Number of columns this widget spans */
|
|
12
|
+
cols: number;
|
|
13
|
+
/** Number of rows this widget spans */
|
|
14
|
+
rows: number;
|
|
15
|
+
/** Display title shown in the card header */
|
|
16
|
+
title: string;
|
|
17
|
+
/** Optional per-widget card background color. Overrides the global theme widgetCardColor. */
|
|
18
|
+
cardColor?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Arbitrary consumer data.
|
|
21
|
+
* The lib stores and syncs this but never inspects it.
|
|
22
|
+
*/
|
|
23
|
+
data?: any;
|
|
24
|
+
}
|
|
25
|
+
export interface Page {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
widgets: Widget[];
|
|
29
|
+
}
|
|
30
|
+
export interface DashboardConfig {
|
|
31
|
+
pages: Page[];
|
|
32
|
+
activePageId: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Theme configuration for the dashboard.
|
|
36
|
+
* All properties are optional — any values not provided will fall back to defaults.
|
|
37
|
+
*/
|
|
38
|
+
export interface DashboardTheme {
|
|
39
|
+
/** Background color of the overall dashboard wrapper. Default: `#000000` */
|
|
40
|
+
backgroundColor?: string;
|
|
41
|
+
/** Background color of the main content panel. Default: `#1c1c1e` */
|
|
42
|
+
panelColor?: string;
|
|
43
|
+
/** Background color of individual widget cards. Default: `#2c2c2e` */
|
|
44
|
+
widgetCardColor?: string;
|
|
45
|
+
/** Primary foreground/text color. Default: `#8e8e93` */
|
|
46
|
+
foreColor?: string;
|
|
47
|
+
/** Primary accent color (active tabs, buttons, etc.). Default: `#0a84ff` */
|
|
48
|
+
accentColor?: string;
|
|
49
|
+
/** Danger color used on remove-widget button hover. Default: `#ff453a` */
|
|
50
|
+
dangerColor?: string;
|
|
51
|
+
/** Font family applied to the whole dashboard. Default: system-ui stack */
|
|
52
|
+
fontFamily?: string;
|
|
53
|
+
}
|