@ogidor/dashboard 1.0.6 → 1.0.8
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 +255 -130
- package/esm2020/app/custom-grid.component.mjs +3 -3
- package/esm2020/app/dashboard.component.mjs +3 -3
- package/esm2020/app/widget-renderer.component.mjs +3 -3
- package/fesm2015/ogidor-dashboard.mjs +6 -6
- package/fesm2015/ogidor-dashboard.mjs.map +1 -1
- package/fesm2020/ogidor-dashboard.mjs +6 -6
- package/fesm2020/ogidor-dashboard.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
# @ogidor/dashboard
|
|
2
2
|
|
|
3
|
-
A lightweight, content-agnostic drag-and-drop dashboard library for Angular 15.
|
|
4
|
-
Drop it into any project,
|
|
3
|
+
A lightweight, content-agnostic drag-and-drop dashboard library for Angular 15.
|
|
4
|
+
Drop it into any project, supply your own card content, and get a fully featured
|
|
5
|
+
multi-workspace grid with persistent layout, pop-out windows, and live cross-tab
|
|
6
|
+
sync — out of the box.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
1. [Features](#features)
|
|
13
|
+
2. [Installation](#installation)
|
|
14
|
+
3. [Quick Start](#quick-start)
|
|
15
|
+
4. [API Reference](#api-reference)
|
|
16
|
+
5. [Models](#models)
|
|
17
|
+
6. [Theming](#theming)
|
|
18
|
+
7. [Pop-out Windows](#pop-out-windows)
|
|
19
|
+
8. [Persisting Layouts](#persisting-layouts)
|
|
20
|
+
9. [Full Integration Example](#full-integration-example)
|
|
21
|
+
10. [Development](#development)
|
|
22
|
+
11. [License](#license)
|
|
5
23
|
|
|
6
24
|
---
|
|
7
25
|
|
|
8
26
|
## Features
|
|
9
27
|
|
|
10
|
-
- **Drag & Drop Grid** — smooth cursor-following drag with
|
|
11
|
-
- **Resize** —
|
|
12
|
-
- **Multi-Workspace** — unlimited tabbed workspaces, each with its own independent
|
|
13
|
-
- **Independent Pop-out Layouts** —
|
|
14
|
-
- **Structural Sync** — adding, removing, or
|
|
15
|
-
- **Persistent Layout** —
|
|
16
|
-
- **Content-Agnostic Cards** — you own the card body;
|
|
17
|
-
- **Fully Themeable** —
|
|
28
|
+
- **Drag & Drop Grid** — smooth cursor-following drag with collision resolution and auto-compaction.
|
|
29
|
+
- **Live Resize** — bottom-right resize handle; surrounding cards shift out of the way in real time.
|
|
30
|
+
- **Multi-Workspace** — unlimited tabbed workspaces, each with its own independent layout.
|
|
31
|
+
- **Independent Pop-out Layouts** — pop a workspace into its own window; dragging there never affects the main window.
|
|
32
|
+
- **Structural Sync** — adding, removing, or renaming a widget in any window instantly appears in all open windows via `BroadcastChannel`.
|
|
33
|
+
- **Persistent Layout** — state is saved to `localStorage` on every change and restored on reload.
|
|
34
|
+
- **Content-Agnostic Cards** — you own the card body; stamp any Angular component, chart, or markup inside via `ng-template`.
|
|
35
|
+
- **Fully Themeable** — 20 colour tokens plus font family, controllable via a typed `DashboardTheme` input or CSS custom properties.
|
|
18
36
|
|
|
19
37
|
---
|
|
20
38
|
|
|
@@ -24,7 +42,7 @@ Drop it into any project, feed it your own card content, and get a fully functio
|
|
|
24
42
|
npm install @ogidor/dashboard line-awesome
|
|
25
43
|
```
|
|
26
44
|
|
|
27
|
-
Add
|
|
45
|
+
Add Line Awesome to your global styles (`styles.scss` or `angular.json`):
|
|
28
46
|
|
|
29
47
|
```scss
|
|
30
48
|
@import "line-awesome/dist/line-awesome/css/line-awesome.css";
|
|
@@ -34,7 +52,7 @@ Add the Line Awesome icon font to your global styles (`angular.json` or `styles.
|
|
|
34
52
|
|
|
35
53
|
## Quick Start
|
|
36
54
|
|
|
37
|
-
### 1. Import
|
|
55
|
+
### 1. Import `DashboardModule`
|
|
38
56
|
|
|
39
57
|
```typescript
|
|
40
58
|
import { NgModule } from '@angular/core';
|
|
@@ -48,14 +66,14 @@ import { DashboardModule } from '@ogidor/dashboard';
|
|
|
48
66
|
export class AppModule {}
|
|
49
67
|
```
|
|
50
68
|
|
|
51
|
-
### 2.
|
|
69
|
+
### 2. Add the component to your template
|
|
52
70
|
|
|
53
71
|
```html
|
|
54
72
|
<app-dashboard
|
|
55
73
|
(addWidgetRequested)="onAddWidget()"
|
|
56
74
|
(editWidgetRequested)="onEditWidget($event)"
|
|
57
75
|
>
|
|
58
|
-
<!--
|
|
76
|
+
<!-- Stamp your own content inside every card -->
|
|
59
77
|
<ng-template gridCell let-widget="widget">
|
|
60
78
|
<div style="padding: 8px; color: white;">
|
|
61
79
|
{{ widget.data | json }}
|
|
@@ -64,32 +82,28 @@ export class AppModule {}
|
|
|
64
82
|
</app-dashboard>
|
|
65
83
|
```
|
|
66
84
|
|
|
67
|
-
### 3. Add widgets programmatically
|
|
68
|
-
|
|
69
|
-
Inject `DashboardStateService` anywhere in your app:
|
|
85
|
+
### 3. Add and edit widgets programmatically
|
|
70
86
|
|
|
71
87
|
```typescript
|
|
72
88
|
import { DashboardStateService, Widget } from '@ogidor/dashboard';
|
|
73
89
|
|
|
74
|
-
@Component({ ... })
|
|
75
90
|
export class AppComponent {
|
|
76
91
|
constructor(private dashboard: DashboardStateService) {}
|
|
77
92
|
|
|
78
93
|
onAddWidget() {
|
|
79
94
|
this.dashboard.addWidget({
|
|
80
|
-
title: '
|
|
95
|
+
title: 'Revenue',
|
|
81
96
|
cols: 6,
|
|
82
97
|
rows: 4,
|
|
83
|
-
cardColor: '#1a1a2e',
|
|
84
|
-
data: { metric: 'revenue'
|
|
98
|
+
cardColor: '#1a1a2e', // optional — overrides the global card color
|
|
99
|
+
data: { metric: 'revenue' },
|
|
85
100
|
});
|
|
86
101
|
}
|
|
87
102
|
|
|
88
103
|
onEditWidget(widget: Widget) {
|
|
89
|
-
// open your own dialog, then:
|
|
104
|
+
// open your own dialog, then patch:
|
|
90
105
|
this.dashboard.updateWidget(widget.id, {
|
|
91
106
|
title: 'Updated Title',
|
|
92
|
-
cardColor: '#0f3460',
|
|
93
107
|
data: { metric: 'users' },
|
|
94
108
|
});
|
|
95
109
|
}
|
|
@@ -100,34 +114,36 @@ export class AppComponent {
|
|
|
100
114
|
|
|
101
115
|
## API Reference
|
|
102
116
|
|
|
103
|
-
###
|
|
117
|
+
### `app-dashboard`
|
|
118
|
+
|
|
119
|
+
The main component. Handles tabs, the Add Widget button, and the grid.
|
|
104
120
|
|
|
105
121
|
#### Inputs
|
|
106
122
|
|
|
107
|
-
| Input | Type |
|
|
108
|
-
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
123
|
+
| Input | Type | Description |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| `theme` | `DashboardTheme` | Override any colour token or font. All fields optional. |
|
|
126
|
+
| `initialLayout` | `string` | JSON from a previous `serializeLayout()` call. Restores the full layout on load. |
|
|
111
127
|
|
|
112
128
|
#### Outputs
|
|
113
129
|
|
|
114
130
|
| Output | Payload | Description |
|
|
115
131
|
|---|---|---|
|
|
116
132
|
| `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.
|
|
133
|
+
| `editWidgetRequested` | `Widget` | Fires when the user clicks the edit icon on a card. |
|
|
118
134
|
|
|
119
135
|
#### Content children
|
|
120
136
|
|
|
121
137
|
| Selector | Description |
|
|
122
138
|
|---|---|
|
|
123
|
-
|
|
|
139
|
+
| `ng-template[gridCell]` | Rendered inside every card body. Template context exposes `widget: Widget`. |
|
|
124
140
|
|
|
125
141
|
#### Methods (via `@ViewChild`)
|
|
126
142
|
|
|
127
143
|
```typescript
|
|
128
144
|
@ViewChild(DashboardComponent) dash!: DashboardComponent;
|
|
129
145
|
|
|
130
|
-
//
|
|
146
|
+
// Returns the current layout as a JSON string
|
|
131
147
|
const json = this.dash.serializeLayout();
|
|
132
148
|
```
|
|
133
149
|
|
|
@@ -135,7 +151,7 @@ const json = this.dash.serializeLayout();
|
|
|
135
151
|
|
|
136
152
|
### `DashboardStateService`
|
|
137
153
|
|
|
138
|
-
|
|
154
|
+
Provided at root. Inject it anywhere for full programmatic control.
|
|
139
155
|
|
|
140
156
|
```typescript
|
|
141
157
|
constructor(private state: DashboardStateService) {}
|
|
@@ -145,14 +161,15 @@ constructor(private state: DashboardStateService) {}
|
|
|
145
161
|
|
|
146
162
|
| Method | Signature | Description |
|
|
147
163
|
|---|---|---|
|
|
148
|
-
| `addWidget` | `(widget: Partial<Widget> & { title: string }) => Widget` |
|
|
149
|
-
| `updateWidget` | `(id: string, patch: Partial<Widget
|
|
164
|
+
| `addWidget` | `(widget: Partial<Widget> & { title: string }) => Widget` | Add a widget to the active workspace. Returns the new widget with its generated `id`. |
|
|
165
|
+
| `updateWidget` | `(id: string, patch: Partial<Omit<Widget, 'id'|'x'|'y'|'cols'|'rows'>>) => void` | Update title, cardColor, or data. Use `updateWidgetPosition` for positional fields. |
|
|
150
166
|
| `removeWidget` | `(id: string) => void` | Remove a widget from the active workspace. |
|
|
151
|
-
| `updateWidgetPosition` | `(pageId, widgetId, x, y, cols, rows) => void` | Manually reposition
|
|
167
|
+
| `updateWidgetPosition` | `(pageId, widgetId, x, y, cols, rows) => void` | Manually reposition or resize a widget. |
|
|
168
|
+
| `getActivePage` | `() => Page \| undefined` | Returns the currently active `Page` object. |
|
|
152
169
|
| `addPage` | `(name: string) => void` | Add a new workspace tab. |
|
|
153
|
-
| `removePage` | `(id: string) => void` | Remove a workspace tab (
|
|
154
|
-
| `setActivePage` | `(id: string) => void` | Switch
|
|
155
|
-
| `popOutPage` | `(id: string) => void` | Open a workspace in
|
|
170
|
+
| `removePage` | `(id: string) => void` | Remove a workspace tab (at least one is always kept). |
|
|
171
|
+
| `setActivePage` | `(id: string) => void` | Switch the active workspace by id. |
|
|
172
|
+
| `popOutPage` | `(id: string) => void` | Open a workspace in a separate browser window. |
|
|
156
173
|
| `loadLayout` | `(config: DashboardConfig) => void` | Load a full layout object (parsed from `serializeLayout`). |
|
|
157
174
|
| `serializeLayout` | `() => string` | Serialize the full layout to a JSON string. |
|
|
158
175
|
|
|
@@ -160,73 +177,163 @@ constructor(private state: DashboardStateService) {}
|
|
|
160
177
|
|
|
161
178
|
| Observable | Type | Description |
|
|
162
179
|
|---|---|---|
|
|
163
|
-
| `pages$` | `Observable<Page[]>` | Emits the
|
|
164
|
-
| `activePageId$` | `Observable<string>` | Emits the active workspace
|
|
180
|
+
| `pages$` | `Observable<Page[]>` | Emits whenever the page list changes. |
|
|
181
|
+
| `activePageId$` | `Observable<string>` | Emits whenever the active workspace changes. |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### `app-grid` (Advanced)
|
|
186
|
+
|
|
187
|
+
The raw grid engine, exported for advanced use cases. `app-dashboard` uses this internally.
|
|
188
|
+
|
|
189
|
+
```html
|
|
190
|
+
<app-grid
|
|
191
|
+
[widgets]="myWidgets"
|
|
192
|
+
[columns]="12"
|
|
193
|
+
[gap]="16"
|
|
194
|
+
[rowHeight]="80"
|
|
195
|
+
(itemChanged)="onItemChanged($event)"
|
|
196
|
+
(layoutChanged)="onLayoutChanged($event)"
|
|
197
|
+
>
|
|
198
|
+
<ng-template gridCell let-widget="widget">
|
|
199
|
+
<!-- your card here -->
|
|
200
|
+
</ng-template>
|
|
201
|
+
</app-grid>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### Inputs
|
|
205
|
+
|
|
206
|
+
| Input | Type | Default | Description |
|
|
207
|
+
|---|---|---|---|
|
|
208
|
+
| `widgets` | `Widget[]` | `[]` | The widgets to render. |
|
|
209
|
+
| `columns` | `number` | `12` | Number of grid columns. |
|
|
210
|
+
| `gap` | `number` | `16` | Gap between cells in pixels. |
|
|
211
|
+
| `rowHeight` | `number` | `80` | Height of one grid row in pixels. |
|
|
212
|
+
| `minItemCols` | `number` | `1` | Minimum column span during resize. |
|
|
213
|
+
| `minItemRows` | `number` | `1` | Minimum row span during resize. |
|
|
214
|
+
|
|
215
|
+
#### Outputs
|
|
216
|
+
|
|
217
|
+
| Output | Payload | Description |
|
|
218
|
+
|---|---|---|
|
|
219
|
+
| `itemChanged` | `Widget` | Fires after a single widget is moved or resized. |
|
|
220
|
+
| `layoutChanged` | `Widget[]` | Fires after any move or resize with the full updated list. |
|
|
165
221
|
|
|
166
222
|
---
|
|
167
223
|
|
|
224
|
+
### Public Exports
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
export { DashboardModule } from '@ogidor/dashboard';
|
|
228
|
+
export { DashboardComponent } from '@ogidor/dashboard';
|
|
229
|
+
export { WidgetRendererComponent } from '@ogidor/dashboard';
|
|
230
|
+
export { CustomGridComponent } from '@ogidor/dashboard';
|
|
231
|
+
export { GridCellDirective } from '@ogidor/dashboard';
|
|
232
|
+
export { DashboardStateService } from '@ogidor/dashboard';
|
|
233
|
+
export { Widget, Page, DashboardConfig, DashboardTheme } from '@ogidor/dashboard';
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Models
|
|
239
|
+
|
|
168
240
|
### `Widget`
|
|
169
241
|
|
|
170
242
|
```typescript
|
|
171
243
|
export interface Widget {
|
|
172
|
-
id: string;
|
|
173
|
-
x: number;
|
|
174
|
-
y: number;
|
|
175
|
-
cols: number;
|
|
176
|
-
rows: number;
|
|
177
|
-
title: string;
|
|
178
|
-
cardColor?: string;
|
|
179
|
-
data?: any;
|
|
244
|
+
id: string; // auto-generated — do not set manually
|
|
245
|
+
x: number; // grid column, 0-based
|
|
246
|
+
y: number; // grid row, 0-based
|
|
247
|
+
cols: number; // column span (default: 4)
|
|
248
|
+
rows: number; // row span (default: 3)
|
|
249
|
+
title: string; // shown in the card header
|
|
250
|
+
cardColor?: string; // per-card background — overrides the global widgetCardColor
|
|
251
|
+
data?: any; // custom payload — stored and synced, never inspected
|
|
180
252
|
}
|
|
181
253
|
```
|
|
182
254
|
|
|
183
|
-
|
|
255
|
+
### `Page`
|
|
184
256
|
|
|
185
|
-
|
|
257
|
+
```typescript
|
|
258
|
+
export interface Page {
|
|
259
|
+
id: string;
|
|
260
|
+
name: string;
|
|
261
|
+
widgets: Widget[];
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### `DashboardConfig`
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
export interface DashboardConfig {
|
|
269
|
+
pages: Page[];
|
|
270
|
+
activePageId: string;
|
|
271
|
+
}
|
|
272
|
+
```
|
|
186
273
|
|
|
187
|
-
|
|
274
|
+
### `DashboardTheme`
|
|
188
275
|
|
|
189
276
|
```typescript
|
|
190
277
|
export interface DashboardTheme {
|
|
191
|
-
//
|
|
192
|
-
backgroundColor?: string;
|
|
193
|
-
panelColor?: string;
|
|
194
|
-
widgetCardColor?: string;
|
|
195
|
-
foreColor?: string;
|
|
196
|
-
accentColor?: string;
|
|
197
|
-
dangerColor?: string;
|
|
198
|
-
fontFamily?: string;
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
tabsContainerColor?: string;
|
|
202
|
-
tabActiveColor?: string;
|
|
203
|
-
tabActiveTextColor?: string;
|
|
204
|
-
tabHoverTextColor?: string;
|
|
205
|
-
addWidgetButtonTextColor?: string;
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
popoutTitleColor?: string;
|
|
209
|
-
|
|
210
|
-
//
|
|
211
|
-
widgetTitleColor?: string;
|
|
212
|
-
dragHandleColor?: string;
|
|
213
|
-
widgetBorderColor?: string;
|
|
214
|
-
widgetButtonBgColor?: string;
|
|
215
|
-
widgetButtonColor?: string;
|
|
216
|
-
|
|
217
|
-
//
|
|
218
|
-
placeholderColor?: string;
|
|
219
|
-
resizeHandleColor?: string;
|
|
278
|
+
// Layout
|
|
279
|
+
backgroundColor?: string; // outer wrapper default: #000000
|
|
280
|
+
panelColor?: string; // main panel default: #1c1c1e
|
|
281
|
+
widgetCardColor?: string; // card background default: #2c2c2e
|
|
282
|
+
foreColor?: string; // text / labels default: #8e8e93
|
|
283
|
+
accentColor?: string; // active states, buttons default: #0a84ff
|
|
284
|
+
dangerColor?: string; // destructive actions default: #ff453a
|
|
285
|
+
fontFamily?: string; // global font stack default: system-ui
|
|
286
|
+
|
|
287
|
+
// Header & Tabs
|
|
288
|
+
tabsContainerColor?: string; // pill background default: rgba(44,44,46,0.6)
|
|
289
|
+
tabActiveColor?: string; // active tab bg default: #3a3a3c
|
|
290
|
+
tabActiveTextColor?: string; // active tab text default: #ffffff
|
|
291
|
+
tabHoverTextColor?: string; // hovered tab text default: #e5e5ea
|
|
292
|
+
addWidgetButtonTextColor?: string; // Add Widget label default: #ffffff
|
|
293
|
+
|
|
294
|
+
// Pop-out Window
|
|
295
|
+
popoutTitleColor?: string; // pop-out title text default: #ffffff
|
|
296
|
+
|
|
297
|
+
// Widget Card
|
|
298
|
+
widgetTitleColor?: string; // card title default: #ffffff
|
|
299
|
+
dragHandleColor?: string; // drag dots default: rgba(255,255,255,0.2)
|
|
300
|
+
widgetBorderColor?: string; // card border default: rgba(255,255,255,0.07)
|
|
301
|
+
widgetButtonBgColor?: string; // icon button bg default: rgba(255,255,255,0.07)
|
|
302
|
+
widgetButtonColor?: string; // icon button colour default: rgba(255,255,255,0.5)
|
|
303
|
+
|
|
304
|
+
// Grid
|
|
305
|
+
placeholderColor?: string; // drag ghost overlay default: #0a84ff
|
|
306
|
+
resizeHandleColor?: string; // resize icon default: rgba(255,255,255,0.25)
|
|
220
307
|
}
|
|
221
308
|
```
|
|
222
309
|
|
|
223
|
-
> **
|
|
310
|
+
> **Per-card colour:** set `cardColor` on any `Widget` to override `widgetCardColor` for just that one card.
|
|
224
311
|
|
|
225
312
|
---
|
|
226
313
|
|
|
227
|
-
##
|
|
314
|
+
## Theming
|
|
315
|
+
|
|
316
|
+
### Theme Input
|
|
228
317
|
|
|
229
|
-
|
|
318
|
+
Pass a partial `DashboardTheme` to `[theme]`. Only provided fields are overridden.
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
theme: DashboardTheme = {
|
|
322
|
+
accentColor: '#6c63ff',
|
|
323
|
+
tabActiveColor: '#2a2a38',
|
|
324
|
+
tabActiveTextColor: '#ffffff',
|
|
325
|
+
widgetTitleColor: '#f0f0f0',
|
|
326
|
+
placeholderColor: '#6c63ff',
|
|
327
|
+
};
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
```html
|
|
331
|
+
<app-dashboard [theme]="theme"></app-dashboard>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### CSS Custom Properties
|
|
335
|
+
|
|
336
|
+
Every token is also available as a CSS variable. Use them in your global stylesheet:
|
|
230
337
|
|
|
231
338
|
```css
|
|
232
339
|
app-dashboard {
|
|
@@ -246,7 +353,7 @@ app-dashboard {
|
|
|
246
353
|
--dash-tab-hover-text: #d0d0d5;
|
|
247
354
|
--dash-add-widget-text: #ffffff;
|
|
248
355
|
|
|
249
|
-
/* Pop-out
|
|
356
|
+
/* Pop-out */
|
|
250
357
|
--dash-popout-title-color: #ffffff;
|
|
251
358
|
|
|
252
359
|
/* Widget card */
|
|
@@ -262,9 +369,9 @@ app-dashboard {
|
|
|
262
369
|
}
|
|
263
370
|
```
|
|
264
371
|
|
|
265
|
-
### CSS
|
|
372
|
+
### CSS Variable Reference
|
|
266
373
|
|
|
267
|
-
| CSS Variable | `DashboardTheme`
|
|
374
|
+
| CSS Variable | `DashboardTheme` field | Default |
|
|
268
375
|
|---|---|---|
|
|
269
376
|
| `--dash-bg` | `backgroundColor` | `#000000` |
|
|
270
377
|
| `--dash-panel-bg` | `panelColor` | `#1c1c1e` |
|
|
@@ -291,32 +398,24 @@ app-dashboard {
|
|
|
291
398
|
|
|
292
399
|
## Pop-out Windows
|
|
293
400
|
|
|
294
|
-
Every workspace tab
|
|
401
|
+
Every workspace tab shows a pop-out button (arrow icon, visible on hover). Clicking it opens that workspace in a new browser window.
|
|
295
402
|
|
|
296
|
-
The
|
|
297
|
-
- shows only that one workspace — no tabs, no "Add Widget" bar, just the grid
|
|
298
|
-
- has its **own independent card layout** — dragging and resizing cards there does not affect any other window
|
|
299
|
-
- **does** receive structural changes from other windows in real time
|
|
403
|
+
The pop-out window:
|
|
300
404
|
|
|
301
|
-
|
|
405
|
+
- Shows only that workspace — no tabs, no Add Widget button, just the grid.
|
|
406
|
+
- Has its **own independent layout** — moving cards there never affects the main window.
|
|
407
|
+
- **Receives** structural changes (add/remove widget, metadata edits) from all other windows in real time.
|
|
302
408
|
|
|
303
|
-
|
|
409
|
+
### Sync behaviour
|
|
304
410
|
|
|
305
|
-
| State | Synced across windows? | Stored
|
|
411
|
+
| State | Synced across windows? | Stored in |
|
|
306
412
|
|---|---|---|
|
|
307
|
-
| Page list (add / remove / rename
|
|
308
|
-
| Widget list (add / remove
|
|
309
|
-
| Widget metadata (title,
|
|
310
|
-
| Card positions and sizes
|
|
311
|
-
|
|
312
|
-
**Example:** you have your main dashboard open in Tab A, and you pop out "Workspace 2" into Window B.
|
|
413
|
+
| Page list (add / remove / rename) | Yes | `ogidor_shared` |
|
|
414
|
+
| Widget list (add / remove) | Yes | `ogidor_shared` |
|
|
415
|
+
| Widget metadata (title, cardColor, data) | Yes | `ogidor_shared` |
|
|
416
|
+
| Card positions and sizes | No — per window | `ogidor_positions_<windowId>` |
|
|
313
417
|
|
|
314
|
-
|
|
315
|
-
- You add a widget from **Tab A** → the new card appears in **Window B** at its default position
|
|
316
|
-
- You delete a widget in **Window B** → it disappears from **Tab A** immediately
|
|
317
|
-
- Each window remembers its own layout between page reloads
|
|
318
|
-
|
|
319
|
-
To pop out a workspace programmatically:
|
|
418
|
+
To pop out programmatically:
|
|
320
419
|
|
|
321
420
|
```typescript
|
|
322
421
|
this.state.popOutPage(pageId);
|
|
@@ -326,32 +425,39 @@ this.state.popOutPage(pageId);
|
|
|
326
425
|
|
|
327
426
|
## Persisting Layouts
|
|
328
427
|
|
|
329
|
-
|
|
428
|
+
State is saved to `localStorage` automatically on every change.
|
|
330
429
|
|
|
331
|
-
| Key |
|
|
430
|
+
| Key | Contents |
|
|
332
431
|
|---|---|
|
|
333
|
-
| `ogidor_shared` | Page list and widget metadata
|
|
334
|
-
| `ogidor_positions_main` |
|
|
335
|
-
| `ogidor_positions_<pageId>` |
|
|
432
|
+
| `ogidor_shared` | Page list and widget metadata. Shared across all windows. |
|
|
433
|
+
| `ogidor_positions_main` | Grid positions for the main window. |
|
|
434
|
+
| `ogidor_positions_<pageId>` | Grid positions for each pop-out window. |
|
|
336
435
|
|
|
337
|
-
To save
|
|
436
|
+
To save and restore via a backend:
|
|
338
437
|
|
|
339
438
|
```typescript
|
|
340
|
-
// Save
|
|
439
|
+
// Save
|
|
341
440
|
const json = this.dash.serializeLayout();
|
|
342
|
-
await
|
|
441
|
+
await api.save(json);
|
|
343
442
|
|
|
344
443
|
// Restore
|
|
345
|
-
const json = await
|
|
444
|
+
const json = await api.load();
|
|
346
445
|
this.dash.stateService.loadLayout(JSON.parse(json));
|
|
347
446
|
```
|
|
348
447
|
|
|
349
448
|
---
|
|
350
449
|
|
|
351
|
-
##
|
|
450
|
+
## Full Integration Example
|
|
352
451
|
|
|
353
452
|
```typescript
|
|
354
|
-
|
|
453
|
+
import { Component, ViewChild } from '@angular/core';
|
|
454
|
+
import {
|
|
455
|
+
DashboardComponent,
|
|
456
|
+
DashboardStateService,
|
|
457
|
+
DashboardTheme,
|
|
458
|
+
Widget,
|
|
459
|
+
} from '@ogidor/dashboard';
|
|
460
|
+
|
|
355
461
|
@Component({
|
|
356
462
|
selector: 'app-root',
|
|
357
463
|
template: `
|
|
@@ -365,38 +471,38 @@ this.dash.stateService.loadLayout(JSON.parse(json));
|
|
|
365
471
|
</ng-template>
|
|
366
472
|
</app-dashboard>
|
|
367
473
|
|
|
368
|
-
|
|
369
|
-
<my-add-widget-dialog
|
|
474
|
+
<my-add-dialog
|
|
370
475
|
*ngIf="addOpen"
|
|
371
476
|
(confirmed)="onAddConfirmed($event)"
|
|
372
477
|
(cancelled)="addOpen = false"
|
|
373
|
-
></my-add-
|
|
478
|
+
></my-add-dialog>
|
|
374
479
|
|
|
375
|
-
<my-edit-
|
|
480
|
+
<my-edit-dialog
|
|
376
481
|
*ngIf="editTarget"
|
|
377
482
|
[widget]="editTarget"
|
|
378
483
|
(confirmed)="onEditConfirmed($event)"
|
|
379
484
|
(cancelled)="editTarget = null"
|
|
380
|
-
></my-edit-
|
|
485
|
+
></my-edit-dialog>
|
|
381
486
|
`,
|
|
382
487
|
})
|
|
383
488
|
export class AppComponent {
|
|
489
|
+
@ViewChild(DashboardComponent) dash!: DashboardComponent;
|
|
490
|
+
|
|
384
491
|
theme: DashboardTheme = {
|
|
385
|
-
accentColor:
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
tabActiveTextColor: '#ffffff',
|
|
389
|
-
widgetTitleColor: '#f0f0f0',
|
|
492
|
+
accentColor: '#6c63ff',
|
|
493
|
+
tabActiveColor: '#2a2a38',
|
|
494
|
+
widgetTitleColor: '#f0f0f0',
|
|
390
495
|
};
|
|
496
|
+
|
|
391
497
|
addOpen = false;
|
|
392
498
|
editTarget: Widget | null = null;
|
|
393
499
|
|
|
394
500
|
constructor(private state: DashboardStateService) {}
|
|
395
501
|
|
|
396
|
-
openAddDialog()
|
|
397
|
-
openEditDialog(w: Widget)
|
|
502
|
+
openAddDialog() { this.addOpen = true; }
|
|
503
|
+
openEditDialog(w: Widget) { this.editTarget = w; }
|
|
398
504
|
|
|
399
|
-
onAddConfirmed(payload: { title: string
|
|
505
|
+
onAddConfirmed(payload: Partial<Widget> & { title: string }) {
|
|
400
506
|
this.state.addWidget(payload);
|
|
401
507
|
this.addOpen = false;
|
|
402
508
|
}
|
|
@@ -405,11 +511,30 @@ export class AppComponent {
|
|
|
405
511
|
this.state.updateWidget(this.editTarget!.id, patch);
|
|
406
512
|
this.editTarget = null;
|
|
407
513
|
}
|
|
514
|
+
|
|
515
|
+
saveLayout() {
|
|
516
|
+
return this.dash.serializeLayout();
|
|
517
|
+
}
|
|
408
518
|
}
|
|
409
519
|
```
|
|
410
520
|
|
|
411
521
|
---
|
|
412
522
|
|
|
523
|
+
## Development
|
|
524
|
+
|
|
525
|
+
```bash
|
|
526
|
+
# Install dependencies
|
|
527
|
+
npm install
|
|
528
|
+
|
|
529
|
+
# Build the library
|
|
530
|
+
npm run build:lib
|
|
531
|
+
|
|
532
|
+
# Run the local demo app
|
|
533
|
+
npm start
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
413
538
|
## License
|
|
414
539
|
|
|
415
540
|
MIT
|