@c8y/ngx-components 1023.97.4 → 1023.97.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/alarms/index.d.ts.map +1 -1
  2. package/context-dashboard/index.d.ts +29 -3
  3. package/context-dashboard/index.d.ts.map +1 -1
  4. package/ecosystem/index.d.ts.map +1 -1
  5. package/fesm2022/c8y-ngx-components-alarms.mjs +10 -7
  6. package/fesm2022/c8y-ngx-components-alarms.mjs.map +1 -1
  7. package/fesm2022/c8y-ngx-components-context-dashboard-asset-add.mjs +2 -2
  8. package/fesm2022/c8y-ngx-components-context-dashboard-asset-add.mjs.map +1 -1
  9. package/fesm2022/c8y-ngx-components-context-dashboard-asset-view.mjs +2 -2
  10. package/fesm2022/c8y-ngx-components-context-dashboard-asset-view.mjs.map +1 -1
  11. package/fesm2022/c8y-ngx-components-context-dashboard-cockpit-home-dashboard.mjs +3 -3
  12. package/fesm2022/c8y-ngx-components-context-dashboard-cockpit-home-dashboard.mjs.map +1 -1
  13. package/fesm2022/c8y-ngx-components-context-dashboard-device-add.mjs +2 -2
  14. package/fesm2022/c8y-ngx-components-context-dashboard-device-add.mjs.map +1 -1
  15. package/fesm2022/c8y-ngx-components-context-dashboard-device-view.mjs +2 -2
  16. package/fesm2022/c8y-ngx-components-context-dashboard-device-view.mjs.map +1 -1
  17. package/fesm2022/c8y-ngx-components-context-dashboard-devicemanagement.mjs +2 -2
  18. package/fesm2022/c8y-ngx-components-context-dashboard-devicemanagement.mjs.map +1 -1
  19. package/fesm2022/c8y-ngx-components-context-dashboard.mjs +142 -19
  20. package/fesm2022/c8y-ngx-components-context-dashboard.mjs.map +1 -1
  21. package/fesm2022/c8y-ngx-components-ecosystem.mjs +6 -7
  22. package/fesm2022/c8y-ngx-components-ecosystem.mjs.map +1 -1
  23. package/fesm2022/c8y-ngx-components-report-dashboard.mjs +4 -4
  24. package/fesm2022/c8y-ngx-components-report-dashboard.mjs.map +1 -1
  25. package/fesm2022/c8y-ngx-components-upgrade.mjs +40 -8
  26. package/fesm2022/c8y-ngx-components-upgrade.mjs.map +1 -1
  27. package/fesm2022/c8y-ngx-components-widgets-implementations-asset-table.mjs +12 -5
  28. package/fesm2022/c8y-ngx-components-widgets-implementations-asset-table.mjs.map +1 -1
  29. package/fesm2022/c8y-ngx-components-widgets-implementations-quick-links.mjs +7 -2
  30. package/fesm2022/c8y-ngx-components-widgets-implementations-quick-links.mjs.map +1 -1
  31. package/fesm2022/c8y-ngx-components-widgets-implementations-three-d-rotation.mjs +19 -0
  32. package/fesm2022/c8y-ngx-components-widgets-implementations-three-d-rotation.mjs.map +1 -1
  33. package/fesm2022/c8y-ngx-components.mjs +229 -31
  34. package/fesm2022/c8y-ngx-components.mjs.map +1 -1
  35. package/index.d.ts +83 -7
  36. package/index.d.ts.map +1 -1
  37. package/locales/de.po +3 -0
  38. package/locales/es.po +3 -0
  39. package/locales/fr.po +3 -0
  40. package/locales/ja_JP.po +3 -0
  41. package/locales/ko.po +3 -0
  42. package/locales/nl.po +3 -0
  43. package/locales/pl.po +3 -0
  44. package/locales/pt_BR.po +3 -0
  45. package/locales/zh_CN.po +3 -0
  46. package/locales/zh_TW.po +3 -0
  47. package/package.json +1 -1
  48. package/upgrade/index.d.ts +8 -0
  49. package/upgrade/index.d.ts.map +1 -1
  50. package/widgets/implementations/asset-table/index.d.ts.map +1 -1
  51. package/widgets/implementations/quick-links/index.d.ts.map +1 -1
  52. package/widgets/implementations/three-d-rotation/index.d.ts.map +1 -1
@@ -12833,7 +12833,12 @@ class HeaderService extends StateService {
12833
12833
  * Delay of 300ms is intended for animation purpose.
12834
12834
  */
12835
12835
  verifyIfNavOpen() {
12836
- this.canToggleNavigator$
12836
+ // Drop any previous subscription before re-subscribing so repeated calls (e.g. a re-initialised
12837
+ // header bar) do not accumulate never-completing subscriptions on the long-lived
12838
+ // canToggleNavigator$. The subscription is kept open intentionally so it still reacts when nodes
12839
+ // are added later (per this method's contract).
12840
+ this.navOpenSubscription?.unsubscribe();
12841
+ this.navOpenSubscription = this.canToggleNavigator$
12837
12842
  .pipe(debounceTime(300))
12838
12843
  .subscribe(hasItems => hasItems && this.shouldToggle && this.toggleNavigator());
12839
12844
  }
@@ -14288,31 +14293,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
14288
14293
  }], ctorParameters: () => [{ type: i0.Injector }, { type: i1$4.Router }, { type: PluginsResolveService }] });
14289
14294
 
14290
14295
  class TitleOutletComponent {
14291
- constructor(headerService, element, zone) {
14296
+ constructor(headerService, element, zone, destroyRef) {
14292
14297
  this.headerService = headerService;
14293
14298
  this.element = element;
14294
14299
  this.zone = zone;
14300
+ this.destroyRef = destroyRef;
14295
14301
  this.title = undefined;
14296
14302
  this.pageTitleUpdate = true;
14303
+ // These subscribe to the root-singleton HeaderService (a never-completing BehaviorSubject), so
14304
+ // without takeUntilDestroyed the observers — which capture this component (and, for the last one,
14305
+ // its host element) — would stay registered on the app-lifetime singleton and leak the destroyed
14306
+ // outlet and its detached DOM.
14297
14307
  const title$ = this.headerService.map(state => state.title);
14298
- title$.subscribe(title => {
14308
+ title$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(title => {
14299
14309
  this.title = title;
14300
14310
  });
14301
14311
  const pageTitleUpdate$ = this.headerService.map(state => state.pageTitleUpdate);
14302
- pageTitleUpdate$.subscribe(pageTitleUpdate => (this.pageTitleUpdate = pageTitleUpdate));
14312
+ pageTitleUpdate$
14313
+ .pipe(takeUntilDestroyed(this.destroyRef))
14314
+ .subscribe(pageTitleUpdate => (this.pageTitleUpdate = pageTitleUpdate));
14303
14315
  this.zone.runOutsideAngular(() => {
14304
14316
  this.headerService.state$
14305
- .pipe(debounceTime(100), filter(state => state.pageTitleUpdate), debounceTime(100), map(() => this.element.nativeElement.querySelector('h1')), filter(el => el !== null), map(el => el.innerText), filter(Boolean))
14317
+ .pipe(debounceTime(100), filter(state => state.pageTitleUpdate), debounceTime(100), map(() => this.element.nativeElement.querySelector('h1')), filter(el => el !== null), map(el => el.innerText), filter(Boolean), takeUntilDestroyed(this.destroyRef))
14306
14318
  .subscribe((title) => headerService.changePageTitle(title));
14307
14319
  });
14308
14320
  }
14309
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TitleOutletComponent, deps: [{ token: HeaderService }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
14321
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TitleOutletComponent, deps: [{ token: HeaderService }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component }); }
14310
14322
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: TitleOutletComponent, isStandalone: true, selector: "c8y-title-outlet", ngImport: i0, template: "<div class=\"c8y-ui-title\" data-cy=\"c8y-title--title-outlet\">\n <div class=\"title\" *c8yOutlet=\"title\"></div>\n</div>\n", dependencies: [{ kind: "directive", type: OutletDirective, selector: "[c8yOutlet]", inputs: ["c8yOutlet", "c8yOutletProperties", "c8yOutletInjector"] }] }); }
14311
14323
  }
14312
14324
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: TitleOutletComponent, decorators: [{
14313
14325
  type: Component,
14314
14326
  args: [{ selector: 'c8y-title-outlet', standalone: true, imports: [OutletDirective], template: "<div class=\"c8y-ui-title\" data-cy=\"c8y-title--title-outlet\">\n <div class=\"title\" *c8yOutlet=\"title\"></div>\n</div>\n" }]
14315
- }], ctorParameters: () => [{ type: HeaderService }, { type: i0.ElementRef }, { type: i0.NgZone }] });
14327
+ }], ctorParameters: () => [{ type: HeaderService }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.DestroyRef }] });
14316
14328
 
14317
14329
  class BreadcrumbOutletComponent {
14318
14330
  constructor() {
@@ -16138,7 +16150,8 @@ class NavigatorNodeComponent {
16138
16150
  this.nodeInit$.next();
16139
16151
  });
16140
16152
  }
16141
- this.node.refreshCallback = this.updateIcon.bind(this);
16153
+ this.refreshCallbackRef = this.updateIcon.bind(this);
16154
+ this.node.refreshCallback = this.refreshCallbackRef;
16142
16155
  }
16143
16156
  /**
16144
16157
  * Returns the expander title.
@@ -16192,6 +16205,16 @@ class NavigatorNodeComponent {
16192
16205
  ngOnDestroy() {
16193
16206
  clearTimeout(this.viewInitTimeout);
16194
16207
  if (this.node) {
16208
+ // The navigator node is long-lived (re-emitted from NavigatorService.items$), so the
16209
+ // back-references this component stored on it must be cleared or they retain the destroyed
16210
+ // component, its PopoverConfirm child and its icon view subtree. Guard by identity so a newer
16211
+ // component for the same persistent node is not clobbered.
16212
+ if (this.node.refreshCallback === this.refreshCallbackRef) {
16213
+ this.node.refreshCallback = undefined;
16214
+ }
16215
+ if (this.node.confirm === this.confirm) {
16216
+ this.node.confirm = undefined;
16217
+ }
16195
16218
  this.node.destroy();
16196
16219
  }
16197
16220
  }
@@ -29338,6 +29361,15 @@ class DynamicComponentComponent {
29338
29361
  this.expandErrorDetails = false;
29339
29362
  this.dynamicComponentErrorStrategy = DynamicComponentErrorStrategy;
29340
29363
  this.destroy$ = new Subject();
29364
+ /**
29365
+ * `ngOnChanges` is async (it awaits the component lookup, lazy chunk loading and resolvers), so a
29366
+ * navigation can destroy this host while a (re-)creation is still in flight. Creating the widget
29367
+ * after destroy would insert it into the destroyed `ViewContainerRef` — such a view is never
29368
+ * destroyed and permanently retains the whole detached dashboard DOM (Angular keeps every live
29369
+ * view in an internal registry until it is destroyed). This flag lets the pending async work
29370
+ * bail out instead.
29371
+ */
29372
+ this.isDestroyed = false;
29341
29373
  }
29342
29374
  /**
29343
29375
  * Calls the dynamic component life cycle hook. Currently only
@@ -29352,6 +29384,9 @@ class DynamicComponentComponent {
29352
29384
  */
29353
29385
  async ngOnChanges(changes) {
29354
29386
  const cmp = await this.dynamicComponentService.getById(this.componentId);
29387
+ if (this.isDestroyed) {
29388
+ return;
29389
+ }
29355
29390
  let errors = new Array();
29356
29391
  if (cmp) {
29357
29392
  this.errorStrategy = cmp.errorStrategy || DynamicComponentErrorStrategy.CUSTOM;
@@ -29359,6 +29394,9 @@ class DynamicComponentComponent {
29359
29394
  const [resolvedConfig] = await this.dynamicComponentService.executeResolvers([
29360
29395
  { componentId: this.componentId, config: this.config }
29361
29396
  ]);
29397
+ if (this.isDestroyed) {
29398
+ return;
29399
+ }
29362
29400
  Object.entries(resolvedConfig).forEach(([key, value]) => set(this.config, key, value));
29363
29401
  }
29364
29402
  const resolvedValuesOfConfiguration = cmp.resolve
@@ -29391,8 +29429,17 @@ class DynamicComponentComponent {
29391
29429
  this.emitChangeOnComponent({ config: new SimpleChange(oldConfig, this.config, false) });
29392
29430
  }
29393
29431
  ngOnDestroy() {
29432
+ this.isDestroyed = true;
29394
29433
  this.destroy$.next();
29395
29434
  this.destroy$.complete();
29435
+ // Release references to the dynamically created widget. The ViewContainerRef destroys the
29436
+ // created component automatically, but the host keeps strong fields pointing at the widget
29437
+ // instance (and, via its config/alerts, at large object graphs). If anything still references
29438
+ // this now-destroyed host, those fields would keep the whole widget — and its detached DOM —
29439
+ // alive. Clearing them breaks that retention edge.
29440
+ this.componentRef = undefined;
29441
+ this.componentInstance = undefined;
29442
+ this.error = undefined;
29396
29443
  }
29397
29444
  emitChangeOnComponent(changes) {
29398
29445
  const ngOnChanges = this.componentRef?.instance?.ngOnChanges;
@@ -29404,6 +29451,12 @@ class DynamicComponentComponent {
29404
29451
  try {
29405
29452
  this.error = undefined;
29406
29453
  const componentType = await this.getComponentType(dynamicComponent);
29454
+ // The lazy-component await above (and the awaits in ngOnChanges) can outlive this host:
29455
+ // creating the widget into the destroyed ViewContainerRef would leak it — and the whole
29456
+ // detached dashboard DOM it references — until a full page reload.
29457
+ if (this.isDestroyed) {
29458
+ return;
29459
+ }
29407
29460
  this.host.clear();
29408
29461
  this.componentRef = this.host.createComponent(componentType, {
29409
29462
  environmentInjector: (dynamicComponent.injector || this.injector)
@@ -29477,19 +29530,18 @@ class DynamicComponentComponent {
29477
29530
  return of(true);
29478
29531
  }
29479
29532
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DynamicComponentComponent, deps: [{ token: DynamicComponentService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component }); }
29480
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: DynamicComponentComponent, isStandalone: true, selector: "c8y-dynamic-component", inputs: { componentId: "componentId", config: "config", mode: "mode", notFoundError: "notFoundError", executeResolvers: "executeResolvers" }, outputs: { updateWidgetClasses: "updateWidgetClasses" }, viewQueries: [{ propertyName: "host", first: true, predicate: ["host"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: "<ng-template #host></ng-template>\n\n<div class=\"alert alert-warning m-8\" role=\"alert\" *ngIf=\"error && notFoundError\">\n <strong class=\"message\">\n {{\n 'This widget cannot be rendered because the current application does not support the following component:'\n | translate\n }}\n {{ componentId }}.\n </strong>\n <p class=\"text-muted m-t-8\">\n <button class=\"btn btn-clean\" (click)=\"expandErrorDetails = !expandErrorDetails\">\n <i c8yIcon=\"chevron-down\"></i>\n <span *ngIf=\"!expandErrorDetails\" translate>Show details</span>\n <span *ngIf=\"expandErrorDetails\" translate>Hide details</span>\n </button>\n </p>\n <div [collapse]=\"!expandErrorDetails\" [isAnimated]=\"true\">\n <pre>\n {{ error }}\n </pre>\n </div>\n</div>\n\n<div\n class=\"overlay-center-vertically d-flex d-col a-i-stretch j-c-start p-16 p-r-24 p-l-24\"\n *ngIf=\"\n errorStrategy === dynamicComponentErrorStrategy.OVERLAY_ERROR &&\n componentInstance?.alerts?.anyAlertExists$ | async\n \"\n>\n <c8y-dynamic-component-alerts [alerts]=\"componentInstance.alerts\"></c8y-dynamic-component-alerts>\n</div>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "ngmodule", type: CollapseModule }, { kind: "directive", type: i2$2.CollapseDirective, selector: "[collapse]", inputs: ["display", "isAnimated", "collapse"], outputs: ["collapsed", "collapses", "expanded", "expands"], exportAs: ["bs-collapse"] }, { kind: "component", type: DynamicComponentAlertsComponent, selector: "c8y-dynamic-component-alerts", inputs: ["alerts"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
29533
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: DynamicComponentComponent, isStandalone: true, selector: "c8y-dynamic-component", inputs: { componentId: "componentId", config: "config", mode: "mode", notFoundError: "notFoundError", executeResolvers: "executeResolvers" }, outputs: { updateWidgetClasses: "updateWidgetClasses" }, viewQueries: [{ propertyName: "host", first: true, predicate: ["host"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: "<ng-template #host></ng-template>\n\n@if (error && notFoundError) {\n <div\n class=\"alert alert-warning m-8\"\n role=\"alert\"\n >\n <strong class=\"message\">\n {{\n 'This widget cannot be rendered because the current application does not support the following component:'\n | translate\n }}\n {{ componentId }}.\n </strong>\n <p class=\"text-muted m-t-8\">\n <button\n class=\"btn btn-clean\"\n (click)=\"expandErrorDetails = !expandErrorDetails\"\n >\n <i c8yIcon=\"chevron-down\"></i>\n @if (!expandErrorDetails) {\n <span translate>Show details</span>\n } @else {\n <span translate>Hide details</span>\n }\n </button>\n </p>\n <div\n [collapse]=\"!expandErrorDetails\"\n [isAnimated]=\"true\"\n >\n <pre>\n {{ error }}\n </pre\n >\n </div>\n </div>\n}\n\n@if (\n errorStrategy === dynamicComponentErrorStrategy.OVERLAY_ERROR &&\n componentInstance?.alerts?.anyAlertExists$ | async\n) {\n <div class=\"overlay-center-vertically d-flex d-col a-i-stretch j-c-start p-16 p-r-24 p-l-24\">\n <c8y-dynamic-component-alerts\n [alerts]=\"componentInstance.alerts\"\n ></c8y-dynamic-component-alerts>\n </div>\n}\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "ngmodule", type: CollapseModule }, { kind: "directive", type: i2$2.CollapseDirective, selector: "[collapse]", inputs: ["display", "isAnimated", "collapse"], outputs: ["collapsed", "collapses", "expanded", "expands"], exportAs: ["bs-collapse"] }, { kind: "component", type: DynamicComponentAlertsComponent, selector: "c8y-dynamic-component-alerts", inputs: ["alerts"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
29481
29534
  }
29482
29535
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: DynamicComponentComponent, decorators: [{
29483
29536
  type: Component,
29484
29537
  args: [{ selector: 'c8y-dynamic-component', standalone: true, imports: [
29485
- NgIf,
29486
29538
  IconDirective,
29487
29539
  C8yTranslateDirective,
29488
29540
  CollapseModule,
29489
29541
  DynamicComponentAlertsComponent,
29490
29542
  C8yTranslatePipe,
29491
29543
  AsyncPipe
29492
- ], template: "<ng-template #host></ng-template>\n\n<div class=\"alert alert-warning m-8\" role=\"alert\" *ngIf=\"error && notFoundError\">\n <strong class=\"message\">\n {{\n 'This widget cannot be rendered because the current application does not support the following component:'\n | translate\n }}\n {{ componentId }}.\n </strong>\n <p class=\"text-muted m-t-8\">\n <button class=\"btn btn-clean\" (click)=\"expandErrorDetails = !expandErrorDetails\">\n <i c8yIcon=\"chevron-down\"></i>\n <span *ngIf=\"!expandErrorDetails\" translate>Show details</span>\n <span *ngIf=\"expandErrorDetails\" translate>Hide details</span>\n </button>\n </p>\n <div [collapse]=\"!expandErrorDetails\" [isAnimated]=\"true\">\n <pre>\n {{ error }}\n </pre>\n </div>\n</div>\n\n<div\n class=\"overlay-center-vertically d-flex d-col a-i-stretch j-c-start p-16 p-r-24 p-l-24\"\n *ngIf=\"\n errorStrategy === dynamicComponentErrorStrategy.OVERLAY_ERROR &&\n componentInstance?.alerts?.anyAlertExists$ | async\n \"\n>\n <c8y-dynamic-component-alerts [alerts]=\"componentInstance.alerts\"></c8y-dynamic-component-alerts>\n</div>\n" }]
29544
+ ], template: "<ng-template #host></ng-template>\n\n@if (error && notFoundError) {\n <div\n class=\"alert alert-warning m-8\"\n role=\"alert\"\n >\n <strong class=\"message\">\n {{\n 'This widget cannot be rendered because the current application does not support the following component:'\n | translate\n }}\n {{ componentId }}.\n </strong>\n <p class=\"text-muted m-t-8\">\n <button\n class=\"btn btn-clean\"\n (click)=\"expandErrorDetails = !expandErrorDetails\"\n >\n <i c8yIcon=\"chevron-down\"></i>\n @if (!expandErrorDetails) {\n <span translate>Show details</span>\n } @else {\n <span translate>Hide details</span>\n }\n </button>\n </p>\n <div\n [collapse]=\"!expandErrorDetails\"\n [isAnimated]=\"true\"\n >\n <pre>\n {{ error }}\n </pre\n >\n </div>\n </div>\n}\n\n@if (\n errorStrategy === dynamicComponentErrorStrategy.OVERLAY_ERROR &&\n componentInstance?.alerts?.anyAlertExists$ | async\n) {\n <div class=\"overlay-center-vertically d-flex d-col a-i-stretch j-c-start p-16 p-r-24 p-l-24\">\n <c8y-dynamic-component-alerts\n [alerts]=\"componentInstance.alerts\"\n ></c8y-dynamic-component-alerts>\n </div>\n}\n" }]
29493
29545
  }], ctorParameters: () => [{ type: DynamicComponentService }, { type: i0.Injector }], propDecorators: { componentId: [{
29494
29546
  type: Input
29495
29547
  }], config: [{
@@ -30180,6 +30232,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
30180
30232
  * ```
30181
30233
  */
30182
30234
  class DashboardChildComponent {
30235
+ static { this.WIDGET_THEME_FOUNDATION_MAP = {
30236
+ 'panel-content-dark': 'c8y-dark-theme',
30237
+ 'panel-content-white': 'c8y-light-theme'
30238
+ }; }
30239
+ static { this.MD_BOOTSTRAP_BREAKPOINT_READONLY_CLASS_PROPERTY = 768; }
30240
+ /**
30241
+ * Emits true in one-column (mobile) view. SHARED across all instances via a single static
30242
+ * `window 'resize'` subscription — previously every widget card created its own listener +
30243
+ * debounce timer (N per dashboard), wasteful on dashboards with many widgets. One listener now
30244
+ * serves them all (refCount:false keeps it warm for the app's lifetime).
30245
+ */
30246
+ static { this.oneColumnView$ = fromEvent(window, 'resize').pipe(debounceTime(200), map(() => document.documentElement.clientWidth <=
30247
+ DashboardChildComponent.MD_BOOTSTRAP_BREAKPOINT_READONLY_CLASS_PROPERTY), startWith(document.documentElement.clientWidth <=
30248
+ DashboardChildComponent.MD_BOOTSTRAP_BREAKPOINT_READONLY_CLASS_PROPERTY), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: false })); }
30183
30249
  set templateActions(actions) {
30184
30250
  this.addActions(actions, true);
30185
30251
  }
@@ -30231,7 +30297,6 @@ class DashboardChildComponent {
30231
30297
  this._pxWidth = '100%';
30232
30298
  this._pxHeight = '100%';
30233
30299
  this.fullscreen = false;
30234
- this.MD_BOOTSTRAP_BREAKPOINT_READONLY_CLASS_PROPERTY = 768;
30235
30300
  /**
30236
30301
  * The width of the component in grid-columns.
30237
30302
  */
@@ -30270,12 +30335,8 @@ class DashboardChildComponent {
30270
30335
  * All classes added to this child
30271
30336
  */
30272
30337
  this.class = {};
30273
- /**
30274
- * Triggers on every resize and returns true if in one column view (mobile view)
30275
- */
30276
- this.isOneColumnView$ = fromEvent(window, 'resize').pipe(map(_ => {
30277
- return (document.documentElement.clientWidth <= this.MD_BOOTSTRAP_BREAKPOINT_READONLY_CLASS_PROPERTY);
30278
- }), debounceTime(200), startWith(document.documentElement.clientWidth <= this.MD_BOOTSTRAP_BREAKPOINT_READONLY_CLASS_PROPERTY));
30338
+ /** Triggers on resize and returns true if in one column view (mobile view). */
30339
+ this.isOneColumnView$ = DashboardChildComponent.oneColumnView$;
30279
30340
  /**
30280
30341
  * An indicator if the child is intersected (that mean visible for the user)
30281
30342
  */
@@ -30286,11 +30347,21 @@ class DashboardChildComponent {
30286
30347
  this.lastChange = null;
30287
30348
  }
30288
30349
  ngOnChanges() {
30350
+ const classObj = Array.isArray(this.class)
30351
+ ? this.class.reduce((acc, cls) => ({ ...acc, [cls]: true }), {})
30352
+ : (this.class ?? {});
30353
+ const foundationClasses = {};
30354
+ for (const [contentClass, foundationClass] of Object.entries(DashboardChildComponent.WIDGET_THEME_FOUNDATION_MAP)) {
30355
+ if (classObj[contentClass]) {
30356
+ foundationClasses[foundationClass] = true;
30357
+ }
30358
+ }
30289
30359
  this.klasses = {
30290
30360
  card: true,
30291
30361
  'card-dashboard': true,
30292
30362
  'on-resize': this.isResize,
30293
- ...this.class
30363
+ ...foundationClasses,
30364
+ ...classObj
30294
30365
  };
30295
30366
  }
30296
30367
  ngOnInit() {
@@ -30298,8 +30369,7 @@ class DashboardChildComponent {
30298
30369
  setTimeout(() => this.setDynamicDimension());
30299
30370
  }
30300
30371
  if (this.useIntersection && 'IntersectionObserver' in window) {
30301
- const intersectionObserver = new IntersectionObserver(events => (this.intersected = this.childInView(events, intersectionObserver)));
30302
- intersectionObserver.observe(this.element.nativeElement);
30372
+ this.observeIntersection();
30303
30373
  }
30304
30374
  else {
30305
30375
  this.intersected = true;
@@ -30352,6 +30422,9 @@ class DashboardChildComponent {
30352
30422
  if (this.changeSubscription) {
30353
30423
  this.changeSubscription.unsubscribe();
30354
30424
  }
30425
+ // Disconnect the IntersectionObserver so it stops retaining this (now detached) card element.
30426
+ this.intersectionObserver?.disconnect();
30427
+ this.intersectionObserver = undefined;
30355
30428
  this._additionalHeaderTemplates.complete();
30356
30429
  this.removeSelfFromDashboard();
30357
30430
  }
@@ -30363,6 +30436,20 @@ class DashboardChildComponent {
30363
30436
  this.actions = [...this.actions, ...actions];
30364
30437
  }
30365
30438
  }
30439
+ /**
30440
+ * Re-arms intersection tracking after a card was restored following a cancelled navigation. The
30441
+ * observer one-shot-unobserves on first intersection, so once a card has been shown and then
30442
+ * blanked it would no longer be watched; this lets the dashboard put it back under observation.
30443
+ */
30444
+ reobserve() {
30445
+ if (this.useIntersection && 'IntersectionObserver' in window) {
30446
+ this.intersectionObserver?.disconnect();
30447
+ this.observeIntersection();
30448
+ }
30449
+ else {
30450
+ this.intersected = true;
30451
+ }
30452
+ }
30366
30453
  removeSelfFromDashboard() {
30367
30454
  const i = this.dashboard.children.indexOf(this);
30368
30455
  if (i >= 0) {
@@ -30375,6 +30462,10 @@ class DashboardChildComponent {
30375
30462
  getOrder() {
30376
30463
  return `${Math.round((this.y + (this.x + 1) / 100) * 100)}`;
30377
30464
  }
30465
+ observeIntersection() {
30466
+ this.intersectionObserver = new IntersectionObserver(events => (this.intersected = this.childInView(events, this.intersectionObserver)));
30467
+ this.intersectionObserver.observe(this.element.nativeElement);
30468
+ }
30378
30469
  childInView(events, observer) {
30379
30470
  const intersectingEvent = events.find(event => event.isIntersecting);
30380
30471
  if (intersectingEvent) {
@@ -32016,6 +32107,16 @@ class WidgetAutoRefreshContextComponent {
32016
32107
  this.listenOnDashboardSaveOperation();
32017
32108
  }
32018
32109
  ngOnDestroy() {
32110
+ // Tear down THIS instance's subscriptions unconditionally. They are per-instance — each pipes
32111
+ // through takeUntil(this.destroy$) and its callback captures this component. Previously destroy$
32112
+ // was only completed when the shared globalRefreshWidgetsCount reached 0, so whenever another
32113
+ // auto-refresh context was still alive (count > 1 — e.g. the brief overlap while navigating
32114
+ // between dashboards, where the next context is created before this one is destroyed) the
32115
+ // destroyed instance's subscriptions to long-lived root streams (Router events, query params,
32116
+ // root BehaviorSubjects) stayed open and retained this component and its detached DOM. Only the
32117
+ // shared service reset must remain gated on the count.
32118
+ this.destroy$.next();
32119
+ this.destroy$.complete();
32019
32120
  this.onContextDestroy();
32020
32121
  }
32021
32122
  onRefresh() {
@@ -32078,8 +32179,6 @@ class WidgetAutoRefreshContextComponent {
32078
32179
  const widgetsCount = this.widgetGlobalAutoRefresh.globalRefreshWidgetsCount;
32079
32180
  widgetsCount.update(count => count - 1);
32080
32181
  if (widgetsCount() === 0) {
32081
- this.destroy$.next();
32082
- this.destroy$.complete();
32083
32182
  this.widgetGlobalAutoRefresh.resetServiceToDefaults();
32084
32183
  }
32085
32184
  }
@@ -32089,7 +32188,7 @@ class WidgetAutoRefreshContextComponent {
32089
32188
  .subscribe();
32090
32189
  }
32091
32190
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: WidgetAutoRefreshContextComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
32092
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: WidgetAutoRefreshContextComponent, isStandalone: true, selector: "c8y-widget-auto-refresh-context", inputs: { editMode$: "editMode$" }, ngImport: i0, template: "<c8y-action-bar-item\n [groupId]=\"ACTION_BAR_GROUP_ID\"\n [priority]=\"ACTION_BAR_PRIORITY\"\n [placement]=\"'left'\"\n>\n <form\n class=\"d-flex\"\n [formGroup]=\"form\"\n >\n <c8y-auto-refresh-control\n [hidden]=\"editMode$ | async\"\n (loading)=\"globalRefreshWidgetLoading = $event\"\n formControlName=\"autoRefresh\"\n ></c8y-auto-refresh-control>\n\n <c8y-auto-refresh-select-control\n [hidden]=\"editMode$ | async\"\n formControlName=\"autoRefreshSeconds\"\n ></c8y-auto-refresh-select-control>\n </form>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default\"\n style=\"border-left: 0\"\n [attr.aria-label]=\"'Refresh' | translate\"\n [tooltip]=\"'Refresh' | translate\"\n placement=\"bottom\"\n type=\"button\"\n *ngIf=\"!(editMode$ | async)\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n [disabled]=\"globalRefreshWidgetLoading\"\n (click)=\"onRefresh()\"\n >\n <i\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': globalRefreshWidgetLoading }\"\n ></i>\n </button>\n </div>\n</c8y-action-bar-item>\n", dependencies: [{ kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$8.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$8.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: AutoRefreshControlComponent, selector: "c8y-auto-refresh-control", outputs: ["loading"] }, { kind: "component", type: AutoRefreshSelectControlComponent, selector: "c8y-auto-refresh-select-control" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i1$5.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
32191
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: WidgetAutoRefreshContextComponent, isStandalone: true, selector: "c8y-widget-auto-refresh-context", inputs: { editMode$: "editMode$" }, ngImport: i0, template: "<c8y-action-bar-item\n [groupId]=\"ACTION_BAR_GROUP_ID\"\n [priority]=\"ACTION_BAR_PRIORITY\"\n [placement]=\"'left'\"\n>\n <form\n class=\"d-flex\"\n [formGroup]=\"form\"\n >\n <c8y-auto-refresh-control\n [hidden]=\"editMode$ | async\"\n (loading)=\"globalRefreshWidgetLoading = $event\"\n formControlName=\"autoRefresh\"\n ></c8y-auto-refresh-control>\n\n <c8y-auto-refresh-select-control\n [hidden]=\"editMode$ | async\"\n formControlName=\"autoRefreshSeconds\"\n ></c8y-auto-refresh-select-control>\n </form>\n <div class=\"input-group-btn\">\n @if (!(editMode$ | async)) {\n <button\n class=\"btn btn-default\"\n style=\"border-left: 0\"\n [attr.aria-label]=\"'Refresh' | translate\"\n [tooltip]=\"'Refresh' | translate\"\n placement=\"bottom\"\n type=\"button\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n [disabled]=\"globalRefreshWidgetLoading\"\n (click)=\"onRefresh()\"\n >\n <i\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': globalRefreshWidgetLoading }\"\n ></i>\n </button>\n }\n </div>\n</c8y-action-bar-item>\n", dependencies: [{ kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "ngmodule", type: FormsModule$1 }, { kind: "directive", type: i1$8.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$8.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$8.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$8.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$8.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: AutoRefreshControlComponent, selector: "c8y-auto-refresh-control", outputs: ["loading"] }, { kind: "component", type: AutoRefreshSelectControlComponent, selector: "c8y-auto-refresh-select-control" }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i1$5.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
32093
32192
  }
32094
32193
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: WidgetAutoRefreshContextComponent, decorators: [{
32095
32194
  type: Component,
@@ -32099,13 +32198,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
32099
32198
  ReactiveFormsModule,
32100
32199
  AutoRefreshControlComponent,
32101
32200
  AutoRefreshSelectControlComponent,
32102
- NgIf,
32103
32201
  TooltipModule,
32104
32202
  IconDirective,
32105
32203
  NgClass,
32106
32204
  C8yTranslatePipe,
32107
32205
  AsyncPipe
32108
- ], template: "<c8y-action-bar-item\n [groupId]=\"ACTION_BAR_GROUP_ID\"\n [priority]=\"ACTION_BAR_PRIORITY\"\n [placement]=\"'left'\"\n>\n <form\n class=\"d-flex\"\n [formGroup]=\"form\"\n >\n <c8y-auto-refresh-control\n [hidden]=\"editMode$ | async\"\n (loading)=\"globalRefreshWidgetLoading = $event\"\n formControlName=\"autoRefresh\"\n ></c8y-auto-refresh-control>\n\n <c8y-auto-refresh-select-control\n [hidden]=\"editMode$ | async\"\n formControlName=\"autoRefreshSeconds\"\n ></c8y-auto-refresh-select-control>\n </form>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default\"\n style=\"border-left: 0\"\n [attr.aria-label]=\"'Refresh' | translate\"\n [tooltip]=\"'Refresh' | translate\"\n placement=\"bottom\"\n type=\"button\"\n *ngIf=\"!(editMode$ | async)\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n [disabled]=\"globalRefreshWidgetLoading\"\n (click)=\"onRefresh()\"\n >\n <i\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': globalRefreshWidgetLoading }\"\n ></i>\n </button>\n </div>\n</c8y-action-bar-item>\n" }]
32206
+ ], template: "<c8y-action-bar-item\n [groupId]=\"ACTION_BAR_GROUP_ID\"\n [priority]=\"ACTION_BAR_PRIORITY\"\n [placement]=\"'left'\"\n>\n <form\n class=\"d-flex\"\n [formGroup]=\"form\"\n >\n <c8y-auto-refresh-control\n [hidden]=\"editMode$ | async\"\n (loading)=\"globalRefreshWidgetLoading = $event\"\n formControlName=\"autoRefresh\"\n ></c8y-auto-refresh-control>\n\n <c8y-auto-refresh-select-control\n [hidden]=\"editMode$ | async\"\n formControlName=\"autoRefreshSeconds\"\n ></c8y-auto-refresh-select-control>\n </form>\n <div class=\"input-group-btn\">\n @if (!(editMode$ | async)) {\n <button\n class=\"btn btn-default\"\n style=\"border-left: 0\"\n [attr.aria-label]=\"'Refresh' | translate\"\n [tooltip]=\"'Refresh' | translate\"\n placement=\"bottom\"\n type=\"button\"\n [adaptivePosition]=\"false\"\n [delay]=\"500\"\n [disabled]=\"globalRefreshWidgetLoading\"\n (click)=\"onRefresh()\"\n >\n <i\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': globalRefreshWidgetLoading }\"\n ></i>\n </button>\n }\n </div>\n</c8y-action-bar-item>\n" }]
32109
32207
  }], propDecorators: { editMode$: [{
32110
32208
  type: Input
32111
32209
  }] } });
@@ -32145,7 +32243,7 @@ class WidgetsDashboardComponent {
32145
32243
  get nativeElement() {
32146
32244
  return this.elementRef.nativeElement;
32147
32245
  }
32148
- constructor(dynamic, translateService, route, modal, widgetGlobalAutoRefresh, router, elementRef) {
32246
+ constructor(dynamic, translateService, route, modal, widgetGlobalAutoRefresh, router, elementRef, cdr) {
32149
32247
  this.dynamic = dynamic;
32150
32248
  this.translateService = translateService;
32151
32249
  this.route = route;
@@ -32153,6 +32251,7 @@ class WidgetsDashboardComponent {
32153
32251
  this.widgetGlobalAutoRefresh = widgetGlobalAutoRefresh;
32154
32252
  this.router = router;
32155
32253
  this.elementRef = elementRef;
32254
+ this.cdr = cdr;
32156
32255
  /**
32157
32256
  * Indicates if device info in config should be overridden with values from context property.
32158
32257
  */
@@ -32189,6 +32288,23 @@ class WidgetsDashboardComponent {
32189
32288
  this.widgetFullscreenChangeListener = null;
32190
32289
  this.inFullScreen$ = fromEvent(document, 'fullscreenchange').pipe(map(() => this.fullScreen()), startWith(this.fullScreen()));
32191
32290
  this.editMode$ = new BehaviorSubject(false);
32291
+ /**
32292
+ * When navigating AWAY from this dashboard to a different one, drop each widget's heavy content
32293
+ * (`intersected = false`) before Angular tears the view down. The widget bodies — the bulk of the
32294
+ * DOM — are removed from the lingering/detached dashboard tree, so the single detached
32295
+ * c8y-context-dashboard entry collapses toward its chrome shell instead of carrying a
32296
+ * multi-thousand-node subtree on every view change. (We intentionally do NOT remove the whole
32297
+ * grid: that orphans every card as a separate multi-root-retained fragment, which clutters the
32298
+ * Detached-elements panel worse and risks accumulating.) Robust across tab switches — the `@for`
32299
+ * just creates fresh children for the next dashboard. No remount cost on a real leave.
32300
+ */
32301
+ this.clearWidgetContentOnLeave = true;
32302
+ /**
32303
+ * The cards blanked by the most recent {@link clearWidgetContentBeforeLeaving}, kept so a
32304
+ * cancelled/failed navigation can restore exactly those (and only those — never a card that was
32305
+ * legitimately below the fold and not yet intersected).
32306
+ */
32307
+ this.clearedChildren = [];
32192
32308
  this.copyDashboardLabel = gettext$1('Copy dashboard');
32193
32309
  this.undoMessage = gettext$1('Undo: "{{ changeToUndo }}"');
32194
32310
  this.redoMessage = gettext$1('Redo: "{{ changeToRedo }}"');
@@ -32199,6 +32315,17 @@ class WidgetsDashboardComponent {
32199
32315
  if (navigation?.extras.state?.[NEW_DASHBOARD_ROUTER_STATE_PROP]) {
32200
32316
  this.enableEditMode();
32201
32317
  }
32318
+ this.router.events.pipe(takeUntilDestroyed()).subscribe(event => {
32319
+ if (event instanceof NavigationStart) {
32320
+ this.clearWidgetContentBeforeLeaving(event);
32321
+ }
32322
+ else if (event instanceof NavigationCancel || event instanceof NavigationError) {
32323
+ // The leave we blanked content for did not complete (e.g. a canDeactivate guard rejected
32324
+ // it, or back/forward was cancelled). This dashboard stays mounted, so restore exactly the
32325
+ // cards we cleared — otherwise they would remain permanently blank.
32326
+ this.restoreClearedWidgetContent();
32327
+ }
32328
+ });
32202
32329
  this.resolvedWidgets$ = this._widgets.pipe(switchMap(widgets => this.executeResolversOfWidgets(widgets)), shareReplay(1));
32203
32330
  this.isExecutingResolvers$ = merge(this._widgets.pipe(map(() => true)), this.resolvedWidgets$.pipe(map(() => false)));
32204
32331
  this.isLoadingWidgets$ = combineLatest([this.isLoading$, this.isExecutingResolvers$]).pipe(map(loadings => loadings.some(loading => loading)), shareReplay(1));
@@ -32311,6 +32438,72 @@ class WidgetsDashboardComponent {
32311
32438
  this.editMode$.next(true);
32312
32439
  this.contextDashboard.historyDescription = {};
32313
32440
  }
32441
+ /**
32442
+ * Clears widget content only when truly leaving this dashboard for a different one. Guards:
32443
+ * - opt-out flag;
32444
+ * - skip in edit mode (navigation may be cancelled by canDeactivate, and editing must keep content);
32445
+ * - skip when only the auxiliary outlet (e.g. the "Dashboard settings" drawer), query params
32446
+ * (auto-refresh / date range) or matrix params change — i.e. the PRIMARY dashboard route is the
32447
+ * same, so the dashboard stays mounted and must NOT be blanked.
32448
+ */
32449
+ clearWidgetContentBeforeLeaving(event) {
32450
+ if (!this.clearWidgetContentOnLeave || this.editMode$.value) {
32451
+ return;
32452
+ }
32453
+ // Only when actually leaving THIS dashboard for a different primary route — not the settings
32454
+ // drawer (auxiliary outlet), query params (auto-refresh/date) or matrix params, which keep the
32455
+ // dashboard mounted and must NOT be blanked.
32456
+ if (this.toPrimaryPath(this.router.url) === this.toPrimaryPath(event.url)) {
32457
+ return;
32458
+ }
32459
+ const children = this.dashboardChildren;
32460
+ if (!children?.length) {
32461
+ return;
32462
+ }
32463
+ // Track only the cards that were actually showing content, so a cancelled navigation restores
32464
+ // exactly those and leaves never-intersected (below-the-fold) cards as they were.
32465
+ this.clearedChildren = children.filter(child => child.intersected);
32466
+ this.clearedChildren.forEach(child => (child.intersected = false));
32467
+ try {
32468
+ this.cdr.detectChanges();
32469
+ }
32470
+ catch {
32471
+ // The view may already be detaching during navigation; the content is dropped regardless.
32472
+ }
32473
+ }
32474
+ /**
32475
+ * Re-shows the cards blanked by a clear that was followed by a cancelled/failed navigation. The
32476
+ * IntersectionObserver one-shot-unobserves a visible card, so it cannot bring the content back on
32477
+ * its own; we restore `intersected` directly and re-arm observation for future scrolling.
32478
+ */
32479
+ restoreClearedWidgetContent() {
32480
+ if (!this.clearedChildren.length) {
32481
+ return;
32482
+ }
32483
+ this.clearedChildren.forEach(child => {
32484
+ child.intersected = true;
32485
+ child.reobserve();
32486
+ });
32487
+ this.clearedChildren = [];
32488
+ try {
32489
+ this.cdr.detectChanges();
32490
+ }
32491
+ catch {
32492
+ // The view may be mid-teardown; restoring is best-effort.
32493
+ }
32494
+ }
32495
+ /**
32496
+ * Primary route path, parsed with the Router (not a hand-rolled regex) so query params, auxiliary
32497
+ * outlets (e.g. the dashboard-settings drawer `(dashboard-details:...)`) and matrix params are
32498
+ * correctly excluded. Two URLs with the same primary path mean the dashboard stays mounted
32499
+ * (settings/refresh/param change) — do not clear.
32500
+ */
32501
+ toPrimaryPath(url) {
32502
+ const primary = this.router.parseUrl(url).root.children[PRIMARY_OUTLET];
32503
+ // Join only the path of each segment, dropping matrix params, so a matrix-param-only change
32504
+ // (which keeps the dashboard mounted) does not read as a different route.
32505
+ return primary ? `/${primary.segments.map(segment => segment.path).join('/')}` : '';
32506
+ }
32314
32507
  exitFullScreen() {
32315
32508
  const doc = document;
32316
32509
  if (doc.exitFullscreen) {
@@ -32348,7 +32541,7 @@ class WidgetsDashboardComponent {
32348
32541
  });
32349
32542
  return true;
32350
32543
  }
32351
- catch (e) {
32544
+ catch {
32352
32545
  return false;
32353
32546
  }
32354
32547
  }
@@ -32405,8 +32598,8 @@ class WidgetsDashboardComponent {
32405
32598
  }
32406
32599
  return result;
32407
32600
  }
32408
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: WidgetsDashboardComponent, deps: [{ token: DynamicComponentService }, { token: i1$1.TranslateService }, { token: i1$4.ActivatedRoute }, { token: ModalService }, { token: WidgetGlobalAutoRefreshService }, { token: i1$4.Router }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
32409
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: WidgetsDashboardComponent, isStandalone: true, selector: "c8y-widgets-dashboard", inputs: { widgets: "widgets", context: "context", contextDashboard: "contextDashboard", _settings: ["settings", "_settings"], isCopyDisabled: "isCopyDisabled", breadcrumb: "breadcrumb", editModeButtons: "editModeButtons", isSaveDisabled: "isSaveDisabled" }, outputs: { onAddWidget: "onAddWidget", onEditWidget: "onEditWidget", onDeleteWidget: "onDeleteWidget", onChangeDashboard: "onChangeDashboard", onResize: "onResize", onEditDashboard: "onEditDashboard", onCopyDashboard: "onCopyDashboard", onDeleteDashboard: "onDeleteDashboard", onChangeStart: "onChangeStart", onChangeEnd: "onChangeEnd", onSaveDashboard: "onSaveDashboard", onCancelDashboard: "onCancelDashboard", revertChange: "revertChange" }, host: { styleAttribute: "\n display: block;\n ", classAttribute: "dashboard c8y-grid-dashboard" }, providers: [WidgetsDashboardEventService], usesOnChanges: true, ngImport: i0, template: "@if (!!settings.title) {\n <c8y-title>\n @let dashboardTitle =\n settings.translateDashboardTitle ? (settings.title | translate) : settings.title;\n {{ dashboardTitle }}\n </c8y-title>\n}\n\n@if (!!breadcrumb) {\n <c8y-breadcrumb>\n <c8y-breadcrumb-item\n [icon]=\"breadcrumb.icon\"\n [label]=\"breadcrumb.label\"\n [path]=\"breadcrumb.path\"\n ></c8y-breadcrumb-item>\n </c8y-breadcrumb>\n}\n\n@if (!(editMode$ | async)) {\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"ACTION_BAR_EDIT_WIDGETS_PRIORITY\"\n >\n <button\n class=\"btn btn-link animated fadeIn hidden-xs\"\n title=\"{{ 'Edit widgets' | translate }}\"\n type=\"button\"\n [disabled]=\"settings.isDisabled\"\n (click)=\"enableEditMode()\"\n data-cy=\"c8y-widget-dashboard--edit-widgets\"\n >\n <i c8yIcon=\"send-backward\"></i>\n <span class=\"m-l-4\">{{ 'Edit widgets' | translate }}</span>\n </button>\n <button\n class=\"btn btn-link visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"send-backward\"></i>\n <span class=\"m-l-4\">{{ 'Edit widgets' | translate }}</span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (editMode$ | async) {\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"ACTION_BAR_EDIT_WIDGETS_PRIORITY\"\n >\n <button\n class=\"btn btn-link animated fadeIn\"\n title=\"{{ 'Add widget' | translate }}\"\n type=\"button\"\n (click)=\"onAddWidget.emit()\"\n data-cy=\"widget-dashboard--Add-widget\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add widget' | translate }}\n </button>\n </c8y-action-bar-item>\n\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"ACTION_BAR_EDIT_WIDGETS_PRIORITY\"\n itemClass=\"d-flex a-i-center gap-8\"\n >\n <div class=\"input-group input-group-sm animated fadeIn\">\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default btn-sm btn-icon\"\n [attr.aria-label]=\"'Undo' | translate\"\n [tooltip]=\"\n editModeButtons.undoButtonDisabled\n ? ''\n : (undoMessage\n | translate: { changeToUndo: editModeButtons.changeToUndoName | translate })\n \"\n container=\"body\"\n (click)=\"revertChange.emit('undo')\"\n [disabled]=\"editModeButtons.undoButtonDisabled\"\n >\n <i [c8yIcon]=\"'undo'\"></i>\n </button>\n </div>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default btn-sm btn-icon\"\n [attr.aria-label]=\"'Redo' | translate\"\n [tooltip]=\"\n editModeButtons.redoButtonDisabled\n ? ''\n : (redoMessage\n | translate: { changeToRedo: editModeButtons.changeToRedoName | translate })\n \"\n container=\"body\"\n (click)=\"revertChange.emit('redo')\"\n [disabled]=\"editModeButtons.redoButtonDisabled\"\n >\n <i [c8yIcon]=\"'redo'\"></i>\n </button>\n </div>\n <span></span>\n </div>\n <div class=\"btn-group animated fadeIn\">\n <button\n class=\"btn btn-default btn-sm\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancelDashboardSave()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary btn-sm m-l-8\"\n title=\"{{ 'Save' | translate }}\"\n type=\"button\"\n [disabled]=\"editModeButtons.undoButtonDisabled && isSaveDisabled\"\n (click)=\"saveDashboard()\"\n data-cy=\"c8y-widgets-dashboard--save\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </c8y-action-bar-item>\n}\n\n@if (onEditDashboard.observers.length) {\n <c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link hidden-xs m-l-0\"\n title=\"{{ 'Dashboard settings' | translate }}\"\n type=\"button\"\n [disabled]=\"settings.isDisabled || (editMode$ | async)\"\n (click)=\"onEditDashboard.emit()\"\n data-cy=\"c8y-widgets-dashboard--edit-dashboard\"\n >\n <i c8yIcon=\"sorting-slider\"></i>\n <span class=\"visible-xs-inline hidden-sm visible-md-inline visible-lg-inline\">\n {{ 'Dashboard settings' | translate }}\n </span>\n </button>\n <button\n class=\"btn btn-link visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"sorting-slider\"></i>\n <span class=\"visible-xs-inline hidden-sm visible-md-inline visible-lg-inline\">\n {{ 'Dashboard settings' | translate }}\n </span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (settings.allowFullscreen) {\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"-5000\"\n itemClass=\"pull-right\"\n >\n <button\n class=\"btn btn-link\"\n [attr.aria-label]=\"'Full screen' | translate\"\n tooltip=\"{{ 'Full screen' | translate }}\"\n placement=\"left\"\n container=\"body\"\n type=\"button\"\n [delay]=\"500\"\n (click)=\"toggleFullscreen()\"\n data-cy=\"widgets-dashboard--Full-screen\"\n >\n <i [c8yIcon]=\"(inFullScreen$ | async) ? 'compress' : 'expand'\"></i>\n <span class=\"visible-xs-inline hidden-sm visible-md-inline visibile-lg-inline\">\n {{ 'Full screen' | translate }}\n </span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (settings.canCopy) {\n <c8y-action-bar-item\n [placement]=\"'more'\"\n [priority]=\"-2000\"\n >\n <div class=\"d-flex a-i-center p-r-16\">\n <button\n class=\"hidden-xs\"\n title=\"{{\n (isCopyDisabled === true || !isCopyDisabled?.state ? 'Disabled' : copyDashboardLabel)\n | translate\n }}\"\n type=\"button\"\n [ngClass]=\"{ 'btn btn-link': !settings.canDelete }\"\n data-cy=\"widgets-dashboard--copy-dashboard\"\n (click)=\"onCopyDashboard.emit()\"\n [disabled]=\"isCopyDisabled === true || !isCopyDisabled?.state || (editMode$ | async)\"\n >\n <i c8yIcon=\"clone\"></i>\n <span>{{ copyDashboardLabel | translate }}</span>\n </button>\n @if (!isCopyDisabled?.state && copyDisabledPopoverMsg) {\n <button\n class=\"btn-help btn-help--sm hidden-xs flex-no-shrink\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"copyDisabledPopoverMsg | translate\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n [adaptivePosition]=\"false\"\n data-cy=\"widgets-dashboard--info-copy-dashboard\"\n (click)=\"$event.preventDefault(); $event.stopPropagation()\"\n ></button>\n }\n </div>\n <button\n class=\"visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n [ngClass]=\"{ 'btn btn-link': !settings.canDelete }\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"clone\"></i>\n <span>{{ copyDashboardLabel | translate }}</span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (settings.canDelete && onDeleteDashboard.observers.length) {\n <c8y-action-bar-item\n [placement]=\"'more'\"\n [priority]=\"-3000\"\n >\n <button\n class=\"hidden-xs\"\n title=\"{{ 'Delete dashboard' | translate }}\"\n type=\"button\"\n data-cy=\"widgets-dashboard--delete-dashboard\"\n [ngClass]=\"{ 'btn btn-link': !settings.canCopy }\"\n (click)=\"onDeleteDashboard.emit()\"\n [disabled]=\"settings.isDisabled || (editMode$ | async)\"\n >\n <i c8yIcon=\"delete\"></i>\n <span translate>Delete dashboard</span>\n </button>\n <button\n class=\"visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n data-cy=\"widgets-dashboard--delete-dashboard-mobile\"\n [ngClass]=\"{ 'btn btn-link': !settings.canCopy }\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"delete\"></i>\n <span translate>Delete dashboard</span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (!(isLoadingWidgets$ | async)) {\n @if (resolvedWidgets$ | async; as widgetsToDisplay) {\n <!-- empty state -->\n @if (widgetsToDisplay?.length === 0) {\n <c8y-ui-empty-state\n [icon]=\"'c8y-device'\"\n [title]=\"'No widgets to display.' | translate\"\n >\n @if (onAddWidget.observers.length) {\n <div>\n @if (editMode$ | async) {\n <p translate>Add widgets to this dashboard.</p>\n }\n @if (!(editMode$ | async)) {\n <p translate>Click \"Edit widgets\" to unlock</p>\n }\n <div>\n @if (!settings.isDisabled && (editMode$ | async)) {\n <button\n class=\"btn btn-primary m-t-16\"\n title=\"{{ 'Add widget' | translate }}\"\n type=\"button\"\n (click)=\"onAddWidget.emit()\"\n data-cy=\"c8y-widgets-dashboard--add-widget\"\n translate\n >\n Add widget\n </button>\n }\n </div>\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a c8y-guide-href=\"/docs/cockpit/working-with-dashboards\">user documentation</a>.\n </small>\n </p>\n </div>\n }\n </c8y-ui-empty-state>\n }\n\n <c8y-dashboard\n [columns]=\"settings.columns\"\n (dashboardChange)=\"onChangeDashboard.emit($event)\"\n #dashboard\n >\n @for (widget of widgetsToDisplay; track widget.id || widget.name || $index) {\n <c8y-dashboard-child\n [class]=\"widget.classes\"\n [x]=\"widget._x\"\n [y]=\"widget._y\"\n [width]=\"widget._width || settings.defaultWidth\"\n [height]=\"widget._height || settings.defaultHeight\"\n [margin]=\"settings.widgetMargin\"\n [data]=\"widget\"\n [useIntersection]=\"true\"\n [editMode]=\"editMode$ | async\"\n (changeStart)=\"\n onChangeStart.emit({ widget: widget, source: child, dashboard: dashboard })\n \"\n (changeEnd)=\"onChangeEnd.emit({ widget: widget, source: child, dashboard: dashboard })\"\n (toggleFullscreen)=\"toggleFullscreenOnWidget(child)\"\n [canToggleFullscreen]=\"!(inFullScreen$ | async) || widgetInFullscreenMode\"\n #child\n >\n @let widgetTitle =\n widget.title | translate: { noTranslateRemoveContext: !settings.translateWidgetTitle };\n <c8y-dashboard-child-title [attr.title]=\"widgetTitle\">\n <span data-cy=\"c8y-dashboard-list--device-widget\">\n {{ widgetTitle }}\n </span>\n </c8y-dashboard-child-title>\n @if (onEditWidget.observers.length) {\n <c8y-dashboard-child-action>\n <button\n title=\"{{ 'Edit widget' | translate }}\"\n type=\"button\"\n data-cy=\"widgets-dashboard--Edit-widget\"\n (click)=\"onEditWidget.emit({ widget: widget, source: child, dashboard: dashboard })\"\n >\n <i c8yIcon=\"pencil\"></i>\n <span translate>Edit</span>\n </button>\n </c8y-dashboard-child-action>\n }\n @if (onDeleteWidget.observers.length) {\n <c8y-dashboard-child-action>\n <button\n title=\"{{ 'Remove widget' | translate }}\"\n type=\"button\"\n data-cy=\"c8y-widgets-dashboard--remove-widget\"\n (click)=\"\n onDeleteWidget.emit({ widget: widget, source: child, dashboard: dashboard })\n \"\n >\n <i c8yIcon=\"delete\"></i>\n <span translate>Remove</span>\n </button>\n </c8y-dashboard-child-action>\n }\n\n @if (child.intersected) {\n <c8y-dynamic-component\n [componentId]=\"widget.componentId || widget.name\"\n [config]=\"\n widget.templateUrl || widget.widgetComponent\n ? { child: widget, dashboard: contextDashboard, context: context }\n : widget.config\n \"\n (updateWidgetClasses)=\"updateWidgetClasses(widget, $event)\"\n ></c8y-dynamic-component>\n }\n </c8y-dashboard-child>\n }\n </c8y-dashboard>\n }\n} @else {\n <c8y-loading class=\"col-xs-12 text-center\"></c8y-loading>\n}\n", dependencies: [{ kind: "component", type: TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "component", type: BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i1$5.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i1$9.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "component", type: GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "directive", type: GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "component", type: DashboardComponent, selector: "c8y-dashboard", inputs: ["columns", "gap", "rows"], outputs: ["dashboardChange"] }, { kind: "component", type: DashboardChildComponent, selector: "c8y-dashboard-child", inputs: ["x", "y", "width", "height", "data", "margin", "useIntersection", "isFrozen", "canToggleFullscreen", "editMode", "class"], outputs: ["changeStart", "changeEnd", "toggleFullscreen"] }, { kind: "component", type: DashboardChildTitleComponent, selector: "c8y-dashboard-child-title" }, { kind: "component", type: DashboardChildActionComponent, selector: "c8y-dashboard-child-action" }, { kind: "component", type: DynamicComponentComponent, selector: "c8y-dynamic-component", inputs: ["componentId", "config", "mode", "notFoundError", "executeResolvers"], outputs: ["updateWidgetClasses"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
32601
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: WidgetsDashboardComponent, deps: [{ token: DynamicComponentService }, { token: i1$1.TranslateService }, { token: i1$4.ActivatedRoute }, { token: ModalService }, { token: WidgetGlobalAutoRefreshService }, { token: i1$4.Router }, { token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
32602
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.25", type: WidgetsDashboardComponent, isStandalone: true, selector: "c8y-widgets-dashboard", inputs: { widgets: "widgets", context: "context", contextDashboard: "contextDashboard", _settings: ["settings", "_settings"], isCopyDisabled: "isCopyDisabled", breadcrumb: "breadcrumb", editModeButtons: "editModeButtons", isSaveDisabled: "isSaveDisabled", clearWidgetContentOnLeave: "clearWidgetContentOnLeave" }, outputs: { onAddWidget: "onAddWidget", onEditWidget: "onEditWidget", onDeleteWidget: "onDeleteWidget", onChangeDashboard: "onChangeDashboard", onResize: "onResize", onEditDashboard: "onEditDashboard", onCopyDashboard: "onCopyDashboard", onDeleteDashboard: "onDeleteDashboard", onChangeStart: "onChangeStart", onChangeEnd: "onChangeEnd", onSaveDashboard: "onSaveDashboard", onCancelDashboard: "onCancelDashboard", revertChange: "revertChange" }, host: { styleAttribute: "\n display: block;\n ", classAttribute: "dashboard c8y-grid-dashboard" }, providers: [WidgetsDashboardEventService], viewQueries: [{ propertyName: "dashboardChildren", predicate: DashboardChildComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (!!settings.title) {\n <c8y-title>\n @let dashboardTitle =\n settings.translateDashboardTitle ? (settings.title | translate) : settings.title;\n {{ dashboardTitle }}\n </c8y-title>\n}\n\n@if (!!breadcrumb) {\n <c8y-breadcrumb>\n <c8y-breadcrumb-item\n [icon]=\"breadcrumb.icon\"\n [label]=\"breadcrumb.label\"\n [path]=\"breadcrumb.path\"\n ></c8y-breadcrumb-item>\n </c8y-breadcrumb>\n}\n\n@if (!(editMode$ | async)) {\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"ACTION_BAR_EDIT_WIDGETS_PRIORITY\"\n >\n <button\n class=\"btn btn-link animated fadeIn hidden-xs\"\n title=\"{{ 'Edit widgets' | translate }}\"\n type=\"button\"\n [disabled]=\"settings.isDisabled\"\n (click)=\"enableEditMode()\"\n data-cy=\"c8y-widget-dashboard--edit-widgets\"\n >\n <i c8yIcon=\"send-backward\"></i>\n <span class=\"m-l-4\">{{ 'Edit widgets' | translate }}</span>\n </button>\n <button\n class=\"btn btn-link visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"send-backward\"></i>\n <span class=\"m-l-4\">{{ 'Edit widgets' | translate }}</span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (editMode$ | async) {\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"ACTION_BAR_EDIT_WIDGETS_PRIORITY\"\n >\n <button\n class=\"btn btn-link animated fadeIn\"\n title=\"{{ 'Add widget' | translate }}\"\n type=\"button\"\n (click)=\"onAddWidget.emit()\"\n data-cy=\"widget-dashboard--Add-widget\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add widget' | translate }}\n </button>\n </c8y-action-bar-item>\n\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"ACTION_BAR_EDIT_WIDGETS_PRIORITY\"\n itemClass=\"d-flex a-i-center gap-8\"\n >\n <div class=\"input-group input-group-sm animated fadeIn\">\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default btn-sm btn-icon\"\n [attr.aria-label]=\"'Undo' | translate\"\n [tooltip]=\"\n editModeButtons.undoButtonDisabled\n ? ''\n : (undoMessage\n | translate: { changeToUndo: editModeButtons.changeToUndoName | translate })\n \"\n container=\"body\"\n (click)=\"revertChange.emit('undo')\"\n [disabled]=\"editModeButtons.undoButtonDisabled\"\n >\n <i [c8yIcon]=\"'undo'\"></i>\n </button>\n </div>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default btn-sm btn-icon\"\n [attr.aria-label]=\"'Redo' | translate\"\n [tooltip]=\"\n editModeButtons.redoButtonDisabled\n ? ''\n : (redoMessage\n | translate: { changeToRedo: editModeButtons.changeToRedoName | translate })\n \"\n container=\"body\"\n (click)=\"revertChange.emit('redo')\"\n [disabled]=\"editModeButtons.redoButtonDisabled\"\n >\n <i [c8yIcon]=\"'redo'\"></i>\n </button>\n </div>\n <span></span>\n </div>\n <div class=\"btn-group animated fadeIn\">\n <button\n class=\"btn btn-default btn-sm\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancelDashboardSave()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary btn-sm m-l-8\"\n title=\"{{ 'Save' | translate }}\"\n type=\"button\"\n [disabled]=\"editModeButtons.undoButtonDisabled && isSaveDisabled\"\n (click)=\"saveDashboard()\"\n data-cy=\"c8y-widgets-dashboard--save\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </c8y-action-bar-item>\n}\n\n@if (onEditDashboard.observers.length) {\n <c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link hidden-xs m-l-0\"\n title=\"{{ 'Dashboard settings' | translate }}\"\n type=\"button\"\n [disabled]=\"settings.isDisabled || (editMode$ | async)\"\n (click)=\"onEditDashboard.emit()\"\n data-cy=\"c8y-widgets-dashboard--edit-dashboard\"\n >\n <i c8yIcon=\"sorting-slider\"></i>\n <span class=\"visible-xs-inline hidden-sm visible-md-inline visible-lg-inline\">\n {{ 'Dashboard settings' | translate }}\n </span>\n </button>\n <button\n class=\"btn btn-link visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"sorting-slider\"></i>\n <span class=\"visible-xs-inline hidden-sm visible-md-inline visible-lg-inline\">\n {{ 'Dashboard settings' | translate }}\n </span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (settings.allowFullscreen) {\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"-5000\"\n itemClass=\"pull-right\"\n >\n <button\n class=\"btn btn-link\"\n [attr.aria-label]=\"'Full screen' | translate\"\n tooltip=\"{{ 'Full screen' | translate }}\"\n placement=\"left\"\n container=\"body\"\n type=\"button\"\n [delay]=\"500\"\n (click)=\"toggleFullscreen()\"\n data-cy=\"widgets-dashboard--Full-screen\"\n >\n <i [c8yIcon]=\"(inFullScreen$ | async) ? 'compress' : 'expand'\"></i>\n <span class=\"visible-xs-inline hidden-sm visible-md-inline visibile-lg-inline\">\n {{ 'Full screen' | translate }}\n </span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (settings.canCopy) {\n <c8y-action-bar-item\n [placement]=\"'more'\"\n [priority]=\"-2000\"\n >\n <div class=\"d-flex a-i-center p-r-16\">\n <button\n class=\"hidden-xs\"\n title=\"{{\n (isCopyDisabled === true || !isCopyDisabled?.state ? 'Disabled' : copyDashboardLabel)\n | translate\n }}\"\n type=\"button\"\n [ngClass]=\"{ 'btn btn-link': !settings.canDelete }\"\n data-cy=\"widgets-dashboard--copy-dashboard\"\n (click)=\"onCopyDashboard.emit()\"\n [disabled]=\"isCopyDisabled === true || !isCopyDisabled?.state || (editMode$ | async)\"\n >\n <i c8yIcon=\"clone\"></i>\n <span>{{ copyDashboardLabel | translate }}</span>\n </button>\n @if (!isCopyDisabled?.state && copyDisabledPopoverMsg) {\n <button\n class=\"btn-help btn-help--sm hidden-xs flex-no-shrink\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"copyDisabledPopoverMsg | translate\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n [adaptivePosition]=\"false\"\n data-cy=\"widgets-dashboard--info-copy-dashboard\"\n (click)=\"$event.preventDefault(); $event.stopPropagation()\"\n ></button>\n }\n </div>\n <button\n class=\"visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n [ngClass]=\"{ 'btn btn-link': !settings.canDelete }\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"clone\"></i>\n <span>{{ copyDashboardLabel | translate }}</span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (settings.canDelete && onDeleteDashboard.observers.length) {\n <c8y-action-bar-item\n [placement]=\"'more'\"\n [priority]=\"-3000\"\n >\n <button\n class=\"hidden-xs\"\n title=\"{{ 'Delete dashboard' | translate }}\"\n type=\"button\"\n data-cy=\"widgets-dashboard--delete-dashboard\"\n [ngClass]=\"{ 'btn btn-link': !settings.canCopy }\"\n (click)=\"onDeleteDashboard.emit()\"\n [disabled]=\"settings.isDisabled || (editMode$ | async)\"\n >\n <i c8yIcon=\"delete\"></i>\n <span translate>Delete dashboard</span>\n </button>\n <button\n class=\"visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n data-cy=\"widgets-dashboard--delete-dashboard-mobile\"\n [ngClass]=\"{ 'btn btn-link': !settings.canCopy }\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"delete\"></i>\n <span translate>Delete dashboard</span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (!(isLoadingWidgets$ | async)) {\n @if (resolvedWidgets$ | async; as widgetsToDisplay) {\n <!-- empty state -->\n @if (widgetsToDisplay?.length === 0) {\n <c8y-ui-empty-state\n [icon]=\"'c8y-device'\"\n [title]=\"'No widgets to display.' | translate\"\n >\n @if (onAddWidget.observers.length) {\n <div>\n @if (editMode$ | async) {\n <p translate>Add widgets to this dashboard.</p>\n }\n @if (!(editMode$ | async)) {\n <p translate>Click \"Edit widgets\" to unlock</p>\n }\n <div>\n @if (!settings.isDisabled && (editMode$ | async)) {\n <button\n class=\"btn btn-primary m-t-16\"\n title=\"{{ 'Add widget' | translate }}\"\n type=\"button\"\n (click)=\"onAddWidget.emit()\"\n data-cy=\"c8y-widgets-dashboard--add-widget\"\n translate\n >\n Add widget\n </button>\n }\n </div>\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a c8y-guide-href=\"/docs/cockpit/working-with-dashboards\">user documentation</a>.\n </small>\n </p>\n </div>\n }\n </c8y-ui-empty-state>\n }\n\n <c8y-dashboard\n [columns]=\"settings.columns\"\n (dashboardChange)=\"onChangeDashboard.emit($event)\"\n #dashboard\n >\n @for (widget of widgetsToDisplay; track widget.id || widget.name || $index) {\n <c8y-dashboard-child\n [class]=\"widget.classes\"\n [x]=\"widget._x\"\n [y]=\"widget._y\"\n [width]=\"widget._width || settings.defaultWidth\"\n [height]=\"widget._height || settings.defaultHeight\"\n [margin]=\"settings.widgetMargin\"\n [data]=\"widget\"\n [useIntersection]=\"true\"\n [editMode]=\"editMode$ | async\"\n (changeStart)=\"\n onChangeStart.emit({ widget: widget, source: child, dashboard: dashboard })\n \"\n (changeEnd)=\"onChangeEnd.emit({ widget: widget, source: child, dashboard: dashboard })\"\n (toggleFullscreen)=\"toggleFullscreenOnWidget(child)\"\n [canToggleFullscreen]=\"!(inFullScreen$ | async) || widgetInFullscreenMode\"\n #child\n >\n @let widgetTitle =\n widget.title | translate: { noTranslateRemoveContext: !settings.translateWidgetTitle };\n <c8y-dashboard-child-title [attr.title]=\"widgetTitle\">\n <span data-cy=\"c8y-dashboard-list--device-widget\">\n {{ widgetTitle }}\n </span>\n </c8y-dashboard-child-title>\n @if (onEditWidget.observers.length) {\n <c8y-dashboard-child-action>\n <button\n title=\"{{ 'Edit widget' | translate }}\"\n type=\"button\"\n data-cy=\"widgets-dashboard--Edit-widget\"\n (click)=\"onEditWidget.emit({ widget: widget, source: child, dashboard: dashboard })\"\n >\n <i c8yIcon=\"pencil\"></i>\n <span translate>Edit</span>\n </button>\n </c8y-dashboard-child-action>\n }\n @if (onDeleteWidget.observers.length) {\n <c8y-dashboard-child-action>\n <button\n title=\"{{ 'Remove widget' | translate }}\"\n type=\"button\"\n data-cy=\"c8y-widgets-dashboard--remove-widget\"\n (click)=\"\n onDeleteWidget.emit({ widget: widget, source: child, dashboard: dashboard })\n \"\n >\n <i c8yIcon=\"delete\"></i>\n <span translate>Remove</span>\n </button>\n </c8y-dashboard-child-action>\n }\n\n @if (child.intersected) {\n <c8y-dynamic-component\n [componentId]=\"widget.componentId || widget.name\"\n [config]=\"\n widget.templateUrl || widget.widgetComponent\n ? { child: widget, dashboard: contextDashboard, context: context }\n : widget.config\n \"\n (updateWidgetClasses)=\"updateWidgetClasses(widget, $event)\"\n ></c8y-dynamic-component>\n }\n </c8y-dashboard-child>\n }\n </c8y-dashboard>\n }\n} @else {\n <c8y-loading class=\"col-xs-12 text-center\"></c8y-loading>\n}\n", dependencies: [{ kind: "component", type: TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "component", type: BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i1$5.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i1$9.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "component", type: GuideDocsComponent, selector: "[c8y-guide-docs]" }, { kind: "directive", type: GuideHrefDirective, selector: "[c8y-guide-href]", inputs: ["c8y-guide-href"] }, { kind: "component", type: DashboardComponent, selector: "c8y-dashboard", inputs: ["columns", "gap", "rows"], outputs: ["dashboardChange"] }, { kind: "component", type: DashboardChildComponent, selector: "c8y-dashboard-child", inputs: ["x", "y", "width", "height", "data", "margin", "useIntersection", "isFrozen", "canToggleFullscreen", "editMode", "class"], outputs: ["changeStart", "changeEnd", "toggleFullscreen"] }, { kind: "component", type: DashboardChildTitleComponent, selector: "c8y-dashboard-child-title" }, { kind: "component", type: DashboardChildActionComponent, selector: "c8y-dashboard-child-action" }, { kind: "component", type: DynamicComponentComponent, selector: "c8y-dynamic-component", inputs: ["componentId", "config", "mode", "notFoundError", "executeResolvers"], outputs: ["updateWidgetClasses"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
32410
32603
  }
32411
32604
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: WidgetsDashboardComponent, decorators: [{
32412
32605
  type: Component,
@@ -32440,7 +32633,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
32440
32633
  C8yTranslatePipe,
32441
32634
  AsyncPipe
32442
32635
  ], template: "@if (!!settings.title) {\n <c8y-title>\n @let dashboardTitle =\n settings.translateDashboardTitle ? (settings.title | translate) : settings.title;\n {{ dashboardTitle }}\n </c8y-title>\n}\n\n@if (!!breadcrumb) {\n <c8y-breadcrumb>\n <c8y-breadcrumb-item\n [icon]=\"breadcrumb.icon\"\n [label]=\"breadcrumb.label\"\n [path]=\"breadcrumb.path\"\n ></c8y-breadcrumb-item>\n </c8y-breadcrumb>\n}\n\n@if (!(editMode$ | async)) {\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"ACTION_BAR_EDIT_WIDGETS_PRIORITY\"\n >\n <button\n class=\"btn btn-link animated fadeIn hidden-xs\"\n title=\"{{ 'Edit widgets' | translate }}\"\n type=\"button\"\n [disabled]=\"settings.isDisabled\"\n (click)=\"enableEditMode()\"\n data-cy=\"c8y-widget-dashboard--edit-widgets\"\n >\n <i c8yIcon=\"send-backward\"></i>\n <span class=\"m-l-4\">{{ 'Edit widgets' | translate }}</span>\n </button>\n <button\n class=\"btn btn-link visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"send-backward\"></i>\n <span class=\"m-l-4\">{{ 'Edit widgets' | translate }}</span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (editMode$ | async) {\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"ACTION_BAR_EDIT_WIDGETS_PRIORITY\"\n >\n <button\n class=\"btn btn-link animated fadeIn\"\n title=\"{{ 'Add widget' | translate }}\"\n type=\"button\"\n (click)=\"onAddWidget.emit()\"\n data-cy=\"widget-dashboard--Add-widget\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add widget' | translate }}\n </button>\n </c8y-action-bar-item>\n\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"ACTION_BAR_EDIT_WIDGETS_PRIORITY\"\n itemClass=\"d-flex a-i-center gap-8\"\n >\n <div class=\"input-group input-group-sm animated fadeIn\">\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default btn-sm btn-icon\"\n [attr.aria-label]=\"'Undo' | translate\"\n [tooltip]=\"\n editModeButtons.undoButtonDisabled\n ? ''\n : (undoMessage\n | translate: { changeToUndo: editModeButtons.changeToUndoName | translate })\n \"\n container=\"body\"\n (click)=\"revertChange.emit('undo')\"\n [disabled]=\"editModeButtons.undoButtonDisabled\"\n >\n <i [c8yIcon]=\"'undo'\"></i>\n </button>\n </div>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-default btn-sm btn-icon\"\n [attr.aria-label]=\"'Redo' | translate\"\n [tooltip]=\"\n editModeButtons.redoButtonDisabled\n ? ''\n : (redoMessage\n | translate: { changeToRedo: editModeButtons.changeToRedoName | translate })\n \"\n container=\"body\"\n (click)=\"revertChange.emit('redo')\"\n [disabled]=\"editModeButtons.redoButtonDisabled\"\n >\n <i [c8yIcon]=\"'redo'\"></i>\n </button>\n </div>\n <span></span>\n </div>\n <div class=\"btn-group animated fadeIn\">\n <button\n class=\"btn btn-default btn-sm\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancelDashboardSave()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary btn-sm m-l-8\"\n title=\"{{ 'Save' | translate }}\"\n type=\"button\"\n [disabled]=\"editModeButtons.undoButtonDisabled && isSaveDisabled\"\n (click)=\"saveDashboard()\"\n data-cy=\"c8y-widgets-dashboard--save\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </c8y-action-bar-item>\n}\n\n@if (onEditDashboard.observers.length) {\n <c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link hidden-xs m-l-0\"\n title=\"{{ 'Dashboard settings' | translate }}\"\n type=\"button\"\n [disabled]=\"settings.isDisabled || (editMode$ | async)\"\n (click)=\"onEditDashboard.emit()\"\n data-cy=\"c8y-widgets-dashboard--edit-dashboard\"\n >\n <i c8yIcon=\"sorting-slider\"></i>\n <span class=\"visible-xs-inline hidden-sm visible-md-inline visible-lg-inline\">\n {{ 'Dashboard settings' | translate }}\n </span>\n </button>\n <button\n class=\"btn btn-link visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"sorting-slider\"></i>\n <span class=\"visible-xs-inline hidden-sm visible-md-inline visible-lg-inline\">\n {{ 'Dashboard settings' | translate }}\n </span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (settings.allowFullscreen) {\n <c8y-action-bar-item\n [placement]=\"'right'\"\n [priority]=\"-5000\"\n itemClass=\"pull-right\"\n >\n <button\n class=\"btn btn-link\"\n [attr.aria-label]=\"'Full screen' | translate\"\n tooltip=\"{{ 'Full screen' | translate }}\"\n placement=\"left\"\n container=\"body\"\n type=\"button\"\n [delay]=\"500\"\n (click)=\"toggleFullscreen()\"\n data-cy=\"widgets-dashboard--Full-screen\"\n >\n <i [c8yIcon]=\"(inFullScreen$ | async) ? 'compress' : 'expand'\"></i>\n <span class=\"visible-xs-inline hidden-sm visible-md-inline visibile-lg-inline\">\n {{ 'Full screen' | translate }}\n </span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (settings.canCopy) {\n <c8y-action-bar-item\n [placement]=\"'more'\"\n [priority]=\"-2000\"\n >\n <div class=\"d-flex a-i-center p-r-16\">\n <button\n class=\"hidden-xs\"\n title=\"{{\n (isCopyDisabled === true || !isCopyDisabled?.state ? 'Disabled' : copyDashboardLabel)\n | translate\n }}\"\n type=\"button\"\n [ngClass]=\"{ 'btn btn-link': !settings.canDelete }\"\n data-cy=\"widgets-dashboard--copy-dashboard\"\n (click)=\"onCopyDashboard.emit()\"\n [disabled]=\"isCopyDisabled === true || !isCopyDisabled?.state || (editMode$ | async)\"\n >\n <i c8yIcon=\"clone\"></i>\n <span>{{ copyDashboardLabel | translate }}</span>\n </button>\n @if (!isCopyDisabled?.state && copyDisabledPopoverMsg) {\n <button\n class=\"btn-help btn-help--sm hidden-xs flex-no-shrink\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"copyDisabledPopoverMsg | translate\"\n placement=\"top\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n [adaptivePosition]=\"false\"\n data-cy=\"widgets-dashboard--info-copy-dashboard\"\n (click)=\"$event.preventDefault(); $event.stopPropagation()\"\n ></button>\n }\n </div>\n <button\n class=\"visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n [ngClass]=\"{ 'btn btn-link': !settings.canDelete }\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"clone\"></i>\n <span>{{ copyDashboardLabel | translate }}</span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (settings.canDelete && onDeleteDashboard.observers.length) {\n <c8y-action-bar-item\n [placement]=\"'more'\"\n [priority]=\"-3000\"\n >\n <button\n class=\"hidden-xs\"\n title=\"{{ 'Delete dashboard' | translate }}\"\n type=\"button\"\n data-cy=\"widgets-dashboard--delete-dashboard\"\n [ngClass]=\"{ 'btn btn-link': !settings.canCopy }\"\n (click)=\"onDeleteDashboard.emit()\"\n [disabled]=\"settings.isDisabled || (editMode$ | async)\"\n >\n <i c8yIcon=\"delete\"></i>\n <span translate>Delete dashboard</span>\n </button>\n <button\n class=\"visible-xs m-l-0\"\n tooltip=\"{{ 'Not available on mobile phone' | translate }}\"\n type=\"button\"\n data-cy=\"widgets-dashboard--delete-dashboard-mobile\"\n [ngClass]=\"{ 'btn btn-link': !settings.canCopy }\"\n [disabled]=\"true\"\n >\n <i c8yIcon=\"delete\"></i>\n <span translate>Delete dashboard</span>\n </button>\n </c8y-action-bar-item>\n}\n\n@if (!(isLoadingWidgets$ | async)) {\n @if (resolvedWidgets$ | async; as widgetsToDisplay) {\n <!-- empty state -->\n @if (widgetsToDisplay?.length === 0) {\n <c8y-ui-empty-state\n [icon]=\"'c8y-device'\"\n [title]=\"'No widgets to display.' | translate\"\n >\n @if (onAddWidget.observers.length) {\n <div>\n @if (editMode$ | async) {\n <p translate>Add widgets to this dashboard.</p>\n }\n @if (!(editMode$ | async)) {\n <p translate>Click \"Edit widgets\" to unlock</p>\n }\n <div>\n @if (!settings.isDisabled && (editMode$ | async)) {\n <button\n class=\"btn btn-primary m-t-16\"\n title=\"{{ 'Add widget' | translate }}\"\n type=\"button\"\n (click)=\"onAddWidget.emit()\"\n data-cy=\"c8y-widgets-dashboard--add-widget\"\n translate\n >\n Add widget\n </button>\n }\n </div>\n <p c8y-guide-docs>\n <small\n translate\n ngNonBindable\n >\n Find out more in the\n <a c8y-guide-href=\"/docs/cockpit/working-with-dashboards\">user documentation</a>.\n </small>\n </p>\n </div>\n }\n </c8y-ui-empty-state>\n }\n\n <c8y-dashboard\n [columns]=\"settings.columns\"\n (dashboardChange)=\"onChangeDashboard.emit($event)\"\n #dashboard\n >\n @for (widget of widgetsToDisplay; track widget.id || widget.name || $index) {\n <c8y-dashboard-child\n [class]=\"widget.classes\"\n [x]=\"widget._x\"\n [y]=\"widget._y\"\n [width]=\"widget._width || settings.defaultWidth\"\n [height]=\"widget._height || settings.defaultHeight\"\n [margin]=\"settings.widgetMargin\"\n [data]=\"widget\"\n [useIntersection]=\"true\"\n [editMode]=\"editMode$ | async\"\n (changeStart)=\"\n onChangeStart.emit({ widget: widget, source: child, dashboard: dashboard })\n \"\n (changeEnd)=\"onChangeEnd.emit({ widget: widget, source: child, dashboard: dashboard })\"\n (toggleFullscreen)=\"toggleFullscreenOnWidget(child)\"\n [canToggleFullscreen]=\"!(inFullScreen$ | async) || widgetInFullscreenMode\"\n #child\n >\n @let widgetTitle =\n widget.title | translate: { noTranslateRemoveContext: !settings.translateWidgetTitle };\n <c8y-dashboard-child-title [attr.title]=\"widgetTitle\">\n <span data-cy=\"c8y-dashboard-list--device-widget\">\n {{ widgetTitle }}\n </span>\n </c8y-dashboard-child-title>\n @if (onEditWidget.observers.length) {\n <c8y-dashboard-child-action>\n <button\n title=\"{{ 'Edit widget' | translate }}\"\n type=\"button\"\n data-cy=\"widgets-dashboard--Edit-widget\"\n (click)=\"onEditWidget.emit({ widget: widget, source: child, dashboard: dashboard })\"\n >\n <i c8yIcon=\"pencil\"></i>\n <span translate>Edit</span>\n </button>\n </c8y-dashboard-child-action>\n }\n @if (onDeleteWidget.observers.length) {\n <c8y-dashboard-child-action>\n <button\n title=\"{{ 'Remove widget' | translate }}\"\n type=\"button\"\n data-cy=\"c8y-widgets-dashboard--remove-widget\"\n (click)=\"\n onDeleteWidget.emit({ widget: widget, source: child, dashboard: dashboard })\n \"\n >\n <i c8yIcon=\"delete\"></i>\n <span translate>Remove</span>\n </button>\n </c8y-dashboard-child-action>\n }\n\n @if (child.intersected) {\n <c8y-dynamic-component\n [componentId]=\"widget.componentId || widget.name\"\n [config]=\"\n widget.templateUrl || widget.widgetComponent\n ? { child: widget, dashboard: contextDashboard, context: context }\n : widget.config\n \"\n (updateWidgetClasses)=\"updateWidgetClasses(widget, $event)\"\n ></c8y-dynamic-component>\n }\n </c8y-dashboard-child>\n }\n </c8y-dashboard>\n }\n} @else {\n <c8y-loading class=\"col-xs-12 text-center\"></c8y-loading>\n}\n" }]
32443
- }], ctorParameters: () => [{ type: DynamicComponentService }, { type: i1$1.TranslateService }, { type: i1$4.ActivatedRoute }, { type: ModalService }, { type: WidgetGlobalAutoRefreshService }, { type: i1$4.Router }, { type: i0.ElementRef }], propDecorators: { widgets: [{
32636
+ }], ctorParameters: () => [{ type: DynamicComponentService }, { type: i1$1.TranslateService }, { type: i1$4.ActivatedRoute }, { type: ModalService }, { type: WidgetGlobalAutoRefreshService }, { type: i1$4.Router }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { widgets: [{
32444
32637
  type: Input
32445
32638
  }], context: [{
32446
32639
  type: Input
@@ -32483,6 +32676,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
32483
32676
  type: Output
32484
32677
  }], revertChange: [{
32485
32678
  type: Output
32679
+ }], dashboardChildren: [{
32680
+ type: ViewChildren,
32681
+ args: [DashboardChildComponent]
32682
+ }], clearWidgetContentOnLeave: [{
32683
+ type: Input
32486
32684
  }] } });
32487
32685
 
32488
32686
  class HelpService {