diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html new file mode 100644 index 0000000000..8db7f074de --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.html @@ -0,0 +1,50 @@ + +
{{ panelTitle }}
+
+
+
+
+
widgets.color.from
+ + + +
widgets.color.to
+ + + + + +
+ +
+
+
+ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts new file mode 100644 index 0000000000..f135d77092 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-list.component.ts @@ -0,0 +1,136 @@ +/// +/// 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, OnDestroy, OnInit } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormGroup, + NG_VALUE_ACCESSOR, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormGroup +} from '@angular/forms'; +import { ColorRange } from '@shared/models/widget-settings.models'; +import { TbPopoverComponent } from '@shared/components/popover.component'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'tb-color-range-list', + templateUrl: './color-range-list.component.html', + styleUrls: ['color-settings-panel.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ColorRangeListComponent), + multi: true + } + ] +}) +export class ColorRangeListComponent implements OnInit, ControlValueAccessor, OnDestroy { + + @Input() + disabled: boolean; + + @Input() + popover: TbPopoverComponent; + + @Input() + panelTitle: string; + + modelValue: any; + + colorRangeListFormGroup: UntypedFormGroup; + + private destroy$ = new Subject(); + + private propagateChange = null; + + constructor(private fb: UntypedFormBuilder) {} + + ngOnInit(): void { + this.colorRangeListFormGroup = this.fb.group({ + rangeList: this.fb.array([]) + }); + + this.colorRangeListFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => this.updateModel()); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: any): void { + if (value && value?.length) { + value.forEach((r) => this.rangeListFormArray.push(this.colorRangeControl(r), {emitEvent: false})); + } + } + + private colorRangeControl(range: ColorRange): UntypedFormGroup { + return this.fb.group({ + from: [range?.from, []], + to: [range?.to, []], + color: [range?.color, []] + }); + } + + get rangeListFormArray(): UntypedFormArray { + return this.colorRangeListFormGroup.get('rangeList') as UntypedFormArray; + } + + get rangeListFormGroups(): FormGroup[] { + return this.rangeListFormArray.controls as FormGroup[]; + } + + trackByRange(index: number, rangeControl: AbstractControl): any { + return rangeControl; + } + + removeRange(index: number) { + this.rangeListFormArray.removeAt(index); + this.colorRangeListFormGroup.markAsDirty(); + setTimeout(() => {this.popover?.updatePosition();}, 0); + } + + addRange() { + const newRange: ColorRange = { + color: 'rgba(0,0,0,0.87)' + }; + this.rangeListFormArray.push(this.colorRangeControl(newRange)); + this.colorRangeListFormGroup.markAsDirty(); + setTimeout(() => {this.popover?.updatePosition();}, 0); + } + + updateModel() { + this.propagateChange(this.colorRangeListFormGroup.get('rangeList').value); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.html new file mode 100644 index 0000000000..449562f184 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.html @@ -0,0 +1,57 @@ + +
+
widgets.color.color-settings
+
+ + +
+
+ + + +
+ +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.ts new file mode 100644 index 0000000000..bdc9500605 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-panel.component.ts @@ -0,0 +1,78 @@ +/// +/// 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 { ColorRange } from '@shared/models/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'; +import { deepClone } from '@core/utils'; +import { + ColorRangeSettingsComponent +} from '@home/components/widget/lib/settings/common/color-range-settings.component'; + +@Component({ + selector: 'tb-color-range-panel', + templateUrl: './color-range-panel.component.html', + providers: [], + styleUrls: ['./color-settings-panel.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class ColorRangePanelComponent extends PageComponent implements OnInit { + + @Input() + colorRangeSettings: Array; + + @Input() + popover: TbPopoverComponent; + + @Input() + settingsComponents: ColorRangeSettingsComponent[]; + + @Output() + colorRangeApplied = new EventEmitter>(); + + colorRangeFormGroup: UntypedFormGroup; + + constructor(private fb: UntypedFormBuilder, + protected store: Store) { + super(store); + } + + ngOnInit(): void { + this.colorRangeFormGroup = this.fb.group({ + rangeList: [this.colorRangeSettings, []] + }); + } + + copyColorSettings(comp: ColorRangeSettingsComponent) { + this.colorRangeSettings = deepClone(comp.modelValue); + this.colorRangeFormGroup.get('rangeList').patchValue(this.colorRangeSettings || [], {emitEvent: false}); + this.colorRangeFormGroup.markAsDirty(); + } + + cancel() { + this.popover?.hide(); + } + + applyColorRangeSettings() { + const colorRangeSettings = this.colorRangeFormGroup.get('rangeList').value; + this.colorRangeApplied.emit(colorRangeSettings); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-settings.component.html new file mode 100644 index 0000000000..cf0298e99a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-settings.component.html @@ -0,0 +1,27 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-settings.component.ts new file mode 100644 index 0000000000..989bdb2844 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-range-settings.component.ts @@ -0,0 +1,166 @@ +/// +/// 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, + Injectable, + Input, + OnDestroy, + OnInit, + Renderer2, + ViewContainerRef +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ColorRange, ComponentStyle } from '@shared/models/widget-settings.models'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { ColorRangePanelComponent } from '@home/components/widget/lib/settings/common/color-range-panel.component'; + +@Injectable() +export class ColorRangeSettingsComponentService { + + private colorSettingsComponents = new Set(); + + constructor() {} + + public registerColorSettingsComponent(comp: ColorRangeSettingsComponent) { + this.colorSettingsComponents.add(comp); + } + + public unregisterColorSettingsComponent(comp: ColorRangeSettingsComponent) { + this.colorSettingsComponents.delete(comp); + } + + public getOtherColorSettingsComponents(comp: ColorRangeSettingsComponent): ColorRangeSettingsComponent[] { + const result: ColorRangeSettingsComponent[] = []; + for (const component of this.colorSettingsComponents.values()) { + if (component.settingsKey && component.modelValue && component !== comp) { + result.push(component); + } + } + return result; + } + +} + +@Component({ + selector: 'tb-color-range-settings', + templateUrl: './color-range-settings.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ColorRangeSettingsComponent), + multi: true + } + ] +}) +export class ColorRangeSettingsComponent implements OnInit, ControlValueAccessor, OnDestroy { + + @Input() + disabled: boolean; + + @Input() + settingsKey: string; + + modelValue: Array; + + colorStyle: ComponentStyle = {}; + + private propagateChange = null; + + constructor(private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef, + private colorSettingsComponentService: ColorRangeSettingsComponentService) {} + + ngOnInit(): void { + this.colorSettingsComponentService.registerColorSettingsComponent(this); + } + + ngOnDestroy() { + this.colorSettingsComponentService.unregisterColorSettingsComponent(this); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + this.updateColorStyle(); + } + + writeValue(value: Array): void { + this.modelValue = value; + this.updateColorStyle(); + } + + openColorRangeSettingsPopup($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 = { + colorRangeSettings: this.modelValue, + settingsComponents: this.colorSettingsComponentService.getOtherColorSettingsComponents(this) + }; + const colorRangeSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, ColorRangePanelComponent, 'left', true, null, + ctx, + {}, + {}, {}, true); + colorRangeSettingsPanelPopover.tbComponentRef.instance.popover = colorRangeSettingsPanelPopover; + colorRangeSettingsPanelPopover.tbComponentRef.instance.colorRangeApplied.subscribe((colorRangeSettings) => { + colorRangeSettingsPanelPopover.hide(); + this.modelValue = colorRangeSettings; + this.updateColorStyle(); + this.propagateChange(this.modelValue); + }); + } + } + + private updateColorStyle() { + if (!this.disabled && this.modelValue) { + let colors: string[] = []; + if (this.modelValue.length) { + const rangeColors = this.modelValue.slice(0, Math.min(3, this.modelValue.length)).map(r => r.color); + colors = colors.concat(rangeColors); + } + if (colors.length === 1) { + this.colorStyle = {backgroundColor: colors[0]}; + } else { + const gradientValues: string[] = []; + const step = 100 / colors.length; + for (let i = 0; i < colors.length; i++) { + gradientValues.push(`${colors[i]} ${step*i}%`); + gradientValues.push(`${colors[i]} ${step*(i+1)}%`); + } + this.colorStyle = {background: `linear-gradient(90deg, ${gradientValues.join(', ')})`}; + } + } else { + this.colorStyle = {}; + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html index 8712322d2c..1864e781dd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.html @@ -34,7 +34,11 @@
- + +
@@ -72,43 +76,6 @@
- -
-
widgets.color.value-range
-
-
-
-
-
widgets.color.from
- - - -
widgets.color.to
- - - - - -
- -
-
-
- -
-
this.colorRangeControl(r))), + rangeList: [this.colorSettings?.rangeList, []], colorFunction: [this.colorSettings?.colorFunction, []] } ); @@ -76,51 +76,15 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit }); } - private colorRangeControl(range: ColorRange): AbstractControl { - return this.fb.group({ - from: [range?.from, []], - to: [range?.to, []], - color: [range?.color, []] - }); - } - - get rangeListFormArray(): UntypedFormArray { - return this.colorSettingsFormGroup.get('rangeList') as UntypedFormArray; - } - - get rangeListFormGroups(): FormGroup[] { - return this.rangeListFormArray.controls as FormGroup[]; - } - - trackByRange(index: number, rangeControl: AbstractControl): any { - return rangeControl; - } - - removeRange(index: number) { - this.rangeListFormArray.removeAt(index); - this.colorSettingsFormGroup.markAsDirty(); - setTimeout(() => {this.popover?.updatePosition();}, 0); - } - - addRange() { - const newRange: ColorRange = { - color: 'rgba(0,0,0,0.87)' - }; - this.rangeListFormArray.push(this.colorRangeControl(newRange)); - this.colorSettingsFormGroup.markAsDirty(); - setTimeout(() => {this.popover?.updatePosition();}, 0); - } - copyColorSettings(comp: ColorSettingsComponent) { const sourceSettings = deepClone(comp.modelValue); this.colorSettings = sourceSettings; this.colorSettingsFormGroup.patchValue({ type: this.colorSettings.type, color: this.colorSettings.color, - colorFunction: this.colorSettings.colorFunction + colorFunction: this.colorSettings.colorFunction, + rangeList: this.colorSettings.rangeList || [] }, {emitEvent: false}); - this.colorSettingsFormGroup.setControl('rangeList', - this.fb.array((this.colorSettings.rangeList || []).map(r => this.colorRangeControl(r))), {emitEvent: false}); this.colorSettingsFormGroup.markAsDirty(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index 4018549863..7c2022c5d4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -45,7 +45,12 @@ import { } from '@home/components/widget/lib/settings/common/background-settings-panel.component'; import { CountWidgetSettingsComponent -} from "@home/components/widget/lib/settings/common/count-widget-settings.component"; +} from '@home/components/widget/lib/settings/common/count-widget-settings.component'; +import { ColorRangeListComponent } from '@home/components/widget/lib/settings/common/color-range-list.component'; +import { ColorRangePanelComponent } from '@home/components/widget/lib/settings/common/color-range-panel.component'; +import { + ColorRangeSettingsComponent, ColorRangeSettingsComponentService +} from '@home/components/widget/lib/settings/common/color-range-settings.component'; @NgModule({ declarations: [ @@ -63,7 +68,10 @@ import { ValueSourceComponent, LegendConfigComponent, WidgetFontComponent, - CountWidgetSettingsComponent + CountWidgetSettingsComponent, + ColorRangeListComponent, + ColorRangePanelComponent, + ColorRangeSettingsComponent ], imports: [ CommonModule, @@ -85,10 +93,14 @@ import { ValueSourceComponent, LegendConfigComponent, WidgetFontComponent, - CountWidgetSettingsComponent + CountWidgetSettingsComponent, + ColorRangeListComponent, + ColorRangePanelComponent, + ColorRangeSettingsComponent ], providers: [ - ColorSettingsComponentService + ColorSettingsComponentService, + ColorRangeSettingsComponentService ] }) export class WidgetSettingsCommonModule {