@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,5 +1,5 @@
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';
@@ -144,15 +144,23 @@ class CustomGridComponent {
144
144
  this.boundMouseUp = this.onMouseUp.bind(this);
145
145
  }
146
146
  ngOnInit() {
147
- this.compactAndApply();
147
+ this.applyCompactionAndSync();
148
148
  this.updateContainerHeight();
149
149
  this.zone.runOutsideAngular(() => {
150
150
  window.addEventListener('mousemove', this.boundMouseMove);
151
151
  window.addEventListener('mouseup', this.boundMouseUp);
152
152
  });
153
153
  }
154
- ngOnChanges(_changes) {
154
+ ngOnChanges(changes) {
155
+ if (this.dragging || this.resizing) {
156
+ this.updateContainerHeight();
157
+ return;
158
+ }
159
+ if (changes['widgets'] || changes['columns']) {
160
+ this.applyCompactionAndSync();
161
+ }
155
162
  this.updateContainerHeight();
163
+ this.cdr.markForCheck();
156
164
  }
157
165
  ngOnDestroy() {
158
166
  window.removeEventListener('mousemove', this.boundMouseMove);
@@ -371,15 +379,32 @@ class CustomGridComponent {
371
379
  // ── Utilities ──
372
380
  compactAndApply() {
373
381
  if (!this.widgets?.length)
374
- return;
382
+ return [];
375
383
  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);
384
+ const changedWidgets = [];
376
385
  for (const rw of compacted) {
377
386
  const w = this.widgets.find(ww => ww.id === rw.id);
378
- if (w) {
387
+ if (!w)
388
+ continue;
389
+ const changed = w.x !== rw.x || w.y !== rw.y || w.cols !== rw.cols || w.rows !== rw.rows;
390
+ if (changed) {
379
391
  w.x = rw.x;
380
392
  w.y = rw.y;
393
+ w.cols = rw.cols;
394
+ w.rows = rw.rows;
395
+ changedWidgets.push(w);
381
396
  }
382
397
  }
398
+ return changedWidgets;
399
+ }
400
+ applyCompactionAndSync() {
401
+ const changedWidgets = this.compactAndApply();
402
+ if (!changedWidgets.length)
403
+ return;
404
+ for (const widget of changedWidgets) {
405
+ this.itemChanged.emit(widget);
406
+ }
407
+ this.layoutChanged.emit(this.widgets);
383
408
  }
384
409
  updateContainerHeight() {
385
410
  if (!this.widgets?.length) {
@@ -511,14 +536,88 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
511
536
  args: [GridCellDirective, { read: TemplateRef }]
512
537
  }] } });
513
538
 
539
+ const DASHBOARD_PERSISTENCE = new InjectionToken('DASHBOARD_PERSISTENCE');
540
+ const DASHBOARD_PERSISTENCE_CONTEXT = new InjectionToken('DASHBOARD_PERSISTENCE_CONTEXT');
541
+ class DashboardPersistence {
542
+ }
543
+ class LocalStorageDashboardPersistence {
544
+ async loadShared(dashboardId) {
545
+ const raw = localStorage.getItem(this._sharedKey(dashboardId));
546
+ if (!raw)
547
+ return null;
548
+ try {
549
+ return JSON.parse(raw);
550
+ }
551
+ catch (e) {
552
+ console.error('[Dashboard] bad shared state', e);
553
+ return null;
554
+ }
555
+ }
556
+ async saveShared(dashboardId, shared) {
557
+ localStorage.setItem(this._sharedKey(dashboardId), JSON.stringify(shared));
558
+ }
559
+ async loadPositions(dashboardId, windowId) {
560
+ const raw = localStorage.getItem(this._positionsKey(dashboardId, windowId));
561
+ if (!raw)
562
+ return null;
563
+ try {
564
+ return JSON.parse(raw);
565
+ }
566
+ catch (e) {
567
+ console.error('[Dashboard] bad positions state', e);
568
+ return null;
569
+ }
570
+ }
571
+ async savePositions(dashboardId, windowId, positions) {
572
+ localStorage.setItem(this._positionsKey(dashboardId, windowId), JSON.stringify(positions));
573
+ }
574
+ _sharedKey(dashboardId) {
575
+ // Keep legacy keys for default dashboard id to avoid breaking existing users.
576
+ if (dashboardId === 'default')
577
+ return 'ogidor_shared';
578
+ return `ogidor_shared_${dashboardId}`;
579
+ }
580
+ _positionsKey(dashboardId, windowId) {
581
+ if (dashboardId === 'default')
582
+ return `ogidor_positions_${windowId}`;
583
+ return `ogidor_positions_${dashboardId}_${windowId}`;
584
+ }
585
+ }
586
+ LocalStorageDashboardPersistence.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LocalStorageDashboardPersistence, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
587
+ LocalStorageDashboardPersistence.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LocalStorageDashboardPersistence, providedIn: 'root' });
588
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: LocalStorageDashboardPersistence, decorators: [{
589
+ type: Injectable,
590
+ args: [{ providedIn: 'root' }]
591
+ }] });
592
+ function resolveDefaultDashboardId() {
593
+ if (typeof window === 'undefined')
594
+ return 'default';
595
+ const path = window.location.pathname?.trim();
596
+ return path ? path : 'default';
597
+ }
598
+ class DashboardPersistenceFacade {
599
+ constructor(context) {
600
+ this.dashboardId = context?.dashboardId?.trim() || resolveDefaultDashboardId();
601
+ }
602
+ }
603
+ 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 });
604
+ DashboardPersistenceFacade.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardPersistenceFacade, providedIn: 'root' });
605
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardPersistenceFacade, decorators: [{
606
+ type: Injectable,
607
+ args: [{ providedIn: 'root' }]
608
+ }], ctorParameters: function () { return [{ type: undefined, decorators: [{
609
+ type: Optional
610
+ }, {
611
+ type: Inject,
612
+ args: [DASHBOARD_PERSISTENCE_CONTEXT]
613
+ }] }]; } });
614
+
514
615
  class DashboardStateService {
515
- constructor(zone) {
616
+ constructor(zone, persistenceFacade, localPersistence, persistence) {
516
617
  this.zone = zone;
517
- /**
518
- * Shared key — stores page list + widget metadata (no positions).
519
- * Read and written by every window.
520
- */
521
- this.SHARED_KEY = 'ogidor_shared';
618
+ this.persistenceFacade = persistenceFacade;
619
+ this.localPersistence = localPersistence;
620
+ this.persistence = persistence;
522
621
  this.CHANNEL_NAME = 'ogidor_dashboard_sync';
523
622
  this.channel = typeof BroadcastChannel !== 'undefined'
524
623
  ? new BroadcastChannel(this.CHANNEL_NAME) : null;
@@ -529,23 +628,12 @@ class DashboardStateService {
529
628
  this.activePageIdSubject = new BehaviorSubject(this.initialPages[0].id);
530
629
  this.pages$ = this.pagesSubject.asObservable();
531
630
  this.activePageId$ = this.activePageIdSubject.asObservable();
532
- // Determine whether we are a pop-out window
533
631
  const hash = typeof window !== 'undefined'
534
632
  ? window.location.hash.replace('#', '').trim() : '';
535
- this.positionsKey = hash ? `ogidor_positions_${hash}` : 'ogidor_positions_main';
536
- // 1. Load the shared structural state
537
- const shared = localStorage.getItem(this.SHARED_KEY);
538
- if (shared) {
539
- try {
540
- this._applyShared(JSON.parse(shared), false);
541
- }
542
- catch (e) {
543
- console.error('[Dashboard] bad shared state', e);
544
- }
545
- }
546
- // 2. Overlay this window's saved positions on top
547
- this._restorePositions();
548
- // 3. Listen for structural changes broadcast by other windows
633
+ this.windowId = hash || 'main';
634
+ this.dashboardId = this.persistenceFacade.dashboardId;
635
+ this.persistence = this.persistence ?? this.localPersistence;
636
+ void this._bootstrapFromPersistence();
549
637
  if (this.channel) {
550
638
  this.channel.onmessage = (ev) => {
551
639
  this.zone.run(() => this._onSyncEvent(ev.data));
@@ -553,7 +641,6 @@ class DashboardStateService {
553
641
  }
554
642
  }
555
643
  ngOnDestroy() { this.channel?.close(); }
556
- // ── Page actions ──────────────────────────────────────────────
557
644
  getActivePage() {
558
645
  return this.pagesSubject.value.find(p => p.id === this.activePageIdSubject.value);
559
646
  }
@@ -577,7 +664,6 @@ class DashboardStateService {
577
664
  this._saveShared();
578
665
  this._broadcast({ type: 'PAGE_REMOVED', pageId: id });
579
666
  }
580
- // ── Widget structural actions (synced to all windows) ─────────
581
667
  addWidget(widget) {
582
668
  const activePage = this.getActivePage();
583
669
  if (!activePage)
@@ -590,7 +676,7 @@ class DashboardStateService {
590
676
  activePage.widgets = [...activePage.widgets, newWidget];
591
677
  this.pagesSubject.next([...this.pagesSubject.value]);
592
678
  this._saveShared();
593
- this._savePositions(); // register default position locally
679
+ this._savePositions();
594
680
  this._broadcast({ type: 'WIDGET_ADDED', pageId: activePage.id, widget: newWidget });
595
681
  return newWidget;
596
682
  }
@@ -618,11 +704,6 @@ class DashboardStateService {
618
704
  this._savePositions();
619
705
  this._broadcast({ type: 'WIDGET_REMOVED', pageId, widgetId });
620
706
  }
621
- // ── Position actions (local to THIS window only) ──────────────
622
- /**
623
- * Update a widget's grid position/size.
624
- * Saved only for this window — never broadcast to others.
625
- */
626
707
  updateWidgetPosition(pageId, widgetId, x, y, cols, rows) {
627
708
  const pages = this.pagesSubject.value;
628
709
  const widget = pages.find(p => p.id === pageId)?.widgets.find(w => w.id === widgetId);
@@ -632,10 +713,9 @@ class DashboardStateService {
632
713
  widget.cols = cols;
633
714
  widget.rows = rows;
634
715
  this.pagesSubject.next([...pages]);
635
- this._savePositions(); // local only — intentionally no _saveShared / no broadcast
716
+ this._savePositions();
636
717
  }
637
718
  }
638
- // ── Serialization ─────────────────────────────────────────────
639
719
  serializeLayout() {
640
720
  return JSON.stringify({
641
721
  pages: this.pagesSubject.value,
@@ -645,7 +725,7 @@ class DashboardStateService {
645
725
  loadLayout(config) {
646
726
  if (!config?.pages)
647
727
  return;
648
- this._applyShared(config, true);
728
+ this._applyShared(config);
649
729
  this._saveShared();
650
730
  this._savePositions();
651
731
  }
@@ -653,26 +733,18 @@ class DashboardStateService {
653
733
  const url = `${window.location.origin}${window.location.pathname}#${pageId}`;
654
734
  window.open(url, `workspace_${pageId}`, 'width=1280,height=800,menubar=no,toolbar=no,location=no,status=no');
655
735
  }
656
- // ── Private helpers ───────────────────────────────────────────
657
- /**
658
- * Save page list + widget metadata (titles, cardColor, data) — no positions.
659
- */
660
736
  _saveShared() {
661
737
  const stripped = this.pagesSubject.value.map(p => ({
662
738
  ...p,
663
- widgets: p.widgets.map(({ id, title, cardColor, data, cols, rows }) =>
664
- // Keep default cols/rows so new windows get a sensible first-open layout.
665
- // x/y are intentionally omitted from shared state.
666
- ({ id, title, cardColor, data, x: 0, y: 0, cols, rows })),
739
+ widgets: p.widgets.map(({ id, title, cardColor, data, cols, rows }) => ({ id, title, cardColor, data, x: 0, y: 0, cols, rows })),
667
740
  }));
668
- localStorage.setItem(this.SHARED_KEY, JSON.stringify({
741
+ const shared = {
669
742
  pages: stripped,
670
743
  activePageId: this.activePageIdSubject.value,
671
- }));
744
+ };
745
+ void this.persistence?.saveShared(this.dashboardId, shared)
746
+ .catch(e => console.error('[Dashboard] failed to persist shared state', e));
672
747
  }
673
- /**
674
- * Save this window's grid positions (x, y, cols, rows) per widget.
675
- */
676
748
  _savePositions() {
677
749
  const positions = {};
678
750
  for (const page of this.pagesSubject.value) {
@@ -680,54 +752,32 @@ class DashboardStateService {
680
752
  positions[`${page.id}:${w.id}`] = { x: w.x, y: w.y, cols: w.cols, rows: w.rows };
681
753
  }
682
754
  }
683
- localStorage.setItem(this.positionsKey, JSON.stringify(positions));
755
+ void this.persistence?.savePositions(this.dashboardId, this.windowId, positions)
756
+ .catch(e => console.error('[Dashboard] failed to persist window positions', e));
684
757
  }
685
- /**
686
- * Overlay the positions saved for THIS window on top of the current pages.
687
- */
688
- _restorePositions() {
689
- const raw = localStorage.getItem(this.positionsKey);
690
- if (!raw)
691
- return;
692
- try {
693
- const positions = JSON.parse(raw);
694
- const pages = this.pagesSubject.value;
695
- for (const page of pages) {
696
- for (const w of page.widgets) {
697
- const pos = positions[`${page.id}:${w.id}`];
698
- if (pos) {
699
- w.x = pos.x;
700
- w.y = pos.y;
701
- w.cols = pos.cols;
702
- w.rows = pos.rows;
703
- }
758
+ _applyPositions(positions) {
759
+ const pages = this.pagesSubject.value;
760
+ for (const page of pages) {
761
+ for (const w of page.widgets) {
762
+ const pos = positions[`${page.id}:${w.id}`];
763
+ if (pos) {
764
+ w.x = pos.x;
765
+ w.y = pos.y;
766
+ w.cols = pos.cols;
767
+ w.rows = pos.rows;
704
768
  }
705
769
  }
706
- this.pagesSubject.next([...pages]);
707
- }
708
- catch (e) {
709
- console.error('[Dashboard] bad positions state', e);
710
770
  }
771
+ this.pagesSubject.next([...pages]);
711
772
  }
712
- /**
713
- * Apply a shared config object (page list + metadata) without touching
714
- * this window's saved positions.
715
- */
716
- _applyShared(config, overwritePositions) {
773
+ _applyShared(config) {
717
774
  if (!config?.pages)
718
775
  return;
719
776
  this.pagesSubject.next(config.pages);
720
777
  if (config.activePageId)
721
778
  this.activePageIdSubject.next(config.activePageId);
722
- if (!overwritePositions)
723
- this._restorePositions();
724
779
  }
725
- /**
726
- * Handle a structural sync event arriving from another window.
727
- * Position changes are never sent so we never receive them here.
728
- */
729
780
  _onSyncEvent(event) {
730
- // Work on a shallow clone of pages so Angular detects the change
731
781
  const pages = this.pagesSubject.value.map(p => ({
732
782
  ...p, widgets: [...p.widgets]
733
783
  }));
@@ -756,8 +806,6 @@ class DashboardStateService {
756
806
  page.widgets = [...page.widgets, { ...event.widget }];
757
807
  this.pagesSubject.next(pages);
758
808
  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
809
  }
762
810
  break;
763
811
  }
@@ -788,13 +836,31 @@ class DashboardStateService {
788
836
  _broadcast(event) {
789
837
  this.channel?.postMessage(event);
790
838
  }
839
+ async _bootstrapFromPersistence() {
840
+ try {
841
+ const shared = await this.persistence?.loadShared(this.dashboardId);
842
+ if (shared)
843
+ this.zone.run(() => this._applyShared(shared));
844
+ const positions = await this.persistence?.loadPositions(this.dashboardId, this.windowId);
845
+ if (positions)
846
+ this.zone.run(() => this._applyPositions(positions));
847
+ }
848
+ catch (e) {
849
+ console.error('[Dashboard] failed to bootstrap persisted state', e);
850
+ }
851
+ }
791
852
  }
792
- DashboardStateService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardStateService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
853
+ 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 });
793
854
  DashboardStateService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardStateService, providedIn: 'root' });
794
855
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardStateService, decorators: [{
795
856
  type: Injectable,
796
857
  args: [{ providedIn: 'root' }]
797
- }], ctorParameters: function () { return [{ type: i0.NgZone }]; } });
858
+ }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: DashboardPersistenceFacade }, { type: LocalStorageDashboardPersistence }, { type: DashboardPersistence, decorators: [{
859
+ type: Optional
860
+ }, {
861
+ type: Inject,
862
+ args: [DASHBOARD_PERSISTENCE]
863
+ }] }]; } });
798
864
 
799
865
  class WidgetRendererComponent {
800
866
  constructor() {
@@ -1448,7 +1514,10 @@ DashboardModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version
1448
1514
  WidgetRendererComponent,
1449
1515
  CustomGridComponent,
1450
1516
  GridCellDirective] });
1451
- DashboardModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, providers: [DashboardStateService], imports: [CommonModule] });
1517
+ DashboardModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, providers: [
1518
+ DashboardStateService,
1519
+ { provide: DASHBOARD_PERSISTENCE, useExisting: LocalStorageDashboardPersistence },
1520
+ ], imports: [CommonModule] });
1452
1521
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: DashboardModule, decorators: [{
1453
1522
  type: NgModule,
1454
1523
  args: [{
@@ -1462,7 +1531,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1462
1531
  imports: [
1463
1532
  CommonModule,
1464
1533
  ],
1465
- providers: [DashboardStateService],
1534
+ providers: [
1535
+ DashboardStateService,
1536
+ { provide: DASHBOARD_PERSISTENCE, useExisting: LocalStorageDashboardPersistence },
1537
+ ],
1466
1538
  exports: [
1467
1539
  DashboardComponent,
1468
1540
  OverflowActivePipe,
@@ -1497,5 +1569,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
1497
1569
  * Generated bundle index. Do not edit.
1498
1570
  */
1499
1571
 
1500
- export { AppModule, CustomGridComponent, DashboardComponent, DashboardModule, DashboardStateService, GridCellDirective, OverflowActivePipe, WidgetRendererComponent };
1572
+ export { AppModule, CustomGridComponent, DASHBOARD_PERSISTENCE, DASHBOARD_PERSISTENCE_CONTEXT, DashboardComponent, DashboardModule, DashboardPersistence, DashboardPersistenceFacade, DashboardStateService, GridCellDirective, LocalStorageDashboardPersistence, OverflowActivePipe, WidgetRendererComponent, resolveDefaultDashboardId };
1501
1573
  //# sourceMappingURL=ogidor-dashboard.mjs.map