@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 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 persistent layout, pop-out windows, and live cross-tab
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
- - **Persistent Layout** — state is saved to `localStorage` on every change and restored on reload.
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
- State is saved to `localStorage` automatically on every change.
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. Shared across all windows. |
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
- To save and restore via a backend:
527
+ Provide your own implementation of `DashboardPersistence` and override the `DASHBOARD_PERSISTENCE` token.
517
528
 
518
529
  ```typescript
519
- // Save
520
- const json = this.dash.serializeLayout();
521
- await api.save(json);
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
- // Restore
524
- const json = await api.load();
525
- this.dash.stateService.loadLayout(JSON.parse(json));
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
- * 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;
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
- static ɵfac: i0.ɵɵFactoryDeclaration<DashboardStateService, never>;
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: [DashboardStateService], imports: [CommonModule] });
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: [DashboardStateService],
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLm1vZHVsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9hcHAvYXBwLm1vZHVsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUMxRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFFL0MsT0FBTyxFQUFFLGtCQUFrQixFQUFFLGtCQUFrQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDL0UsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDdEUsT0FBTyxFQUFFLG1CQUFtQixFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDakYsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7O0FBc0JsRSxNQUFNLE9BQU8sZUFBZTs7NkdBQWYsZUFBZTs4R0FBZixlQUFlLGlCQWxCeEIsa0JBQWtCO1FBQ2xCLGtCQUFrQjtRQUNsQix1QkFBdUI7UUFDdkIsbUJBQW1CO1FBQ25CLGlCQUFpQixhQUdqQixZQUFZLGFBSVosa0JBQWtCO1FBQ2xCLGtCQUFrQjtRQUNsQix1QkFBdUI7UUFDdkIsbUJBQW1CO1FBQ25CLGlCQUFpQjs4R0FHUixlQUFlLGFBVGYsQ0FBQyxxQkFBcUIsQ0FBQyxZQUZoQyxZQUFZOzRGQVdILGVBQWU7a0JBcEIzQixRQUFRO21CQUFDO29CQUNSLFlBQVksRUFBRTt3QkFDWixrQkFBa0I7d0JBQ2xCLGtCQUFrQjt3QkFDbEIsdUJBQXVCO3dCQUN2QixtQkFBbUI7d0JBQ25CLGlCQUFpQjtxQkFDbEI7b0JBQ0QsT0FBTyxFQUFFO3dCQUNQLFlBQVk7cUJBQ2I7b0JBQ0QsU0FBUyxFQUFFLENBQUMscUJBQXFCLENBQUM7b0JBQ2xDLE9BQU8sRUFBRTt3QkFDUCxrQkFBa0I7d0JBQ2xCLGtCQUFrQjt3QkFDbEIsdUJBQXVCO3dCQUN2QixtQkFBbUI7d0JBQ25CLGlCQUFpQjtxQkFDbEI7aUJBQ0Y7O0FBR0Q7OztHQUdHO0FBS0gsTUFBTSxPQUFPLFNBQVM7O3VHQUFULFNBQVM7d0dBQVQsU0FBUyxjQUZSLGtCQUFrQixhQURwQixhQUFhLEVBUFosZUFBZTt3R0FVZixTQUFTLFlBSFYsYUFBYSxFQUFFLGVBQWU7NEZBRzdCLFNBQVM7a0JBSnJCLFFBQVE7bUJBQUM7b0JBQ1IsT0FBTyxFQUFFLENBQUMsYUFBYSxFQUFFLGVBQWUsQ0FBQztvQkFDekMsU0FBUyxFQUFFLENBQUMsa0JBQWtCLENBQUM7aUJBQ2hDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgTmdNb2R1bGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEJyb3dzZXJNb2R1bGUgfSBmcm9tICdAYW5ndWxhci9wbGF0Zm9ybS1icm93c2VyJztcbmltcG9ydCB7IENvbW1vbk1vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbic7XG5cbmltcG9ydCB7IERhc2hib2FyZENvbXBvbmVudCwgT3ZlcmZsb3dBY3RpdmVQaXBlIH0gZnJvbSAnLi9kYXNoYm9hcmQuY29tcG9uZW50JztcbmltcG9ydCB7IFdpZGdldFJlbmRlcmVyQ29tcG9uZW50IH0gZnJvbSAnLi93aWRnZXQtcmVuZGVyZXIuY29tcG9uZW50JztcbmltcG9ydCB7IEN1c3RvbUdyaWRDb21wb25lbnQsIEdyaWRDZWxsRGlyZWN0aXZlIH0gZnJvbSAnLi9jdXN0b20tZ3JpZC5jb21wb25lbnQnO1xuaW1wb3J0IHsgRGFzaGJvYXJkU3RhdGVTZXJ2aWNlIH0gZnJvbSAnLi9kYXNoYm9hcmQtc3RhdGUuc2VydmljZSc7XG5cbkBOZ01vZHVsZSh7XG4gIGRlY2xhcmF0aW9uczogW1xuICAgIERhc2hib2FyZENvbXBvbmVudCxcbiAgICBPdmVyZmxvd0FjdGl2ZVBpcGUsXG4gICAgV2lkZ2V0UmVuZGVyZXJDb21wb25lbnQsXG4gICAgQ3VzdG9tR3JpZENvbXBvbmVudCxcbiAgICBHcmlkQ2VsbERpcmVjdGl2ZSxcbiAgXSxcbiAgaW1wb3J0czogW1xuICAgIENvbW1vbk1vZHVsZSxcbiAgXSxcbiAgcHJvdmlkZXJzOiBbRGFzaGJvYXJkU3RhdGVTZXJ2aWNlXSxcbiAgZXhwb3J0czogW1xuICAgIERhc2hib2FyZENvbXBvbmVudCxcbiAgICBPdmVyZmxvd0FjdGl2ZVBpcGUsXG4gICAgV2lkZ2V0UmVuZGVyZXJDb21wb25lbnQsXG4gICAgQ3VzdG9tR3JpZENvbXBvbmVudCxcbiAgICBHcmlkQ2VsbERpcmVjdGl2ZSxcbiAgXSxcbn0pXG5leHBvcnQgY2xhc3MgRGFzaGJvYXJkTW9kdWxlIHt9XG5cbi8qKlxuICogUm9vdCBtb2R1bGUgZm9yIGxvY2FsIGRlbW8gLyB0ZXN0aW5nIHB1cnBvc2VzIG9ubHkuXG4gKiBMaWJyYXJ5IGNvbnN1bWVycyBpbXBvcnQgRGFzaGJvYXJkTW9kdWxlLCBub3QgQXBwTW9kdWxlLlxuICovXG5ATmdNb2R1bGUoe1xuICBpbXBvcnRzOiBbQnJvd3Nlck1vZHVsZSwgRGFzaGJvYXJkTW9kdWxlXSxcbiAgYm9vdHN0cmFwOiBbRGFzaGJvYXJkQ29tcG9uZW50XSxcbn0pXG5leHBvcnQgY2xhc3MgQXBwTW9kdWxlIHt9XG4iXX0=
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