-
-
-
-
-
-
- {{ 'widget-config.drop-shadow' | translate }}
-
-
- {{ 'widget-config.enable-fullscreen' | translate }}
-
-
-
-
- widget-config.advanced-settings
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ 'widget-config.display-legend' | translate }}
-
-
-
- widget-config.advanced-settings
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ 'widget-config.mobile-hide' | translate }}
-
-
- {{ 'widget-config.desktop-hide' | translate }}
-
-
-
- widget-config.advanced-settings
-
-
-
-
-
- widget-config.order
-
-
-
- widget-config.height
-
-
-
-
-
-
+
-
-
-
-
-
+
+
+
+ {{ 'widget-config.mobile-hide' | translate }}
+
+
+
+
+ {{ 'widget-config.desktop-hide' | translate }}
+
+
+
+
+
+
+
+
+
+
{{basicModeDirectiveError}}
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss
index 2ae2eaa211..91b3368ad0 100644
--- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss
+++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss
@@ -13,118 +13,46 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+@import "../../../../../theme";
@import '../../../../../scss/constants';
.tb-widget-config {
- .tb-panel-hint {
- font-size: 12px;
- color: #808080;
- }
-}
-
-.tb-datasource-list-item {
- &.mat-mdc-list-item {
- height: auto;
- min-height: 68px;
- display: block;
- padding: 0;
- }
- &.tb-draggable {
- &.cdk-drag-preview {
- background: #fff;
- }
- }
-}
-
-.tb-datasource-params {
- position: relative;
- padding: 0 0 0 10px;
- margin: 5px;
- tb-error.tb-datasource-error {
- position: absolute;
- bottom: 4px;
- left: 8px;
- }
- .tb-datasource-section {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ .tb-widget-config-header {
+ padding: 24px 24px 8px;
+ height: 56px;
display: flex;
- flex-direction: column;
- align-items: stretch;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ }
+ .tb-widget-config-content {
flex: 1;
- padding-top: 20px;
- @media #{$mat-gt-sm} {
- flex-direction: row;
- align-items: center;
- justify-content: flex-start;
+ overflow: auto;
+ & > div {
+ padding: 16px;
}
- }
- .tb-datasource-type {
- min-width: 160px;
- @media #{$mat-gt-sm} {
- max-width: 160px;
+ .mat-content {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
}
}
- .tb-datasource {
- @media #{$mat-gt-sm} {
- padding-left: 8px;
- width: 208px;
- max-width: 208px;
- }
- }
- .tb-data-keys {
- @media #{$mat-gt-sm} {
- padding-left: 8px;
- }
- }
-}
-
-:host {
- .tb-widget-config {
- .tb-advanced-widget-config {
- height: 100%;
- }
- .tb-datasources {
-
- .handle {
- cursor: move;
- }
-
- .mat-mdc-list {
- min-height: 68px;
- padding-left: 0;
- }
- }
- .fields-group {
- padding: 0 16px 8px;
- margin-bottom: 10px;
- border: 1px groove rgba(0, 0, 0, .25);
- border-radius: 4px;
- legend {
- color: rgba(0, 0, 0, .7);
- width: fit-content;
- }
- }
- .fields-group-slider {
- padding: 0;
- legend {
- margin-left: 16px;
- }
- .tb-settings {
- padding: 0 16px 8px;
- }
- }
- .tb-widget-style {
- margin-top: 16px;
- }
+ .tb-basic-mode-directive-error {
+ font-size: 13px;
+ font-weight: 400;
+ color: rgb(221, 44, 0);
}
}
:host ::ng-deep {
.tb-widget-config {
- tb-alarm-filter-config {
- .mdc-button {
- width: 100%;
- height: 100%;
- justify-content: flex-start;
+ .tb-widget-config-header {
+ .mat-button-toggle-appearance-standard .mat-button-toggle-label-content {
+ padding: 0 20px;
}
}
.mat-mdc-tab-body-wrapper {
@@ -134,53 +62,5 @@
right: 0;
bottom: 0;
}
- .mat-expansion-panel {
- &.tb-settings {
- box-shadow: none;
- .mat-content {
- overflow: visible;
- }
- .mat-expansion-panel-header {
- padding: 0;
- &:hover {
- background: none;
- }
- .mat-expansion-indicator {
- padding: 2px;
- }
- }
- .mat-expansion-panel-header-description {
- align-items: center;
- }
- > .mat-expansion-panel-content {
- > .mat-expansion-panel-body {
- padding: 0;
- }
- }
- .tb-json-object-panel, .tb-css-content-panel {
- margin: 0 0 8px;
- }
- }
- &.tb-datasources {
- &.mat-expanded {
- overflow: visible;
- }
- .mat-expansion-panel-body{
- padding: 0 12px 16px;
- }
- }
- .mat-expansion-panel-content {
- font: inherit;
- }
- }
- .mat-slide {
- margin: 8px 0;
- }
- .slide-block {
- display: block;
- &:not(:last-child) {
- margin-bottom: 8px;
- }
- }
}
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
index 1514c1b929..744747a50a 100644
--- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
@@ -14,29 +14,38 @@
/// limitations under the License.
///
-import { Component, forwardRef, Input, OnInit } from '@angular/core';
+import {
+ ChangeDetectorRef,
+ Component,
+ ComponentFactoryResolver,
+ ComponentRef,
+ forwardRef,
+ Input,
+ OnChanges,
+ OnDestroy,
+ OnInit,
+ SimpleChanges,
+ ViewChild,
+ ViewContainerRef
+} from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import {
DataKey,
- Datasource,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation,
- DatasourceType,
- datasourceTypeTranslationMap,
- defaultLegendConfig,
GroupInfo,
JsonSchema,
JsonSettingsSchema,
Widget,
+ WidgetConfigMode,
widgetType
} from '@shared/models/widget.models';
import {
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
- UntypedFormArray,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
@@ -53,7 +62,10 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { TranslateService } from '@ngx-translate/core';
import { EntityType } from '@shared/models/entity-type.models';
import { Observable, of, Subscription } from 'rxjs';
-import { WidgetConfigCallbacks } from '@home/components/widget/widget-config.component.models';
+import {
+ IBasicWidgetConfigComponent,
+ WidgetConfigCallbacks
+} from '@home/components/widget/config/widget-config.component.models';
import {
EntityAliasDialogComponent,
EntityAliasDialogData
@@ -62,12 +74,15 @@ import { catchError, mergeMap, tap } from 'rxjs/operators';
import { MatDialog } from '@angular/material/dialog';
import { EntityService } from '@core/http/entity.service';
import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models';
-import { WidgetActionsData } from './action/manage-widget-actions.component.models';
import { Dashboard } from '@shared/models/dashboard.models';
import { entityFields } from '@shared/models/entity.models';
-import { Filter } from '@shared/models/query/query.models';
+import { Filter, singleEntityFilterFromDeviceId } from '@shared/models/query/query.models';
import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component';
-import { CdkDragDrop } from '@angular/cdk/drag-drop';
+import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
+import { coerceBoolean } from '@shared/decorators/coercion';
+import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module';
+import Timeout = NodeJS.Timeout;
+import { TimewindowConfigData } from '@home/components/widget/config/timewindow-config-panel.component';
const emptySettingsSchema: JsonSchema = {
type: 'object',
@@ -81,7 +96,7 @@ const defaultSettingsForm = [
@Component({
selector: 'tb-widget-config',
templateUrl: './widget-config.component.html',
- styleUrls: ['./widget-config.component.scss'],
+ styleUrls: ['./widget-config.component.scss', './config/widget-config.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
@@ -95,10 +110,14 @@ const defaultSettingsForm = [
}
]
})
-export class WidgetConfigComponent extends PageComponent implements OnInit, ControlValueAccessor, Validator {
+export class WidgetConfigComponent extends PageComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator, OnChanges {
+
+ @ViewChild('basicModeContainer', {read: ViewContainerRef, static: false}) basicModeContainer: ViewContainerRef;
widgetTypes = widgetType;
+ widgetConfigModes = WidgetConfigMode;
+
entityTypes = EntityType;
@Input()
@@ -116,65 +135,76 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
@Input()
functionsOnly: boolean;
+ @Input()
+ @coerceBoolean()
+ hideHeader = false;
+
+ @Input()
+ @coerceBoolean()
+ hideToggleHeader = false;
+
@Input() disabled: boolean;
- widgetType: widgetType;
+ @Input()
+ widgetConfigMode = WidgetConfigMode.advanced;
- datasourceType = DatasourceType;
- datasourceTypes: Array
= [];
- datasourceTypesTranslations = datasourceTypeTranslationMap;
+ widgetType: widgetType;
widgetConfigCallbacks: WidgetConfigCallbacks = {
createEntityAlias: this.createEntityAlias.bind(this),
createFilter: this.createFilter.bind(this),
generateDataKey: this.generateDataKey.bind(this),
+ fetchEntityKeysForDevice: this.fetchEntityKeysForDevice.bind(this),
fetchEntityKeys: this.fetchEntityKeys.bind(this),
fetchDashboardStates: this.fetchDashboardStates.bind(this)
};
widgetEditMode = this.utils.widgetEditMode;
- selectedTab: number;
+ basicModeDirectiveError: string;
modelValue: WidgetConfigComponentData;
- showLegendFieldset = true;
-
private propagateChange = null;
+ headerOptions: ToggleHeaderOption[] = [];
+ selectedOption: string;
+
public dataSettings: UntypedFormGroup;
public targetDeviceSettings: UntypedFormGroup;
- public alarmSourceSettings: UntypedFormGroup;
public widgetSettings: UntypedFormGroup;
public layoutSettings: UntypedFormGroup;
public advancedSettings: UntypedFormGroup;
public actionsSettings: UntypedFormGroup;
- public openExtensionPanel = true;
- public timeseriesKeyError = false;
- public datasourceError: string[] = [];
+ private createBasicModeComponentTimeout: Timeout;
+ private basicModeComponentRef: ComponentRef;
+ private basicModeComponent: IBasicWidgetConfigComponent;
+ private basicModeComponentChangeSubscription: Subscription;
private dataSettingsChangesSubscription: Subscription;
private targetDeviceSettingsSubscription: Subscription;
- private alarmSourceSettingsSubscription: Subscription;
private widgetSettingsSubscription: Subscription;
private layoutSettingsSubscription: Subscription;
private advancedSettingsSubscription: Subscription;
private actionsSettingsSubscription: Subscription;
+ private defaultConfigFormsType: widgetType;
+
constructor(protected store: Store,
private utils: UtilsService,
private entityService: EntityService,
private dialog: MatDialog,
- private translate: TranslateService,
- private fb: UntypedFormBuilder) {
+ public translate: TranslateService,
+ private cfr: ComponentFactoryResolver,
+ private fb: UntypedFormBuilder,
+ private cd: ChangeDetectorRef) {
super(store);
}
ngOnInit(): void {
this.dataSettings = this.fb.group({});
this.targetDeviceSettings = this.fb.group({});
- this.alarmSourceSettings = this.fb.group({});
this.advancedSettings = this.fb.group({});
this.widgetSettings = this.fb.group({
title: [null, []],
@@ -196,41 +226,16 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
pageSize: [1024, [Validators.min(1), Validators.pattern(/^\d*$/)]],
units: [null, []],
decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]],
- noDataDisplayMessage: [null, []],
- showLegend: [null, []],
- legendConfig: [null, []]
- });
- this.widgetSettings.get('showTitle').valueChanges.subscribe((value: boolean) => {
- if (value) {
- this.widgetSettings.get('titleStyle').enable({emitEvent: false});
- this.widgetSettings.get('titleTooltip').enable({emitEvent: false});
- this.widgetSettings.get('showTitleIcon').enable({emitEvent: false});
- } else {
- this.widgetSettings.get('titleStyle').disable({emitEvent: false});
- this.widgetSettings.get('titleTooltip').disable({emitEvent: false});
- this.widgetSettings.get('showTitleIcon').patchValue(false);
- this.widgetSettings.get('showTitleIcon').disable({emitEvent: false});
- }
+ noDataDisplayMessage: [null, []]
});
- this.widgetSettings.get('showTitleIcon').valueChanges.subscribe((value: boolean) => {
- if (value) {
- this.widgetSettings.get('titleIcon').enable({emitEvent: false});
- this.widgetSettings.get('iconColor').enable({emitEvent: false});
- this.widgetSettings.get('iconSize').enable({emitEvent: false});
- } else {
- this.widgetSettings.get('titleIcon').disable({emitEvent: false});
- this.widgetSettings.get('iconColor').disable({emitEvent: false});
- this.widgetSettings.get('iconSize').disable({emitEvent: false});
- }
+ this.widgetSettings.get('showTitle').valueChanges.subscribe(() => {
+ this.updateWidgetSettingsEnabledState();
});
- this.widgetSettings.get('showLegend').valueChanges.subscribe((value: boolean) => {
- if (value) {
- this.widgetSettings.get('legendConfig').enable({emitEvent: false});
- } else {
- this.widgetSettings.get('legendConfig').disable({emitEvent: false});
- }
+ this.widgetSettings.get('showTitleIcon').valueChanges.subscribe(() => {
+ this.updateWidgetSettingsEnabledState();
});
+
this.layoutSettings = this.fb.group({
mobileOrder: [null, [Validators.pattern(/^-?[0-9]+$/)]],
mobileHeight: [null, [Validators.min(1), Validators.pattern(/^\d*$/)]],
@@ -238,11 +243,25 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
desktopHide: [false]
});
this.actionsSettings = this.fb.group({
- actionsData: [null, []]
+ actions: [null, []]
});
}
+ ngOnChanges(changes: SimpleChanges): void {
+ for (const propName of Object.keys(changes)) {
+ const change = changes[propName];
+ if (!change.firstChange && change.currentValue !== change.previousValue) {
+ if (propName === 'widgetConfigMode') {
+ if (this.hasBasicModeDirective) {
+ this.setupConfig();
+ }
+ }
+ }
+ }
+ }
+
ngOnDestroy(): void {
+ this.destroyBasicModeComponent();
this.removeChangeSubscriptions();
}
@@ -255,10 +274,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
this.targetDeviceSettingsSubscription.unsubscribe();
this.targetDeviceSettingsSubscription = null;
}
- if (this.alarmSourceSettingsSubscription) {
- this.alarmSourceSettingsSubscription.unsubscribe();
- this.alarmSourceSettingsSubscription = null;
- }
if (this.widgetSettingsSubscription) {
this.widgetSettingsSubscription.unsubscribe();
this.widgetSettingsSubscription = null;
@@ -284,9 +299,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
this.targetDeviceSettingsSubscription = this.targetDeviceSettings.valueChanges.subscribe(
() => this.updateTargetDeviceSettings()
);
- this.alarmSourceSettingsSubscription = this.alarmSourceSettings.valueChanges.subscribe(
- () => this.updateAlarmSourceSettings()
- );
this.widgetSettingsSubscription = this.widgetSettings.valueChanges.subscribe(
() => this.updateWidgetSettings()
);
@@ -301,34 +313,57 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
);
}
- private buildForms() {
- if (this.functionsOnly) {
- this.datasourceTypes = [DatasourceType.function];
- } else {
- this.datasourceTypes = [DatasourceType.function, DatasourceType.entity];
- if (this.widgetType === widgetType.latest) {
- this.datasourceTypes.push(DatasourceType.entityCount);
- this.datasourceTypes.push(DatasourceType.alarmCount);
+ private buildHeader() {
+ this.headerOptions.length = 0;
+ if (this.widgetType !== widgetType.static) {
+ this.headerOptions.push(
+ {
+ name: this.translate.instant('widget-config.data'),
+ value: 'data'
+ }
+ );
+ }
+ if (this.displayAppearance) {
+ this.headerOptions.push(
+ {
+ name: this.translate.instant('widget-config.appearance'),
+ value: 'appearance'
+ }
+ );
+ }
+ this.headerOptions.push(
+ {
+ name: this.translate.instant('widget-config.widget-card'),
+ value: 'card'
+ }
+ );
+ this.headerOptions.push(
+ {
+ name: this.translate.instant('widget-config.actions'),
+ value: 'actions'
+ }
+ );
+ this.headerOptions.push(
+ {
+ name: this.translate.instant('widget-config.mobile'),
+ value: 'mobile'
}
+ );
+ if (!this.selectedOption || !this.headerOptions.find(o => o.value === this.selectedOption)) {
+ this.selectedOption = this.headerOptions[0].value;
}
+ }
+ private buildForms() {
this.dataSettings = this.fb.group({});
this.targetDeviceSettings = this.fb.group({});
- this.alarmSourceSettings = this.fb.group({});
this.advancedSettings = this.fb.group({});
if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm || this.widgetType === widgetType.latest) {
- this.dataSettings.addControl('useDashboardTimewindow', this.fb.control(true));
- this.dataSettings.addControl('displayTimewindow', this.fb.control({value: true, disabled: true}));
- this.dataSettings.addControl('timewindow', this.fb.control({value: null, disabled: true}));
- this.dataSettings.get('useDashboardTimewindow').valueChanges.subscribe((value: boolean) => {
- if (value) {
- this.dataSettings.get('displayTimewindow').disable({emitEvent: false});
- this.dataSettings.get('timewindow').disable({emitEvent: false});
- } else {
- this.dataSettings.get('displayTimewindow').enable({emitEvent: false});
- this.dataSettings.get('timewindow').enable({emitEvent: false});
- }
- });
+ this.dataSettings.addControl('timewindowConfig', this.fb.control({
+ useDashboardTimewindow: true,
+ displayTimewindow: true,
+ timewindow: null
+ }));
if (this.widgetType === widgetType.alarm) {
this.dataSettings.addControl('alarmFilterConfig', this.fb.control(null));
}
@@ -337,24 +372,19 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
if (this.widgetType !== widgetType.rpc &&
this.widgetType !== widgetType.alarm &&
this.widgetType !== widgetType.static) {
- this.dataSettings.addControl('datasources',
- this.fb.array([]));
+ this.dataSettings.addControl('datasources', this.fb.control(null));
} else if (this.widgetType === widgetType.rpc) {
this.targetDeviceSettings.addControl('targetDeviceAliasId',
this.fb.control(null,
this.widgetEditMode ? [] : [Validators.required]));
} else if (this.widgetType === widgetType.alarm) {
- this.alarmSourceSettings = this.buildDatasourceForm();
+ this.dataSettings.addControl('alarmSource', this.fb.control(null));
}
}
this.advancedSettings.addControl('settings',
this.fb.control(null, []));
}
- datasourcesFormArray(): UntypedFormArray {
- return this.dataSettings.get('datasources') as UntypedFormArray;
- }
-
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
@@ -368,213 +398,199 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
writeValue(value: WidgetConfigComponentData): void {
this.modelValue = value;
- this.removeChangeSubscriptions();
+ this.widgetType = this.modelValue?.widgetType;
+ this.setupConfig();
+ }
+
+ private setupConfig() {
if (this.modelValue) {
- if (this.widgetType !== this.modelValue.widgetType) {
- this.widgetType = this.modelValue.widgetType;
- this.showLegendFieldset = (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.latest);
- this.buildForms();
+ this.destroyBasicModeComponent();
+ this.removeChangeSubscriptions();
+ if (this.hasBasicModeDirective && this.widgetConfigMode === WidgetConfigMode.basic) {
+ this.setupBasicModeConfig();
+ } else {
+ this.setupDefaultConfig();
}
- const config = this.modelValue.config;
- const layout = this.modelValue.layout;
- if (config) {
- this.selectedTab = 0;
- const displayWidgetTitle = isDefined(config.showTitle) ? config.showTitle : false;
- this.widgetSettings.patchValue({
- title: config.title,
- showTitleIcon: isDefined(config.showTitleIcon) && displayWidgetTitle ? config.showTitleIcon : false,
- titleIcon: isDefined(config.titleIcon) ? config.titleIcon : '',
- iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)',
- iconSize: isDefined(config.iconSize) ? config.iconSize : '24px',
- titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '',
- showTitle: displayWidgetTitle,
- dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true,
- enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true,
- backgroundColor: config.backgroundColor,
- color: config.color,
- padding: config.padding,
- margin: config.margin,
- widgetStyle: isDefined(config.widgetStyle) ? config.widgetStyle : {},
- widgetCss: isDefined(config.widgetCss) ? config.widgetCss : '',
- titleStyle: isDefined(config.titleStyle) ? config.titleStyle : {
- fontSize: '16px',
- fontWeight: 400
- },
- pageSize: isDefined(config.pageSize) ? config.pageSize : 1024,
- units: config.units,
- decimals: config.decimals,
- noDataDisplayMessage: isDefined(config.noDataDisplayMessage) ? config.noDataDisplayMessage : '',
- showLegend: isDefined(config.showLegend) ? config.showLegend :
- this.widgetType === widgetType.timeseries,
- legendConfig: config.legendConfig || defaultLegendConfig(this.widgetType)
- },
- {emitEvent: false}
- );
- const showTitle: boolean = this.widgetSettings.get('showTitle').value;
- if (showTitle) {
- this.widgetSettings.get('titleTooltip').enable({emitEvent: false});
- this.widgetSettings.get('titleStyle').enable({emitEvent: false});
- this.widgetSettings.get('showTitleIcon').enable({emitEvent: false});
- } else {
- this.widgetSettings.get('titleTooltip').disable({emitEvent: false});
- this.widgetSettings.get('titleStyle').disable({emitEvent: false});
- this.widgetSettings.get('showTitleIcon').disable({emitEvent: false});
- }
- const showTitleIcon: boolean = this.widgetSettings.get('showTitleIcon').value;
- if (showTitleIcon) {
- this.widgetSettings.get('titleIcon').enable({emitEvent: false});
- this.widgetSettings.get('iconColor').enable({emitEvent: false});
- this.widgetSettings.get('iconSize').enable({emitEvent: false});
- } else {
- this.widgetSettings.get('titleIcon').disable({emitEvent: false});
- this.widgetSettings.get('iconColor').disable({emitEvent: false});
- this.widgetSettings.get('iconSize').disable({emitEvent: false});
- }
- const showLegend: boolean = this.widgetSettings.get('showLegend').value;
- if (showLegend) {
- this.widgetSettings.get('legendConfig').enable({emitEvent: false});
- } else {
- this.widgetSettings.get('legendConfig').disable({emitEvent: false});
- }
- const actionsData: WidgetActionsData = {
- actionsMap: config.actions || {},
- actionSources: this.modelValue.actionSources || {}
- };
- this.actionsSettings.patchValue(
- {
- actionsData
+ }
+ }
+
+ private setupBasicModeConfig() {
+ const componentType = basicWidgetConfigComponentsMap[this.modelValue.basicModeDirective];
+ if (!componentType) {
+ this.basicModeDirectiveError = this.translate.instant('widget-config.settings-component-not-found',
+ {selector: this.modelValue.basicModeDirective});
+ } else {
+ const factory = this.cfr.resolveComponentFactory(componentType);
+ this.createBasicModeComponentTimeout = setTimeout(() => {
+ this.createBasicModeComponentTimeout = null;
+ this.basicModeComponentRef = this.basicModeContainer.createComponent(factory);
+ this.basicModeComponent = this.basicModeComponentRef.instance;
+ this.basicModeComponent.widgetConfig = this.modelValue;
+ this.basicModeComponentChangeSubscription = this.basicModeComponent.widgetConfigChanged.subscribe((data) => {
+ this.modelValue = data;
+ this.propagateChange(this.modelValue);
+ });
+ this.cd.markForCheck();
+ }, 0);
+ }
+ }
+
+ private destroyBasicModeComponent() {
+ this.basicModeDirectiveError = null;
+ if (this.basicModeComponentChangeSubscription) {
+ this.basicModeComponentChangeSubscription.unsubscribe();
+ this.basicModeComponentChangeSubscription = null;
+ }
+ if (this.createBasicModeComponentTimeout) {
+ clearTimeout(this.createBasicModeComponentTimeout);
+ this.createBasicModeComponentTimeout = null;
+ }
+ if (this.basicModeComponentRef) {
+ this.basicModeComponentRef.destroy();
+ this.basicModeComponentRef = null;
+ this.basicModeComponent = null;
+ }
+ if (this.basicModeContainer) {
+ this.basicModeContainer.clear();
+ }
+ }
+
+ private setupDefaultConfig() {
+ if (this.defaultConfigFormsType !== this.widgetType) {
+ this.defaultConfigFormsType = this.widgetType;
+ this.buildForms();
+ }
+ this.buildHeader();
+ const config = this.modelValue.config;
+ const layout = this.modelValue.layout;
+ if (config) {
+ const displayWidgetTitle = isDefined(config.showTitle) ? config.showTitle : false;
+ this.widgetSettings.patchValue({
+ title: config.title,
+ showTitleIcon: isDefined(config.showTitleIcon) && displayWidgetTitle ? config.showTitleIcon : false,
+ titleIcon: isDefined(config.titleIcon) ? config.titleIcon : '',
+ iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)',
+ iconSize: isDefined(config.iconSize) ? config.iconSize : '24px',
+ titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '',
+ showTitle: displayWidgetTitle,
+ dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true,
+ enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true,
+ backgroundColor: config.backgroundColor,
+ color: config.color,
+ padding: config.padding,
+ margin: config.margin,
+ widgetStyle: isDefined(config.widgetStyle) ? config.widgetStyle : {},
+ widgetCss: isDefined(config.widgetCss) ? config.widgetCss : '',
+ titleStyle: isDefined(config.titleStyle) ? config.titleStyle : {
+ fontSize: '16px',
+ fontWeight: 400
},
- {emitEvent: false}
- );
- if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm || this.widgetType === widgetType.latest) {
- const useDashboardTimewindow = isDefined(config.useDashboardTimewindow) ?
- config.useDashboardTimewindow : true;
- this.dataSettings.patchValue(
- { useDashboardTimewindow }, {emitEvent: false}
- );
- if (useDashboardTimewindow) {
- this.dataSettings.get('displayTimewindow').disable({emitEvent: false});
- this.dataSettings.get('timewindow').disable({emitEvent: false});
- } else {
- this.dataSettings.get('displayTimewindow').enable({emitEvent: false});
- this.dataSettings.get('timewindow').enable({emitEvent: false});
- }
- this.dataSettings.patchValue(
- { displayTimewindow: isDefined(config.displayTimewindow) ?
- config.displayTimewindow : true }, {emitEvent: false}
- );
- this.dataSettings.patchValue(
- { timewindow: config.timewindow }, {emitEvent: false}
- );
- }
- if (this.modelValue.isDataEnabled) {
- if (this.widgetType !== widgetType.rpc &&
- this.widgetType !== widgetType.alarm &&
- this.widgetType !== widgetType.static) {
- const datasourcesFormArray = this.dataSettings.get('datasources') as UntypedFormArray;
- datasourcesFormArray.clear();
- if (config.datasources) {
- config.datasources.forEach((datasource) => {
- datasourcesFormArray.push(this.buildDatasourceForm(datasource));
- });
- }
- } else if (this.widgetType === widgetType.rpc) {
- let targetDeviceAliasId: string;
- if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) {
- const aliasId = config.targetDeviceAliasIds[0];
- const entityAliases = this.aliasController.getEntityAliases();
- if (entityAliases[aliasId]) {
- targetDeviceAliasId = entityAliases[aliasId].id;
- } else {
- targetDeviceAliasId = null;
- }
+ pageSize: isDefined(config.pageSize) ? config.pageSize : 1024,
+ units: config.units,
+ decimals: config.decimals,
+ noDataDisplayMessage: isDefined(config.noDataDisplayMessage) ? config.noDataDisplayMessage : ''
+ },
+ {emitEvent: false}
+ );
+ this.updateWidgetSettingsEnabledState();
+ this.actionsSettings.patchValue(
+ {
+ actions: config.actions || {}
+ },
+ {emitEvent: false}
+ );
+ if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm || this.widgetType === widgetType.latest) {
+ const useDashboardTimewindow = isDefined(config.useDashboardTimewindow) ?
+ config.useDashboardTimewindow : true;
+ this.dataSettings.get('timewindowConfig').patchValue({
+ useDashboardTimewindow,
+ displayTimewindow: isDefined(config.displayTimewindow) ?
+ config.displayTimewindow : true,
+ timewindow: config.timewindow
+ }, {emitEvent: false});
+ }
+ if (this.modelValue.isDataEnabled) {
+ if (this.widgetType !== widgetType.rpc &&
+ this.widgetType !== widgetType.alarm &&
+ this.widgetType !== widgetType.static) {
+ this.dataSettings.patchValue({ datasources: config.datasources},
+ {emitEvent: false});
+ } else if (this.widgetType === widgetType.rpc) {
+ let targetDeviceAliasId: string;
+ if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) {
+ const aliasId = config.targetDeviceAliasIds[0];
+ const entityAliases = this.aliasController.getEntityAliases();
+ if (entityAliases[aliasId]) {
+ targetDeviceAliasId = entityAliases[aliasId].id;
} else {
targetDeviceAliasId = null;
}
- this.targetDeviceSettings.patchValue({
- targetDeviceAliasId
- }, {emitEvent: false});
- } else if (this.widgetType === widgetType.alarm) {
- this.dataSettings.patchValue(
- { alarmFilterConfig: isDefined(config.alarmFilterConfig) ?
- config.alarmFilterConfig : { statusList: [AlarmSearchStatus.ACTIVE], searchPropagatedAlarms: true } }, {emitEvent: false}
- );
- this.alarmSourceSettings.patchValue(
- config.alarmSource, {emitEvent: false}
- );
- const alarmSourceType: DatasourceType = this.alarmSourceSettings.get('type').value;
- this.alarmSourceSettings.get('entityAliasId').setValidators(
- alarmSourceType === DatasourceType.entity ? [Validators.required] : []
- );
- this.alarmSourceSettings.get('entityAliasId').updateValueAndValidity({emitEvent: false});
+ } else {
+ targetDeviceAliasId = null;
}
- }
-
- this.updateSchemaForm(config.settings);
-
- if (layout) {
- this.layoutSettings.patchValue(
- {
- mobileOrder: layout.mobileOrder,
- mobileHeight: layout.mobileHeight,
- mobileHide: layout.mobileHide,
- desktopHide: layout.desktopHide
- },
- {emitEvent: false}
- );
- } else {
- this.layoutSettings.patchValue(
- {
- mobileOrder: null,
- mobileHeight: null,
- mobileHide: false,
- desktopHide: false
- },
- {emitEvent: false}
+ this.targetDeviceSettings.patchValue({
+ targetDeviceAliasId
+ }, {emitEvent: false});
+ } else if (this.widgetType === widgetType.alarm) {
+ this.dataSettings.patchValue(
+ { alarmFilterConfig: isDefined(config.alarmFilterConfig) ?
+ config.alarmFilterConfig :
+ { statusList: [AlarmSearchStatus.ACTIVE], searchPropagatedAlarms: true },
+ alarmSource: config.alarmSource }, {emitEvent: false}
);
}
}
- this.createChangeSubscriptions();
+
+ this.updateSchemaForm(config.settings);
+
+ if (layout) {
+ this.layoutSettings.patchValue(
+ {
+ mobileOrder: layout.mobileOrder,
+ mobileHeight: layout.mobileHeight,
+ mobileHide: layout.mobileHide,
+ desktopHide: layout.desktopHide
+ },
+ {emitEvent: false}
+ );
+ } else {
+ this.layoutSettings.patchValue(
+ {
+ mobileOrder: null,
+ mobileHeight: null,
+ mobileHide: false,
+ desktopHide: false
+ },
+ {emitEvent: false}
+ );
+ }
}
+ this.createChangeSubscriptions();
}
- public dataKeysOptional(type?: DatasourceType): boolean {
- if (this.widgetType === widgetType.timeseries && this.modelValue?.typeParameters?.hasAdditionalLatestDataKeys) {
- return true;
+ private updateWidgetSettingsEnabledState() {
+ const showTitle: boolean = this.widgetSettings.get('showTitle').value;
+ const showTitleIcon: boolean = this.widgetSettings.get('showTitleIcon').value;
+ if (showTitle) {
+ this.widgetSettings.get('title').enable({emitEvent: false});
+ this.widgetSettings.get('titleTooltip').enable({emitEvent: false});
+ this.widgetSettings.get('titleStyle').enable({emitEvent: false});
+ this.widgetSettings.get('showTitleIcon').enable({emitEvent: false});
} else {
- return this.modelValue.typeParameters && this.modelValue.typeParameters.dataKeysOptional
- && type !== DatasourceType.entityCount && type !== DatasourceType.alarmCount;
+ this.widgetSettings.get('title').disable({emitEvent: false});
+ this.widgetSettings.get('titleTooltip').disable({emitEvent: false});
+ this.widgetSettings.get('titleStyle').disable({emitEvent: false});
+ this.widgetSettings.get('showTitleIcon').disable({emitEvent: false});
}
- }
-
- private buildDatasourceForm(datasource?: Datasource): UntypedFormGroup {
- const dataKeysRequired = !this.dataKeysOptional(datasource?.type);
- const datasourceFormGroup = this.fb.group(
- {
- type: [datasource ? datasource.type : null, [Validators.required]],
- name: [datasource ? datasource.name : null, []],
- entityAliasId: [datasource ? datasource.entityAliasId : null,
- datasource && (datasource.type === DatasourceType.entity ||
- datasource.type === DatasourceType.entityCount) ? [Validators.required] : []],
- filterId: [datasource ? datasource.filterId : null, []],
- dataKeys: [datasource ? datasource.dataKeys : null, dataKeysRequired ? [Validators.required] : []],
- alarmFilterConfig: [datasource && datasource.alarmFilterConfig ?
- datasource.alarmFilterConfig : { statusList: [AlarmSearchStatus.ACTIVE] }]
- }
- );
- if (this.widgetType === widgetType.timeseries && this.modelValue?.typeParameters?.hasAdditionalLatestDataKeys) {
- datasourceFormGroup.addControl('latestDataKeys', this.fb.control(datasource ? datasource.latestDataKeys : null));
+ if (showTitle && showTitleIcon) {
+ this.widgetSettings.get('titleIcon').enable({emitEvent: false});
+ this.widgetSettings.get('iconColor').enable({emitEvent: false});
+ this.widgetSettings.get('iconSize').enable({emitEvent: false});
+ } else {
+ this.widgetSettings.get('titleIcon').disable({emitEvent: false});
+ this.widgetSettings.get('iconColor').disable({emitEvent: false});
+ this.widgetSettings.get('iconSize').disable({emitEvent: false});
}
- datasourceFormGroup.get('type').valueChanges.subscribe((type: DatasourceType) => {
- datasourceFormGroup.get('entityAliasId').setValidators(
- (type === DatasourceType.entity || type === DatasourceType.entityCount) ? [Validators.required] : []
- );
- const newDataKeysRequired = !this.dataKeysOptional(type);
- datasourceFormGroup.get('dataKeys').setValidators(newDataKeysRequired ? [Validators.required] : []);
- datasourceFormGroup.get('entityAliasId').updateValueAndValidity();
- datasourceFormGroup.get('dataKeys').updateValueAndValidity();
- });
- return datasourceFormGroup;
}
private updateSchemaForm(settings?: any) {
@@ -597,7 +613,13 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
private updateDataSettings() {
if (this.modelValue) {
if (this.modelValue.config) {
- Object.assign(this.modelValue.config, this.dataSettings.value);
+ let data = this.dataSettings.value;
+ if (data.timewindowConfig) {
+ const timewindowConfig: TimewindowConfigData = data.timewindowConfig;
+ data = {...data, ...timewindowConfig};
+ delete data.timewindowConfig;
+ }
+ Object.assign(this.modelValue.config, data);
}
this.propagateChange(this.modelValue);
}
@@ -617,16 +639,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
}
}
- private updateAlarmSourceSettings() {
- if (this.modelValue) {
- if (this.modelValue.config) {
- const alarmSource: Datasource = this.alarmSourceSettings.value;
- this.modelValue.config.alarmSource = alarmSource;
- }
- this.propagateChange(this.modelValue);
- }
- }
-
private updateWidgetSettings() {
if (this.modelValue) {
if (this.modelValue.config) {
@@ -648,8 +660,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
private updateAdvancedSettings() {
if (this.modelValue) {
if (this.modelValue.config) {
- const settings = this.advancedSettings.get('settings').value?.model;
- this.modelValue.config.settings = settings;
+ this.modelValue.config.settings = this.advancedSettings.get('settings').value?.model;
}
this.propagateChange(this.modelValue);
}
@@ -658,19 +669,30 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
private updateActionSettings() {
if (this.modelValue) {
if (this.modelValue.config) {
- const actions = (this.actionsSettings.get('actionsData').value as WidgetActionsData).actionsMap;
- this.modelValue.config.actions = actions;
+ this.modelValue.config.actions = this.actionsSettings.get('actions').value;
}
this.propagateChange(this.modelValue);
}
}
- public displayAdvanced(): boolean {
+ public get hasBasicModeDirective(): boolean {
+ return this.modelValue?.basicModeDirective?.length > 0;
+ }
+
+ public get useDefinedBasicModeDirective(): boolean {
+ return this.modelValue?.basicModeDirective?.length && !this.basicModeDirectiveError;
+ }
+
+ public get displayAppearance(): boolean {
+ return this.displayAppearanceDataSettings || this.displayAdvancedAppearance;
+ }
+
+ public get displayAdvancedAppearance(): boolean {
return !!this.modelValue && (!!this.modelValue.settingsSchema && !!this.modelValue.settingsSchema.schema ||
!!this.modelValue.settingsDirective && !!this.modelValue.settingsDirective.length);
}
- public displayTimewindowConfig(): boolean {
+ public get displayTimewindowConfig(): boolean {
if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm) {
return true;
} else if (this.widgetType === widgetType.latest) {
@@ -679,40 +701,30 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
}
}
- public onlyHistoryTimewindow(): boolean {
- if (this.widgetType === widgetType.latest) {
- const datasources = this.dataSettings.get('datasources').value;
- return datasourcesHasOnlyComparisonAggregation(datasources);
- } else {
- return false;
- }
+ public get displayLimits(): boolean {
+ return this.widgetType !== widgetType.rpc && this.widgetType !== widgetType.alarm &&
+ this.modelValue?.isDataEnabled && !this.modelValue?.typeParameters?.singleEntity;
+ }
+
+ public get displayAppearanceDataSettings(): boolean {
+ return this.displayUnitsConfig || this.displayNoDataDisplayMessageConfig;
}
- public onDatasourceDrop(event: CdkDragDrop) {
- const datasourcesFormArray = this.datasourcesFormArray();
- const datasourceForm = datasourcesFormArray.at(event.previousIndex);
- datasourcesFormArray.removeAt(event.previousIndex);
- datasourcesFormArray.insert(event.currentIndex, datasourceForm);
+ public get displayUnitsConfig(): boolean {
+ return this.widgetType === widgetType.latest || this.widgetType === widgetType.timeseries;
}
- public removeDatasource(index: number) {
- this.datasourcesFormArray().removeAt(index);
+ public get displayNoDataDisplayMessageConfig(): boolean {
+ return this.widgetType !== widgetType.static && !this.modelValue?.typeParameters?.processNoDataByWidget;
}
- public addDatasource() {
- let newDatasource: Datasource;
- if (this.functionsOnly) {
- newDatasource = deepClone(this.utils.getDefaultDatasource(this.modelValue.dataKeySettingsSchema.schema));
- newDatasource.dataKeys = [this.generateDataKey('Sin', DataKeyType.function, this.modelValue.dataKeySettingsSchema)];
+ public onlyHistoryTimewindow(): boolean {
+ if (this.widgetType === widgetType.latest) {
+ const datasources = this.dataSettings.get('datasources').value;
+ return datasourcesHasOnlyComparisonAggregation(datasources);
} else {
- newDatasource = { type: DatasourceType.entity,
- dataKeys: []
- };
- }
- if (this.modelValue?.typeParameters?.hasAdditionalLatestDataKeys) {
- newDatasource.latestDataKeys = [];
+ return false;
}
- this.datasourcesFormArray().push(this.buildDatasourceForm(newDatasource));
}
public generateDataKey(chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema): DataKey {
@@ -810,17 +822,26 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
);
}
+ private fetchEntityKeysForDevice(deviceId: string, dataKeyTypes: Array): Observable> {
+ const entityFilter = singleEntityFilterFromDeviceId(deviceId);
+ return this.entityService.getEntityKeysByEntityFilter(
+ entityFilter,
+ dataKeyTypes,
+ {ignoreLoading: true, ignoreErrors: true}
+ ).pipe(
+ catchError(() => of([]))
+ );
+ }
+
private fetchEntityKeys(entityAliasId: string, dataKeyTypes: Array): Observable> {
return this.aliasController.getAliasInfo(entityAliasId).pipe(
- mergeMap((aliasInfo) => {
- return this.entityService.getEntityKeysByEntityFilter(
+ mergeMap((aliasInfo) => this.entityService.getEntityKeysByEntityFilter(
aliasInfo.entityFilter,
dataKeyTypes,
{ignoreLoading: true, ignoreErrors: true}
).pipe(
catchError(() => of([]))
- );
- }),
+ )),
catchError(() => of([] as Array))
);
}
@@ -841,9 +862,15 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
}
public validate(c: UntypedFormControl) {
- this.timeseriesKeyError = false;
- this.datasourceError = [];
- if (!this.dataSettings.valid) {
+ if (this.basicModeComponent) {
+ if (!this.basicModeComponent.validateConfig()) {
+ return {
+ basicWidgetConfig: {
+ valid: false
+ }
+ };
+ }
+ } else if (!this.dataSettings.valid) {
return {
dataSettings: {
valid: false
@@ -861,7 +888,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
valid: false
}
};
- } else if (!this.advancedSettings.valid || (this.displayAdvanced() && !this.modelValue.config.settings)) {
+ } else if (!this.advancedSettings.valid || (this.displayAdvancedAppearance && !this.modelValue.config.settings)) {
return {
advancedSettings: {
valid: false
@@ -877,54 +904,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
}
};
}
- } else if (this.widgetType === widgetType.alarm && this.modelValue.isDataEnabled) {
- if (!this.alarmSourceSettings.valid || !config.alarmSource) {
- return {
- alarmSource: {
- valid: false
- }
- };
- }
- } else if (this.widgetType !== widgetType.static && this.modelValue.isDataEnabled) {
- if (!this.modelValue.typeParameters.datasourcesOptional && (!config.datasources || !config.datasources.length)) {
- return {
- datasources: {
- valid: false
- }
- };
- }
- if (this.widgetType === widgetType.timeseries && this.modelValue?.typeParameters?.hasAdditionalLatestDataKeys) {
- let valid = config.datasources.filter(datasource => datasource?.dataKeys?.length).length > 0;
- if (!valid) {
- this.timeseriesKeyError = true;
- return {
- timeseriesDataKeys: {
- valid: false
- }
- };
- } else {
- const emptyDatasources = config.datasources.filter(datasource => !datasource?.dataKeys?.length &&
- !datasource?.latestDataKeys?.length);
- valid = emptyDatasources.length === 0;
- if (!valid) {
- for (const emptyDatasource of emptyDatasources) {
- const i = config.datasources.indexOf(emptyDatasource);
- this.datasourceError[i] = 'At least one data key should be specified';
- }
- return {
- dataKeys: {
- valid: false
- }
- };
- }
- }
- }
}
}
return null;
}
-
- public extensionPanelIsOpen(value) {
- this.openExtensionPanel = value;
- }
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html
new file mode 100644
index 0000000000..870cb08513
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.scss
new file mode 100644
index 0000000000..1782251f61
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.scss
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016-2023 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+:host {
+ z-index: 10;
+ background: #F3F6FA;
+ .tb-preview-dashboard {
+ position: absolute;
+ top: 15%;
+ bottom: 15%;
+ left: 0;
+ right: 0;
+ }
+ .tb-preview-panel {
+ position: absolute;
+ top: 16px;
+ left: 24px;
+ }
+}
+
+:host ::ng-deep {
+ tb-dashboard.tb-preview-dashboard {
+ .tb-dashboard-content {
+ background-color: transparent !important;
+ }
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.ts
new file mode 100644
index 0000000000..cd088e5744
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/widget-preview.component.ts
@@ -0,0 +1,79 @@
+///
+/// Copyright © 2016-2023 The Thingsboard Authors
+///
+/// Licensed under the Apache License, Version 2.0 (the "License");
+/// you may not use this file except in compliance with the License.
+/// You may obtain a copy of the License at
+///
+/// http://www.apache.org/licenses/LICENSE-2.0
+///
+/// Unless required by applicable law or agreed to in writing, software
+/// distributed under the License is distributed on an "AS IS" BASIS,
+/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+/// See the License for the specific language governing permissions and
+/// limitations under the License.
+///
+
+import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
+import { PageComponent } from '@shared/components/page.component';
+import { IAliasController, IStateController } from '@core/api/widget-api.models';
+import { Widget, WidgetConfig } from '@shared/models/widget.models';
+import { Store } from '@ngrx/store';
+import { AppState } from '@core/core.state';
+import { deepClone } from '@core/utils';
+
+@Component({
+ selector: 'tb-widget-preview',
+ templateUrl: './widget-preview.component.html',
+ styleUrls: ['./widget-preview.component.scss']
+})
+export class WidgetPreviewComponent extends PageComponent implements OnInit, OnChanges {
+
+ @Input()
+ aliasController: IAliasController;
+
+ @Input()
+ stateController: IStateController;
+
+ @Input()
+ widget: Widget;
+
+ @Input()
+ widgetConfig: WidgetConfig;
+
+ widgets: Widget[];
+
+ constructor(protected store: Store) {
+ super(store);
+ }
+
+ ngOnInit(): void {
+ this.loadPreviewWidget();
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ let reloadPreviewWidget = false;
+ for (const propName of Object.keys(changes)) {
+ const change = changes[propName];
+ if (!change.firstChange && change.currentValue !== change.previousValue) {
+ if (['widget', 'widgetConfig'].includes(propName)) {
+ reloadPreviewWidget = true;
+ }
+ }
+ }
+ if (reloadPreviewWidget) {
+ this.loadPreviewWidget();
+ }
+ }
+
+ private loadPreviewWidget() {
+ const widget = deepClone(this.widget);
+ widget.sizeX = 24;
+ widget.sizeY = this.widget.sizeY * 2;
+ widget.row = 0;
+ widget.col = 0;
+ widget.config = this.widgetConfig;
+ this.widgets = [widget];
+ }
+
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.html b/ui-ngx/src/app/modules/home/components/widget/widget.component.html
index ce5a5a57e7..cf287790a0 100644
--- a/ui-ngx/src/app/modules/home/components/widget/widget.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.html
@@ -15,23 +15,11 @@
limitations under the License.
-->
-
' + widgetErrorData.message) | safe:'html'">
@@ -42,5 +30,5 @@
class="tb-absolute-fill">{{ noDataDisplayMessageText }}
-
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts
index 8424999155..7e9b9cb6e7 100644
--- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts
@@ -39,10 +39,6 @@ import {
} from '@angular/core';
import { DashboardWidget } from '@home/models/dashboard-component.models';
import {
- defaultLegendConfig,
- LegendConfig,
- LegendData,
- LegendPosition,
Widget,
WidgetActionDescriptor,
widgetActionSources,
@@ -152,13 +148,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
displayNoData = false;
noDataDisplayMessageText: string;
- displayLegend: boolean;
- legendConfig: LegendConfig;
- legendData: LegendData;
- isLegendFirst: boolean;
- legendContainerLayoutType: string;
- legendStyle: {[klass: string]: any};
-
dynamicWidgetComponentRef: ComponentRef;
dynamicWidgetComponent: IDynamicWidgetComponent;
@@ -220,57 +209,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.widget = this.dashboardWidget.widget;
- this.displayLegend = isDefined(this.widget.config.showLegend) ? this.widget.config.showLegend
- : this.widget.type === widgetType.timeseries;
-
- this.legendContainerLayoutType = 'column';
-
- if (this.displayLegend) {
- this.legendConfig = this.widget.config.legendConfig || defaultLegendConfig(this.widget.type);
- this.legendData = {
- keys: [],
- data: []
- };
- if (this.legendConfig.position === LegendPosition.top ||
- this.legendConfig.position === LegendPosition.bottom) {
- this.legendContainerLayoutType = 'column';
- this.isLegendFirst = this.legendConfig.position === LegendPosition.top;
- } else {
- this.legendContainerLayoutType = 'row';
- this.isLegendFirst = this.legendConfig.position === LegendPosition.left;
- }
- switch (this.legendConfig.position) {
- case LegendPosition.top:
- this.legendStyle = {
- paddingBottom: '8px',
- maxHeight: '50%',
- overflowY: 'auto'
- };
- break;
- case LegendPosition.bottom:
- this.legendStyle = {
- paddingTop: '8px',
- maxHeight: '50%',
- overflowY: 'auto'
- };
- break;
- case LegendPosition.left:
- this.legendStyle = {
- paddingRight: '0px',
- maxWidth: '50%',
- overflowY: 'auto'
- };
- break;
- case LegendPosition.right:
- this.legendStyle = {
- paddingLeft: '0px',
- maxWidth: '50%',
- overflowY: 'auto'
- };
- break;
- }
- }
-
const actionDescriptorsBySourceId: {[actionSourceId: string]: Array} = {};
if (this.widget.config.actions) {
for (const actionSourceId of Object.keys(this.widget.config.actions)) {
@@ -463,13 +401,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
}
}
- public onLegendKeyHiddenChange(index: number) {
- for (const id of Object.keys(this.widgetContext.subscriptions)) {
- const subscription = this.widgetContext.subscriptions[id];
- subscription.updateDataVisibility(index);
- }
- }
-
private loadFromWidgetInfo() {
this.widgetContext.widgetNamespace =
`widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`;
@@ -884,9 +815,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
}
this.createSubscription(options, subscribe).subscribe(
(subscription) => {
- if (useDefaultComponents) {
- this.defaultSubscriptionOptions(subscription, options);
- }
createSubscriptionSubject.next(subscription);
createSubscriptionSubject.complete();
},
@@ -904,8 +832,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
? this.widget.config.displayTimewindow : !options.useDashboardTimewindow;
options.timeWindowConfig = options.useDashboardTimewindow ? this.widgetContext.dashboardTimewindow : this.widget.config.timewindow;
options.legendConfig = null;
- if (this.displayLegend) {
- options.legendConfig = this.legendConfig;
+ if (this.widget.config.settings.showLegend === true) {
+ options.legendConfig = this.widget.config.settings.legendConfig;
}
options.decimals = this.widgetContext.decimals;
options.units = this.widgetContext.units;
@@ -974,13 +902,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
});
}
};
-
- }
-
- private defaultSubscriptionOptions(subscription: IWidgetSubscription, options: WidgetSubscriptionOptions) {
- if (this.displayLegend) {
- this.legendData = subscription.legendData;
- }
}
private createDefaultSubscription(): Observable {
@@ -1011,7 +932,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.createSubscription(options).subscribe(
(subscription) => {
- this.defaultSubscriptionOptions(subscription, options);
// backward compatibility
this.widgetContext.datasources = subscription.datasources;
@@ -1491,7 +1411,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
}
}
- private loadCustomActionResources(actionNamespace: string, customCss: string, customResources: Array, actionDescriptor: WidgetActionDescriptor): Observable {
+ private loadCustomActionResources(actionNamespace: string, customCss: string, customResources: Array,
+ actionDescriptor: WidgetActionDescriptor): Observable {
const resourceTasks: Observable[] = [];
const modulesTasks: Observable[] = [];
diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts
index 8ddb3d6218..c00d0a8e82 100644
--- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts
+++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts
@@ -447,6 +447,7 @@ export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescri
}
export interface WidgetConfigComponentData {
+ widgetName: string;
config: WidgetConfig;
layout: WidgetLayout;
widgetType: widgetType;
@@ -459,6 +460,7 @@ export interface WidgetConfigComponentData {
settingsDirective: string;
dataKeySettingsDirective: string;
latestDataKeySettingsDirective: string;
+ basicModeDirective: string;
}
export const MissingWidgetType: WidgetInfo = {
@@ -551,6 +553,8 @@ export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo {
settingsDirective: widgetTypeEntity.descriptor.settingsDirective,
dataKeySettingsDirective: widgetTypeEntity.descriptor.dataKeySettingsDirective,
latestDataKeySettingsDirective: widgetTypeEntity.descriptor.latestDataKeySettingsDirective,
+ hasBasicMode: widgetTypeEntity.descriptor.hasBasicMode,
+ basicModeDirective: widgetTypeEntity.descriptor.basicModeDirective,
defaultConfig: widgetTypeEntity.descriptor.defaultConfig
};
}
@@ -581,6 +585,8 @@ export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
settingsDirective: widgetInfo.settingsDirective,
dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective,
latestDataKeySettingsDirective: widgetInfo.latestDataKeySettingsDirective,
+ hasBasicMode: widgetInfo.hasBasicMode,
+ basicModeDirective: widgetInfo.basicModeDirective,
defaultConfig: widgetInfo.defaultConfig
};
return {
diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html
index c4b859038e..046ad5acb2 100644
--- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html
+++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html
@@ -270,6 +270,17 @@
[(ngModel)]="widget.latestDataKeySettingsDirective"
(ngModelChange)="isDirty = true"/>
+
+ {{ 'widget.has-basic-mode' | translate }}
+
+
+ widget.basic-mode-form-selector
+
+