Browse Source

Merge branch 'master' into feature/new-menu

pull/15888/head
Igor Kulikov 3 months ago
parent
commit
908b593f19
  1. 4
      ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts
  2. 23
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts
  3. 7
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.scss
  4. 10
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts
  5. 10
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.html
  6. 17
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.html
  7. 1
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.scss
  8. 10
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.ts
  9. 2
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.html
  10. 4
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.ts
  11. 8
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.ts
  12. 2
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-update-dialog.component.html
  13. 4
      ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-update-dialog.component.ts
  14. 5
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html
  15. 11
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.ts
  16. 5
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html
  17. 13
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts
  18. 1
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
  19. 3
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts
  20. 6
      ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts
  21. 4
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-tooltip.models.ts
  22. 1
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts
  23. 2
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts
  24. 76
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  25. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html
  26. 10
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts
  27. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html
  28. 10
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts
  29. 2
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
  30. 5
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts
  31. 3
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  32. 2
      ui-ngx/src/app/modules/home/home.component.html
  33. 3
      ui-ngx/src/app/modules/home/home.component.ts
  34. 2
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  35. 8
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss
  36. 31
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts
  37. 4
      ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.html
  38. 12
      ui-ngx/src/app/shared/models/iot-hub/iot-hub-installed-item.models.ts
  39. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
ui-ngx/src/app/modules/home/components/iot-hub/device-install-dialog/device-install-dialog.component.ts

@ -884,7 +884,7 @@ export class TbDeviceInstallDialogComponent extends DialogComponent<TbDeviceInst
return { id: gatewayOutput.id, name: connectorName }; return { id: gatewayOutput.id, name: connectorName };
} }
case InstallStepType.DASHBOARD: { case InstallStepType.DASHBOARD: {
const result = await firstValueFrom(this.dashboardService.saveDashboard(template, {ignoreErrors: true})); const result = await firstValueFrom(this.dashboardService.saveDashboard(template, {ignoreErrors: false}));
return { id: result.id.id, name: result.title, url: `/dashboards/${result.id.id}` }; return { id: result.id.id, name: result.title, url: `/dashboards/${result.id.id}` };
} }
case InstallStepType.RULE_CHAIN: { case InstallStepType.RULE_CHAIN: {
@ -999,7 +999,7 @@ export class TbDeviceInstallDialogComponent extends DialogComponent<TbDeviceInst
} }
case InstallStepType.DASHBOARD: { case InstallStepType.DASHBOARD: {
template.id = { id: existing.id, entityType: 'DASHBOARD' }; template.id = { id: existing.id, entityType: 'DASHBOARD' };
const result = await firstValueFrom(this.dashboardService.saveDashboard(template, {ignoreErrors: true})); const result = await firstValueFrom(this.dashboardService.saveDashboard(template, {ignoreErrors: false}));
return { id: result.id.id, name: result.title, url: `/dashboards/${result.id.id}` }; return { id: result.id.id, name: result.title, url: `/dashboards/${result.id.id}` };
} }
case InstallStepType.RULE_CHAIN: { case InstallStepType.RULE_CHAIN: {

23
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-actions.service.ts

@ -16,8 +16,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Observable, of, EMPTY } from 'rxjs'; import { Observable, of } from 'rxjs';
import { filter, mergeMap } from 'rxjs/operators'; import { map, mergeMap } from 'rxjs/operators';
import { MpItemVersionView } from '@shared/models/iot-hub/iot-hub-version.models'; import { MpItemVersionView } from '@shared/models/iot-hub/iot-hub-version.models';
import { ItemType } from '@shared/models/iot-hub/iot-hub-item.models'; import { ItemType } from '@shared/models/iot-hub/iot-hub-item.models';
import { DeviceInstalledItemDescriptor, IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-item.models'; import { DeviceInstalledItemDescriptor, IotHubInstalledItem } from '@shared/models/iot-hub/iot-hub-installed-item.models';
@ -83,11 +83,11 @@ export class IotHubActionsService {
}).afterClosed(); }).afterClosed();
} }
updateItem(installedItem: IotHubInstalledItem, version: string, versionId: string): Observable<string> { updateItem(installedItem: IotHubInstalledItem, version: string, versionId: string): Observable<string | boolean> {
if (!installedItem) { if (!installedItem) {
return EMPTY; return of(false);
} }
return this.dialog.open(TbIotHubUpdateDialogComponent, { return this.dialog.open<TbIotHubUpdateDialogComponent, IotHubUpdateDialogData, string | boolean>(TbIotHubUpdateDialogComponent, {
panelClass: ['tb-dialog'], panelClass: ['tb-dialog'],
disableClose: true, disableClose: true,
autoFocus: false, autoFocus: false,
@ -97,7 +97,7 @@ export class IotHubActionsService {
itemType: installedItem.itemType as ItemType, itemType: installedItem.itemType as ItemType,
version, version,
versionId versionId
} as IotHubUpdateDialogData }
}).afterClosed(); }).afterClosed();
} }
@ -105,15 +105,16 @@ export class IotHubActionsService {
if (!installedItem) { if (!installedItem) {
return of(false); return of(false);
} }
return this.dialog.open(TbIotHubDeleteDialogComponent, { return this.dialog.open<TbIotHubDeleteDialogComponent, IotHubDeleteDialogData, boolean>(TbIotHubDeleteDialogComponent, {
panelClass: ['tb-dialog'], panelClass: ['tb-dialog'],
disableClose: true, disableClose: true,
autoFocus: false, autoFocus: false,
data: { itemName: installedItem.itemName, itemType: installedItem.itemType } as IotHubDeleteDialogData data: { itemName: installedItem.itemName, itemType: installedItem.itemType }
}).afterClosed().pipe( }).afterClosed().pipe(
filter(confirmed => !!confirmed), mergeMap((confirmed) =>
mergeMap(() => this.iotHubApiService.deleteInstalledItem(installedItem.id.id)), confirmed
mergeMap(() => of(true)) ? this.iotHubApiService.deleteInstalledItem(installedItem.id.id).pipe(map(() => true))
: of(false) )
); );
} }

7
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.scss

@ -359,14 +359,17 @@
} }
} }
// Filter chips row — Design: 32px tall, no padding, centered
.tb-iot-hub-sort-row { .tb-iot-hub-sort-row {
height: 32px; min-height: 32px;
align-items: center; align-items: center;
padding: 0; padding: 0;
margin-top: 16px; margin-top: 16px;
margin-bottom: 12px; margin-bottom: 12px;
::ng-deep .mat-mdc-chip-set .mdc-evolution-chip-set__chips {
row-gap: 8px;
}
.mat-mdc-chip { .mat-mdc-chip {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;

10
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-browse.component.ts

@ -580,8 +580,7 @@ export class TbIotHubBrowseComponent implements OnInit, AfterViewInit, OnDestroy
updateItem(item: MpItemVersionView): void { updateItem(item: MpItemVersionView): void {
const installedItem = this.getInstalledItem(item); const installedItem = this.getInstalledItem(item);
if (!installedItem) { return; } this.iotHubActions.updateItem(installedItem, item.version, item.id).subscribe(result => {
this.iotHubActions.updateItem(installedItem, item.version, item.id as string).subscribe(result => {
if (result === 'updated') { if (result === 'updated') {
this.reloadInstalledItems(); this.reloadInstalledItems();
} }
@ -590,9 +589,10 @@ export class TbIotHubBrowseComponent implements OnInit, AfterViewInit, OnDestroy
deleteInstalledItem(item: MpItemVersionView): void { deleteInstalledItem(item: MpItemVersionView): void {
const installedItem = this.getInstalledItem(item); const installedItem = this.getInstalledItem(item);
if (!installedItem) { return; } this.iotHubActions.deleteItem(installedItem).subscribe((deleted) => {
this.iotHubActions.deleteItem(installedItem).subscribe(() => { if (deleted) {
this.reloadInstalledItems(); this.reloadInstalledItems();
}
}); });
} }

10
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-install-dialog.component.html

@ -157,7 +157,7 @@
<button mat-button [disabled]="resolvingPlan" (click)="cancel()">{{ 'action.cancel' | translate }}</button> <button mat-button [disabled]="resolvingPlan" (click)="cancel()">{{ 'action.cancel' | translate }}</button>
<button mat-flat-button color="primary" [disabled]="resolvingPlan" (click)="install()"> <button mat-flat-button color="primary" [disabled]="resolvingPlan" (click)="install()">
@if (resolvingPlan) { @if (resolvingPlan) {
<mat-spinner diameter="18" class="mr-2 inline-block align-middle"></mat-spinner> <mat-spinner matButtonIcon diameter="18" class="mr-2"></mat-spinner>
} }
{{ 'iot-hub.install' | translate }} {{ 'iot-hub.install' | translate }}
</button> </button>
@ -169,7 +169,7 @@
[disabled]="ruleChainInstallForm.invalid || resolvingPlan" [disabled]="ruleChainInstallForm.invalid || resolvingPlan"
(click)="onRuleChainInstall()"> (click)="onRuleChainInstall()">
@if (resolvingPlan) { @if (resolvingPlan) {
<mat-spinner diameter="18" class="mr-2 inline-block align-middle"></mat-spinner> <mat-spinner matButtonIcon diameter="18" class="mr-2"></mat-spinner>
} }
{{ 'iot-hub.install' | translate }} {{ 'iot-hub.install' | translate }}
</button> </button>
@ -178,7 +178,7 @@
[disabled]="(activeSelectEntityConfig.required && !selectedEntityId) || resolvingPlan" [disabled]="(activeSelectEntityConfig.required && !selectedEntityId) || resolvingPlan"
(click)="onEntitySelectInstall()"> (click)="onEntitySelectInstall()">
@if (resolvingPlan) { @if (resolvingPlan) {
<mat-spinner diameter="18" class="mr-2 inline-block align-middle"></mat-spinner> <mat-spinner matButtonIcon diameter="18" class="mr-2"></mat-spinner>
} }
{{ 'iot-hub.install' | translate }} {{ 'iot-hub.install' | translate }}
</button> </button>
@ -188,7 +188,7 @@
<button mat-button [disabled]="resolvingPlan" (click)="confirmOverwriteCancel()">{{ 'action.cancel' | translate }}</button> <button mat-button [disabled]="resolvingPlan" (click)="confirmOverwriteCancel()">{{ 'action.cancel' | translate }}</button>
<button mat-flat-button color="primary" [disabled]="resolvingPlan" (click)="confirmOverwriteReplace()"> <button mat-flat-button color="primary" [disabled]="resolvingPlan" (click)="confirmOverwriteReplace()">
@if (resolvingPlan) { @if (resolvingPlan) {
<mat-spinner diameter="18" class="mr-2 inline-block align-middle"></mat-spinner> <mat-spinner matButtonIcon diameter="18" class="mr-2"></mat-spinner>
} }
{{ 'iot-hub.rule-chain-overwrite-replace' | translate }} {{ 'iot-hub.rule-chain-overwrite-replace' | translate }}
</button> </button>
@ -204,7 +204,7 @@
@case ('installing') { @case ('installing') {
<button mat-button disabled>{{ 'action.cancel' | translate }}</button> <button mat-button disabled>{{ 'action.cancel' | translate }}</button>
<button mat-flat-button color="primary" disabled> <button mat-flat-button color="primary" disabled>
<mat-spinner diameter="18" class="mr-2 inline-block align-middle"></mat-spinner> <mat-spinner matButtonIcon diameter="18" class="mr-2"></mat-spinner>
{{ 'iot-hub.installing' | translate }} {{ 'iot-hub.installing' | translate }}
</button> </button>
} }

17
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.html

@ -135,14 +135,8 @@
<tr mat-row *matRowDef="let _row; columns: displayedColumns;"></tr> <tr mat-row *matRowDef="let _row; columns: displayedColumns;"></tr>
</table> </table>
@if (isLoading) {
<div class="flex flex-1 items-center justify-center py-12">
<mat-spinner diameter="40"></mat-spinner>
</div>
}
@if (!isLoading && dataSource.length === 0) { @if (!isLoading && dataSource.length === 0) {
<div class="tb-iot-hub-empty-state flex flex-1 flex-col items-center justify-center py-20 text-center"> <div class="tb-iot-hub-empty-state flex flex-1 flex-col items-center justify-center text-center">
<div class="tb-no-data-bg"></div> <div class="tb-no-data-bg"></div>
<h3>{{ 'iot-hub.no-installed-items' | translate }}</h3> <h3>{{ 'iot-hub.no-installed-items' | translate }}</h3>
<p>{{ 'iot-hub.no-installed-items-text' | translate }}</p> <p>{{ 'iot-hub.no-installed-items-text' | translate }}</p>
@ -166,4 +160,13 @@
(page)="onPageChange($event)" (page)="onPageChange($event)"
[showFirstLastButtons]="true"> [showFirstLastButtons]="true">
</mat-paginator> </mat-paginator>
@if (isLoading) {
<div class="absolute inset-0 backdrop-grayscale" style="background: rgba(255, 255, 255, 0.72); z-index: 100;">
<div class="flex h-full items-center justify-center">
<mat-spinner diameter="40"></mat-spinner>
</div>
</div>
}
</div> </div>

1
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.scss

@ -25,6 +25,7 @@
// Table + paginator container — border // Table + paginator container — border
.tb-installed-table-container { .tb-installed-table-container {
position: relative;
border: 1px solid rgba(0, 0, 0, 0.12); border: 1px solid rgba(0, 0, 0, 0.12);
} }

10
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-installed-items-table.component.ts

@ -156,9 +156,12 @@ export class TbIotHubInstalledItemsTableComponent implements OnInit, OnChanges,
} }
deleteItem(item: IotHubInstalledItem): void { deleteItem(item: IotHubInstalledItem): void {
this.iotHubActions.deleteItem(item).subscribe(confirmed => { this.isLoading = true;
if (confirmed) { this.iotHubActions.deleteItem(item).subscribe(deleted => {
if (deleted) {
this.loadData(); this.loadData();
} else {
this.isLoading = false;
} }
}); });
} }
@ -244,9 +247,12 @@ export class TbIotHubInstalledItemsTableComponent implements OnInit, OnChanges,
} }
updateItem(item: IotHubInstalledItem, publishedInfo: ItemPublishedVersionInfo): void { updateItem(item: IotHubInstalledItem, publishedInfo: ItemPublishedVersionInfo): void {
this.isLoading = true;
this.iotHubActions.updateItem(item, publishedInfo.publishedVersion, publishedInfo.publishedVersionId).subscribe(result => { this.iotHubActions.updateItem(item, publishedInfo.publishedVersion, publishedInfo.publishedVersionId).subscribe(result => {
if (result === 'updated') { if (result === 'updated') {
this.loadData(); this.loadData();
} else {
this.isLoading = false;
} }
}); });
} }

2
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.html

@ -96,7 +96,7 @@
</div> </div>
} @else { } @else {
<!-- Widget/Dashboard/Solution: image + description side by side --> <!-- Widget/Dashboard/Solution: image + description side by side -->
<div class="flex gap-6"> <div class="flex gap-6 lt-md:flex-col">
<div class="dlg-preview" <div class="dlg-preview"
[class.dlg-preview-carousel]="item.type === ItemType.SOLUTION_TEMPLATE && carouselImages.length > 1"> [class.dlg-preview-carousel]="item.type === ItemType.SOLUTION_TEMPLATE && carouselImages.length > 1">
@if (item.type === ItemType.SOLUTION_TEMPLATE && carouselImages.length > 1) { @if (item.type === ItemType.SOLUTION_TEMPLATE && carouselImages.length > 1) {

4
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-item-detail-dialog.component.ts

@ -220,8 +220,8 @@ export class TbIotHubItemDetailDialogComponent extends DialogComponent<TbIotHubI
} }
deleteItem(): void { deleteItem(): void {
this.iotHubActions.deleteItem(this.installedItem).subscribe(confirmed => { this.iotHubActions.deleteItem(this.installedItem).subscribe(deleted => {
if (confirmed) { if (deleted) {
this.dialogRef.close('deleted'); this.dialogRef.close('deleted');
} }
}); });

8
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-search.component.ts

@ -247,7 +247,6 @@ export class TbIotHubSearchComponent implements OnInit, OnDestroy {
updateItem(item: MpItemVersionView): void { updateItem(item: MpItemVersionView): void {
const installedItem = this.getInstalledItem(item); const installedItem = this.getInstalledItem(item);
if (!installedItem) { return; }
this.iotHubActions.updateItem(installedItem, item.version, item.id as string).subscribe(result => { this.iotHubActions.updateItem(installedItem, item.version, item.id as string).subscribe(result => {
if (result === 'updated') { if (result === 'updated') {
this.reloadInstalledItems(); this.reloadInstalledItems();
@ -257,9 +256,10 @@ export class TbIotHubSearchComponent implements OnInit, OnDestroy {
deleteInstalledItem(item: MpItemVersionView): void { deleteInstalledItem(item: MpItemVersionView): void {
const installedItem = this.getInstalledItem(item); const installedItem = this.getInstalledItem(item);
if (!installedItem) { return; } this.iotHubActions.deleteItem(installedItem).subscribe((deleted) => {
this.iotHubActions.deleteItem(installedItem).subscribe(() => { if (deleted) {
this.reloadInstalledItems(); this.reloadInstalledItems();
}
}); });
} }

2
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-update-dialog.component.html

@ -51,7 +51,7 @@
@case ('updating') { @case ('updating') {
<button mat-button disabled>{{ 'action.cancel' | translate }}</button> <button mat-button disabled>{{ 'action.cancel' | translate }}</button>
<button mat-flat-button color="primary" disabled> <button mat-flat-button color="primary" disabled>
<mat-spinner diameter="18" class="mr-2 inline-block align-middle"></mat-spinner> <mat-spinner matButtonIcon diameter="18" class="mr-2"></mat-spinner>
{{ 'iot-hub.updating' | translate }} {{ 'iot-hub.updating' | translate }}
</button> </button>
} }

4
ui-ngx/src/app/modules/home/components/iot-hub/iot-hub-update-dialog.component.ts

@ -46,7 +46,7 @@ export type UpdateState = 'confirm' | 'updating' | 'success' | 'error';
templateUrl: './iot-hub-update-dialog.component.html', templateUrl: './iot-hub-update-dialog.component.html',
styleUrls: ['./iot-hub-install-dialog.component.scss'] styleUrls: ['./iot-hub-install-dialog.component.scss']
}) })
export class TbIotHubUpdateDialogComponent extends DialogComponent<TbIotHubUpdateDialogComponent> { export class TbIotHubUpdateDialogComponent extends DialogComponent<TbIotHubUpdateDialogComponent, string | boolean> {
ItemType = ItemType; ItemType = ItemType;
@ -58,7 +58,7 @@ export class TbIotHubUpdateDialogComponent extends DialogComponent<TbIotHubUpdat
constructor( constructor(
protected store: Store<AppState>, protected store: Store<AppState>,
protected router: Router, protected router: Router,
protected dialogRef: MatDialogRef<TbIotHubUpdateDialogComponent>, protected dialogRef: MatDialogRef<TbIotHubUpdateDialogComponent, string | boolean>,
@Inject(MAT_DIALOG_DATA) public data: IotHubUpdateDialogData, @Inject(MAT_DIALOG_DATA) public data: IotHubUpdateDialogData,
private dialog: MatDialog, private dialog: MatDialog,
private dialogService: DialogService, private dialogService: DialogService,

5
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html

@ -98,6 +98,11 @@
{{ 'widgets.time-series-chart.data-zoom' | translate }} {{ 'widgets.time-series-chart.data-zoom' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="dataZoomUpdateTimewindow">
{{ 'widgets.time-series-chart.data-zoom-update-timewindow' | translate }}
</mat-slide-toggle>
</div>
<tb-time-series-chart-grid-settings <tb-time-series-chart-grid-settings
formControlName="grid"> formControlName="grid">
</tb-time-series-chart-grid-settings> </tb-time-series-chart-grid-settings>

11
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.ts

@ -116,6 +116,7 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom
iconColor: [configData.config.iconColor, []], iconColor: [configData.config.iconColor, []],
dataZoom: [settings.dataZoom, []], dataZoom: [settings.dataZoom, []],
dataZoomUpdateTimewindow: [settings.dataZoomUpdateTimewindow, []],
showBarLabel: [settings.showBarLabel, []], showBarLabel: [settings.showBarLabel, []],
barLabelFont: [settings.barLabelFont, []], barLabelFont: [settings.barLabelFont, []],
@ -193,6 +194,7 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.dataZoom = config.dataZoom; this.widgetConfig.config.settings.dataZoom = config.dataZoom;
this.widgetConfig.config.settings.dataZoomUpdateTimewindow = config.dataZoomUpdateTimewindow;
this.widgetConfig.config.settings.showBarLabel = config.showBarLabel; this.widgetConfig.config.settings.showBarLabel = config.showBarLabel;
this.widgetConfig.config.settings.barLabelFont = config.barLabelFont; this.widgetConfig.config.settings.barLabelFont = config.barLabelFont;
@ -248,10 +250,11 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {
return ['showTitle', 'showIcon', 'showBarLabel', 'showBarValue', 'showBarBorder', 'showLegend', 'showTooltip', 'tooltipShowDate']; return ['dataZoom', 'showTitle', 'showIcon', 'showBarLabel', 'showBarValue', 'showBarBorder', 'showLegend', 'showTooltip', 'tooltipShowDate'];
} }
protected updateValidators(emitEvent: boolean, trigger?: string) { protected updateValidators(emitEvent: boolean, trigger?: string) {
const dataZoom: boolean = this.barChartWidgetConfigForm.get('dataZoom').value;
const showTitle: boolean = this.barChartWidgetConfigForm.get('showTitle').value; const showTitle: boolean = this.barChartWidgetConfigForm.get('showTitle').value;
const showIcon: boolean = this.barChartWidgetConfigForm.get('showIcon').value; const showIcon: boolean = this.barChartWidgetConfigForm.get('showIcon').value;
const showBarLabel: boolean = this.barChartWidgetConfigForm.get('showBarLabel').value; const showBarLabel: boolean = this.barChartWidgetConfigForm.get('showBarLabel').value;
@ -261,6 +264,12 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom
const showTooltip: boolean = this.barChartWidgetConfigForm.get('showTooltip').value; const showTooltip: boolean = this.barChartWidgetConfigForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.barChartWidgetConfigForm.get('tooltipShowDate').value; const tooltipShowDate: boolean = this.barChartWidgetConfigForm.get('tooltipShowDate').value;
if (dataZoom) {
this.barChartWidgetConfigForm.get('dataZoomUpdateTimewindow').enable();
} else {
this.barChartWidgetConfigForm.get('dataZoomUpdateTimewindow').disable();
}
if (showTitle) { if (showTitle) {
this.barChartWidgetConfigForm.get('title').enable(); this.barChartWidgetConfigForm.get('title').enable();
this.barChartWidgetConfigForm.get('titleFont').enable(); this.barChartWidgetConfigForm.get('titleFont').enable();

5
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html

@ -180,6 +180,11 @@
{{ 'widgets.time-series-chart.data-zoom' | translate }} {{ 'widgets.time-series-chart.data-zoom' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="dataZoomUpdateTimewindow">
{{ 'widgets.time-series-chart.data-zoom-update-timewindow' | translate }}
</mat-slide-toggle>
</div>
<tb-time-series-chart-grid-settings <tb-time-series-chart-grid-settings
formControlName="grid"> formControlName="grid">
</tb-time-series-chart-grid-settings> </tb-time-series-chart-grid-settings>

13
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts

@ -96,7 +96,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
seriesMode = 'series'; seriesMode = 'series';
predefinedValues = widgetTitleAutocompleteValues; predefinedValues = widgetTitleAutocompleteValues;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent, protected widgetConfigComponent: WidgetConfigComponent,
private $injector: Injector, private $injector: Injector,
@ -164,6 +164,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
iconColor: [configData.config.iconColor, []], iconColor: [configData.config.iconColor, []],
dataZoom: [settings.dataZoom, []], dataZoom: [settings.dataZoom, []],
dataZoomUpdateTimewindow: [settings.dataZoomUpdateTimewindow, []],
stack: [settings.stack, []], stack: [settings.stack, []],
grid: [settings.grid, []], grid: [settings.grid, []],
@ -241,6 +242,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
this.widgetConfig.config.settings.thresholds = config.thresholds; this.widgetConfig.config.settings.thresholds = config.thresholds;
this.widgetConfig.config.settings.dataZoom = config.dataZoom; this.widgetConfig.config.settings.dataZoom = config.dataZoom;
this.widgetConfig.config.settings.dataZoomUpdateTimewindow = config.dataZoomUpdateTimewindow;
this.widgetConfig.config.settings.stack = config.stack; this.widgetConfig.config.settings.stack = config.stack;
this.widgetConfig.config.settings.grid = config.grid; this.widgetConfig.config.settings.grid = config.grid;
@ -292,11 +294,12 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {
return ['comparisonEnabled', 'showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate', 'stack']; return ['comparisonEnabled', 'dataZoom', 'showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate', 'stack'];
} }
protected updateValidators(emitEvent: boolean, trigger?: string) { protected updateValidators(emitEvent: boolean, trigger?: string) {
const comparisonEnabled: boolean = this.timeSeriesChartWidgetConfigForm.get('comparisonEnabled').value; const comparisonEnabled: boolean = this.timeSeriesChartWidgetConfigForm.get('comparisonEnabled').value;
const dataZoom: boolean = this.timeSeriesChartWidgetConfigForm.get('dataZoom').value;
const showTitle: boolean = this.timeSeriesChartWidgetConfigForm.get('showTitle').value; const showTitle: boolean = this.timeSeriesChartWidgetConfigForm.get('showTitle').value;
const showIcon: boolean = this.timeSeriesChartWidgetConfigForm.get('showIcon').value; const showIcon: boolean = this.timeSeriesChartWidgetConfigForm.get('showIcon').value;
const showLegend: boolean = this.timeSeriesChartWidgetConfigForm.get('showLegend').value; const showLegend: boolean = this.timeSeriesChartWidgetConfigForm.get('showLegend').value;
@ -304,6 +307,12 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
const tooltipShowDate: boolean = this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').value; const tooltipShowDate: boolean = this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').value;
const stack: boolean = this.timeSeriesChartWidgetConfigForm.get('stack').value; const stack: boolean = this.timeSeriesChartWidgetConfigForm.get('stack').value;
if (dataZoom) {
this.timeSeriesChartWidgetConfigForm.get('dataZoomUpdateTimewindow').enable();
} else {
this.timeSeriesChartWidgetConfigForm.get('dataZoomUpdateTimewindow').disable();
}
if (comparisonEnabled) { if (comparisonEnabled) {
this.timeSeriesChartWidgetConfigForm.get('timeForComparison').enable(); this.timeSeriesChartWidgetConfigForm.get('timeForComparison').enable();
this.timeSeriesChartWidgetConfigForm.get('comparisonCustomIntervalValue').enable(); this.timeSeriesChartWidgetConfigForm.get('comparisonCustomIntervalValue').enable();

1
ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts

@ -114,6 +114,7 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
ngAfterViewInit() { ngAfterViewInit() {
const settings = barChartWithLabelsTimeSeriesSettings(this.settings); const settings = barChartWithLabelsTimeSeriesSettings(this.settings);
this.timeSeriesChart = new TbTimeSeriesChart(this.ctx, settings, this.chartShape.nativeElement, this.renderer); this.timeSeriesChart = new TbTimeSeriesChart(this.ctx, settings, this.chartShape.nativeElement, this.renderer);
this.ctx.widgetActions = this.timeSeriesChart.getWidgetActions();
} }
ngOnDestroy() { ngOnDestroy() {

3
ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts

@ -53,6 +53,7 @@ import {
export interface BarChartWithLabelsWidgetSettings extends TimeSeriesChartTooltipWidgetSettings { export interface BarChartWithLabelsWidgetSettings extends TimeSeriesChartTooltipWidgetSettings {
dataZoom: boolean; dataZoom: boolean;
dataZoomUpdateTimewindow: boolean;
showBarLabel: boolean; showBarLabel: boolean;
barLabelFont: Font; barLabelFont: Font;
barLabelColor: string; barLabelColor: string;
@ -79,6 +80,7 @@ export interface BarChartWithLabelsWidgetSettings extends TimeSeriesChartTooltip
export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings = { export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings = {
dataZoom: false, dataZoom: false,
dataZoomUpdateTimewindow: false,
showBarLabel: true, showBarLabel: true,
barLabelFont: { barLabelFont: {
family: 'Roboto', family: 'Roboto',
@ -181,6 +183,7 @@ export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings
export const barChartWithLabelsTimeSeriesSettings = (settings: BarChartWithLabelsWidgetSettings): DeepPartial<TimeSeriesChartSettings> => ({ export const barChartWithLabelsTimeSeriesSettings = (settings: BarChartWithLabelsWidgetSettings): DeepPartial<TimeSeriesChartSettings> => ({
dataZoom: settings.dataZoom, dataZoom: settings.dataZoom,
dataZoomUpdateTimewindow: settings.dataZoomUpdateTimewindow,
grid: settings.grid, grid: settings.grid,
yAxes: { yAxes: {
default: settings.yAxis default: settings.yAxis

6
ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts

@ -262,3 +262,9 @@ export const getFocusedSeriesIndex = (chart: ECharts): number => {
} }
return -1; return -1;
}; };
export interface DataZoomEvent {
start: number;
end: number;
batch?: Array<{ start: number; end: number }>;
}

4
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-tooltip.models.ts

@ -293,7 +293,9 @@ private static mapTooltipParams(params: CallbackDataParams[] | CallbackDataParam
TimeSeriesChartTooltip.appendTooltipItem(result, seriesParams, series); TimeSeriesChartTooltip.appendTooltipItem(result, seriesParams, series);
} else if (Array.isArray(params)) { } else if (Array.isArray(params)) {
for (seriesParams of params) { for (seriesParams of params) {
TimeSeriesChartTooltip.appendTooltipItem(result, seriesParams, series); if (seriesParams.value) {
TimeSeriesChartTooltip.appendTooltipItem(result, seriesParams, series);
}
} }
} }
return result; return result;

1
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.ts

@ -137,6 +137,7 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV
ngAfterViewInit() { ngAfterViewInit() {
this.timeSeriesChart = new TbTimeSeriesChart(this.ctx, this.settings, this.chartShape.nativeElement, this.renderer); this.timeSeriesChart = new TbTimeSeriesChart(this.ctx, this.settings, this.chartShape.nativeElement, this.renderer);
this.ctx.widgetActions = this.timeSeriesChart.getWidgetActions();
} }
ngOnDestroy() { ngOnDestroy() {

2
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts

@ -699,6 +699,7 @@ export interface TimeSeriesChartSettings extends TimeSeriesChartTooltipWidgetSet
thresholds: TimeSeriesChartThreshold[]; thresholds: TimeSeriesChartThreshold[];
darkMode: boolean; darkMode: boolean;
dataZoom: boolean; dataZoom: boolean;
dataZoomUpdateTimewindow: boolean;
stack: boolean; stack: boolean;
grid: TimeSeriesChartGridSettings; grid: TimeSeriesChartGridSettings;
yAxes: TimeSeriesChartYAxes; yAxes: TimeSeriesChartYAxes;
@ -714,6 +715,7 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
thresholds: [], thresholds: [],
darkMode: false, darkMode: false,
dataZoom: true, dataZoom: true,
dataZoomUpdateTimewindow: false,
stack: false, stack: false,
grid: mergeDeep({} as TimeSeriesChartGridSettings, grid: mergeDeep({} as TimeSeriesChartGridSettings,
timeSeriesChartGridDefaultSettings), timeSeriesChartGridDefaultSettings),

76
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetAction, WidgetContext } from '@home/models/widget-component.models';
import { import {
adjustTimeAxisExtentToData, adjustTimeAxisExtentToData,
calculateThresholdsOffset, calculateThresholdsOffset,
@ -23,7 +23,8 @@ import {
createTimeSeriesYAxis, createTimeSeriesYAxis,
defaultTimeSeriesChartYAxisSettings, defaultTimeSeriesChartYAxisSettings,
generateChartData, generateChartData,
LineSeriesStepType, normalizeAxisLimit, LineSeriesStepType,
normalizeAxisLimit,
parseThresholdData, parseThresholdData,
TimeSeriesChartAxis, TimeSeriesChartAxis,
TimeSeriesChartDataItem, TimeSeriesChartDataItem,
@ -47,9 +48,11 @@ import {
} from '@home/components/widget/lib/chart/time-series-chart.models'; } from '@home/components/widget/lib/chart/time-series-chart.models';
import { import {
calculateAxisSize, calculateAxisSize,
DataZoomEvent,
ECharts, ECharts,
echartsModule, echartsModule,
EChartsOption, EChartsOption,
getAxis,
getAxisExtent, getAxisExtent,
getFocusedSeriesIndex, getFocusedSeriesIndex,
measureAxisNameSize measureAxisNameSize
@ -141,6 +144,13 @@ export class TbTimeSeriesChart {
private hasVisualMap = false; private hasVisualMap = false;
private visualMapSelectedRanges: {[key: number]: boolean}; private visualMapSelectedRanges: {[key: number]: boolean};
private dataZoomResetting = false;
private dataZoomUpdatePending = false;
private dataZoomDebounce: ReturnType<typeof setTimeout> | null = null;
private lastDataZoomStart = 0;
private lastDataZoomEnd = 100;
private timewindowChanged = false;
private timeSeriesChart: ECharts; private timeSeriesChart: ECharts;
private timeSeriesChartOptions: EChartsOption; private timeSeriesChartOptions: EChartsOption;
@ -265,6 +275,12 @@ export class TbTimeSeriesChart {
if (this.highlightedDataKey) { if (this.highlightedDataKey) {
this.keyEnter(this.highlightedDataKey); this.keyEnter(this.highlightedDataKey);
} }
if (this.dataZoomUpdatePending) {
this.dataZoomUpdatePending = false;
this.dataZoomResetting = true;
this.timeSeriesChart.dispatchAction({ type: 'dataZoom', start: 0, end: 100 });
this.dataZoomResetting = false;
}
} }
} }
@ -386,6 +402,10 @@ export class TbTimeSeriesChart {
} }
public destroy(): void { public destroy(): void {
if (this.dataZoomDebounce !== null) {
clearTimeout(this.dataZoomDebounce);
this.dataZoomDebounce = null;
}
if (this.shapeResize$) { if (this.shapeResize$) {
this.shapeResize$.disconnect(); this.shapeResize$.disconnect();
} }
@ -417,6 +437,25 @@ export class TbTimeSeriesChart {
return this.darkMode; return this.darkMode;
} }
public getWidgetActions(): WidgetAction[] {
if (this.settings.dataZoom && this.settings.dataZoomUpdateTimewindow) {
return [{
name: 'widgets.time-series-chart.reset-timewindow',
icon: 'restore',
onAction: () => {
if (this.dataZoomDebounce !== null) {
clearTimeout(this.dataZoomDebounce);
this.dataZoomDebounce = null;
}
this.timewindowChanged = false;
this.ctx.defaultSubscription.onResetTimewindow();
},
show: ()=> this.timewindowChanged
}];
}
return [];
}
private setupData(): void { private setupData(): void {
const noAggregationBarWidthSettings = this.settings.noAggregationBarWidthSettings; const noAggregationBarWidthSettings = this.settings.noAggregationBarWidthSettings;
const targetBarWidth = noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group ? const targetBarWidth = noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group ?
@ -846,8 +885,39 @@ export class TbTimeSeriesChart {
this.updateAxes(false); this.updateAxes(false);
if (this.settings.dataZoom) { if (this.settings.dataZoom) {
this.timeSeriesChart.on('datazoom', () => { this.timeSeriesChart.on('datazoom', (event: DataZoomEvent) => {
this.updateAxes(); this.updateAxes();
if (this.settings.dataZoomUpdateTimewindow && !this.dataZoomResetting) {
const evStart = event.batch?.length ? event.batch[0].start : event.start;
const evEnd = event.batch?.length ? event.batch[0].end : event.end;
this.lastDataZoomStart = evStart;
this.lastDataZoomEnd = evEnd;
if (this.dataZoomDebounce !== null) {
clearTimeout(this.dataZoomDebounce);
}
this.dataZoomDebounce = setTimeout(() => {
this.dataZoomDebounce = null;
if (this.timeSeriesChart?.isDisposed()) {
return;
}
if (Math.round(this.lastDataZoomStart) === 0 && Math.round(this.lastDataZoomEnd) === 100) {
this.ctx.defaultSubscription.onResetTimewindow();
this.timewindowChanged = false;
} else {
const axis = getAxis(this.timeSeriesChart, 'xAxis', 'main');
if (axis) {
const extent = axis.scale.getExtent();
const startMs = Math.round(extent[0]);
const endMs = Math.round(extent[1]);
if (startMs < endMs) {
this.dataZoomUpdatePending = true;
this.timewindowChanged = true;
this.ctx.defaultSubscription.onUpdateTimewindow(startMs, endMs);
}
}
}
}, 500);
}
}); });
} }
} }

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html

@ -23,6 +23,11 @@
{{ 'widgets.time-series-chart.data-zoom' | translate }} {{ 'widgets.time-series-chart.data-zoom' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="dataZoomUpdateTimewindow">
{{ 'widgets.time-series-chart.data-zoom-update-timewindow' | translate }}
</mat-slide-toggle>
</div>
<tb-time-series-chart-grid-settings <tb-time-series-chart-grid-settings
formControlName="grid"> formControlName="grid">
</tb-time-series-chart-grid-settings> </tb-time-series-chart-grid-settings>

10
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts

@ -78,6 +78,7 @@ export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsCom
this.barChartWidgetSettingsForm = this.fb.group({ this.barChartWidgetSettingsForm = this.fb.group({
dataZoom: [settings.dataZoom, []], dataZoom: [settings.dataZoom, []],
dataZoomUpdateTimewindow: [settings.dataZoomUpdateTimewindow, []],
showBarLabel: [settings.showBarLabel, []], showBarLabel: [settings.showBarLabel, []],
barLabelFont: [settings.barLabelFont, []], barLabelFont: [settings.barLabelFont, []],
@ -130,10 +131,11 @@ export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsCom
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {
return ['showBarLabel', 'showBarValue', 'showBarBorder', 'showLegend', 'showTooltip', 'tooltipShowDate']; return ['dataZoom', 'showBarLabel', 'showBarValue', 'showBarBorder', 'showLegend', 'showTooltip', 'tooltipShowDate'];
} }
protected updateValidators(emitEvent: boolean) { protected updateValidators(emitEvent: boolean) {
const dataZoom: boolean = this.barChartWidgetSettingsForm.get('dataZoom').value;
const showBarLabel: boolean = this.barChartWidgetSettingsForm.get('showBarLabel').value; const showBarLabel: boolean = this.barChartWidgetSettingsForm.get('showBarLabel').value;
const showBarValue: boolean = this.barChartWidgetSettingsForm.get('showBarValue').value; const showBarValue: boolean = this.barChartWidgetSettingsForm.get('showBarValue').value;
const showBarBorder: boolean = this.barChartWidgetSettingsForm.get('showBarBorder').value; const showBarBorder: boolean = this.barChartWidgetSettingsForm.get('showBarBorder').value;
@ -141,6 +143,12 @@ export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsCom
const showTooltip: boolean = this.barChartWidgetSettingsForm.get('showTooltip').value; const showTooltip: boolean = this.barChartWidgetSettingsForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.barChartWidgetSettingsForm.get('tooltipShowDate').value; const tooltipShowDate: boolean = this.barChartWidgetSettingsForm.get('tooltipShowDate').value;
if (dataZoom) {
this.barChartWidgetSettingsForm.get('dataZoomUpdateTimewindow').enable();
} else {
this.barChartWidgetSettingsForm.get('dataZoomUpdateTimewindow').disable();
}
if (showBarLabel) { if (showBarLabel) {
this.barChartWidgetSettingsForm.get('barLabelFont').enable(); this.barChartWidgetSettingsForm.get('barLabelFont').enable();
this.barChartWidgetSettingsForm.get('barLabelColor').enable(); this.barChartWidgetSettingsForm.get('barLabelColor').enable();

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html

@ -86,6 +86,11 @@
{{ 'widgets.time-series-chart.data-zoom' | translate }} {{ 'widgets.time-series-chart.data-zoom' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="dataZoomUpdateTimewindow">
{{ 'widgets.time-series-chart.data-zoom-update-timewindow' | translate }}
</mat-slide-toggle>
</div>
<tb-time-series-chart-grid-settings <tb-time-series-chart-grid-settings
formControlName="grid"> formControlName="grid">
</tb-time-series-chart-grid-settings> </tb-time-series-chart-grid-settings>

10
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts

@ -135,6 +135,7 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
thresholds: [settings.thresholds, []], thresholds: [settings.thresholds, []],
dataZoom: [settings.dataZoom, []], dataZoom: [settings.dataZoom, []],
dataZoomUpdateTimewindow: [settings.dataZoomUpdateTimewindow, []],
stack: [settings.stack, []], stack: [settings.stack, []],
grid: [settings.grid, []], grid: [settings.grid, []],
@ -181,16 +182,23 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {
return ['comparisonEnabled', 'showLegend', 'showTooltip', 'tooltipShowDate', 'stack']; return ['comparisonEnabled', 'dataZoom', 'showLegend', 'showTooltip', 'tooltipShowDate', 'stack'];
} }
protected updateValidators(emitEvent: boolean) { protected updateValidators(emitEvent: boolean) {
const comparisonEnabled: boolean = this.timeSeriesChartWidgetSettingsForm.get('comparisonEnabled').value; const comparisonEnabled: boolean = this.timeSeriesChartWidgetSettingsForm.get('comparisonEnabled').value;
const dataZoom: boolean = this.timeSeriesChartWidgetSettingsForm.get('dataZoom').value;
const showLegend: boolean = this.timeSeriesChartWidgetSettingsForm.get('showLegend').value; const showLegend: boolean = this.timeSeriesChartWidgetSettingsForm.get('showLegend').value;
const showTooltip: boolean = this.timeSeriesChartWidgetSettingsForm.get('showTooltip').value; const showTooltip: boolean = this.timeSeriesChartWidgetSettingsForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.timeSeriesChartWidgetSettingsForm.get('tooltipShowDate').value; const tooltipShowDate: boolean = this.timeSeriesChartWidgetSettingsForm.get('tooltipShowDate').value;
const stack: boolean = this.timeSeriesChartWidgetSettingsForm.get('stack').value; const stack: boolean = this.timeSeriesChartWidgetSettingsForm.get('stack').value;
if (dataZoom) {
this.timeSeriesChartWidgetSettingsForm.get('dataZoomUpdateTimewindow').enable();
} else {
this.timeSeriesChartWidgetSettingsForm.get('dataZoomUpdateTimewindow').disable();
}
if (comparisonEnabled) { if (comparisonEnabled) {
this.timeSeriesChartWidgetSettingsForm.get('timeForComparison').enable(); this.timeSeriesChartWidgetSettingsForm.get('timeForComparison').enable();
this.timeSeriesChartWidgetSettingsForm.get('comparisonCustomIntervalValue').enable(); this.timeSeriesChartWidgetSettingsForm.get('comparisonCustomIntervalValue').enable();

2
ui-ngx/src/app/modules/home/components/widget/widget-container.component.html

@ -103,7 +103,7 @@
@for (action of widget.widgetActions; track action) { @for (action of widget.widgetActions; track action) {
<button mat-icon-button <button mat-icon-button
type="button" type="button"
[class.!hidden]="isEdit || !action.show" [class.!hidden]="isEdit || !actionVisible(action)"
(click)="action.onAction($event)" (click)="action.onAction($event)"
matTooltip="{{ action.name | translate }}" matTooltip="{{ action.name | translate }}"
matTooltipPosition="above"> matTooltipPosition="above">

5
ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts

@ -49,6 +49,7 @@ import { WidgetHeaderActionButtonType } from '@shared/models/widget.models';
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
import ITooltipsterGeoHelper = JQueryTooltipster.ITooltipsterGeoHelper; import ITooltipsterGeoHelper = JQueryTooltipster.ITooltipsterGeoHelper;
import { WidgetComponent } from '@home/components/widget/widget.component'; import { WidgetComponent } from '@home/components/widget/widget.component';
import { WidgetAction } from '@home/models/widget-component.models';
export enum WidgetComponentActionType { export enum WidgetComponentActionType {
MOUSE_DOWN, MOUSE_DOWN,
@ -294,6 +295,10 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
} }
} }
actionVisible(action: WidgetAction): boolean {
return typeof action.show === 'function' ? action.show() : action.show;
}
private initEditWidgetActionTooltip(parent: HTMLElement) { private initEditWidgetActionTooltip(parent: HTMLElement) {
let componentRef: ComponentRef<EditWidgetActionsTooltipComponent>; let componentRef: ComponentRef<EditWidgetActionsTooltipComponent>;
from(import('tooltipster')).subscribe(() => { from(import('tooltipster')).subscribe(() => {

3
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -685,6 +685,7 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
} }
private reInitImpl() { private reInitImpl() {
this.displayNoData = false;
this.onDestroy(); this.onDestroy();
if (!this.typeParameters.useCustomDatasources) { if (!this.typeParameters.useCustomDatasources) {
this.createDefaultSubscription().subscribe({ this.createDefaultSubscription().subscribe({
@ -728,7 +729,6 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds); subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds);
} }
if (subscriptionChanged && !this.typeParameters.useCustomDatasources) { if (subscriptionChanged && !this.typeParameters.useCustomDatasources) {
this.displayNoData = false;
this.reInit(); this.reInit();
} }
} }
@ -742,7 +742,6 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
subscriptionChanged = subscriptionChanged || subscription.onFiltersChanged(filterIds); subscriptionChanged = subscriptionChanged || subscription.onFiltersChanged(filterIds);
} }
if (subscriptionChanged && !this.typeParameters.useCustomDatasources) { if (subscriptionChanged && !this.typeParameters.useCustomDatasources) {
this.displayNoData = false;
this.reInit(); this.reInit();
} }
} }

2
ui-ngx/src/app/modules/home/home.component.html

@ -97,7 +97,7 @@
> >
</mat-progress-bar> </mat-progress-bar>
} }
<div tb-toast class="tb-main-content flex flex-1 flex-col"> <div #mainContent tb-toast class="tb-main-content flex flex-1 flex-col">
<router-outlet (activate)="activeComponentChanged($event)"></router-outlet> <router-outlet (activate)="activeComponentChanged($event)"></router-outlet>
</div> </div>
</div> </div>

3
ui-ngx/src/app/modules/home/home.component.ts

@ -73,6 +73,8 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
@ViewChild('sidenav') @ViewChild('sidenav')
sidenav: MatSidenav; sidenav: MatSidenav;
@ViewChild('mainContent', { static: true }) mainContent: ElementRef<HTMLElement>;
@ViewChild('searchInput') searchInputField: ElementRef; @ViewChild('searchInput') searchInputField: ElementRef;
fullscreenEnabled = screenfull.isEnabled; fullscreenEnabled = screenfull.isEnabled;
@ -164,6 +166,7 @@ export class HomeComponent extends PageComponent implements AfterViewInit, OnIni
activeComponentChanged(activeComponent: any) { activeComponentChanged(activeComponent: any) {
this.activeComponentService.setCurrentActiveComponent(activeComponent); this.activeComponentService.setCurrentActiveComponent(activeComponent);
this.mainContent?.nativeElement?.scrollTo({ top: 0, left: 0 });
if (!this.activeComponent) { if (!this.activeComponent) {
setTimeout(() => { setTimeout(() => {
this.updateActiveComponent(activeComponent); this.updateActiveComponent(activeComponent);

2
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -151,7 +151,7 @@ export interface WidgetHeaderAction extends IWidgetAction {
} }
export interface WidgetAction extends IWidgetAction { export interface WidgetAction extends IWidgetAction {
show: boolean; show: boolean | (()=> boolean);
} }
export interface IDashboardWidget { export interface IDashboardWidget {

8
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.scss

@ -757,6 +757,14 @@
padding: 140px 16px 40px; padding: 140px 16px 40px;
} }
.tb-iot-hub-hero-cluster {
top: 15%;
}
.tb-iot-hub-hero-title {
font-size: 28px;
}
.tb-iot-hub-categories { .tb-iot-hub-categories {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }

31
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-home.component.ts

@ -381,7 +381,6 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
updateItem(item: MpItemVersionView): void { updateItem(item: MpItemVersionView): void {
const installedItem = this.findInstalledItem(item); const installedItem = this.findInstalledItem(item);
if (!installedItem) { return; }
this.iotHubActions.updateItem(installedItem, item.version, item.id as string).subscribe(result => { this.iotHubActions.updateItem(installedItem, item.version, item.id as string).subscribe(result => {
if (result === 'updated') { if (result === 'updated') {
this.reloadInstalledItems(item.type); this.reloadInstalledItems(item.type);
@ -404,20 +403,22 @@ export class TbIotHubHomeComponent implements OnInit, OnDestroy {
deleteInstalledItem(item: MpItemVersionView): void { deleteInstalledItem(item: MpItemVersionView): void {
const installedItem = this.findInstalledItem(item); const installedItem = this.findInstalledItem(item);
if (!installedItem) { return; } if (!installedItem) { return; }
this.iotHubActions.deleteItem(installedItem).subscribe(() => { this.iotHubActions.deleteItem(installedItem).subscribe((deleted) => {
this.installedItemsCount = Math.max(0, this.installedItemsCount - 1); if (deleted) {
if (item.type === ItemType.WIDGET) { this.installedItemsCount = Math.max(0, this.installedItemsCount - 1);
this.installedWidgets = this.installedWidgets.filter(i => i.id.id !== installedItem.id.id); if (item.type === ItemType.WIDGET) {
} else if (item.type === ItemType.SOLUTION_TEMPLATE) { this.installedWidgets = this.installedWidgets.filter(i => i.id.id !== installedItem.id.id);
this.installedSolutionTemplates = this.installedSolutionTemplates.filter(i => i.id.id !== installedItem.id.id); } else if (item.type === ItemType.SOLUTION_TEMPLATE) {
} else if (item.type === ItemType.DEVICE && this.installedDeviceCounts[item.itemId]) { this.installedSolutionTemplates = this.installedSolutionTemplates.filter(i => i.id.id !== installedItem.id.id);
this.installedDeviceCounts[item.itemId] = Math.max(0, this.installedDeviceCounts[item.itemId] - 1); } else if (item.type === ItemType.DEVICE && this.installedDeviceCounts[item.itemId]) {
} else if (item.type === ItemType.CALCULATED_FIELD && this.installedCalcFieldCounts[item.itemId]) { this.installedDeviceCounts[item.itemId] = Math.max(0, this.installedDeviceCounts[item.itemId] - 1);
this.installedCalcFieldCounts[item.itemId] = Math.max(0, this.installedCalcFieldCounts[item.itemId] - 1); } else if (item.type === ItemType.CALCULATED_FIELD && this.installedCalcFieldCounts[item.itemId]) {
} else if (item.type === ItemType.ALARM_RULE && this.installedAlarmRuleCounts[item.itemId]) { this.installedCalcFieldCounts[item.itemId] = Math.max(0, this.installedCalcFieldCounts[item.itemId] - 1);
this.installedAlarmRuleCounts[item.itemId] = Math.max(0, this.installedAlarmRuleCounts[item.itemId] - 1); } else if (item.type === ItemType.ALARM_RULE && this.installedAlarmRuleCounts[item.itemId]) {
} else if (item.type === ItemType.RULE_CHAIN && this.installedRuleChainCounts[item.itemId]) { this.installedAlarmRuleCounts[item.itemId] = Math.max(0, this.installedAlarmRuleCounts[item.itemId] - 1);
this.installedRuleChainCounts[item.itemId] = Math.max(0, this.installedRuleChainCounts[item.itemId] - 1); } else if (item.type === ItemType.RULE_CHAIN && this.installedRuleChainCounts[item.itemId]) {
this.installedRuleChainCounts[item.itemId] = Math.max(0, this.installedRuleChainCounts[item.itemId] - 1);
}
} }
}); });
} }

4
ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-installed-items.component.html

@ -28,9 +28,9 @@
@if (!updatesChecked) { @if (!updatesChecked) {
<button mat-flat-button color="primary" (click)="checkForUpdates()" [disabled]="isCheckingUpdates"> <button mat-flat-button color="primary" (click)="checkForUpdates()" [disabled]="isCheckingUpdates">
@if (isCheckingUpdates) { @if (isCheckingUpdates) {
<mat-spinner diameter="18"></mat-spinner> <mat-spinner matButtonIcon class="mr-2" diameter="18"></mat-spinner>
} @else { } @else {
<mat-icon>update</mat-icon> <mat-icon matButtonIcon>update</mat-icon>
} }
{{ 'iot-hub.check-for-updates' | translate }} {{ 'iot-hub.check-for-updates' | translate }}
</button> </button>

12
ui-ngx/src/app/shared/models/iot-hub/iot-hub-installed-item.models.ts

@ -139,8 +139,16 @@ export const getInstalledItemUrl = (descriptor?: IotHubInstalledItemDescriptor):
let entityType: EntityType | null = null; let entityType: EntityType | null = null;
switch (descriptor.type) { switch (descriptor.type) {
case 'DEVICE': case 'DEVICE':
entityId = descriptor.dashboardId?.id; if (descriptor.dashboardId) {
entityType = EntityType.DASHBOARD; entityId = descriptor.dashboardId?.id;
entityType = EntityType.DASHBOARD;
} else if (descriptor.createdEntityIds) {
const found = descriptor.createdEntityIds.find(id => id.entityType === EntityType.DEVICE);
if (found) {
entityId = found.id;
entityType = EntityType.DEVICE;
}
}
break; break;
case 'WIDGET': case 'WIDGET':
entityId = descriptor.widgetTypeId?.id; entityId = descriptor.widgetTypeId?.id;

2
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -9992,6 +9992,8 @@
"chart": "Chart", "chart": "Chart",
"chart-style": "Chart style", "chart-style": "Chart style",
"data-zoom": "Data zoom", "data-zoom": "Data zoom",
"data-zoom-update-timewindow": "Update time window on zoom",
"reset-timewindow": "Back to original time window",
"stack-mode": "Stack mode", "stack-mode": "Stack mode",
"stack-mode-hint": "Stacks series on the chart. The series with the same unit would be put on top of each other.", "stack-mode-hint": "Stacks series on the chart. The series with the same unit would be put on top of each other.",
"axes": "Axes", "axes": "Axes",

Loading…
Cancel
Save