@ogidor/dashboard 1.0.16 → 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.
@@ -1,9 +1,10 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Directive, EventEmitter, TemplateRef, Component, ChangeDetectionStrategy, Input, Output, ViewChild, ContentChild, Injectable, Pipe, HostListener, NgModule } from '@angular/core';
2
+ import { Directive, EventEmitter, TemplateRef, Component, ChangeDetectionStrategy, Input, Output, ViewChild, ContentChild, InjectionToken, Injectable, Optional, Inject, Pipe, HostListener, NgModule } from '@angular/core';
3
3
  import { BrowserModule } from '@angular/platform-browser';
4
4
  import * as i1 from '@angular/common';
5
5
  import { CommonModule } from '@angular/common';
6
6
  import { BehaviorSubject, Subscription } from 'rxjs';
7
+ import { __awaiter } from 'tslib';
7
8
 
8
9
  /**
9
10
  * Directive applied to the template that should be stamped inside each grid cell.
@@ -144,15 +145,23 @@ class CustomGridComponent {
144
145
  this.boundMouseUp = this.onMouseUp.bind(this);
145
146
  }
146
147
  ngOnInit() {
147
- this.compactAndApply();
148
+ this.applyCompactionAndSync();
148
149
  this.updateContainerHeight();
149
150
  this.zone.runOutsideAngular(() => {
150
151
  window.addEventListener('mousemove', this.boundMouseMove);
151
152
  window.addEventListener('mouseup', this.boundMouseUp);
152
153
  });
153
154
  }
154
- ngOnChanges(_changes) {
155
+ ngOnChanges(changes) {
156
+ if (this.dragging || this.resizing) {
157
+ this.updateContainerHeight();
158
+ return;
159
+ }
160
+ if (changes['widgets'] || changes['columns']) {
161
+ this.applyCompactionAndSync();
162
+ }
155
163
  this.updateContainerHeight();
164
+ this.cdr.markForCheck();
156
165
  }
157
166
  ngOnDestroy() {
158
167
  window.removeEventListener('mousemove', this.boundMouseMove);
@@ -377,15 +386,32 @@ class CustomGridComponent {
377
386
  compactAndApply() {
378
387
  var _a;
379
388
  if (!((_a = this.widgets) === null || _a === void 0 ? void 0 : _a.length))
380
- return;
389
+ return [];
381
390
  const compacted = GridEngine.compact(this.widgets.map(w => ({ id: w.id, x: w.x, y: w.y, cols: w.cols, rows: w.rows })), this.columns);
391
+ const changedWidgets = [];
382
392
  for (const rw of compacted) {
383
393
  const w = this.widgets.find(ww => ww.id === rw.id);
384
- if (w) {
394
+ if (!w)
395
+ continue;
396
+ const changed = w.x !== rw.x || w.y !== rw.y || w.cols !== rw.cols || w.rows !== rw.rows;
397
+ if (changed) {
385
398
  w.x = rw.x;
386
399
  w.y = rw.y;
400
+ w.cols = rw.cols;
401
+ w.rows = rw.rows;
402
+ changedWidgets.push(w);
387
403
  }
388
404
  }
405
+ return changedWidgets;
406
+ }
407
+ applyCompactionAndSync() {
408
+ const changedWidgets = this.compactAndApply();
409
+ if (!changedWidgets.length)
410
+ return;
411
+ for (const widget of changedWidgets) {
412
+ this.itemChanged.emit(widget);
413
+ }
414
+ this.layoutChanged.emit(this.widgets);
389
415
  }
390
416
  updateContainerHeight() {
391
417
  var _a;
@@ -518,14 +544,101 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
518
544
  args: [GridCellDirective, { read: TemplateRef }]
519
545
  }] } });
520
546
 
547
+ const DASHBOARD_PERSISTENCE = new InjectionToken('DASHBOARD_PERSISTENCE');
548
+ const DASHBOARD_PERSISTENCE_CONTEXT = new InjectionToken('DASHBOARD_PERSISTENCE_CONTEXT');
549
+ class DashboardPersistence {
550
+ }
551
+ class LocalStorageDashboardPersistence {
552
+ loadShared(dashboardId) {
553
+ return __awaiter(this, void 0, void 0, function* () {
554
+ const raw = localStorage.getItem(this._sharedKey(dashboardId));
555
+ if (!raw)
556
+ return null;
557
+ try {
558
+ return JSON.parse(raw);
559
+ }
560
+ catch (e) {
561
+ console.error('[Dashboard] bad shared state', e);
562
+ return null;
563
+ }
564
+ });
565
+ }
566
+ saveShared(dashboardId, shared) {
567
+ return __awaiter(this, void 0, void 0, function* () {
568
+ localStorage.setItem(this._sharedKey(dashboardId), JSON.stringify(shared));
569
+ });
570
+ }
571
+ loadPositions(dashboardId, windowId) {
572
+ return __awaiter(this, void 0, void 0, function* () {
573
+ const raw = localStorage.getItem(this._positionsKey(dashboardId, windowId));
574
+ if (!raw)
575
+ return null;
576
+ try {
577
+ return JSON.parse(raw);
578
+ }
579
+ catch (e) {
580
+ console.error('[Dashboard] bad positions state', e);
581
+ return null;
582
+ }
583
+ });
584
+ }
585
+ savePositions(dashboardId, windowId, positions) {
586
+ return __awaiter(this, void 0, void 0, function* () {
587
+ localStorage.setItem(this._positionsKey(dashboardId, windowId), JSON.stringify(positions));
588
+ });
589
+ }
590
+ _sharedKey(dashboardId) {
591
+ // Keep legacy keys for default dashboard id to avoid breaking existing users.
592
+ if (dashboardId === 'default')
593
+ return 'ogidor_shared';
594
+ return `ogidor_shared_${dashboardId}`;
595
+ }
596
+ _positionsKey(dashboardId, windowId) {
597
+ if (dashboardId === 'default')
598
+ return `ogidor_positions_${windowId}`;
599
+ return `ogidor_positions_${dashboardId}_${windowId}`;
600
+ }
601
+ }
602
+ LocalStorageDashboardPersistence.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LocalStorageDashboardPersistence, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
603
+ LocalStorageDashboardPersistence.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LocalStorageDashboardPersistence, providedIn: 'root' });
604
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LocalStorageDashboardPersistence, decorators: [{
605
+ type: Injectable,
606
+ args: [{ providedIn: 'root' }]
607
+ }] });
608
+ function resolveDefaultDashboardId() {
609
+ var _a;
610
+ if (typeof window === 'undefined')
611
+ return 'default';
612
+ const path = (_a = window.location.pathname) === null || _a === void 0 ? void 0 : _a.trim();
613
+ return path ? path : 'default';
614
+ }
615
+ class DashboardPersistenceFacade {
616
+ constructor(context) {
617
+ var _a;
618
+ this.dashboardId = ((_a = context === null || context === void 0 ? void 0 : context.dashboardId) === null || _a === void 0 ? void 0 : _a.trim()) || resolveDefaultDashboardId();
619
+ }
620
+ }
621
+ 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 });
622
+ DashboardPersistenceFacade.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardPersistenceFacade, providedIn: 'root' });
623
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardPersistenceFacade, decorators: [{
624
+ type: Injectable,
625
+ args: [{ providedIn: 'root' }]
626
+ }], ctorParameters: function () {
627
+ return [{ type: undefined, decorators: [{
628
+ type: Optional
629
+ }, {
630
+ type: Inject,
631
+ args: [DASHBOARD_PERSISTENCE_CONTEXT]
632
+ }] }];
633
+ } });
634
+
521
635
  class DashboardStateService {
522
- constructor(zone) {
636
+ constructor(zone, persistenceFacade, localPersistence, persistence) {
637
+ var _a;
523
638
  this.zone = zone;
524
- /**
525
- * Shared key — stores page list + widget metadata (no positions).
526
- * Read and written by every window.
527
- */
528
- this.SHARED_KEY = 'ogidor_shared';
639
+ this.persistenceFacade = persistenceFacade;
640
+ this.localPersistence = localPersistence;
641
+ this.persistence = persistence;
529
642
  this.CHANNEL_NAME = 'ogidor_dashboard_sync';
530
643
  this.channel = typeof BroadcastChannel !== 'undefined'
531
644
  ? new BroadcastChannel(this.CHANNEL_NAME) : null;
@@ -536,23 +649,12 @@ class DashboardStateService {
536
649
  this.activePageIdSubject = new BehaviorSubject(this.initialPages[0].id);
537
650
  this.pages$ = this.pagesSubject.asObservable();
538
651
  this.activePageId$ = this.activePageIdSubject.asObservable();
539
- // Determine whether we are a pop-out window
540
652
  const hash = typeof window !== 'undefined'
541
653
  ? window.location.hash.replace('#', '').trim() : '';
542
- this.positionsKey = hash ? `ogidor_positions_${hash}` : 'ogidor_positions_main';
543
- // 1. Load the shared structural state
544
- const shared = localStorage.getItem(this.SHARED_KEY);
545
- if (shared) {
546
- try {
547
- this._applyShared(JSON.parse(shared), false);
548
- }
549
- catch (e) {
550
- console.error('[Dashboard] bad shared state', e);
551
- }
552
- }
553
- // 2. Overlay this window's saved positions on top
554
- this._restorePositions();
555
- // 3. Listen for structural changes broadcast by other windows
654
+ this.windowId = hash || 'main';
655
+ this.dashboardId = this.persistenceFacade.dashboardId;
656
+ this.persistence = (_a = this.persistence) !== null && _a !== void 0 ? _a : this.localPersistence;
657
+ void this._bootstrapFromPersistence();
556
658
  if (this.channel) {
557
659
  this.channel.onmessage = (ev) => {
558
660
  this.zone.run(() => this._onSyncEvent(ev.data));
@@ -560,7 +662,6 @@ class DashboardStateService {
560
662
  }
561
663
  }
562
664
  ngOnDestroy() { var _a; (_a = this.channel) === null || _a === void 0 ? void 0 : _a.close(); }
563
- // ── Page actions ──────────────────────────────────────────────
564
665
  getActivePage() {
565
666
  return this.pagesSubject.value.find(p => p.id === this.activePageIdSubject.value);
566
667
  }
@@ -584,7 +685,6 @@ class DashboardStateService {
584
685
  this._saveShared();
585
686
  this._broadcast({ type: 'PAGE_REMOVED', pageId: id });
586
687
  }
587
- // ── Widget structural actions (synced to all windows) ─────────
588
688
  addWidget(widget) {
589
689
  const activePage = this.getActivePage();
590
690
  if (!activePage)
@@ -593,7 +693,7 @@ class DashboardStateService {
593
693
  activePage.widgets = [...activePage.widgets, newWidget];
594
694
  this.pagesSubject.next([...this.pagesSubject.value]);
595
695
  this._saveShared();
596
- this._savePositions(); // register default position locally
696
+ this._savePositions();
597
697
  this._broadcast({ type: 'WIDGET_ADDED', pageId: activePage.id, widget: newWidget });
598
698
  return newWidget;
599
699
  }
@@ -621,11 +721,6 @@ class DashboardStateService {
621
721
  this._savePositions();
622
722
  this._broadcast({ type: 'WIDGET_REMOVED', pageId, widgetId });
623
723
  }
624
- // ── Position actions (local to THIS window only) ──────────────
625
- /**
626
- * Update a widget's grid position/size.
627
- * Saved only for this window — never broadcast to others.
628
- */
629
724
  updateWidgetPosition(pageId, widgetId, x, y, cols, rows) {
630
725
  var _a;
631
726
  const pages = this.pagesSubject.value;
@@ -636,10 +731,9 @@ class DashboardStateService {
636
731
  widget.cols = cols;
637
732
  widget.rows = rows;
638
733
  this.pagesSubject.next([...pages]);
639
- this._savePositions(); // local only — intentionally no _saveShared / no broadcast
734
+ this._savePositions();
640
735
  }
641
736
  }
642
- // ── Serialization ─────────────────────────────────────────────
643
737
  serializeLayout() {
644
738
  return JSON.stringify({
645
739
  pages: this.pagesSubject.value,
@@ -649,7 +743,7 @@ class DashboardStateService {
649
743
  loadLayout(config) {
650
744
  if (!(config === null || config === void 0 ? void 0 : config.pages))
651
745
  return;
652
- this._applyShared(config, true);
746
+ this._applyShared(config);
653
747
  this._saveShared();
654
748
  this._savePositions();
655
749
  }
@@ -657,79 +751,49 @@ class DashboardStateService {
657
751
  const url = `${window.location.origin}${window.location.pathname}#${pageId}`;
658
752
  window.open(url, `workspace_${pageId}`, 'width=1280,height=800,menubar=no,toolbar=no,location=no,status=no');
659
753
  }
660
- // ── Private helpers ───────────────────────────────────────────
661
- /**
662
- * Save page list + widget metadata (titles, cardColor, data) — no positions.
663
- */
664
754
  _saveShared() {
665
- const stripped = this.pagesSubject.value.map(p => (Object.assign(Object.assign({}, p), { widgets: p.widgets.map(({ id, title, cardColor, data, cols, rows }) =>
666
- // Keep default cols/rows so new windows get a sensible first-open layout.
667
- // x/y are intentionally omitted from shared state.
668
- ({ id, title, cardColor, data, x: 0, y: 0, cols, rows })) })));
669
- localStorage.setItem(this.SHARED_KEY, JSON.stringify({
755
+ var _a;
756
+ const stripped = this.pagesSubject.value.map(p => (Object.assign(Object.assign({}, p), { widgets: p.widgets.map(({ id, title, cardColor, data, cols, rows }) => ({ id, title, cardColor, data, x: 0, y: 0, cols, rows })) })));
757
+ const shared = {
670
758
  pages: stripped,
671
759
  activePageId: this.activePageIdSubject.value,
672
- }));
760
+ };
761
+ void ((_a = this.persistence) === null || _a === void 0 ? void 0 : _a.saveShared(this.dashboardId, shared).catch(e => console.error('[Dashboard] failed to persist shared state', e)));
673
762
  }
674
- /**
675
- * Save this window's grid positions (x, y, cols, rows) per widget.
676
- */
677
763
  _savePositions() {
764
+ var _a;
678
765
  const positions = {};
679
766
  for (const page of this.pagesSubject.value) {
680
767
  for (const w of page.widgets) {
681
768
  positions[`${page.id}:${w.id}`] = { x: w.x, y: w.y, cols: w.cols, rows: w.rows };
682
769
  }
683
770
  }
684
- localStorage.setItem(this.positionsKey, JSON.stringify(positions));
771
+ void ((_a = this.persistence) === null || _a === void 0 ? void 0 : _a.savePositions(this.dashboardId, this.windowId, positions).catch(e => console.error('[Dashboard] failed to persist window positions', e)));
685
772
  }
686
- /**
687
- * Overlay the positions saved for THIS window on top of the current pages.
688
- */
689
- _restorePositions() {
690
- const raw = localStorage.getItem(this.positionsKey);
691
- if (!raw)
692
- return;
693
- try {
694
- const positions = JSON.parse(raw);
695
- const pages = this.pagesSubject.value;
696
- for (const page of pages) {
697
- for (const w of page.widgets) {
698
- const pos = positions[`${page.id}:${w.id}`];
699
- if (pos) {
700
- w.x = pos.x;
701
- w.y = pos.y;
702
- w.cols = pos.cols;
703
- w.rows = pos.rows;
704
- }
773
+ _applyPositions(positions) {
774
+ const pages = this.pagesSubject.value;
775
+ for (const page of pages) {
776
+ for (const w of page.widgets) {
777
+ const pos = positions[`${page.id}:${w.id}`];
778
+ if (pos) {
779
+ w.x = pos.x;
780
+ w.y = pos.y;
781
+ w.cols = pos.cols;
782
+ w.rows = pos.rows;
705
783
  }
706
784
  }
707
- this.pagesSubject.next([...pages]);
708
- }
709
- catch (e) {
710
- console.error('[Dashboard] bad positions state', e);
711
785
  }
786
+ this.pagesSubject.next([...pages]);
712
787
  }
713
- /**
714
- * Apply a shared config object (page list + metadata) without touching
715
- * this window's saved positions.
716
- */
717
- _applyShared(config, overwritePositions) {
788
+ _applyShared(config) {
718
789
  if (!(config === null || config === void 0 ? void 0 : config.pages))
719
790
  return;
720
791
  this.pagesSubject.next(config.pages);
721
792
  if (config.activePageId)
722
793
  this.activePageIdSubject.next(config.activePageId);
723
- if (!overwritePositions)
724
- this._restorePositions();
725
794
  }
726
- /**
727
- * Handle a structural sync event arriving from another window.
728
- * Position changes are never sent so we never receive them here.
729
- */
730
795
  _onSyncEvent(event) {
731
796
  var _a, _b;
732
- // Work on a shallow clone of pages so Angular detects the change
733
797
  const pages = this.pagesSubject.value.map(p => (Object.assign(Object.assign({}, p), { widgets: [...p.widgets] })));
734
798
  switch (event.type) {
735
799
  case 'PAGE_ADDED':
@@ -756,8 +820,6 @@ class DashboardStateService {
756
820
  page.widgets = [...page.widgets, Object.assign({}, event.widget)];
757
821
  this.pagesSubject.next(pages);
758
822
  this._saveShared();
759
- // Don't touch positions — this window will keep its own layout for
760
- // existing widgets; the new one starts at its default position.
761
823
  }
762
824
  break;
763
825
  }
@@ -789,13 +851,36 @@ class DashboardStateService {
789
851
  var _a;
790
852
  (_a = this.channel) === null || _a === void 0 ? void 0 : _a.postMessage(event);
791
853
  }
854
+ _bootstrapFromPersistence() {
855
+ var _a, _b;
856
+ return __awaiter(this, void 0, void 0, function* () {
857
+ try {
858
+ const shared = yield ((_a = this.persistence) === null || _a === void 0 ? void 0 : _a.loadShared(this.dashboardId));
859
+ if (shared)
860
+ this.zone.run(() => this._applyShared(shared));
861
+ const positions = yield ((_b = this.persistence) === null || _b === void 0 ? void 0 : _b.loadPositions(this.dashboardId, this.windowId));
862
+ if (positions)
863
+ this.zone.run(() => this._applyPositions(positions));
864
+ }
865
+ catch (e) {
866
+ console.error('[Dashboard] failed to bootstrap persisted state', e);
867
+ }
868
+ });
869
+ }
792
870
  }
793
- DashboardStateService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardStateService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
871
+ DashboardStateService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardStateService, deps: [{ token: i0.NgZone }, { token: DashboardPersistenceFacade }, { token: LocalStorageDashboardPersistence }, { token: DASHBOARD_PERSISTENCE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
794
872
  DashboardStateService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardStateService, providedIn: 'root' });
795
873
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardStateService, decorators: [{
796
874
  type: Injectable,
797
875
  args: [{ providedIn: 'root' }]
798
- }], ctorParameters: function () { return [{ type: i0.NgZone }]; } });
876
+ }], ctorParameters: function () {
877
+ return [{ type: i0.NgZone }, { type: DashboardPersistenceFacade }, { type: LocalStorageDashboardPersistence }, { type: DashboardPersistence, decorators: [{
878
+ type: Optional
879
+ }, {
880
+ type: Inject,
881
+ args: [DASHBOARD_PERSISTENCE]
882
+ }] }];
883
+ } });
799
884
 
800
885
  class WidgetRendererComponent {
801
886
  constructor() {
@@ -1454,7 +1539,10 @@ DashboardModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version
1454
1539
  WidgetRendererComponent,
1455
1540
  CustomGridComponent,
1456
1541
  GridCellDirective] });
1457
- DashboardModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, providers: [DashboardStateService], imports: [CommonModule] });
1542
+ DashboardModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, providers: [
1543
+ DashboardStateService,
1544
+ { provide: DASHBOARD_PERSISTENCE, useExisting: LocalStorageDashboardPersistence },
1545
+ ], imports: [CommonModule] });
1458
1546
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, decorators: [{
1459
1547
  type: NgModule,
1460
1548
  args: [{
@@ -1468,7 +1556,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1468
1556
  imports: [
1469
1557
  CommonModule,
1470
1558
  ],
1471
- providers: [DashboardStateService],
1559
+ providers: [
1560
+ DashboardStateService,
1561
+ { provide: DASHBOARD_PERSISTENCE, useExisting: LocalStorageDashboardPersistence },
1562
+ ],
1472
1563
  exports: [
1473
1564
  DashboardComponent,
1474
1565
  OverflowActivePipe,
@@ -1503,5 +1594,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1503
1594
  * Generated bundle index. Do not edit.
1504
1595
  */
1505
1596
 
1506
- export { AppModule, CustomGridComponent, DashboardComponent, DashboardModule, DashboardStateService, GridCellDirective, OverflowActivePipe, WidgetRendererComponent };
1597
+ export { AppModule, CustomGridComponent, DASHBOARD_PERSISTENCE, DASHBOARD_PERSISTENCE_CONTEXT, DashboardComponent, DashboardModule, DashboardPersistence, DashboardPersistenceFacade, DashboardStateService, GridCellDirective, LocalStorageDashboardPersistence, OverflowActivePipe, WidgetRendererComponent, resolveDefaultDashboardId };
1507
1598
  //# sourceMappingURL=ogidor-dashboard.mjs.map