diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss
index 7d01f41cb5..1664dafb7f 100644
--- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss
+++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss
@@ -49,7 +49,7 @@
padding: 3px;
height: 24px;
cursor: move;
- mat-icon {
+ .mat-icon {
pointer-events: none;
}
}
@@ -65,9 +65,11 @@
font-weight: normal;
font-size: 14px;
line-height: 20px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ &.tb-chip-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
.mat-icon.tb-datakey-icon {
margin-right: 4px;
margin-left: 4px;
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts
index cc482bfd4e..bf2144cdfd 100644
--- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts
@@ -44,6 +44,7 @@ import { widgetSettingsComponentsMap } from '@home/components/widget/lib/setting
import { Dashboard } from '@shared/models/dashboard.models';
import { WidgetService } from '@core/http/widget.service';
import { IAliasController } from '@core/api/widget-api.models';
+import { WidgetConfigComponentData } from '@home/models/widget-component.models';
@Component({
selector: 'tb-widget-settings',
@@ -73,6 +74,9 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On
@Input()
widget: Widget;
+ @Input()
+ widgetConfig: WidgetConfigComponentData;
+
private settingsDirective: string;
definedDirectiveError: string;
@@ -126,6 +130,11 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On
this.definedSettingsComponent.aliasController = this.aliasController;
}
}
+ if (propName === 'widgetConfig') {
+ if (this.definedSettingsComponent) {
+ this.definedSettingsComponent.widgetConfig = this.widgetConfig;
+ }
+ }
}
}
}
@@ -214,6 +223,7 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On
this.definedSettingsComponent.aliasController = this.aliasController;
this.definedSettingsComponent.dashboard = this.dashboard;
this.definedSettingsComponent.widget = this.widget;
+ this.definedSettingsComponent.widgetConfig = this.widgetConfig;
this.definedSettingsComponent.functionScopeVariables = this.widgetService.getWidgetScopeVariables();
this.changeSubscription = this.definedSettingsComponent.settingsChanged.subscribe((settings) => {
this.updateModel(settings);
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts
index 34f2ac464b..0fa061de72 100644
--- a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts
@@ -276,6 +276,14 @@ export enum BackgroundType {
color = 'color'
}
+export const backgroundTypeTranslations = new Map
(
+ [
+ [BackgroundType.image, 'widgets.background.background-type-image'],
+ [BackgroundType.imageUrl, 'widgets.background.background-type-image-url'],
+ [BackgroundType.color, 'widgets.background.background-type-color']
+ ]
+);
+
export interface OverlaySettings {
enabled: boolean;
color: string;
@@ -313,11 +321,13 @@ export const backgroundStyle = (background: BackgroundSettings): ComponentStyle
};
} else {
const imageUrl = background.type === BackgroundType.image ? background.imageBase64 : background.imageUrl;
- return {
- background: `url(${imageUrl}) no-repeat`,
- backgroundSize: 'cover',
- backgroundPosition: '50% 50%'
- };
+ if (imageUrl) {
+ return {
+ background: `url(${imageUrl}) no-repeat 50% 50% / cover`
+ };
+ } else {
+ return {};
+ }
}
};
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss
index f5844d8aac..9b2d35e030 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/edges-overview-widget.component.scss
@@ -71,7 +71,7 @@
background-size: 18px 18px;
}
- mat-icon.node-icon {
+ .mat-icon.node-icon {
width: 22px;
min-width: 22px;
height: 22px;
@@ -109,7 +109,7 @@
background-size: 24px 24px;
}
- mat-icon.node-icon {
+ .mat-icon.node-icon {
width: 40px;
min-width: 40px;
height: 40px;
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss
index 6731690b0b..426d81b723 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.scss
@@ -64,7 +64,7 @@
background-size: 18px 18px;
}
- mat-icon.node-icon {
+ .mat-icon.node-icon {
width: 22px;
min-width: 22px;
height: 22px;
@@ -102,7 +102,7 @@
background-size: 24px 24px;
}
- mat-icon.node-icon {
+ .mat-icon.node-icon {
width: 40px;
min-width: 40px;
height: 40px;
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss
index a04f82dce7..b9c3e034a7 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss
@@ -31,7 +31,7 @@
display: flex;
flex-direction: column;
align-items: center;
- mat-icon {
+ .mat-icon {
margin: auto !important;
}
span.mdc-button__label {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html
new file mode 100644
index 0000000000..423c727a8d
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html
@@ -0,0 +1,89 @@
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts
new file mode 100644
index 0000000000..6f76546ac1
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts
@@ -0,0 +1,200 @@
+///
+/// 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, Injector } from '@angular/core';
+import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
+import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
+import { Store } from '@ngrx/store';
+import { AppState } from '@core/core.state';
+import {
+ valueCardDefaultSettings,
+ ValueCardLayout, valueCardLayoutImages,
+ valueCardLayouts, valueCardLayoutTranslations
+} from '@home/components/widget/lib/cards/value-card-widget.models';
+import { formatValue, isDefinedAndNotNull } from '@core/utils';
+import { WidgetConfigComponentData } from '@home/models/widget-component.models';
+import {
+ DateFormatProcessor,
+ DateFormatSettings,
+ getLabel
+} from '@home/components/widget/config/widget-settings.models';
+
+@Component({
+ selector: 'tb-value-card-widget-settings',
+ templateUrl: './value-card-widget-settings.component.html',
+ styleUrls: []
+})
+export class ValueCardWidgetSettingsComponent extends WidgetSettingsComponent {
+
+ valueCardLayouts: ValueCardLayout[] = [];
+
+ valueCardLayoutTranslationMap = valueCardLayoutTranslations;
+ valueCardLayoutImageMap = valueCardLayoutImages;
+
+ horizontal = false;
+
+ valueCardWidgetSettingsForm: UntypedFormGroup;
+
+ valuePreviewFn = this._valuePreviewFn.bind(this);
+
+ datePreviewFn = this._datePreviewFn.bind(this);
+
+
+ get label(): string {
+ return getLabel(this.widgetConfig.config.datasources);
+ }
+
+ get dateEnabled(): boolean {
+ const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value;
+ return ![ValueCardLayout.vertical, ValueCardLayout.simplified].includes(layout);
+ }
+
+ get iconEnabled(): boolean {
+ const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value;
+ return layout !== ValueCardLayout.simplified;
+ }
+
+ constructor(protected store: Store,
+ private $injector: Injector,
+ private fb: UntypedFormBuilder) {
+ super(store);
+ }
+
+ protected settingsForm(): UntypedFormGroup {
+ return this.valueCardWidgetSettingsForm;
+ }
+
+ protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) {
+ const params = widgetConfig.typeParameters as any;
+ this.horizontal = isDefinedAndNotNull(params.horizontal) ? params.horizontal : false;
+ this.valueCardLayouts = valueCardLayouts(this.horizontal);
+ }
+
+ protected defaultSettings(): WidgetSettings {
+ return valueCardDefaultSettings(this.horizontal);
+ }
+
+ protected onSettingsSet(settings: WidgetSettings) {
+ this.valueCardWidgetSettingsForm = this.fb.group({
+ layout: [settings.layout, []],
+
+ showLabel: [settings.showLabel, []],
+ labelFont: [settings.labelFont, []],
+ labelColor: [settings.labelColor, []],
+
+ showIcon: [settings.showIcon, []],
+ iconSize: [settings.iconSize, [Validators.min(0)]],
+ iconSizeUnit: [settings.iconSizeUnit, []],
+ icon: [settings.icon, []],
+ iconColor: [settings.iconColor, []],
+
+ valueFont: [settings.valueFont, []],
+ valueColor: [settings.valueColor, []],
+
+ showDate: [settings.showDate, []],
+ dateFormat: [settings.dateFormat, []],
+ dateFont: [settings.dateFont, []],
+ dateColor: [settings.dateColor, []],
+
+ background: [settings.background, []]
+ });
+ }
+
+ protected validatorTriggers(): string[] {
+ return ['layout', 'showLabel', 'showIcon', 'showDate'];
+ }
+
+ protected updateValidators(emitEvent: boolean) {
+ const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value;
+ const showLabel: boolean = this.valueCardWidgetSettingsForm.get('showLabel').value;
+ const showIcon: boolean = this.valueCardWidgetSettingsForm.get('showIcon').value;
+ const showDate: boolean = this.valueCardWidgetSettingsForm.get('showDate').value;
+
+ const dateEnabled = ![ValueCardLayout.vertical, ValueCardLayout.simplified].includes(layout);
+ const iconEnabled = layout !== ValueCardLayout.simplified;
+
+ if (showLabel) {
+ this.valueCardWidgetSettingsForm.get('labelFont').enable();
+ this.valueCardWidgetSettingsForm.get('labelColor').enable();
+ } else {
+ this.valueCardWidgetSettingsForm.get('labelFont').disable();
+ this.valueCardWidgetSettingsForm.get('labelColor').disable();
+ }
+
+ if (iconEnabled) {
+ this.valueCardWidgetSettingsForm.get('showIcon').enable({emitEvent: false});
+ if (showIcon) {
+ this.valueCardWidgetSettingsForm.get('iconSize').enable();
+ this.valueCardWidgetSettingsForm.get('iconSizeUnit').enable();
+ this.valueCardWidgetSettingsForm.get('icon').enable();
+ this.valueCardWidgetSettingsForm.get('iconColor').enable();
+ } else {
+ this.valueCardWidgetSettingsForm.get('iconSize').disable();
+ this.valueCardWidgetSettingsForm.get('iconSizeUnit').disable();
+ this.valueCardWidgetSettingsForm.get('icon').disable();
+ this.valueCardWidgetSettingsForm.get('iconColor').disable();
+ }
+ } else {
+ this.valueCardWidgetSettingsForm.get('showIcon').disable({emitEvent: false});
+ this.valueCardWidgetSettingsForm.get('iconSize').disable();
+ this.valueCardWidgetSettingsForm.get('iconSizeUnit').disable();
+ this.valueCardWidgetSettingsForm.get('icon').disable();
+ this.valueCardWidgetSettingsForm.get('iconColor').disable();
+ }
+
+ if (dateEnabled) {
+ this.valueCardWidgetSettingsForm.get('showDate').enable({emitEvent: false});
+ if (showDate) {
+ this.valueCardWidgetSettingsForm.get('dateFormat').enable();
+ this.valueCardWidgetSettingsForm.get('dateFont').enable();
+ this.valueCardWidgetSettingsForm.get('dateColor').enable();
+ } else {
+ this.valueCardWidgetSettingsForm.get('dateFormat').disable();
+ this.valueCardWidgetSettingsForm.get('dateFont').disable();
+ this.valueCardWidgetSettingsForm.get('dateColor').disable();
+ }
+ } else {
+ this.valueCardWidgetSettingsForm.get('showDate').disable({emitEvent: false});
+ this.valueCardWidgetSettingsForm.get('dateFormat').disable();
+ this.valueCardWidgetSettingsForm.get('dateFont').disable();
+ this.valueCardWidgetSettingsForm.get('dateColor').disable();
+ }
+ this.valueCardWidgetSettingsForm.get('showIcon').updateValueAndValidity({emitEvent: false});
+ this.valueCardWidgetSettingsForm.get('showDate').updateValueAndValidity({emitEvent: false});
+ this.valueCardWidgetSettingsForm.get('labelFont').updateValueAndValidity({emitEvent});
+ this.valueCardWidgetSettingsForm.get('labelColor').updateValueAndValidity({emitEvent});
+ this.valueCardWidgetSettingsForm.get('iconSize').updateValueAndValidity({emitEvent});
+ this.valueCardWidgetSettingsForm.get('iconSizeUnit').updateValueAndValidity({emitEvent});
+ this.valueCardWidgetSettingsForm.get('icon').updateValueAndValidity({emitEvent});
+ this.valueCardWidgetSettingsForm.get('iconColor').updateValueAndValidity({emitEvent});
+ this.valueCardWidgetSettingsForm.get('dateFormat').updateValueAndValidity({emitEvent});
+ this.valueCardWidgetSettingsForm.get('dateFont').updateValueAndValidity({emitEvent});
+ this.valueCardWidgetSettingsForm.get('dateColor').updateValueAndValidity({emitEvent});
+ }
+
+ private _valuePreviewFn(): string {
+ const units: string = this.widgetConfig.config.units;
+ const decimals: number = this.widgetConfig.config.decimals;
+ return formatValue(22, decimals, units, true);
+ }
+
+ private _datePreviewFn(): string {
+ const dateFormat: DateFormatSettings = this.valueCardWidgetSettingsForm.get('dateFormat').value;
+ const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat);
+ processor.update(Date.now());
+ return processor.formatted;
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html
new file mode 100644
index 0000000000..ca5d4bc8b9
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html
@@ -0,0 +1,87 @@
+
+
+
widgets.background.background-settings
+
+
+
+
+ widgets.background.preview
+
+
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss
new file mode 100644
index 0000000000..258117512a
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss
@@ -0,0 +1,73 @@
+/**
+ * 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 '../../../../../../../../scss/constants';
+
+.tb-background-settings-panel {
+ width: 620px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ @media #{$mat-lt-md} {
+ width: 90vw;
+ }
+ .tb-background-settings-title {
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 24px;
+ letter-spacing: 0.25px;
+ color: rgba(0, 0, 0, 0.87);
+ }
+ .tb-background-settings-preview {
+ flex: 1;
+ background: rgba(0, 0, 0, 0.04);
+ display: flex;
+ flex-direction: column;
+ padding: 12px 16px 24px 16px;
+ align-items: center;
+ gap: 12px;
+ }
+ .tb-background-settings-preview-title {
+ align-self: stretch;
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 24px;
+ color: rgba(0, 0, 0, 0.38);
+ }
+ .tb-background-settings-preview-box {
+ position: relative;
+ width: 136px;
+ height: 118px;
+ border-radius: 2.666px;
+ }
+ .tb-background-settings-preview-overlay {
+ position: absolute;
+ border-radius: 2.666px;
+ top: 7.998px;
+ bottom: 7.998px;
+ left: 7.998px;
+ right: 7.998px;
+ }
+ .tb-background-settings-panel-buttons {
+ height: 40px;
+ display: flex;
+ flex-direction: row;
+ gap: 16px;
+ justify-content: flex-end;
+ align-items: flex-end;
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts
new file mode 100644
index 0000000000..51d2ddec1b
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts
@@ -0,0 +1,120 @@
+///
+/// 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, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
+import { PageComponent } from '@shared/components/page.component';
+import {
+ backgroundStyle,
+ overlayStyle,
+ BackgroundSettings,
+ BackgroundType,
+ backgroundTypeTranslations, ComponentStyle
+} from '@home/components/widget/config/widget-settings.models';
+import { TbPopoverComponent } from '@shared/components/popover.component';
+import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
+import { Store } from '@ngrx/store';
+import { AppState } from '@core/core.state';
+
+@Component({
+ selector: 'tb-background-settings-panel',
+ templateUrl: './background-settings-panel.component.html',
+ providers: [],
+ styleUrls: ['./background-settings-panel.component.scss'],
+ encapsulation: ViewEncapsulation.None
+})
+export class BackgroundSettingsPanelComponent extends PageComponent implements OnInit {
+
+ @Input()
+ backgroundSettings: BackgroundSettings;
+
+ @Input()
+ popover: TbPopoverComponent;
+
+ @Output()
+ backgroundSettingsApplied = new EventEmitter();
+
+ backgroundType = BackgroundType;
+
+ backgroundTypes = Object.keys(BackgroundType) as BackgroundType[];
+
+ backgroundTypeTranslationsMap = backgroundTypeTranslations;
+
+ backgroundSettingsFormGroup: UntypedFormGroup;
+
+ backgroundStyle: ComponentStyle = {};
+ overlayStyle: ComponentStyle = {};
+
+ constructor(private fb: UntypedFormBuilder,
+ protected store: Store) {
+ super(store);
+ }
+
+ ngOnInit(): void {
+ this.backgroundSettingsFormGroup = this.fb.group(
+ {
+ type: [this.backgroundSettings?.type, []],
+ imageBase64: [this.backgroundSettings?.imageBase64, []],
+ imageUrl: [this.backgroundSettings?.imageUrl, []],
+ color: [this.backgroundSettings?.color, []],
+ overlay: this.fb.group({
+ enabled: [this.backgroundSettings?.overlay?.enabled, []],
+ color: [this.backgroundSettings?.overlay?.color, []],
+ blur: [this.backgroundSettings?.overlay?.blur, []]
+ })
+ }
+ );
+ this.backgroundSettingsFormGroup.get('type').valueChanges.subscribe(() => {
+ setTimeout(() => {this.popover?.updatePosition();}, 0);
+ });
+ this.backgroundSettingsFormGroup.get('overlay').get('enabled').valueChanges.subscribe(() => {
+ this.updateValidators();
+ });
+ this.backgroundSettingsFormGroup.valueChanges.subscribe(() => {
+ this.updateBackgroundStyle();
+ });
+ this.updateValidators();
+ this.updateBackgroundStyle();
+ }
+
+ cancel() {
+ this.popover?.hide();
+ }
+
+ applyColorSettings() {
+ const backgroundSettings = this.backgroundSettingsFormGroup.value;
+ this.backgroundSettingsApplied.emit(backgroundSettings);
+ }
+
+ private updateValidators() {
+ const overlayEnabled: boolean = this.backgroundSettingsFormGroup.get('overlay').get('enabled').value;
+ if (overlayEnabled) {
+ this.backgroundSettingsFormGroup.get('overlay').get('color').enable();
+ this.backgroundSettingsFormGroup.get('overlay').get('blur').enable();
+ } else {
+ this.backgroundSettingsFormGroup.get('overlay').get('color').disable();
+ this.backgroundSettingsFormGroup.get('overlay').get('blur').disable();
+ }
+ this.backgroundSettingsFormGroup.get('overlay').get('color').updateValueAndValidity({emitEvent: false});
+ this.backgroundSettingsFormGroup.get('overlay').get('blur').updateValueAndValidity({emitEvent: false});
+ }
+
+ private updateBackgroundStyle() {
+ const background: BackgroundSettings = this.backgroundSettingsFormGroup.value;
+ this.backgroundStyle = backgroundStyle(background);
+ this.overlayStyle = overlayStyle(background.overlay);
+ }
+
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html
new file mode 100644
index 0000000000..e9e1b99b0e
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html
@@ -0,0 +1,30 @@
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss
new file mode 100644
index 0000000000..6f73fbffa4
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss
@@ -0,0 +1,41 @@
+/**
+ * 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.
+ */
+button.mat-mdc-button-base.tb-box-button.tb-background-settings {
+ padding: 0;
+ .mat-mdc-button-persistent-ripple {
+ z-index: 2;
+ }
+ .tb-color-preview {
+ width: 38px;
+ min-width: 38px;
+ height: 38px;
+ &.box {
+ .tb-color-result {
+ &:after {
+ border: none;
+ }
+ }
+ .tb-color-overlay {
+ position: absolute;
+ border-radius: 3px;
+ top: 4px;
+ bottom: 4px;
+ left: 4px;
+ right: 4px;
+ }
+ }
+ }
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts
new file mode 100644
index 0000000000..f8162575a3
--- /dev/null
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts
@@ -0,0 +1,120 @@
+///
+/// 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, forwardRef, Input, OnInit, Renderer2, ViewContainerRef, ViewEncapsulation } from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+import {
+ BackgroundSettings,
+ backgroundStyle,
+ BackgroundType,
+ ComponentStyle,
+ overlayStyle
+} from '@home/components/widget/config/widget-settings.models';
+import { MatButton } from '@angular/material/button';
+import { TbPopoverService } from '@shared/components/popover.service';
+import {
+ BackgroundSettingsPanelComponent
+} from '@home/components/widget/lib/settings/common/background-settings-panel.component';
+
+@Component({
+ selector: 'tb-background-settings',
+ templateUrl: './background-settings.component.html',
+ styleUrls: ['./background-settings.component.scss'],
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => BackgroundSettingsComponent),
+ multi: true
+ }
+ ],
+ encapsulation: ViewEncapsulation.None
+})
+export class BackgroundSettingsComponent implements OnInit, ControlValueAccessor {
+
+ @Input()
+ disabled: boolean;
+
+ backgroundType = BackgroundType;
+
+ modelValue: BackgroundSettings;
+
+ backgroundStyle: ComponentStyle = {};
+
+ overlayStyle: ComponentStyle = {};
+
+ private propagateChange = null;
+
+ constructor(private popoverService: TbPopoverService,
+ private renderer: Renderer2,
+ private viewContainerRef: ViewContainerRef) {}
+
+ ngOnInit(): void {
+ }
+
+ registerOnChange(fn: any): void {
+ this.propagateChange = fn;
+ }
+
+ registerOnTouched(fn: any): void {
+ }
+
+ setDisabledState(isDisabled: boolean): void {
+ this.disabled = isDisabled;
+ this.updateBackgroundStyle();
+ }
+
+ writeValue(value: BackgroundSettings): void {
+ this.modelValue = value;
+ this.updateBackgroundStyle();
+ }
+
+ openBackgroundSettingsPopup($event: Event, matButton: MatButton) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ const trigger = matButton._elementRef.nativeElement;
+ if (this.popoverService.hasPopover(trigger)) {
+ this.popoverService.hidePopover(trigger);
+ } else {
+ const ctx: any = {
+ backgroundSettings: this.modelValue
+ };
+ const backgroundSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
+ this.viewContainerRef, BackgroundSettingsPanelComponent, 'left', true, null,
+ ctx,
+ {},
+ {}, {}, true);
+ backgroundSettingsPanelPopover.tbComponentRef.instance.popover = backgroundSettingsPanelPopover;
+ backgroundSettingsPanelPopover.tbComponentRef.instance.backgroundSettingsApplied.subscribe((backgroundSettings) => {
+ backgroundSettingsPanelPopover.hide();
+ this.modelValue = backgroundSettings;
+ this.updateBackgroundStyle();
+ this.propagateChange(this.modelValue);
+ });
+ }
+ }
+
+ private updateBackgroundStyle() {
+ if (!this.disabled) {
+ this.backgroundStyle = backgroundStyle(this.modelValue);
+ this.overlayStyle = overlayStyle(this.modelValue.overlay);
+ } else {
+ this.backgroundStyle = {};
+ this.overlayStyle = {};
+ }
+ }
+
+}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts
index e538653703..d75c4a5fc9 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts
@@ -21,14 +21,14 @@ import {
Directive,
ElementRef,
forwardRef,
- Input,
+ Input, OnChanges,
OnDestroy, OnInit,
- QueryList,
+ QueryList, SimpleChanges,
ViewEncapsulation
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms';
import { coerceBoolean } from '@shared/decorators/coercion';
-import { Observable, Subject } from 'rxjs';
+import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
import { map, share, startWith, takeUntil } from 'rxjs/operators';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
@@ -73,7 +73,7 @@ export class ImageCardsSelectOptionDirective {
],
encapsulation: ViewEncapsulation.None
})
-export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit, AfterContentInit, OnDestroy {
+export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit, OnChanges, AfterContentInit, OnDestroy {
@ContentChildren(ImageCardsSelectOptionDirective) imageCardsSelectOptions: QueryList;
@@ -107,20 +107,33 @@ export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit,
private _destroyed = new Subject();
+ private _colsChanged = new BehaviorSubject(null);
+
constructor(private breakpointObserver: BreakpointObserver) {
this.valueFormControl = new UntypedFormControl('');
}
ngOnInit(): void {
const gridColumns = this.breakpointObserver.isMatched(MediaBreakpoints['lt-md']) ? this.colsLtMd : this.cols;
- this.cols$ = this.breakpointObserver
- .observe(MediaBreakpoints['lt-md']).pipe(
- map((state) => state.matches ? this.colsLtMd : this.cols),
+ this.cols$ = combineLatest({state: this.breakpointObserver
+ .observe(MediaBreakpoints['lt-md']), colsChanged: this._colsChanged.asObservable()}).pipe(
+ map((data) => data.state.matches ? this.colsLtMd : this.cols),
startWith(gridColumns),
share()
);
}
+ ngOnChanges(changes: SimpleChanges): void {
+ for (const propName of Object.keys(changes)) {
+ const change = changes[propName];
+ if (!change.firstChange && change.currentValue !== change.previousValue) {
+ if (['cols', 'colsLtMd'].includes(propName)) {
+ this._colsChanged.next(null);
+ }
+ }
+ }
+ }
+
ngAfterContentInit(): void {
this.imageCardsSelectOptions.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => {
this.syncImageCardsSelectOptions();
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
index 68b84578c1..748d90649d 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
@@ -281,6 +281,13 @@ import { DateFormatSelectComponent } from '@home/components/widget/lib/settings/
import {
DateFormatSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/date-format-settings-panel.component';
+import { BackgroundSettingsComponent } from '@home/components/widget/lib/settings/common/background-settings.component';
+import {
+ BackgroundSettingsPanelComponent
+} from '@home/components/widget/lib/settings/common/background-settings-panel.component';
+import {
+ ValueCardWidgetSettingsComponent
+} from '@home/components/widget/lib/settings/cards/value-card-widget-settings.component';
@NgModule({
declarations: [
@@ -391,7 +398,10 @@ import {
ColorSettingsPanelComponent,
CssUnitSelectComponent,
DateFormatSelectComponent,
- DateFormatSettingsPanelComponent
+ DateFormatSettingsPanelComponent,
+ BackgroundSettingsComponent,
+ BackgroundSettingsPanelComponent,
+ ValueCardWidgetSettingsComponent
],
imports: [
CommonModule,
@@ -506,7 +516,10 @@ import {
ColorSettingsPanelComponent,
CssUnitSelectComponent,
DateFormatSelectComponent,
- DateFormatSettingsPanelComponent
+ DateFormatSettingsPanelComponent,
+ BackgroundSettingsComponent,
+ BackgroundSettingsPanelComponent,
+ ValueCardWidgetSettingsComponent
]
})
export class WidgetSettingsModule {
@@ -575,5 +588,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
index 6601aa96db..43b54884ec 100644
--- a/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
@@ -19,7 +19,6 @@
[fullscreenBackgroundStyle]="dashboardStyle"
[fullscreenBackgroundImage]="backgroundImage"
(fullscreenChanged)="onFullscreenChanged($event)"
- fxLayout="column"
class="tb-widget"
[ngClass]="{
'tb-highlighted': isHighlighted(widget),
@@ -32,8 +31,11 @@
(mousedown)="onMouseDown($event)"
(click)="onClicked($event)"
(contextmenu)="onContextMenu($event)">
-