diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 7e2e5d1e52..95615cf516 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -17,7 +17,7 @@ import _ from 'lodash'; import { from, Observable, of, ReplaySubject, Subject } from 'rxjs'; import { catchError, finalize, share } from 'rxjs/operators'; -import { Datasource, DatasourceData, FormattedData, ReplaceInfo } from '@app/shared/models/widget.models'; +import { DataKey, Datasource, DatasourceData, FormattedData, ReplaceInfo } from '@app/shared/models/widget.models'; import { EntityId } from '@shared/models/id/entity-id'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { baseDetailsPageByEntityType, EntityType } from '@shared/models/entity-type.models'; @@ -846,7 +846,7 @@ function prepareMessageFromData(data): string { } } -export function genNextLabel(name: string, datasources: Datasource[]): string { +export const genNextLabel = (name: string, datasources: Datasource[]): string => { let label = name; let i = 1; let matches = false; @@ -880,6 +880,25 @@ export function genNextLabel(name: string, datasources: Datasource[]): string { return label; } +export const genNextLabelForDataKeys = (name: string, dataKeys: DataKey[]): string => { + let label = name; + let i = 1; + let matches = false; + if (dataKeys) { + do { + matches = false; + dataKeys.forEach((dataKey) => { + if (dataKey?.label === label) { + i++; + label = name + ' ' + i; + matches = true; + } + }); + } while (matches) + } + return label; +} + export const getOS = (): string => { const userAgent = window.navigator.userAgent.toLowerCase(); const macosPlatforms = /(macintosh|macintel|macppc|mac68k|macos|mac_powerpc)/i; diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index 9e65bdb4db..040edd19c3 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -232,9 +232,9 @@ import * as EntityAliasDialogComponent from '@home/components/alias/entity-alias import * as EntityFilterComponent from '@home/components/entity/entity-filter.component'; import * as RelationFiltersComponent from '@home/components/relation/relation-filters.component'; import * as EntityAliasSelectComponent from '@home/components/widget/lib/settings/common/alias/entity-alias-select.component'; -import * as DataKeysComponent from '@home/components/widget/config/data-keys.component'; -import * as DataKeyConfigDialogComponent from '@home/components/widget/config/data-key-config-dialog.component'; -import * as DataKeyConfigComponent from '@home/components/widget/config/data-key-config.component'; +import * as DataKeysComponent from '@home/components/widget/lib/settings/common/key/data-keys.component'; +import * as DataKeyConfigDialogComponent from '@home/components/widget/lib/settings/common/key/data-key-config-dialog.component'; +import * as DataKeyConfigComponent from '@home/components/widget/lib/settings/common/key/data-key-config.component'; import * as LegendConfigComponent from '@home/components/widget/lib/settings/common/legend-config.component'; import * as ManageWidgetActionsComponent from '@home/components/widget/action/manage-widget-actions.component'; import * as WidgetActionDialogComponent from '@home/components/widget/action/widget-action-dialog.component'; @@ -268,7 +268,7 @@ import * as ComplexFilterPredicateDialogComponent from '@home/components/filter/ import * as KeyFilterDialogComponent from '@home/components/filter/key-filter-dialog.component'; import * as FiltersDialogComponent from '@home/components/filter/filters-dialog.component'; import * as FilterDialogComponent from '@home/components/filter/filter-dialog.component'; -import * as FilterSelectComponent from '@home/components/filter/filter-select.component'; +import * as FilterSelectComponent from '@home/components/widget/lib/settings/common/filter/filter-select.component'; import * as FiltersEditComponent from '@home/components/filter/filters-edit.component'; import * as FiltersEditPanelComponent from '@home/components/filter/filters-edit-panel.component'; import * as UserFilterDialogComponent from '@home/components/filter/user-filter-dialog.component'; @@ -578,9 +578,9 @@ class ModulesMap implements IModulesMap { '@home/components/entity/entity-filter.component': EntityFilterComponent, '@home/components/relation/relation-filters.component': RelationFiltersComponent, '@home/components/widget/lib/settings/common/alias/entity-alias-select.component': EntityAliasSelectComponent, - '@home/components/widget/config/data-keys.component': DataKeysComponent, - '@home/components/widget/config/data-key-config-dialog.component': DataKeyConfigDialogComponent, - '@home/components/widget/config/data-key-config.component': DataKeyConfigComponent, + '@home/components/widget/lib/settings/common/key/data-keys.component': DataKeysComponent, + '@home/components/widget/lib/settings/common/key/data-key-config-dialog.component': DataKeyConfigDialogComponent, + '@home/components/widget/lib/settings/common/key/data-key-config.component': DataKeyConfigComponent, '@home/components/widget/lib/settings/common/legend-config.component': LegendConfigComponent, '@home/components/widget/action/manage-widget-actions.component': ManageWidgetActionsComponent, '@home/components/widget/action/widget-action-dialog.component': WidgetActionDialogComponent, @@ -610,7 +610,7 @@ class ModulesMap implements IModulesMap { '@home/components/filter/key-filter-dialog.component': KeyFilterDialogComponent, '@home/components/filter/filters-dialog.component': FiltersDialogComponent, '@home/components/filter/filter-dialog.component': FilterDialogComponent, - '@home/components/filter/filter-select.component': FilterSelectComponent, + '@home/components/widget/lib/settings/common/filter/filter-select.component': FilterSelectComponent, '@home/components/filter/filters-edit.component': FiltersEditComponent, '@home/components/filter/filters-edit-panel.component': FiltersEditPanelComponent, '@home/components/filter/user-filter-dialog.component': UserFilterDialogComponent, diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts index fe96529a1d..a89df99db1 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts @@ -29,7 +29,7 @@ import { WidgetConfigComponentData, WidgetInfo } from '@home/models/widget-compo import { isDefined, isDefinedAndNotNull } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; -import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeySettingsFunction } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; export interface AddWidgetDialogData { dashboard: Dashboard; diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts index 191bf17700..d89de90fef 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts @@ -27,7 +27,7 @@ import { WidgetConfigComponentData } from '../../models/widget-component.models' import { isDefined, isDefinedAndNotNull } from '@core/utils'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; -import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeySettingsFunction } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-key-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-key-row.component.ts index 5f100dbe3e..76bdddadf3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-key-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-key-row.component.ts @@ -45,7 +45,7 @@ import { TruncatePipe } from '@shared/pipe/truncate.pipe'; import { DataKeyConfigDialogComponent, DataKeyConfigDialogData -} from '@home/components/widget/config/data-key-config-dialog.component'; +} from '@home/components/widget/lib/settings/common/key/data-key-config-dialog.component'; import { deepClone, formatValue } from '@core/utils'; import { AggregatedValueCardKeyPosition, @@ -59,7 +59,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'tb-aggregated-data-key-row', templateUrl: './aggregated-data-key-row.component.html', - styleUrls: ['./aggregated-data-key-row.component.scss', '../../data-keys.component.scss'], + styleUrls: ['./aggregated-data-key-row.component.scss', '../../../lib/settings/common/key/data-keys.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-keys-panel.component.ts index 39481874e3..c6706cc32b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-keys-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-keys-panel.component.ts @@ -38,7 +38,7 @@ import { WidgetConfigComponent } from '@home/components/widget/widget-config.com import { DataKey, DatasourceType, widgetType } from '@shared/models/widget.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { UtilsService } from '@core/services/utils.service'; -import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeysCallbacks } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; import { aggregatedValueCardDefaultKeySettings } from '@home/components/widget/lib/cards/aggregated-value-card.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-key-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-key-row.component.ts index 48ffda9c7c..02e397cf85 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-key-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-key-row.component.ts @@ -34,7 +34,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'tb-comparison-key-row', templateUrl: './comparison-key-row.component.html', - styleUrls: ['./comparison-key-row.component.scss', '../../data-keys.component.scss'], + styleUrls: ['./comparison-key-row.component.scss', '../../../lib/settings/common/key/data-keys.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts index 95332c3791..f749d1201c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts @@ -41,12 +41,12 @@ import { MatDialog } from '@angular/material/dialog'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { DataKey, DataKeyConfigMode, DatasourceType, Widget, widgetType } from '@shared/models/widget.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeySettingsFunction } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; import { merge } from 'rxjs'; import { DataKeyConfigDialogComponent, DataKeyConfigDialogData -} from '@home/components/widget/config/data-key-config-dialog.component'; +} from '@home/components/widget/lib/settings/common/key/data-key-config-dialog.component'; import { deepClone } from '@core/utils'; import { Dashboard } from '@shared/models/dashboard.models'; import { IAliasController } from '@core/api/widget-api.models'; @@ -78,7 +78,7 @@ export const dataKeyRowValidator = (control: AbstractControl): ValidationErrors @Component({ selector: 'tb-data-key-row', templateUrl: './data-key-row.component.html', - styleUrls: ['./data-key-row.component.scss', '../../data-keys.component.scss'], + styleUrls: ['./data-key-row.component.scss', '../../../lib/settings/common/key/data-keys.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts index a9bea410ab..e5ede137bc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts @@ -43,7 +43,7 @@ import { dataKeyRowValidator, dataKeyValid } from '@home/components/widget/confi import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { UtilsService } from '@core/services/utils.service'; -import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; import { coerceBoolean } from '@shared/decorators/coercion'; import { TimeSeriesChartYAxisId } from '@home/components/widget/lib/chart/time-series-chart.models'; import { FormProperty } from '@shared/models/dynamic-form.models'; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html b/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html index 8af7753525..b19398faab 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html @@ -66,6 +66,10 @@ { @@ -26,6 +26,8 @@ class SidebarControl extends L.Control { private map: L.Map; + private buttonContainer: JQuery; + constructor(options: TB.SidebarControlOptions) { super(options); this.sidebar = $('
'); @@ -38,8 +40,9 @@ class SidebarControl extends L.Control { } } - addPane(pane: JQuery): this { + addPane(pane: JQuery, button: JQuery): this { pane.hide().appendTo(this.sidebar); + button.appendTo(this.buttonContainer); return this; } @@ -50,6 +53,7 @@ class SidebarControl extends L.Control { this.current.hide().trigger('hide'); this.currentButton.removeClass('active'); + if (this.current === pane) { if (['topleft', 'bottomleft'].includes(position)) { this.map.panBy([-paneWidth, 0], { animate: false }); @@ -58,20 +62,26 @@ class SidebarControl extends L.Control { this.current = this.currentButton = $(); } else { this.sidebar.show(); - this.current = pane; - this.currentButton = button || $(); - if (['topleft', 'bottomleft'].includes(position)) { + if (['topleft', 'bottomleft'].includes(position) && !this.current.length) { this.map.panBy([paneWidth, 0], { animate: false }); } + this.current = pane; + this.currentButton = button || $(); } - this.map.invalidateSize({ pan: false, animate: false }); this.current.show().trigger('show'); this.currentButton.addClass('active'); + this.map.invalidateSize({ pan: false, animate: false }); + } + + onAdd(map: L.Map): HTMLElement { + this.buttonContainer = $("
") + .attr('class', 'leaflet-bar'); + return this.buttonContainer[0]; } addTo(map: L.Map): this { this.map = map; - return this; + return super.addTo(map); } } @@ -84,9 +94,7 @@ class SidebarPaneControl extends L.Contr super(options); } - onAdd(map: L.Map): HTMLElement { - const $container = $("
") - .attr('class', 'leaflet-bar'); + addTo(map: L.Map): this { this.button = $("") .attr('class', 'tb-control-button') @@ -98,7 +106,6 @@ class SidebarPaneControl extends L.Contr if (this.options.buttonTitle) { this.button.attr('title', this.options.buttonTitle); } - this.button.appendTo($container); this.$ui = $('
') .attr('class', this.options.uiClass); @@ -117,13 +124,13 @@ class SidebarPaneControl extends L.Contr this.toggle(e); }))); - this.options.sidebar.addPane(this.$ui); + this.options.sidebar.addPane(this.$ui, this.button); this.onAddPane(map, this.button, this.$ui, (e) => { this.toggle(e); }); - return $container[0]; + return this; } public onAddPane(map: L.Map, button: JQuery, $ui: JQuery, toggle: (e: JQuery.MouseEventBase) => void) {} @@ -216,6 +223,53 @@ class LayersControl extends SidebarPaneControl { } } +class GroupsControl extends SidebarPaneControl { + constructor(options: TB.GroupsControlOptions) { + super(options); + } + + public onAddPane(map: L.Map, button: JQuery, $ui: JQuery, toggle: (e: JQuery.MouseEventBase) => void) { + const paneId = guid(); + const groups = this.options.groups; + const baseSection = $("
") + .attr('class', 'tb-layers-container') + .appendTo($ui); + + groups.forEach((groupData, i) => { + const id = `map-group-layer-${paneId}-${i}`; + const checkBoxContainer = $('
') + .appendTo(baseSection); + const input = $('') + .prop('id', id) + .prop('checked', groupData.enabled) + .appendTo(checkBoxContainer); + + $('
+ + {{ requiredText | translate }} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-key-input.component.scss similarity index 70% rename from ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-key-input.component.scss index f8564a46ac..cb01c98c37 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-key-input.component.scss @@ -14,7 +14,7 @@ * limitations under the License. */ .tb-data-key-input { - .mat-mdc-form-field.tb-inline-field.tb-key-field { + .mat-mdc-form-field.tb-key-field { width: 100%; &.mat-form-field-appearance-fill { .mdc-text-field--filled.mdc-text-field--disabled { @@ -23,14 +23,25 @@ } } } - .mat-mdc-text-field-wrapper:not(.mdc-text-field--outlined) { - padding-left: 8px; - padding-right: 0; - + .mat-mdc-text-field-wrapper { + &:not(.mdc-text-field--outlined) { + padding-left: 8px; + padding-right: 0; + .mat-mdc-form-field-infix { + padding-top: 0; + padding-bottom: 6px; + } + } + &.mdc-text-field--outlined { + .mat-mdc-form-field-infix { + padding-top: 12px; + padding-bottom: 12px; + } + .mdc-evolution-chip-set__chips { + margin-left: 0; + } + } .mat-mdc-form-field-infix { - padding-top: 0; - padding-bottom: 6px; - .mdc-evolution-chip-set .mdc-evolution-chip { margin: 0; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-key-input.component.ts similarity index 96% rename from ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-key-input.component.ts index e08ec36d8d..6ea515e096 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-key-input.component.ts @@ -50,13 +50,14 @@ import { UtilsService } from '@core/services/utils.service'; import { alarmFields } from '@shared/models/alarm.models'; import { filter, map, mergeMap, publishReplay, refCount, share, tap } from 'rxjs/operators'; import { AggregationType } from '@shared/models/time/time.models'; -import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeysCallbacks } from './data-keys.component.models'; import { IAliasController } from '@core/api/widget-api.models'; +import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; @Component({ selector: 'tb-data-key-input', templateUrl: './data-key-input.component.html', - styleUrls: ['./data-key-input.component.scss', '../../../config/data-keys.component.scss'], + styleUrls: ['./data-key-input.component.scss', './data-keys.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -83,6 +84,19 @@ export class DataKeyInputComponent implements ControlValueAccessor, OnInit, OnCh @Input() disabled: boolean; + @Input() + label: string; + + @Input() + appearance: MatFormFieldAppearance = 'fill'; + + @Input() + subscriptSizing: SubscriptSizing = 'fixed'; + + @Input() + @coerceBoolean() + inlineField = true; + @Input() @coerceBoolean() required = false; diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-keys.component.html similarity index 97% rename from ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-keys.component.html index 9cb03bde9e..c60d29a9d1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-keys.component.html @@ -15,8 +15,10 @@ limitations under the License. --> - - {{placeholder}} + + {{ label ? label : placeholder}}
drag_indicator
-
+
DataKey = (key) => key; + private requiredValue: boolean; get required(): boolean { return this.requiredValue || !this.optDataKeys || this.isCountDatasource; @@ -222,7 +242,6 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange private keysValidator = this._keysValidator.bind(this); constructor(@SkipSelf() private errorStateMatcher: ErrorStateMatcher, - private datasourceComponent: DatasourceComponent, private translate: TranslateService, private utils: UtilsService, private dialog: MatDialog, @@ -463,8 +482,13 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange } private addFromChipValue(chip: DataKey) { - const key = this.callbacks.generateDataKey(chip.name, chip.type, this.dataKeySettingsForm, this.latestDataKeys, - this.datakeySettingsFunction); + let key: DataKey; + if (this.generateKey) { + key = this.generateKey(chip); + } else { + key = this.callbacks.generateDataKey(chip.name, chip.type, this.dataKeySettingsForm, this.latestDataKeys, + this.datakeySettingsFunction); + } this.addKey(key); } @@ -687,7 +711,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange } get dragDisabled(): boolean { - return this.keys.length < 2; + return this.keys.length < 2 || this.disableDrag; } get maxDataKeysSet(): boolean { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html new file mode 100644 index 0000000000..872b94a20a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html @@ -0,0 +1,198 @@ + +
+ +

{{dialogTitle}}

+ + +
+
+
+
+
{{ 'widget-config.datasource' | translate }}
+ + {{ datasourceTypesTranslations.get(type) | translate }} + +
+
+ + + + + + +
+
+
+
{{ 'datakey.keys' | translate }}
+
+
+ + + + +
+ + + + + + +
+
+
+
{{ 'widget-config.appearance' | translate }}
+
+
+
{{ 'widgets.maps.data-layer.groups' | translate }}
+ + +
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.scss new file mode 100644 index 0000000000..8fc035a151 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.scss @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2024 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. + */ +.tb-map-data-layer-dialog { + --mdc-outlined-text-field-outline-color: rgba(0,0,0,0.12); + --mat-form-field-trailing-icon-color: rgba(0,0,0,0.38); + .tb-inline-chips { + --mat-form-field-container-vertical-padding: 12px; + .mat-mdc-text-field-wrapper { + &.mdc-text-field--outlined { + padding-left: 16px; + padding-right: 16px; + .mat-mdc-form-field-infix { + .mdc-evolution-chip-set .mdc-evolution-chip { + margin: 0; + } + .mdc-evolution-chip-set__chips { + gap: 8px; + margin-left: 0; + } + input.mat-mdc-chip-input { + height: 32px; + margin-left: 0; + } + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts new file mode 100644 index 0000000000..9b7ea1b757 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts @@ -0,0 +1,239 @@ +/// +/// Copyright © 2016-2024 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, DestroyRef, Inject, ViewEncapsulation } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { + CirclesDataLayerSettings, + MapDataLayerSettings, + MapDataLayerType, + MapType, + MarkersDataLayerSettings, + PolygonsDataLayerSettings +} from '@home/components/widget/lib/maps/map.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { FormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { DataKey, DatasourceType, datasourceTypeTranslationMap, widgetType } from '@shared/models/widget.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { EntityType } from '@shared/models/entity-type.models'; +import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models'; +import { genNextLabelForDataKeys } from '@core/utils'; + +export interface MapDataLayerDialogData { + settings: MapDataLayerSettings; + mapType: MapType; + dataLayerType: MapDataLayerType; + context: MapSettingsContext; +} + +@Component({ + selector: 'tb-map-data-layer-dialog', + templateUrl: './map-data-layer-dialog.component.html', + styleUrls: ['./map-data-layer-dialog.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class MapDataLayerDialogComponent extends DialogComponent { + + DatasourceType = DatasourceType; + + EntityType = EntityType; + + MapType = MapType; + + DataKeyType = DataKeyType; + + widgetType = widgetType; + + datasourceTypes: Array = []; + datasourceTypesTranslations = datasourceTypeTranslationMap; + + dataLayerFormGroup: UntypedFormGroup; + + settings = this.data.settings; + mapType = this.data.mapType; + dataLayerType = this.data.dataLayerType; + context = this.data.context; + + generateAdditionalDataKey = this.generateDataKey.bind(this); + + dialogTitle: string; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: MapDataLayerDialogData, + public dialogRef: MatDialogRef, + private fb: FormBuilder, + private destroyRef: DestroyRef) { + super(store, router, dialogRef); + + if (this.context.functionsOnly) { + this.datasourceTypes = [DatasourceType.function]; + } else { + this.datasourceTypes = [DatasourceType.function, DatasourceType.device, DatasourceType.entity]; + } + + this.dataLayerFormGroup = this.fb.group({ + dsType: [this.settings.dsType, [Validators.required]], + dsDeviceId: [this.settings.dsDeviceId, [Validators.required]], + dsEntityAliasId: [this.settings.dsEntityAliasId, [Validators.required]], + dsFilterId: [this.settings.dsFilterId, []], + additionalDataKeys: [this.settings.additionalDataKeys, []], + groups: [this.settings.groups, []] + }); + + switch (this.dataLayerType) { + case 'markers': + const markersDataLayer = this.settings as MarkersDataLayerSettings; + this.dialogTitle = 'widgets.maps.data-layer.marker.marker-configuration'; + this.dataLayerFormGroup.addControl('xKey', this.fb.control(markersDataLayer.xKey, Validators.required)); + this.dataLayerFormGroup.addControl('yKey', this.fb.control(markersDataLayer.yKey, Validators.required)); + break; + case 'polygons': + const polygonsDataLayer = this.settings as PolygonsDataLayerSettings; + this.dialogTitle = 'widgets.maps.data-layer.polygon.polygon-configuration'; + this.dataLayerFormGroup.addControl('polygonKey', this.fb.control(polygonsDataLayer.polygonKey, Validators.required)); + break; + case 'circles': + const circlesDataLayer = this.settings as CirclesDataLayerSettings; + this.dialogTitle = 'widgets.maps.data-layer.circle.circle-configuration'; + this.dataLayerFormGroup.addControl('circleKey', this.fb.control(circlesDataLayer.circleKey, Validators.required)); + break; + } + this.dataLayerFormGroup.get('dsType').valueChanges.pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe( + (newDsType: DatasourceType) => this.onDsTypeChanged(newDsType) + ); + this.updateValidators(); + } + + private onDsTypeChanged(newDsType: DatasourceType) { + switch (this.dataLayerType) { + case 'markers': + const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value; + if (this.updateDataKeyToNewDsType(xKey, newDsType)) { + this.dataLayerFormGroup.get('xKey').patchValue(xKey, {emitEvent: false}); + } + const yKey: DataKey = this.dataLayerFormGroup.get('yKey').value; + if (this.updateDataKeyToNewDsType(yKey, newDsType)) { + this.dataLayerFormGroup.get('yKey').patchValue(yKey, {emitEvent: false}); + } + break; + case 'polygons': + const polygonKey: DataKey = this.dataLayerFormGroup.get('polygonKey').value; + if (this.updateDataKeyToNewDsType(polygonKey, newDsType)) { + this.dataLayerFormGroup.get('polygonKey').patchValue(polygonKey, {emitEvent: false}); + } + break; + case 'circles': + const circleKey: DataKey = this.dataLayerFormGroup.get('circleKey').value; + if (this.updateDataKeyToNewDsType(circleKey, newDsType)) { + this.dataLayerFormGroup.get('circleKey').patchValue(circleKey, {emitEvent: false}); + } + break; + } + this.updateValidators(); + } + + private updateDataKeyToNewDsType(dataKey: DataKey, newDsType: DatasourceType): boolean { + if (newDsType === DatasourceType.function) { + if (dataKey.type !== DataKeyType.function) { + dataKey.type = DataKeyType.function; + return true; + } + } else { + if (dataKey.type === DataKeyType.function) { + dataKey.type = DataKeyType.attribute; + return true; + } + } + return false; + } + + private updateValidators() { + const dsType: DatasourceType = this.dataLayerFormGroup.get('dsType').value; + if (dsType === DatasourceType.function) { + this.dataLayerFormGroup.get('dsDeviceId').disable({emitEvent: false}); + this.dataLayerFormGroup.get('dsEntityAliasId').disable({emitEvent: false}); + } else if (dsType === DatasourceType.device) { + this.dataLayerFormGroup.get('dsDeviceId').enable({emitEvent: false}); + this.dataLayerFormGroup.get('dsEntityAliasId').disable({emitEvent: false}); + } else { + this.dataLayerFormGroup.get('dsDeviceId').disable({emitEvent: false}); + this.dataLayerFormGroup.get('dsEntityAliasId').enable({emitEvent: false}); + } + } + + editKey(keyType: 'xKey' | 'yKey' | 'polygonKey' | 'circleKey') { + const targetDataKey: DataKey = this.dataLayerFormGroup.get(keyType).value; + this.context.editKey(targetDataKey, + this.dataLayerFormGroup.get('dsDeviceId').value, this.dataLayerFormGroup.get('dsEntityAliasId').value).subscribe( + (updatedDataKey) => { + if (updatedDataKey) { + this.dataLayerFormGroup.get(keyType).patchValue(updatedDataKey); + } + } + ); + } + + private generateDataKey(key: DataKey): DataKey { + const dataKey = this.context.callbacks.generateDataKey(key.name, key.type, null, false, null); + const dataKeys: DataKey[] = []; + switch (this.dataLayerType) { + case 'markers': + const xKey: DataKey = this.dataLayerFormGroup.get('xKey').value; + if (xKey) { + dataKeys.push(xKey); + } + const yKey: DataKey = this.dataLayerFormGroup.get('yKey').value; + if (yKey) { + dataKeys.push(yKey); + } + break; + case 'polygons': + const polygonKey: DataKey = this.dataLayerFormGroup.get('polygonKey').value; + if (polygonKey) { + dataKeys.push(polygonKey); + } + break; + case 'circles': + const circleKey: DataKey = this.dataLayerFormGroup.get('circleKey').value; + if (circleKey) { + dataKeys.push(circleKey); + } + break; + } + const additionalKeys: DataKey[] = this.dataLayerFormGroup.get('additionalDataKeys').value; + if (additionalKeys) { + dataKeys.push(...additionalKeys); + } + dataKey.label = genNextLabelForDataKeys(dataKey.label, dataKeys); + return dataKey; + } + + cancel(): void { + this.dialogRef.close(null); + } + + save(): void { + const settings: MapDataLayerSettings = this.dataLayerFormGroup.getRawValue(); + this.dialogRef.close(settings); + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html index fcbda3bead..e55df4c8f9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html @@ -38,9 +38,9 @@ *ngIf="dataLayerFormGroup.get('dsType').value === DatasourceType.entity" inlineField tbRequired - [aliasController]="aliasController" + [aliasController]="context.aliasController" formControlName="dsEntityAliasId" - [callbacks]="entityAliasSelectCallbacks"> + [callbacks]="context.callbacks">
@@ -67,11 +68,12 @@ [datasourceType]="dataLayerFormGroup.get('dsType').value" [entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value" [deviceId]="dataLayerFormGroup.get('dsDeviceId').value" - [aliasController]="aliasController" - [dataKeyType]="functionsOnly ? DataKeyType.function : null" + [aliasController]="context.aliasController" + [widgetType]="widgetType.latest" + [dataKeyType]="context.functionsOnly ? DataKeyType.function : null" [dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" - [callbacks]="dataKeyCallbacks" - [generateKey]="generateDataKey" + [callbacks]="context.callbacks" + [generateKey]="context.generateDataKey" (keyEdit)="editKey('yKey')" formControlName="yKey"> @@ -83,11 +85,12 @@ [datasourceType]="dataLayerFormGroup.get('dsType').value" [entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value" [deviceId]="dataLayerFormGroup.get('dsDeviceId').value" - [aliasController]="aliasController" - [dataKeyType]="functionsOnly ? DataKeyType.function : null" + [aliasController]="context.aliasController" + [widgetType]="widgetType.latest" + [dataKeyType]="context.functionsOnly ? DataKeyType.function : null" [dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" - [callbacks]="dataKeyCallbacks" - [generateKey]="generateDataKey" + [callbacks]="context.callbacks" + [generateKey]="context.generateDataKey" (keyEdit)="editKey('polygonKey')" formControlName="polygonKey"> @@ -99,11 +102,12 @@ [datasourceType]="dataLayerFormGroup.get('dsType').value" [entityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value" [deviceId]="dataLayerFormGroup.get('dsDeviceId').value" - [aliasController]="aliasController" - [dataKeyType]="functionsOnly ? DataKeyType.function : null" + [aliasController]="context.aliasController" + [widgetType]="widgetType.latest" + [dataKeyType]="context.functionsOnly ? DataKeyType.function : null" [dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" - [callbacks]="dataKeyCallbacks" - [generateKey]="generateDataKey" + [callbacks]="context.callbacks" + [generateKey]="context.generateDataKey" (keyEdit)="editKey('circleKey')" formControlName="circleKey"> diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.ts index 1d9d2014e2..814796cf09 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.ts @@ -23,8 +23,6 @@ import { Input, OnInit, Output, - Renderer2, - ViewContainerRef, ViewEncapsulation } from '@angular/core'; import { @@ -35,8 +33,6 @@ import { Validators } from '@angular/forms'; import { MatButton } from '@angular/material/button'; -import { TbPopoverService } from '@shared/components/popover.service'; -import { TranslateService } from '@ngx-translate/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { CirclesDataLayerSettings, @@ -46,27 +42,17 @@ import { MarkersDataLayerSettings, PolygonsDataLayerSettings } from '@home/components/widget/lib/maps/map.models'; -import { - DataKey, - DataKeyConfigMode, - DatasourceType, - datasourceTypeTranslationMap, - widgetType -} from '@shared/models/widget.models'; +import { DataKey, DatasourceType, datasourceTypeTranslationMap, widgetType } from '@shared/models/widget.models'; import { EntityType } from '@shared/models/entity-type.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { MapSettingsComponent } from '@home/components/widget/lib/settings/common/map/map-settings.component'; -import { IAliasController } from '@core/api/widget-api.models'; -import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; -import { - DataKeyConfigDialogComponent, - DataKeyConfigDialogData -} from '@home/components/widget/config/data-key-config-dialog.component'; import { deepClone } from '@core/utils'; import { MatDialog } from '@angular/material/dialog'; import { - EntityAliasSelectCallbacks -} from '@home/components/widget/lib/settings/common/alias/entity-alias-select.component.models'; + MapDataLayerDialogComponent, + MapDataLayerDialogData +} from '@home/components/widget/lib/settings/common/map/map-data-layer-dialog.component'; +import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models'; @Component({ selector: 'tb-map-data-layer-row', @@ -90,6 +76,8 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit { MapType = MapType; + widgetType = widgetType; + datasourceTypes: Array = []; datasourceTypesTranslations = datasourceTypeTranslationMap; @@ -102,27 +90,12 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit { @Input() dataLayerType: MapDataLayerType = 'markers'; - get functionsOnly(): boolean { - return this.mapSettingsComponent.functionsOnly; - } - - get aliasController(): IAliasController { - return this.mapSettingsComponent.aliasController; - } - - get dataKeyCallbacks(): DataKeysCallbacks { - return this.mapSettingsComponent.callbacks; - } - - public get entityAliasSelectCallbacks(): EntityAliasSelectCallbacks { - return this.mapSettingsComponent.callbacks; - } + @Input() + context: MapSettingsContext; @Output() dataLayerRemoved = new EventEmitter(); - generateDataKey = this._generateDataKey.bind(this); - dataLayerFormGroup: UntypedFormGroup; modelValue: MapDataLayerSettings; @@ -136,16 +109,12 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit { constructor(private mapSettingsComponent: MapSettingsComponent, private fb: UntypedFormBuilder, private dialog: MatDialog, - private translate: TranslateService, - private popoverService: TbPopoverService, - private renderer: Renderer2, - private viewContainerRef: ViewContainerRef, private cd: ChangeDetectorRef, private destroyRef: DestroyRef) { } ngOnInit() { - if (this.functionsOnly) { + if (this.context.functionsOnly) { this.datasourceTypes = [DatasourceType.function]; } else { this.datasourceTypes = [DatasourceType.function, DatasourceType.device, DatasourceType.entity]; @@ -244,66 +213,37 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit { editKey(keyType: 'xKey' | 'yKey' | 'polygonKey' | 'circleKey') { const targetDataKey: DataKey = this.dataLayerFormGroup.get(keyType).value; - this.dialog.open(DataKeyConfigDialogComponent, - { - disableClose: true, - panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], - data: { - dataKey: deepClone(targetDataKey), - dataKeyConfigMode: DataKeyConfigMode.general, - aliasController: this.aliasController, - widgetType: widgetType.latest, - deviceId: this.dataLayerFormGroup.get('dsDeviceId').value, - entityAliasId: this.dataLayerFormGroup.get('dsEntityAliasId').value, - showPostProcessing: true, - callbacks: this.mapSettingsComponent.callbacks, - hideDataKeyColor: true, - hideDataKeyDecimals: true, - hideDataKeyUnits: true, - widget: this.mapSettingsComponent.widget, - dashboard: null, - dataKeySettingsForm: null, - dataKeySettingsDirective: null + this.context.editKey(targetDataKey, + this.dataLayerFormGroup.get('dsDeviceId').value, this.dataLayerFormGroup.get('dsEntityAliasId').value).subscribe( + (updatedDataKey) => { + if (updatedDataKey) { + this.dataLayerFormGroup.get(keyType).patchValue(updatedDataKey); } - }).afterClosed().subscribe((updatedDataKey) => { - if (updatedDataKey) { - this.dataLayerFormGroup.get(keyType).patchValue(updatedDataKey); } - }); + ); } editDataLayer($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 = { - mapLayerSettings: deepClone(this.modelValue) - }; - const mapLayerSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, - this.viewContainerRef, MapLayerSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null, - ctx, - {}, - {}, {}, true); - mapLayerSettingsPanelPopover.tbComponentRef.instance.popover = mapLayerSettingsPanelPopover; - mapLayerSettingsPanelPopover.tbComponentRef.instance.mapLayerSettingsApplied.subscribe((layer) => { - mapLayerSettingsPanelPopover.hide(); - this.layerFormGroup.patchValue( - layer, - {emitEvent: false}); + this.dialog.open(MapDataLayerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + settings: deepClone(this.modelValue), + mapType: this.mapType, + dataLayerType: this.dataLayerType, + context: this.context + } + }).afterClosed().subscribe((settings) => { + if (settings) { + this.modelValue = settings; + this.dataLayerFormGroup.patchValue(settings); this.updateValidators(); - this.updateModel(); - });*/ - } - } - - private _generateDataKey(key: DataKey): DataKey { - key = this.dataKeyCallbacks.generateDataKey(key.name, key.type, null, false, - null); - return key; + } + }); } private onDsTypeChanged(newDsType: DatasourceType) { @@ -375,6 +315,4 @@ export class MapDataLayerRowComponent implements ControlValueAccessor, OnInit { this.modelValue = {...this.modelValue, ...this.dataLayerFormGroup.value}; this.propagateChange(this.modelValue); } - - protected readonly datasourceType = DatasourceType; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.html index 0c19ff6a9b..02bd08e7bd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.html @@ -34,6 +34,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.ts index 5c8aa5534d..09d3c0a2dc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layers.component.ts @@ -37,6 +37,7 @@ import { MapType } from '@home/components/widget/lib/maps/map.models'; import { MapSettingsComponent } from '@home/components/widget/lib/settings/common/map/map-settings.component'; +import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models'; @Component({ selector: 'tb-map-data-layers', @@ -69,9 +70,8 @@ export class MapDataLayersComponent implements ControlValueAccessor, OnInit, Val @Input() dataLayerType: MapDataLayerType = 'markers'; - get functionsOnly(): boolean { - return this.mapSettingsComponent.functionsOnly; - } + @Input() + context: MapSettingsContext; dataLayersFormGroup: UntypedFormGroup; @@ -161,7 +161,7 @@ export class MapDataLayersComponent implements ControlValueAccessor, OnInit, Val addDataLayer() { const dataLayer = mergeDeep({} as MapDataLayerSettings, - defaultMapDataLayerSettings(this.mapType, this.dataLayerType, this.functionsOnly)); + defaultMapDataLayerSettings(this.mapType, this.dataLayerType, this.context.functionsOnly)); const dataLayersArray = this.dataLayersFormGroup.get('dataLayers') as UntypedFormArray; const dataLayerControl = this.fb.control(dataLayer, [mapDataLayerValidator(this.dataLayerType)]); dataLayersArray.push(dataLayerControl); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html index 9652daaf7b..dd0b91b11e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html @@ -44,14 +44,17 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.models.ts new file mode 100644 index 0000000000..931a424ca3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.models.ts @@ -0,0 +1,29 @@ +/// +/// Copyright © 2016-2024 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 { IAliasController } from '@core/api/widget-api.models'; +import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models'; +import { DataKey, Widget } from '@shared/models/widget.models'; +import { Observable } from 'rxjs'; + +export interface MapSettingsContext { + functionsOnly: boolean; + aliasController: IAliasController; + callbacks: WidgetConfigCallbacks; + widget: Widget; + editKey: (key: DataKey, deviceId: string, entityAliasId: string) => Observable; + generateDataKey: (key: DataKey) => DataKey; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts index 864a0f3ea9..ece61c0992 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts @@ -19,17 +19,25 @@ import { ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, - UntypedFormBuilder, UntypedFormControl, - UntypedFormGroup, Validator + UntypedFormBuilder, + UntypedFormControl, + UntypedFormGroup, + Validator } from '@angular/forms'; import { ImageSourceType, MapDataLayerType, MapSetting, MapType } from '@home/components/widget/lib/maps/map.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { merge } from 'rxjs'; +import { merge, Observable } from 'rxjs'; import { coerceBoolean } from '@shared/decorators/coercion'; import { IAliasController } from '@core/api/widget-api.models'; -import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models'; -import { Widget } from '@shared/models/widget.models'; +import { DataKey, DataKeyConfigMode, Widget, widgetType } from '@shared/models/widget.models'; +import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models'; +import { + DataKeyConfigDialogComponent, + DataKeyConfigDialogData +} from '@home/components/widget/lib/settings/common/key/data-key-config-dialog.component'; +import { deepClone } from '@core/utils'; +import { MatDialog } from '@angular/material/dialog'; @Component({ selector: 'tb-map-settings', @@ -68,6 +76,8 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid @Input() widget: Widget; + context: MapSettingsContext; + private modelValue: MapSetting; private propagateChange = null; @@ -77,10 +87,21 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid dataLayerMode: MapDataLayerType = 'markers'; constructor(private fb: UntypedFormBuilder, + private dialog: MatDialog, private destroyRef: DestroyRef) { } ngOnInit(): void { + + this.context = { + functionsOnly: this.functionsOnly, + aliasController: this.aliasController, + callbacks: this.callbacks, + widget: this.widget, + editKey: this.editKey.bind(this), + generateDataKey: this.generateDataKey.bind(this) + }; + this.mapSettingsFormGroup = this.fb.group({ mapType: [null, []], layers: [null, []], @@ -176,4 +197,33 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid this.modelValue = this.mapSettingsFormGroup.getRawValue(); this.propagateChange(this.modelValue); } + + private editKey(key: DataKey, deviceId: string, entityAliasId: string): Observable { + return this.dialog.open(DataKeyConfigDialogComponent, + { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + dataKey: deepClone(key), + dataKeyConfigMode: DataKeyConfigMode.general, + aliasController: this.aliasController, + widgetType: widgetType.latest, + deviceId, + entityAliasId, + showPostProcessing: true, + callbacks: this.callbacks, + hideDataKeyColor: true, + hideDataKeyDecimals: true, + hideDataKeyUnits: true, + widget: this.widget, + dashboard: null, + dataKeySettingsForm: null, + dataKeySettingsDirective: null + } + }).afterClosed(); + } + + private generateDataKey(key: DataKey): DataKey { + return this.callbacks.generateDataKey(key.name, key.type, null, false, null); + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.ts index a541291b08..807e38019b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/value-source-data-key.component.ts @@ -29,7 +29,7 @@ import { AppState } from '@core/core.state'; import { IAliasController } from '@core/api/widget-api.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models'; -import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeysCallbacks } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; import { ValueSourceConfig, ValueSourceType, 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 7018862d08..2416b32ade 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 @@ -102,7 +102,7 @@ import { import { TimeSeriesChartThresholdRowComponent } from '@home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component'; -import { DataKeyInputComponent } from '@home/components/widget/lib/settings/common/data-key-input.component'; +import { DataKeyInputComponent } from '@home/components/widget/lib/settings/common/key/data-key-input.component'; import { EntityAliasInputComponent } from '@home/components/widget/lib/settings/common/entity-alias-input.component'; import { TimeSeriesChartThresholdSettingsPanelComponent @@ -191,10 +191,19 @@ import { } from '@home/components/widget/lib/settings/common/map/map-layer-settings-panel.component'; import { MapDataLayersComponent } from '@home/components/widget/lib/settings/common/map/map-data-layers.component'; import { MapDataLayerRowComponent } from '@home/components/widget/lib/settings/common/map/map-data-layer-row.component'; -import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module'; import { EntityAliasSelectComponent } from '@home/components/widget/lib/settings/common/alias/entity-alias-select.component'; +import { + MapDataLayerDialogComponent +} from '@home/components/widget/lib/settings/common/map/map-data-layer-dialog.component'; +import { FilterSelectComponent } from '@home/components/widget/lib/settings/common/filter/filter-select.component'; +import { DataKeysComponent } from '@home/components/widget/lib/settings/common/key/data-keys.component'; +import { + DataKeyConfigDialogComponent +} from '@home/components/widget/lib/settings/common/key/data-key-config-dialog.component'; +import { DataKeyConfigComponent } from '@home/components/widget/lib/settings/common/key/data-key-config.component'; +import { WidgetSettingsComponent } from '@home/components/widget/lib/settings/common/widget/widget-settings.component'; @NgModule({ declarations: [ @@ -268,10 +277,16 @@ import { MapLayerSettingsPanelComponent, MapLayerRowComponent, MapLayersComponent, + MapDataLayerDialogComponent, MapDataLayerRowComponent, MapDataLayersComponent, MapSettingsComponent, - EntityAliasSelectComponent + EntityAliasSelectComponent, + FilterSelectComponent, + DataKeysComponent, + DataKeyConfigDialogComponent, + DataKeyConfigComponent, + WidgetSettingsComponent ], imports: [ CommonModule, @@ -347,7 +362,12 @@ import { DynamicFormComponent, DynamicFormArrayComponent, MapSettingsComponent, - EntityAliasSelectComponent + EntityAliasSelectComponent, + FilterSelectComponent, + DataKeysComponent, + DataKeyConfigDialogComponent, + DataKeyConfigComponent, + WidgetSettingsComponent ], providers: [ ColorSettingsComponentService, diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget/widget-settings.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget/widget-settings.component.html diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget/widget-settings.component.scss similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget/widget-settings.component.scss 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/lib/settings/common/widget/widget-settings.component.ts similarity index 100% rename from ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget/widget-settings.component.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/device-key-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/device-key-autocomplete.component.html index 668613012e..ae42e2df2f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/device-key-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/control/device-key-autocomplete.component.html @@ -15,11 +15,10 @@ limitations under the License. --> - {{ (keyType === dataKeyType.attribute ? attributeLabel diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts index 200c4d749a..5c7c2f6052 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/gauge/tick-value.component.ts @@ -22,7 +22,7 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { TranslateService } from '@ngx-translate/core'; import { IAliasController } from '@core/api/widget-api.models'; -import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeysCallbacks } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; import { Datasource } from '@shared/models/widget.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; 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 78dd827403..215a33cd4a 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 @@ -81,7 +81,7 @@ 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 { TimewindowConfigData } from '@home/components/widget/config/timewindow-config-panel.component'; -import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeySettingsFunction } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; import { defaultFormProperties, FormProperty } from '@shared/models/dynamic-form.models'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import Timeout = NodeJS.Timeout; 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 b1958cb3db..c599fbf16d 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 @@ -102,7 +102,7 @@ import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time- import { SharedTelemetrySubscriber, TelemetrySubscriber } from '@shared/models/telemetry/telemetry.models'; import { UserId } from '@shared/models/id/user-id'; import { UserSettingsService } from '@core/http/user-settings.service'; -import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeySettingsFunction } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; import { UtilsService } from '@core/services/utils.service'; import { CompiledTbFunction } from '@shared/models/js-function.models'; import { FormProperty } from '@shared/models/dynamic-form.models'; diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.html b/ui-ngx/src/app/shared/components/string-items-list.component.html index eb4c15fc18..cf8ba1d150 100644 --- a/ui-ngx/src/app/shared/components/string-items-list.component.html +++ b/ui-ngx/src/app/shared/components/string-items-list.component.html @@ -16,7 +16,7 @@ --> diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.scss b/ui-ngx/src/app/shared/components/string-items-list.component.scss new file mode 100644 index 0000000000..70e487f998 --- /dev/null +++ b/ui-ngx/src/app/shared/components/string-items-list.component.scss @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2024 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. + */ +.tb-string-items-list { + .mat-mdc-standard-chip { + .mdc-evolution-chip__cell--primary, .mat-mdc-chip-action-label { + overflow: hidden; + } + } +} diff --git a/ui-ngx/src/app/shared/components/string-items-list.component.ts b/ui-ngx/src/app/shared/components/string-items-list.component.ts index 4121a7e8d7..b1447825ed 100644 --- a/ui-ngx/src/app/shared/components/string-items-list.component.ts +++ b/ui-ngx/src/app/shared/components/string-items-list.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; import { AbstractControl, ControlValueAccessor, @@ -38,14 +38,15 @@ export interface StringItemsOption { @Component({ selector: 'tb-string-items-list', templateUrl: './string-items-list.component.html', - styleUrls: [], + styleUrls: ['./string-items-list.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StringItemsListComponent), multi: true } - ] + ], + encapsulation: ViewEncapsulation.None }) export class StringItemsListComponent implements ControlValueAccessor, OnInit { diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index bbb462d9af..8b6558a11f 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -42,7 +42,7 @@ import { WidgetConfigComponentData } from '@home/models/widget-component.models' import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { HasTenantId, HasVersion } from '@shared/models/entity.models'; -import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; +import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models'; import { TbFunction } from '@shared/models/js-function.models'; import { FormProperty, jsonFormSchemaToFormProperties } from '@shared/models/dynamic-form.models'; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 946f338ddd..7ac6789d6b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1349,6 +1349,7 @@ "general": "General", "advanced": "Advanced", "key": "Key", + "keys": "Keys", "label": "Label", "color": "Color", "units": "Special symbol to show next to value", @@ -6869,6 +6870,8 @@ }, "data-layer": { "source": "Source", + "additional-data-keys": "Additional data keys", + "groups": "Groups", "marker": { "latitude-key": "Latitude key", "longitude-key": "Longitude key", diff --git a/ui-ngx/src/typings/leaflet-extend-tb.d.ts b/ui-ngx/src/typings/leaflet-extend-tb.d.ts index 9b8f357c18..d1ba09b66c 100644 --- a/ui-ngx/src/typings/leaflet-extend-tb.d.ts +++ b/ui-ngx/src/typings/leaflet-extend-tb.d.ts @@ -39,7 +39,7 @@ declare module 'leaflet' { class SidebarControl extends Control { constructor(options: SidebarControlOptions); - addPane(pane: JQuery): this; + addPane(pane: JQuery, button: JQuery): this; togglePane(pane: JQuery, button: JQuery): void; } @@ -70,12 +70,33 @@ declare module 'leaflet' { constructor(options: LayersControlOptions); } + interface DataLayer { + toggleGroup(group: string): boolean; + } + + interface GroupData { + title: string; + group: string; + enabled: boolean; + dataLayers: DataLayer[]; + } + + interface GroupsControlOptions extends SidebarPaneControlOptions { + groups: GroupData[]; + } + + class GroupsControl extends SidebarPaneControl { + constructor(options: GroupsControlOptions); + } + function sidebar(options: SidebarControlOptions): SidebarControl; function sidebarPane(options: O): SidebarPaneControl; function layers(options: LayersControlOptions): LayersControl; + function groups(options: GroupsControlOptions): GroupsControl; + namespace TileLayer { interface ChinaProvidersData {