@ogidor/dashboard 1.0.5 → 1.0.7
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 +309 -107
- package/app/models.d.ts +26 -0
- package/esm2020/app/custom-grid.component.mjs +3 -3
- package/esm2020/app/dashboard.component.mjs +41 -10
- package/esm2020/app/models.mjs +1 -1
- package/esm2020/app/widget-renderer.component.mjs +3 -3
- package/fesm2015/ogidor-dashboard.mjs +44 -13
- package/fesm2015/ogidor-dashboard.mjs.map +1 -1
- package/fesm2020/ogidor-dashboard.mjs +44 -13
- 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
|
-
- **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,92 +177,245 @@ 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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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)
|
|
198
307
|
}
|
|
199
308
|
```
|
|
200
309
|
|
|
310
|
+
> **Per-card colour:** set `cardColor` on any `Widget` to override `widgetCardColor` for just that one card.
|
|
311
|
+
|
|
201
312
|
---
|
|
202
313
|
|
|
203
|
-
##
|
|
314
|
+
## Theming
|
|
315
|
+
|
|
316
|
+
### Theme Input
|
|
317
|
+
|
|
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
|
|
204
335
|
|
|
205
|
-
|
|
336
|
+
Every token is also available as a CSS variable. Use them in your global stylesheet:
|
|
206
337
|
|
|
207
338
|
```css
|
|
208
339
|
app-dashboard {
|
|
209
|
-
|
|
210
|
-
--dash-
|
|
211
|
-
--dash-
|
|
212
|
-
--dash-
|
|
213
|
-
--dash-
|
|
214
|
-
--dash-
|
|
215
|
-
--dash-
|
|
340
|
+
/* Layout */
|
|
341
|
+
--dash-bg: #0d0d0d;
|
|
342
|
+
--dash-panel-bg: #161616;
|
|
343
|
+
--dash-card-bg: #1f1f1f;
|
|
344
|
+
--dash-fore-color: #a0a0a0;
|
|
345
|
+
--dash-accent-color: #6c63ff;
|
|
346
|
+
--dash-danger-color: #e84545;
|
|
347
|
+
--dash-font-family: 'Inter', sans-serif;
|
|
348
|
+
|
|
349
|
+
/* Header / Tabs */
|
|
350
|
+
--dash-tabs-container-color: rgba(30,30,32,0.7);
|
|
351
|
+
--dash-tab-active-color: #2a2a2e;
|
|
352
|
+
--dash-tab-active-text: #ffffff;
|
|
353
|
+
--dash-tab-hover-text: #d0d0d5;
|
|
354
|
+
--dash-add-widget-text: #ffffff;
|
|
355
|
+
|
|
356
|
+
/* Pop-out */
|
|
357
|
+
--dash-popout-title-color: #ffffff;
|
|
358
|
+
|
|
359
|
+
/* Widget card */
|
|
360
|
+
--dash-widget-title-color: #ffffff;
|
|
361
|
+
--dash-drag-handle-color: rgba(255,255,255,0.2);
|
|
362
|
+
--dash-widget-border-color: rgba(255,255,255,0.07);
|
|
363
|
+
--dash-widget-btn-bg: rgba(255,255,255,0.07);
|
|
364
|
+
--dash-widget-btn-color: rgba(255,255,255,0.5);
|
|
365
|
+
|
|
366
|
+
/* Grid */
|
|
367
|
+
--dash-placeholder-color: #6c63ff;
|
|
368
|
+
--dash-resize-handle-color: rgba(255,255,255,0.25);
|
|
216
369
|
}
|
|
217
370
|
```
|
|
218
371
|
|
|
372
|
+
### CSS Variable Reference
|
|
373
|
+
|
|
374
|
+
| CSS Variable | `DashboardTheme` field | Default |
|
|
375
|
+
|---|---|---|
|
|
376
|
+
| `--dash-bg` | `backgroundColor` | `#000000` |
|
|
377
|
+
| `--dash-panel-bg` | `panelColor` | `#1c1c1e` |
|
|
378
|
+
| `--dash-card-bg` | `widgetCardColor` | `#2c2c2e` |
|
|
379
|
+
| `--dash-fore-color` | `foreColor` | `#8e8e93` |
|
|
380
|
+
| `--dash-accent-color` | `accentColor` | `#0a84ff` |
|
|
381
|
+
| `--dash-danger-color` | `dangerColor` | `#ff453a` |
|
|
382
|
+
| `--dash-font-family` | `fontFamily` | system-ui stack |
|
|
383
|
+
| `--dash-tabs-container-color` | `tabsContainerColor` | `rgba(44,44,46,0.6)` |
|
|
384
|
+
| `--dash-tab-active-color` | `tabActiveColor` | `#3a3a3c` |
|
|
385
|
+
| `--dash-tab-active-text` | `tabActiveTextColor` | `#ffffff` |
|
|
386
|
+
| `--dash-tab-hover-text` | `tabHoverTextColor` | `#e5e5ea` |
|
|
387
|
+
| `--dash-add-widget-text` | `addWidgetButtonTextColor` | `#ffffff` |
|
|
388
|
+
| `--dash-popout-title-color` | `popoutTitleColor` | `#ffffff` |
|
|
389
|
+
| `--dash-widget-title-color` | `widgetTitleColor` | `#ffffff` |
|
|
390
|
+
| `--dash-drag-handle-color` | `dragHandleColor` | `rgba(255,255,255,0.2)` |
|
|
391
|
+
| `--dash-widget-border-color` | `widgetBorderColor` | `rgba(255,255,255,0.07)` |
|
|
392
|
+
| `--dash-widget-btn-bg` | `widgetButtonBgColor` | `rgba(255,255,255,0.07)` |
|
|
393
|
+
| `--dash-widget-btn-color` | `widgetButtonColor` | `rgba(255,255,255,0.5)` |
|
|
394
|
+
| `--dash-placeholder-color` | `placeholderColor` | `#0a84ff` |
|
|
395
|
+
| `--dash-resize-handle-color` | `resizeHandleColor` | `rgba(255,255,255,0.25)` |
|
|
396
|
+
|
|
219
397
|
---
|
|
220
398
|
|
|
221
399
|
## Pop-out Windows
|
|
222
400
|
|
|
223
|
-
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.
|
|
224
402
|
|
|
225
|
-
The
|
|
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
|
|
403
|
+
The pop-out window:
|
|
229
404
|
|
|
230
|
-
|
|
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.
|
|
231
408
|
|
|
232
|
-
|
|
409
|
+
### Sync behaviour
|
|
233
410
|
|
|
234
|
-
| State | Synced across windows? | Stored
|
|
411
|
+
| State | Synced across windows? | Stored in |
|
|
235
412
|
|---|---|---|
|
|
236
|
-
| Page list (add / remove / rename
|
|
237
|
-
| Widget list (add / remove
|
|
238
|
-
| Widget metadata (title,
|
|
239
|
-
| Card positions and sizes
|
|
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
|
|
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>` |
|
|
247
417
|
|
|
248
|
-
To pop out
|
|
418
|
+
To pop out programmatically:
|
|
249
419
|
|
|
250
420
|
```typescript
|
|
251
421
|
this.state.popOutPage(pageId);
|
|
@@ -255,32 +425,39 @@ this.state.popOutPage(pageId);
|
|
|
255
425
|
|
|
256
426
|
## Persisting Layouts
|
|
257
427
|
|
|
258
|
-
|
|
428
|
+
State is saved to `localStorage` automatically on every change.
|
|
259
429
|
|
|
260
|
-
| Key |
|
|
430
|
+
| Key | Contents |
|
|
261
431
|
|---|---|
|
|
262
|
-
| `ogidor_shared` | Page list and widget metadata
|
|
263
|
-
| `ogidor_positions_main` |
|
|
264
|
-
| `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. |
|
|
265
435
|
|
|
266
|
-
To save
|
|
436
|
+
To save and restore via a backend:
|
|
267
437
|
|
|
268
438
|
```typescript
|
|
269
|
-
// Save
|
|
439
|
+
// Save
|
|
270
440
|
const json = this.dash.serializeLayout();
|
|
271
|
-
await
|
|
441
|
+
await api.save(json);
|
|
272
442
|
|
|
273
443
|
// Restore
|
|
274
|
-
const json = await
|
|
444
|
+
const json = await api.load();
|
|
275
445
|
this.dash.stateService.loadLayout(JSON.parse(json));
|
|
276
446
|
```
|
|
277
447
|
|
|
278
448
|
---
|
|
279
449
|
|
|
280
|
-
##
|
|
450
|
+
## Full Integration Example
|
|
281
451
|
|
|
282
452
|
```typescript
|
|
283
|
-
|
|
453
|
+
import { Component, ViewChild } from '@angular/core';
|
|
454
|
+
import {
|
|
455
|
+
DashboardComponent,
|
|
456
|
+
DashboardStateService,
|
|
457
|
+
DashboardTheme,
|
|
458
|
+
Widget,
|
|
459
|
+
} from '@ogidor/dashboard';
|
|
460
|
+
|
|
284
461
|
@Component({
|
|
285
462
|
selector: 'app-root',
|
|
286
463
|
template: `
|
|
@@ -294,32 +471,38 @@ this.dash.stateService.loadLayout(JSON.parse(json));
|
|
|
294
471
|
</ng-template>
|
|
295
472
|
</app-dashboard>
|
|
296
473
|
|
|
297
|
-
|
|
298
|
-
<my-add-widget-dialog
|
|
474
|
+
<my-add-dialog
|
|
299
475
|
*ngIf="addOpen"
|
|
300
476
|
(confirmed)="onAddConfirmed($event)"
|
|
301
477
|
(cancelled)="addOpen = false"
|
|
302
|
-
></my-add-
|
|
478
|
+
></my-add-dialog>
|
|
303
479
|
|
|
304
|
-
<my-edit-
|
|
480
|
+
<my-edit-dialog
|
|
305
481
|
*ngIf="editTarget"
|
|
306
482
|
[widget]="editTarget"
|
|
307
483
|
(confirmed)="onEditConfirmed($event)"
|
|
308
484
|
(cancelled)="editTarget = null"
|
|
309
|
-
></my-edit-
|
|
485
|
+
></my-edit-dialog>
|
|
310
486
|
`,
|
|
311
487
|
})
|
|
312
488
|
export class AppComponent {
|
|
313
|
-
|
|
489
|
+
@ViewChild(DashboardComponent) dash!: DashboardComponent;
|
|
490
|
+
|
|
491
|
+
theme: DashboardTheme = {
|
|
492
|
+
accentColor: '#6c63ff',
|
|
493
|
+
tabActiveColor: '#2a2a38',
|
|
494
|
+
widgetTitleColor: '#f0f0f0',
|
|
495
|
+
};
|
|
496
|
+
|
|
314
497
|
addOpen = false;
|
|
315
498
|
editTarget: Widget | null = null;
|
|
316
499
|
|
|
317
500
|
constructor(private state: DashboardStateService) {}
|
|
318
501
|
|
|
319
|
-
openAddDialog()
|
|
320
|
-
openEditDialog(w: Widget)
|
|
502
|
+
openAddDialog() { this.addOpen = true; }
|
|
503
|
+
openEditDialog(w: Widget) { this.editTarget = w; }
|
|
321
504
|
|
|
322
|
-
onAddConfirmed(payload: { title: string
|
|
505
|
+
onAddConfirmed(payload: Partial<Widget> & { title: string }) {
|
|
323
506
|
this.state.addWidget(payload);
|
|
324
507
|
this.addOpen = false;
|
|
325
508
|
}
|
|
@@ -328,11 +511,30 @@ export class AppComponent {
|
|
|
328
511
|
this.state.updateWidget(this.editTarget!.id, patch);
|
|
329
512
|
this.editTarget = null;
|
|
330
513
|
}
|
|
514
|
+
|
|
515
|
+
saveLayout() {
|
|
516
|
+
return this.dash.serializeLayout();
|
|
517
|
+
}
|
|
331
518
|
}
|
|
332
519
|
```
|
|
333
520
|
|
|
334
521
|
---
|
|
335
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
|
+
|
|
336
538
|
## License
|
|
337
539
|
|
|
338
540
|
MIT
|
package/app/models.d.ts
CHANGED
|
@@ -50,4 +50,30 @@ export interface DashboardTheme {
|
|
|
50
50
|
dangerColor?: string;
|
|
51
51
|
/** Font family applied to the whole dashboard. Default: system-ui stack */
|
|
52
52
|
fontFamily?: string;
|
|
53
|
+
/** Background of the tabs pill container. Default: `rgba(44,44,46,0.6)` */
|
|
54
|
+
tabsContainerColor?: string;
|
|
55
|
+
/** Background of the active/selected tab. Default: `#3a3a3c` */
|
|
56
|
+
tabActiveColor?: string;
|
|
57
|
+
/** Text color of the active tab. Default: `#ffffff` */
|
|
58
|
+
tabActiveTextColor?: string;
|
|
59
|
+
/** Text color of hovered (inactive) tabs. Default: `#e5e5ea` */
|
|
60
|
+
tabHoverTextColor?: string;
|
|
61
|
+
/** Text color of the "Add Widget" button. Default: `#ffffff` */
|
|
62
|
+
addWidgetButtonTextColor?: string;
|
|
63
|
+
/** Text color of the pop-out page title. Default: `#ffffff` */
|
|
64
|
+
popoutTitleColor?: string;
|
|
65
|
+
/** Text color of the widget card title. Default: `#ffffff` */
|
|
66
|
+
widgetTitleColor?: string;
|
|
67
|
+
/** Color of the drag-handle dots on a widget card. Default: `rgba(255,255,255,0.2)` */
|
|
68
|
+
dragHandleColor?: string;
|
|
69
|
+
/** Border color of widget cards. Default: `rgba(255,255,255,0.07)` */
|
|
70
|
+
widgetBorderColor?: string;
|
|
71
|
+
/** Background of the icon buttons (edit/remove) on widget hover. Default: `rgba(255,255,255,0.07)` */
|
|
72
|
+
widgetButtonBgColor?: string;
|
|
73
|
+
/** Default text color of icon buttons. Default: `rgba(255,255,255,0.5)` */
|
|
74
|
+
widgetButtonColor?: string;
|
|
75
|
+
/** Color of the drag/resize placeholder overlay. Default: same as accentColor */
|
|
76
|
+
placeholderColor?: string;
|
|
77
|
+
/** Color of the resize handle icon. Default: `rgba(255,255,255,0.25)` */
|
|
78
|
+
resizeHandleColor?: string;
|
|
53
79
|
}
|