@ogidor/dashboard 1.0.17 → 1.0.18
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 +83 -13
- package/app/dashboard-persistence.d.ts +37 -0
- package/app/dashboard-state.service.d.ts +10 -37
- package/esm2020/app/app.module.mjs +10 -3
- package/esm2020/app/dashboard-persistence.mjs +78 -0
- package/esm2020/app/dashboard-state.service.mjs +54 -87
- package/esm2020/public-api.mjs +2 -1
- package/fesm2015/ogidor-dashboard.mjs +155 -89
- package/fesm2015/ogidor-dashboard.mjs.map +1 -1
- package/fesm2020/ogidor-dashboard.mjs +136 -89
- package/fesm2020/ogidor-dashboard.mjs.map +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +1 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A lightweight, content-agnostic drag-and-drop dashboard library for Angular 15.
|
|
4
4
|
Drop it into any project, supply your own card content, and get a fully featured
|
|
5
|
-
multi-workspace grid with
|
|
5
|
+
multi-workspace grid with pluggable persistence, pop-out windows, and live cross-tab
|
|
6
6
|
sync — out of the box.
|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -30,7 +30,7 @@ sync — out of the box.
|
|
|
30
30
|
- **Multi-Workspace** — unlimited tabbed workspaces, each with its own independent layout.
|
|
31
31
|
- **Independent Pop-out Layouts** — pop a workspace into its own window; dragging there never affects the main window.
|
|
32
32
|
- **Structural Sync** — adding, removing, or renaming a widget in any window instantly appears in all open windows via `BroadcastChannel`.
|
|
33
|
-
- **
|
|
33
|
+
- **Pluggable Persistence** — localStorage by default, or inject your own DB/API adapter.
|
|
34
34
|
- **Content-Agnostic Cards** — you own the card body; stamp any Angular component, chart, or markup inside via `ng-template`.
|
|
35
35
|
- **Fully Themeable** — 36 colour tokens plus font family, controllable via a typed `DashboardTheme` input or CSS custom properties.
|
|
36
36
|
|
|
@@ -252,6 +252,13 @@ export { WidgetRendererComponent } from '@ogidor/dashboard';
|
|
|
252
252
|
export { CustomGridComponent } from '@ogidor/dashboard';
|
|
253
253
|
export { GridCellDirective } from '@ogidor/dashboard';
|
|
254
254
|
export { DashboardStateService } from '@ogidor/dashboard';
|
|
255
|
+
export {
|
|
256
|
+
DashboardPersistence,
|
|
257
|
+
LocalStorageDashboardPersistence,
|
|
258
|
+
DASHBOARD_PERSISTENCE,
|
|
259
|
+
DASHBOARD_PERSISTENCE_CONTEXT,
|
|
260
|
+
PositionMap,
|
|
261
|
+
} from '@ogidor/dashboard';
|
|
255
262
|
export { Widget, Page, DashboardConfig, DashboardTheme } from '@ogidor/dashboard';
|
|
256
263
|
```
|
|
257
264
|
|
|
@@ -505,26 +512,89 @@ this.state.popOutPage(pageId);
|
|
|
505
512
|
|
|
506
513
|
## Persisting Layouts
|
|
507
514
|
|
|
508
|
-
|
|
515
|
+
By default, state is saved to `localStorage` automatically on every change.
|
|
509
516
|
|
|
510
517
|
| Key | Contents |
|
|
511
518
|
|---|---|
|
|
512
|
-
| `ogidor_shared` | Page list and widget metadata
|
|
513
|
-
| `ogidor_positions_main` | Grid positions for the main window. |
|
|
514
|
-
| `ogidor_positions_<pageId>` | Grid positions for each pop-out window. |
|
|
519
|
+
| `ogidor_shared` | Page list and widget metadata for the default dashboard context. |
|
|
520
|
+
| `ogidor_positions_main` | Grid positions for the main window (default context). |
|
|
521
|
+
| `ogidor_positions_<pageId>` | Grid positions for each pop-out window (default context). |
|
|
522
|
+
|
|
523
|
+
For multi-tenant scenarios, provide `DASHBOARD_PERSISTENCE_CONTEXT` with a custom `dashboardId`; keys are namespaced automatically in the default localStorage adapter.
|
|
524
|
+
|
|
525
|
+
### Use a database/API instead of localStorage
|
|
515
526
|
|
|
516
|
-
|
|
527
|
+
Provide your own implementation of `DashboardPersistence` and override the `DASHBOARD_PERSISTENCE` token.
|
|
517
528
|
|
|
518
529
|
```typescript
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
530
|
+
import { Injectable, NgModule } from '@angular/core';
|
|
531
|
+
import { HttpClient } from '@angular/common/http';
|
|
532
|
+
import {
|
|
533
|
+
DashboardConfig,
|
|
534
|
+
DashboardModule,
|
|
535
|
+
DashboardPersistence,
|
|
536
|
+
DASHBOARD_PERSISTENCE,
|
|
537
|
+
DASHBOARD_PERSISTENCE_CONTEXT,
|
|
538
|
+
PositionMap,
|
|
539
|
+
} from '@ogidor/dashboard';
|
|
540
|
+
|
|
541
|
+
@Injectable()
|
|
542
|
+
export class HttpDashboardPersistence extends DashboardPersistence {
|
|
543
|
+
constructor(private http: HttpClient) { super(); }
|
|
544
|
+
|
|
545
|
+
loadShared(dashboardId: string) {
|
|
546
|
+
return this.http
|
|
547
|
+
.get<DashboardConfig | null>(`/api/dashboards/${encodeURIComponent(dashboardId)}/shared`)
|
|
548
|
+
.toPromise();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
saveShared(dashboardId: string, shared: DashboardConfig) {
|
|
552
|
+
return this.http
|
|
553
|
+
.put<void>(`/api/dashboards/${encodeURIComponent(dashboardId)}/shared`, shared)
|
|
554
|
+
.toPromise()
|
|
555
|
+
.then(() => undefined);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
loadPositions(dashboardId: string, windowId: string) {
|
|
559
|
+
return this.http
|
|
560
|
+
.get<PositionMap | null>(`/api/dashboards/${encodeURIComponent(dashboardId)}/positions/${encodeURIComponent(windowId)}`)
|
|
561
|
+
.toPromise();
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
savePositions(dashboardId: string, windowId: string, positions: PositionMap) {
|
|
565
|
+
return this.http
|
|
566
|
+
.put<void>(`/api/dashboards/${encodeURIComponent(dashboardId)}/positions/${encodeURIComponent(windowId)}`, positions)
|
|
567
|
+
.toPromise()
|
|
568
|
+
.then(() => undefined);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
522
571
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
572
|
+
@NgModule({
|
|
573
|
+
imports: [DashboardModule],
|
|
574
|
+
providers: [
|
|
575
|
+
{ provide: DASHBOARD_PERSISTENCE, useClass: HttpDashboardPersistence },
|
|
576
|
+
{ provide: DASHBOARD_PERSISTENCE_CONTEXT, useValue: { dashboardId: 'customer-123' } },
|
|
577
|
+
],
|
|
578
|
+
})
|
|
579
|
+
export class AppModule {}
|
|
526
580
|
```
|
|
527
581
|
|
|
582
|
+
Recommended backend endpoints:
|
|
583
|
+
|
|
584
|
+
- `GET /api/dashboards/:dashboardId/shared`
|
|
585
|
+
- `PUT /api/dashboards/:dashboardId/shared`
|
|
586
|
+
- `GET /api/dashboards/:dashboardId/positions/:windowId`
|
|
587
|
+
- `PUT /api/dashboards/:dashboardId/positions/:windowId`
|
|
588
|
+
|
|
589
|
+
### Migration tip
|
|
590
|
+
|
|
591
|
+
If users already have data in localStorage, run a one-time migration in your app:
|
|
592
|
+
|
|
593
|
+
1. Read `ogidor_shared` and `ogidor_positions_*` from localStorage.
|
|
594
|
+
2. Upload them to your backend using the new API.
|
|
595
|
+
3. Set a migration flag (for example `ogidor_migrated=true`).
|
|
596
|
+
4. Use your HTTP persistence adapter as the source of truth from then on.
|
|
597
|
+
|
|
528
598
|
---
|
|
529
599
|
|
|
530
600
|
## Full Integration Example
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
import { DashboardConfig } from './models';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export type PositionMap = Record<string, {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
cols: number;
|
|
8
|
+
rows: number;
|
|
9
|
+
}>;
|
|
10
|
+
export interface DashboardPersistenceContext {
|
|
11
|
+
dashboardId: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const DASHBOARD_PERSISTENCE: InjectionToken<DashboardPersistence>;
|
|
14
|
+
export declare const DASHBOARD_PERSISTENCE_CONTEXT: InjectionToken<DashboardPersistenceContext>;
|
|
15
|
+
export declare abstract class DashboardPersistence {
|
|
16
|
+
abstract loadShared(dashboardId: string): Promise<DashboardConfig | null>;
|
|
17
|
+
abstract saveShared(dashboardId: string, shared: DashboardConfig): Promise<void>;
|
|
18
|
+
abstract loadPositions(dashboardId: string, windowId: string): Promise<PositionMap | null>;
|
|
19
|
+
abstract savePositions(dashboardId: string, windowId: string, positions: PositionMap): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare class LocalStorageDashboardPersistence implements DashboardPersistence {
|
|
22
|
+
loadShared(dashboardId: string): Promise<DashboardConfig | null>;
|
|
23
|
+
saveShared(dashboardId: string, shared: DashboardConfig): Promise<void>;
|
|
24
|
+
loadPositions(dashboardId: string, windowId: string): Promise<PositionMap | null>;
|
|
25
|
+
savePositions(dashboardId: string, windowId: string, positions: PositionMap): Promise<void>;
|
|
26
|
+
private _sharedKey;
|
|
27
|
+
private _positionsKey;
|
|
28
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LocalStorageDashboardPersistence, never>;
|
|
29
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<LocalStorageDashboardPersistence>;
|
|
30
|
+
}
|
|
31
|
+
export declare function resolveDefaultDashboardId(): string;
|
|
32
|
+
export declare class DashboardPersistenceFacade {
|
|
33
|
+
readonly dashboardId: string;
|
|
34
|
+
constructor(context: DashboardPersistenceContext | null);
|
|
35
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DashboardPersistenceFacade, [{ optional: true; }]>;
|
|
36
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<DashboardPersistenceFacade>;
|
|
37
|
+
}
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
import { NgZone, OnDestroy } from '@angular/core';
|
|
2
2
|
import { Page, Widget } from './models';
|
|
3
|
+
import { DashboardPersistence, DashboardPersistenceFacade, LocalStorageDashboardPersistence } from './dashboard-persistence';
|
|
3
4
|
import * as i0 from "@angular/core";
|
|
4
5
|
export declare class DashboardStateService implements OnDestroy {
|
|
5
6
|
private zone;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
private readonly
|
|
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;
|
|
7
|
+
private persistenceFacade;
|
|
8
|
+
private localPersistence;
|
|
9
|
+
private persistence;
|
|
10
|
+
private readonly windowId;
|
|
11
|
+
private readonly dashboardId;
|
|
19
12
|
private readonly CHANNEL_NAME;
|
|
20
13
|
private channel;
|
|
21
14
|
private readonly initialPages;
|
|
@@ -23,7 +16,7 @@ export declare class DashboardStateService implements OnDestroy {
|
|
|
23
16
|
private activePageIdSubject;
|
|
24
17
|
pages$: import("rxjs").Observable<Page[]>;
|
|
25
18
|
activePageId$: import("rxjs").Observable<string>;
|
|
26
|
-
constructor(zone: NgZone);
|
|
19
|
+
constructor(zone: NgZone, persistenceFacade: DashboardPersistenceFacade, localPersistence: LocalStorageDashboardPersistence, persistence: DashboardPersistence | null);
|
|
27
20
|
ngOnDestroy(): void;
|
|
28
21
|
getActivePage(): Page | undefined;
|
|
29
22
|
setActivePage(id: string): void;
|
|
@@ -34,37 +27,17 @@ export declare class DashboardStateService implements OnDestroy {
|
|
|
34
27
|
}): Widget;
|
|
35
28
|
updateWidget(widgetId: string, patch: Partial<Omit<Widget, 'id' | 'x' | 'y' | 'cols' | 'rows'>>): void;
|
|
36
29
|
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
30
|
updateWidgetPosition(pageId: string, widgetId: string, x: number, y: number, cols: number, rows: number): void;
|
|
42
31
|
serializeLayout(): string;
|
|
43
32
|
loadLayout(config: any): void;
|
|
44
33
|
popOutPage(pageId: string): void;
|
|
45
|
-
/**
|
|
46
|
-
* Save page list + widget metadata (titles, cardColor, data) — no positions.
|
|
47
|
-
*/
|
|
48
34
|
private _saveShared;
|
|
49
|
-
/**
|
|
50
|
-
* Save this window's grid positions (x, y, cols, rows) per widget.
|
|
51
|
-
*/
|
|
52
35
|
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
|
-
*/
|
|
36
|
+
private _applyPositions;
|
|
61
37
|
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
38
|
private _onSyncEvent;
|
|
67
39
|
private _broadcast;
|
|
68
|
-
|
|
40
|
+
private _bootstrapFromPersistence;
|
|
41
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DashboardStateService, [null, null, null, { optional: true; }]>;
|
|
69
42
|
static ɵprov: i0.ɵɵInjectableDeclaration<DashboardStateService>;
|
|
70
43
|
}
|
|
@@ -5,6 +5,7 @@ import { DashboardComponent, OverflowActivePipe } from './dashboard.component';
|
|
|
5
5
|
import { WidgetRendererComponent } from './widget-renderer.component';
|
|
6
6
|
import { CustomGridComponent, GridCellDirective } from './custom-grid.component';
|
|
7
7
|
import { DashboardStateService } from './dashboard-state.service';
|
|
8
|
+
import { DASHBOARD_PERSISTENCE, LocalStorageDashboardPersistence, } from './dashboard-persistence';
|
|
8
9
|
import * as i0 from "@angular/core";
|
|
9
10
|
export class DashboardModule {
|
|
10
11
|
}
|
|
@@ -18,7 +19,10 @@ DashboardModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version
|
|
|
18
19
|
WidgetRendererComponent,
|
|
19
20
|
CustomGridComponent,
|
|
20
21
|
GridCellDirective] });
|
|
21
|
-
DashboardModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, providers: [
|
|
22
|
+
DashboardModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, providers: [
|
|
23
|
+
DashboardStateService,
|
|
24
|
+
{ provide: DASHBOARD_PERSISTENCE, useExisting: LocalStorageDashboardPersistence },
|
|
25
|
+
], imports: [CommonModule] });
|
|
22
26
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, decorators: [{
|
|
23
27
|
type: NgModule,
|
|
24
28
|
args: [{
|
|
@@ -32,7 +36,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
32
36
|
imports: [
|
|
33
37
|
CommonModule,
|
|
34
38
|
],
|
|
35
|
-
providers: [
|
|
39
|
+
providers: [
|
|
40
|
+
DashboardStateService,
|
|
41
|
+
{ provide: DASHBOARD_PERSISTENCE, useExisting: LocalStorageDashboardPersistence },
|
|
42
|
+
],
|
|
36
43
|
exports: [
|
|
37
44
|
DashboardComponent,
|
|
38
45
|
OverflowActivePipe,
|
|
@@ -58,4 +65,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
58
65
|
bootstrap: [DashboardComponent],
|
|
59
66
|
}]
|
|
60
67
|
}] });
|
|
61
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
68
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLm1vZHVsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9hcHAvYXBwLm1vZHVsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUMxRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFFL0MsT0FBTyxFQUFFLGtCQUFrQixFQUFFLGtCQUFrQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDL0UsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDdEUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDakYsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDbEUsT0FBTyxFQUNMLHFCQUFxQixFQUNyQixnQ0FBZ0MsR0FDakMsTUFBTSx5QkFBeUIsQ0FBQzs7QUF5QmpDLE1BQU0sT0FBTyxlQUFlOzs2R0FBZixlQUFlOzhHQUFmLGVBQWUsaUJBckJ4QixrQkFBa0I7UUFDbEIsa0JBQWtCO1FBQ2xCLHVCQUF1QjtRQUN2QixtQkFBbUI7UUFDbkIsaUJBQWlCLGFBR2pCLFlBQVksYUFPWixrQkFBa0I7UUFDbEIsa0JBQWtCO1FBQ2xCLHVCQUF1QjtRQUN2QixtQkFBbUI7UUFDbkIsaUJBQWlCOzhHQUdSLGVBQWUsYUFaZjtRQUNULHFCQUFxQjtRQUNyQixFQUFFLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxXQUFXLEVBQUUsZ0NBQWdDLEVBQUU7S0FDbEYsWUFMQyxZQUFZOzRGQWNILGVBQWU7a0JBdkIzQixRQUFRO21CQUFDO29CQUNSLFlBQVksRUFBRTt3QkFDWixrQkFBa0I7d0JBQ2xCLGtCQUFrQjt3QkFDbEIsdUJBQXVCO3dCQUN2QixtQkFBbUI7d0JBQ25CLGlCQUFpQjtxQkFDbEI7b0JBQ0QsT0FBTyxFQUFFO3dCQUNQLFlBQVk7cUJBQ2I7b0JBQ0QsU0FBUyxFQUFFO3dCQUNULHFCQUFxQjt3QkFDckIsRUFBRSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsV0FBVyxFQUFFLGdDQUFnQyxFQUFFO3FCQUNsRjtvQkFDRCxPQUFPLEVBQUU7d0JBQ1Asa0JBQWtCO3dCQUNsQixrQkFBa0I7d0JBQ2xCLHVCQUF1Qjt3QkFDdkIsbUJBQW1CO3dCQUNuQixpQkFBaUI7cUJBQ2xCO2lCQUNGOztBQUdEOzs7R0FHRztBQUtILE1BQU0sT0FBTyxTQUFTOzt1R0FBVCxTQUFTO3dHQUFULFNBQVMsY0FGUixrQkFBa0IsYUFEcEIsYUFBYSxFQVBaLGVBQWU7d0dBVWYsU0FBUyxZQUhWLGFBQWEsRUFBRSxlQUFlOzRGQUc3QixTQUFTO2tCQUpyQixRQUFRO21CQUFDO29CQUNSLE9BQU8sRUFBRSxDQUFDLGFBQWEsRUFBRSxlQUFlLENBQUM7b0JBQ3pDLFNBQVMsRUFBRSxDQUFDLGtCQUFrQixDQUFDO2lCQUNoQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IE5nTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBCcm93c2VyTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvcGxhdGZvcm0tYnJvd3Nlcic7XG5pbXBvcnQgeyBDb21tb25Nb2R1bGUgfSBmcm9tICdAYW5ndWxhci9jb21tb24nO1xuXG5pbXBvcnQgeyBEYXNoYm9hcmRDb21wb25lbnQsIE92ZXJmbG93QWN0aXZlUGlwZSB9IGZyb20gJy4vZGFzaGJvYXJkLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBXaWRnZXRSZW5kZXJlckNvbXBvbmVudCB9IGZyb20gJy4vd2lkZ2V0LXJlbmRlcmVyLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBDdXN0b21HcmlkQ29tcG9uZW50LCBHcmlkQ2VsbERpcmVjdGl2ZSB9IGZyb20gJy4vY3VzdG9tLWdyaWQuY29tcG9uZW50JztcbmltcG9ydCB7IERhc2hib2FyZFN0YXRlU2VydmljZSB9IGZyb20gJy4vZGFzaGJvYXJkLXN0YXRlLnNlcnZpY2UnO1xuaW1wb3J0IHtcbiAgREFTSEJPQVJEX1BFUlNJU1RFTkNFLFxuICBMb2NhbFN0b3JhZ2VEYXNoYm9hcmRQZXJzaXN0ZW5jZSxcbn0gZnJvbSAnLi9kYXNoYm9hcmQtcGVyc2lzdGVuY2UnO1xuXG5ATmdNb2R1bGUoe1xuICBkZWNsYXJhdGlvbnM6IFtcbiAgICBEYXNoYm9hcmRDb21wb25lbnQsXG4gICAgT3ZlcmZsb3dBY3RpdmVQaXBlLFxuICAgIFdpZGdldFJlbmRlcmVyQ29tcG9uZW50LFxuICAgIEN1c3RvbUdyaWRDb21wb25lbnQsXG4gICAgR3JpZENlbGxEaXJlY3RpdmUsXG4gIF0sXG4gIGltcG9ydHM6IFtcbiAgICBDb21tb25Nb2R1bGUsXG4gIF0sXG4gIHByb3ZpZGVyczogW1xuICAgIERhc2hib2FyZFN0YXRlU2VydmljZSxcbiAgICB7IHByb3ZpZGU6IERBU0hCT0FSRF9QRVJTSVNURU5DRSwgdXNlRXhpc3Rpbmc6IExvY2FsU3RvcmFnZURhc2hib2FyZFBlcnNpc3RlbmNlIH0sXG4gIF0sXG4gIGV4cG9ydHM6IFtcbiAgICBEYXNoYm9hcmRDb21wb25lbnQsXG4gICAgT3ZlcmZsb3dBY3RpdmVQaXBlLFxuICAgIFdpZGdldFJlbmRlcmVyQ29tcG9uZW50LFxuICAgIEN1c3RvbUdyaWRDb21wb25lbnQsXG4gICAgR3JpZENlbGxEaXJlY3RpdmUsXG4gIF0sXG59KVxuZXhwb3J0IGNsYXNzIERhc2hib2FyZE1vZHVsZSB7fVxuXG4vKipcbiAqIFJvb3QgbW9kdWxlIGZvciBsb2NhbCBkZW1vIC8gdGVzdGluZyBwdXJwb3NlcyBvbmx5LlxuICogTGlicmFyeSBjb25zdW1lcnMgaW1wb3J0IERhc2hib2FyZE1vZHVsZSwgbm90IEFwcE1vZHVsZS5cbiAqL1xuQE5nTW9kdWxlKHtcbiAgaW1wb3J0czogW0Jyb3dzZXJNb2R1bGUsIERhc2hib2FyZE1vZHVsZV0sXG4gIGJvb3RzdHJhcDogW0Rhc2hib2FyZENvbXBvbmVudF0sXG59KVxuZXhwb3J0IGNsYXNzIEFwcE1vZHVsZSB7fVxuIl19
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Inject, Injectable, InjectionToken, Optional } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export const DASHBOARD_PERSISTENCE = new InjectionToken('DASHBOARD_PERSISTENCE');
|
|
4
|
+
export const DASHBOARD_PERSISTENCE_CONTEXT = new InjectionToken('DASHBOARD_PERSISTENCE_CONTEXT');
|
|
5
|
+
export class DashboardPersistence {
|
|
6
|
+
}
|
|
7
|
+
export class LocalStorageDashboardPersistence {
|
|
8
|
+
async loadShared(dashboardId) {
|
|
9
|
+
const raw = localStorage.getItem(this._sharedKey(dashboardId));
|
|
10
|
+
if (!raw)
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
console.error('[Dashboard] bad shared state', e);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async saveShared(dashboardId, shared) {
|
|
21
|
+
localStorage.setItem(this._sharedKey(dashboardId), JSON.stringify(shared));
|
|
22
|
+
}
|
|
23
|
+
async loadPositions(dashboardId, windowId) {
|
|
24
|
+
const raw = localStorage.getItem(this._positionsKey(dashboardId, windowId));
|
|
25
|
+
if (!raw)
|
|
26
|
+
return null;
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(raw);
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
console.error('[Dashboard] bad positions state', e);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async savePositions(dashboardId, windowId, positions) {
|
|
36
|
+
localStorage.setItem(this._positionsKey(dashboardId, windowId), JSON.stringify(positions));
|
|
37
|
+
}
|
|
38
|
+
_sharedKey(dashboardId) {
|
|
39
|
+
// Keep legacy keys for default dashboard id to avoid breaking existing users.
|
|
40
|
+
if (dashboardId === 'default')
|
|
41
|
+
return 'ogidor_shared';
|
|
42
|
+
return `ogidor_shared_${dashboardId}`;
|
|
43
|
+
}
|
|
44
|
+
_positionsKey(dashboardId, windowId) {
|
|
45
|
+
if (dashboardId === 'default')
|
|
46
|
+
return `ogidor_positions_${windowId}`;
|
|
47
|
+
return `ogidor_positions_${dashboardId}_${windowId}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
LocalStorageDashboardPersistence.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LocalStorageDashboardPersistence, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
51
|
+
LocalStorageDashboardPersistence.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LocalStorageDashboardPersistence, providedIn: 'root' });
|
|
52
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LocalStorageDashboardPersistence, decorators: [{
|
|
53
|
+
type: Injectable,
|
|
54
|
+
args: [{ providedIn: 'root' }]
|
|
55
|
+
}] });
|
|
56
|
+
export function resolveDefaultDashboardId() {
|
|
57
|
+
if (typeof window === 'undefined')
|
|
58
|
+
return 'default';
|
|
59
|
+
const path = window.location.pathname?.trim();
|
|
60
|
+
return path ? path : 'default';
|
|
61
|
+
}
|
|
62
|
+
export class DashboardPersistenceFacade {
|
|
63
|
+
constructor(context) {
|
|
64
|
+
this.dashboardId = context?.dashboardId?.trim() || resolveDefaultDashboardId();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
DashboardPersistenceFacade.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardPersistenceFacade, deps: [{ token: DASHBOARD_PERSISTENCE_CONTEXT, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
68
|
+
DashboardPersistenceFacade.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardPersistenceFacade, providedIn: 'root' });
|
|
69
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardPersistenceFacade, decorators: [{
|
|
70
|
+
type: Injectable,
|
|
71
|
+
args: [{ providedIn: 'root' }]
|
|
72
|
+
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
|
|
73
|
+
type: Optional
|
|
74
|
+
}, {
|
|
75
|
+
type: Inject,
|
|
76
|
+
args: [DASHBOARD_PERSISTENCE_CONTEXT]
|
|
77
|
+
}] }]; } });
|
|
78
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGFzaGJvYXJkLXBlcnNpc3RlbmNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2FwcC9kYXNoYm9hcmQtcGVyc2lzdGVuY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsY0FBYyxFQUFFLFFBQVEsRUFBRSxNQUFNLGVBQWUsQ0FBQzs7QUFTN0UsTUFBTSxDQUFDLE1BQU0scUJBQXFCLEdBQUcsSUFBSSxjQUFjLENBQXVCLHVCQUF1QixDQUFDLENBQUM7QUFDdkcsTUFBTSxDQUFDLE1BQU0sNkJBQTZCLEdBQUcsSUFBSSxjQUFjLENBQThCLCtCQUErQixDQUFDLENBQUM7QUFFOUgsTUFBTSxPQUFnQixvQkFBb0I7Q0FNekM7QUFHRCxNQUFNLE9BQU8sZ0NBQWdDO0lBQzNDLEtBQUssQ0FBQyxVQUFVLENBQUMsV0FBbUI7UUFDbEMsTUFBTSxHQUFHLEdBQUcsWUFBWSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUM7UUFDL0QsSUFBSSxDQUFDLEdBQUc7WUFBRSxPQUFPLElBQUksQ0FBQztRQUN0QixJQUFJO1lBQ0YsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBb0IsQ0FBQztTQUMzQztRQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ1YsT0FBTyxDQUFDLEtBQUssQ0FBQyw4QkFBOEIsRUFBRSxDQUFDLENBQUMsQ0FBQztZQUNqRCxPQUFPLElBQUksQ0FBQztTQUNiO0lBQ0gsQ0FBQztJQUVELEtBQUssQ0FBQyxVQUFVLENBQUMsV0FBbUIsRUFBRSxNQUF1QjtRQUMzRCxZQUFZLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsV0FBVyxDQUFDLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQzdFLENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUFDLFdBQW1CLEVBQUUsUUFBZ0I7UUFDdkQsTUFBTSxHQUFHLEdBQUcsWUFBWSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLFdBQVcsRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDO1FBQzVFLElBQUksQ0FBQyxHQUFHO1lBQUUsT0FBTyxJQUFJLENBQUM7UUFDdEIsSUFBSTtZQUNGLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQWdCLENBQUM7U0FDdkM7UUFBQyxPQUFPLENBQUMsRUFBRTtZQUNWLE9BQU8sQ0FBQyxLQUFLLENBQUMsaUNBQWlDLEVBQUUsQ0FBQyxDQUFDLENBQUM7WUFDcEQsT0FBTyxJQUFJLENBQUM7U0FDYjtJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUFDLFdBQW1CLEVBQUUsUUFBZ0IsRUFBRSxTQUFzQjtRQUMvRSxZQUFZLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsV0FBVyxFQUFFLFFBQVEsQ0FBQyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztJQUM3RixDQUFDO0lBRU8sVUFBVSxDQUFDLFdBQW1CO1FBQ3BDLDhFQUE4RTtRQUM5RSxJQUFJLFdBQVcsS0FBSyxTQUFTO1lBQUUsT0FBTyxlQUFlLENBQUM7UUFDdEQsT0FBTyxpQkFBaUIsV0FBVyxFQUFFLENBQUM7SUFDeEMsQ0FBQztJQUVPLGFBQWEsQ0FBQyxXQUFtQixFQUFFLFFBQWdCO1FBQ3pELElBQUksV0FBVyxLQUFLLFNBQVM7WUFBRSxPQUFPLG9CQUFvQixRQUFRLEVBQUUsQ0FBQztRQUNyRSxPQUFPLG9CQUFvQixXQUFXLElBQUksUUFBUSxFQUFFLENBQUM7SUFDdkQsQ0FBQzs7OEhBeENVLGdDQUFnQztrSUFBaEMsZ0NBQWdDLGNBRG5CLE1BQU07NEZBQ25CLGdDQUFnQztrQkFENUMsVUFBVTttQkFBQyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUU7O0FBNENsQyxNQUFNLFVBQVUseUJBQXlCO0lBQ3ZDLElBQUksT0FBTyxNQUFNLEtBQUssV0FBVztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQ3BELE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxRQUFRLENBQUMsUUFBUSxFQUFFLElBQUksRUFBRSxDQUFDO0lBQzlDLE9BQU8sSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztBQUNqQyxDQUFDO0FBR0QsTUFBTSxPQUFPLDBCQUEwQjtJQUdyQyxZQUNxRCxPQUEyQztRQUU5RixJQUFJLENBQUMsV0FBVyxHQUFHLE9BQU8sRUFBRSxXQUFXLEVBQUUsSUFBSSxFQUFFLElBQUkseUJBQXlCLEVBQUUsQ0FBQztJQUNqRixDQUFDOzt3SEFQVSwwQkFBMEIsa0JBSWYsNkJBQTZCOzRIQUp4QywwQkFBMEIsY0FEYixNQUFNOzRGQUNuQiwwQkFBMEI7a0JBRHRDLFVBQVU7bUJBQUMsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFOzswQkFLN0IsUUFBUTs7MEJBQUksTUFBTTsyQkFBQyw2QkFBNkIiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3QsIEluamVjdGFibGUsIEluamVjdGlvblRva2VuLCBPcHRpb25hbCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgRGFzaGJvYXJkQ29uZmlnIH0gZnJvbSAnLi9tb2RlbHMnO1xuXG5leHBvcnQgdHlwZSBQb3NpdGlvbk1hcCA9IFJlY29yZDxzdHJpbmcsIHsgeDogbnVtYmVyOyB5OiBudW1iZXI7IGNvbHM6IG51bWJlcjsgcm93czogbnVtYmVyIH0+O1xuXG5leHBvcnQgaW50ZXJmYWNlIERhc2hib2FyZFBlcnNpc3RlbmNlQ29udGV4dCB7XG4gIGRhc2hib2FyZElkOiBzdHJpbmc7XG59XG5cbmV4cG9ydCBjb25zdCBEQVNIQk9BUkRfUEVSU0lTVEVOQ0UgPSBuZXcgSW5qZWN0aW9uVG9rZW48RGFzaGJvYXJkUGVyc2lzdGVuY2U+KCdEQVNIQk9BUkRfUEVSU0lTVEVOQ0UnKTtcbmV4cG9ydCBjb25zdCBEQVNIQk9BUkRfUEVSU0lTVEVOQ0VfQ09OVEVYVCA9IG5ldyBJbmplY3Rpb25Ub2tlbjxEYXNoYm9hcmRQZXJzaXN0ZW5jZUNvbnRleHQ+KCdEQVNIQk9BUkRfUEVSU0lTVEVOQ0VfQ09OVEVYVCcpO1xuXG5leHBvcnQgYWJzdHJhY3QgY2xhc3MgRGFzaGJvYXJkUGVyc2lzdGVuY2Uge1xuICBhYnN0cmFjdCBsb2FkU2hhcmVkKGRhc2hib2FyZElkOiBzdHJpbmcpOiBQcm9taXNlPERhc2hib2FyZENvbmZpZyB8IG51bGw+O1xuICBhYnN0cmFjdCBzYXZlU2hhcmVkKGRhc2hib2FyZElkOiBzdHJpbmcsIHNoYXJlZDogRGFzaGJvYXJkQ29uZmlnKTogUHJvbWlzZTx2b2lkPjtcblxuICBhYnN0cmFjdCBsb2FkUG9zaXRpb25zKGRhc2hib2FyZElkOiBzdHJpbmcsIHdpbmRvd0lkOiBzdHJpbmcpOiBQcm9taXNlPFBvc2l0aW9uTWFwIHwgbnVsbD47XG4gIGFic3RyYWN0IHNhdmVQb3NpdGlvbnMoZGFzaGJvYXJkSWQ6IHN0cmluZywgd2luZG93SWQ6IHN0cmluZywgcG9zaXRpb25zOiBQb3NpdGlvbk1hcCk6IFByb21pc2U8dm9pZD47XG59XG5cbkBJbmplY3RhYmxlKHsgcHJvdmlkZWRJbjogJ3Jvb3QnIH0pXG5leHBvcnQgY2xhc3MgTG9jYWxTdG9yYWdlRGFzaGJvYXJkUGVyc2lzdGVuY2UgaW1wbGVtZW50cyBEYXNoYm9hcmRQZXJzaXN0ZW5jZSB7XG4gIGFzeW5jIGxvYWRTaGFyZWQoZGFzaGJvYXJkSWQ6IHN0cmluZyk6IFByb21pc2U8RGFzaGJvYXJkQ29uZmlnIHwgbnVsbD4ge1xuICAgIGNvbnN0IHJhdyA9IGxvY2FsU3RvcmFnZS5nZXRJdGVtKHRoaXMuX3NoYXJlZEtleShkYXNoYm9hcmRJZCkpO1xuICAgIGlmICghcmF3KSByZXR1cm4gbnVsbDtcbiAgICB0cnkge1xuICAgICAgcmV0dXJuIEpTT04ucGFyc2UocmF3KSBhcyBEYXNoYm9hcmRDb25maWc7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgY29uc29sZS5lcnJvcignW0Rhc2hib2FyZF0gYmFkIHNoYXJlZCBzdGF0ZScsIGUpO1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICB9XG5cbiAgYXN5bmMgc2F2ZVNoYXJlZChkYXNoYm9hcmRJZDogc3RyaW5nLCBzaGFyZWQ6IERhc2hib2FyZENvbmZpZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGxvY2FsU3RvcmFnZS5zZXRJdGVtKHRoaXMuX3NoYXJlZEtleShkYXNoYm9hcmRJZCksIEpTT04uc3RyaW5naWZ5KHNoYXJlZCkpO1xuICB9XG5cbiAgYXN5bmMgbG9hZFBvc2l0aW9ucyhkYXNoYm9hcmRJZDogc3RyaW5nLCB3aW5kb3dJZDogc3RyaW5nKTogUHJvbWlzZTxQb3NpdGlvbk1hcCB8IG51bGw+IHtcbiAgICBjb25zdCByYXcgPSBsb2NhbFN0b3JhZ2UuZ2V0SXRlbSh0aGlzLl9wb3NpdGlvbnNLZXkoZGFzaGJvYXJkSWQsIHdpbmRvd0lkKSk7XG4gICAgaWYgKCFyYXcpIHJldHVybiBudWxsO1xuICAgIHRyeSB7XG4gICAgICByZXR1cm4gSlNPTi5wYXJzZShyYXcpIGFzIFBvc2l0aW9uTWFwO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoJ1tEYXNoYm9hcmRdIGJhZCBwb3NpdGlvbnMgc3RhdGUnLCBlKTtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgfVxuXG4gIGFzeW5jIHNhdmVQb3NpdGlvbnMoZGFzaGJvYXJkSWQ6IHN0cmluZywgd2luZG93SWQ6IHN0cmluZywgcG9zaXRpb25zOiBQb3NpdGlvbk1hcCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGxvY2FsU3RvcmFnZS5zZXRJdGVtKHRoaXMuX3Bvc2l0aW9uc0tleShkYXNoYm9hcmRJZCwgd2luZG93SWQpLCBKU09OLnN0cmluZ2lmeShwb3NpdGlvbnMpKTtcbiAgfVxuXG4gIHByaXZhdGUgX3NoYXJlZEtleShkYXNoYm9hcmRJZDogc3RyaW5nKTogc3RyaW5nIHtcbiAgICAvLyBLZWVwIGxlZ2FjeSBrZXlzIGZvciBkZWZhdWx0IGRhc2hib2FyZCBpZCB0byBhdm9pZCBicmVha2luZyBleGlzdGluZyB1c2Vycy5cbiAgICBpZiAoZGFzaGJvYXJkSWQgPT09ICdkZWZhdWx0JykgcmV0dXJuICdvZ2lkb3Jfc2hhcmVkJztcbiAgICByZXR1cm4gYG9naWRvcl9zaGFyZWRfJHtkYXNoYm9hcmRJZH1gO1xuICB9XG5cbiAgcHJpdmF0ZSBfcG9zaXRpb25zS2V5KGRhc2hib2FyZElkOiBzdHJpbmcsIHdpbmRvd0lkOiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIGlmIChkYXNoYm9hcmRJZCA9PT0gJ2RlZmF1bHQnKSByZXR1cm4gYG9naWRvcl9wb3NpdGlvbnNfJHt3aW5kb3dJZH1gO1xuICAgIHJldHVybiBgb2dpZG9yX3Bvc2l0aW9uc18ke2Rhc2hib2FyZElkfV8ke3dpbmRvd0lkfWA7XG4gIH1cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVEZWZhdWx0RGFzaGJvYXJkSWQoKTogc3RyaW5nIHtcbiAgaWYgKHR5cGVvZiB3aW5kb3cgPT09ICd1bmRlZmluZWQnKSByZXR1cm4gJ2RlZmF1bHQnO1xuICBjb25zdCBwYXRoID0gd2luZG93LmxvY2F0aW9uLnBhdGhuYW1lPy50cmltKCk7XG4gIHJldHVybiBwYXRoID8gcGF0aCA6ICdkZWZhdWx0Jztcbn1cblxuQEluamVjdGFibGUoeyBwcm92aWRlZEluOiAncm9vdCcgfSlcbmV4cG9ydCBjbGFzcyBEYXNoYm9hcmRQZXJzaXN0ZW5jZUZhY2FkZSB7XG4gIHJlYWRvbmx5IGRhc2hib2FyZElkOiBzdHJpbmc7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgQE9wdGlvbmFsKCkgQEluamVjdChEQVNIQk9BUkRfUEVSU0lTVEVOQ0VfQ09OVEVYVCkgY29udGV4dDogRGFzaGJvYXJkUGVyc2lzdGVuY2VDb250ZXh0IHwgbnVsbFxuICApIHtcbiAgICB0aGlzLmRhc2hib2FyZElkID0gY29udGV4dD8uZGFzaGJvYXJkSWQ/LnRyaW0oKSB8fCByZXNvbHZlRGVmYXVsdERhc2hib2FyZElkKCk7XG4gIH1cbn1cblxuIl19
|