Browse Source

UI: Map additional datasources.

pull/12723/head
Igor Kulikov 2 years ago
parent
commit
a98f6ddfb9
  1. 5
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts
  2. 25
      ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts
  3. 7
      ui-ngx/src/app/modules/home/components/widget/lib/maps/models/marker-shape.models.ts
  4. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-key-input.component.html
  5. 19
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-keys.component.html
  6. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-keys.component.ts
  7. 10
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts
  8. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.scss
  9. 80
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-source-row.component.html
  10. 36
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-source-row.component.scss
  11. 207
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-source-row.component.ts
  12. 43
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-sources.component.html
  13. 36
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-sources.component.scss
  14. 147
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-sources.component.ts
  15. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html
  16. 6
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  17. 5
      ui-ngx/src/assets/locale/locale.constant-en_US.json

5
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts

@ -401,10 +401,11 @@ export class TbMarkersDataLayer extends TbMapDataLayer<MarkersDataLayerSettings,
safeExecuteTbFunction(this.clusterMarkerColorFunction, [data, childCount]) : null;
if (isDefinedAndNotNull(markerColor) && tinycolor(markerColor).isValid()) {
const parsedColor = tinycolor(markerColor);
const alpha = parsedColor.getAlpha();
return L.divIcon({
html: `<div style="background-color: ${parsedColor.setAlpha(0.4).toRgbString()};" ` +
html: `<div style="background-color: ${parsedColor.setAlpha(alpha * 0.4).toRgbString()};" ` +
`class="marker-cluster tb-cluster-marker-element">` +
`<div style="background-color: ${parsedColor.setAlpha(0.9).toRgbString()};"><span>` + childCount + '</span></div></div>',
`<div style="background-color: ${parsedColor.setAlpha(alpha * 0.9).toRgbString()};"><span>` + childCount + '</span></div></div>',
iconSize: new L.Point(40, 40),
className: 'tb-cluster-marker-container'
});

25
ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts

@ -421,6 +421,31 @@ export const additionalMapDataSourcesToDatasources = (additionalMapDataSources:
});
};
export const additionalMapDataSourceValid = (dataSource: AdditionalMapDataSourceSettings): boolean => {
if (!dataSource.dsType || ![DatasourceType.function, DatasourceType.device, DatasourceType.entity].includes(dataSource.dsType)) {
return false;
}
return !!dataSource.dataKeys?.length;
};
export const additionalMapDataSourceValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
const dataSource: AdditionalMapDataSourceSettings = control.value;
if (!additionalMapDataSourceValid(dataSource)) {
return {
dataSource: true
};
}
return null;
};
export const defaultAdditionalMapDataSourceSettings = (functionsOnly = false): AdditionalMapDataSourceSettings => {
return {
dsType: functionsOnly ? DatasourceType.function : DatasourceType.entity,
dsLabel: functionsOnly ? 'Additional data' : '',
dataKeys: []
};
};
export enum MapControlsPosition {
topleft = 'topleft',
topright = 'topright',

7
ui-ngx/src/app/modules/home/components/widget/lib/maps/models/marker-shape.models.ts

@ -58,10 +58,12 @@ const createColorMarkerShape = (iconRegistry: MatIconRegistry, domSanitizer: Dom
const colorElements = Array.from(svgElement.getElementsByClassName('marker-color'));
colorElements.forEach(el => {
el.setAttribute('fill', '#'+color.toHex());
el.setAttribute('fill-opacity', `${color.getAlpha()}`);
});
const strokeElements = Array.from(svgElement.getElementsByClassName('marker-stroke'));
strokeElements.forEach(el => {
el.setAttribute('stroke', '#'+color.toHex());
el.setAttribute('stroke-opacity', `${color.getAlpha()}`);
});
return svgElement;
})
@ -90,8 +92,8 @@ const createIconElement = (iconRegistry: MatIconRegistry, icon: string, size: nu
map((svgElement) => {
const element = new Element(svgElement.firstChild);
element.fill('#'+iconColor.toHex());
//const box = element.bbox();
const scale = size / 24;//box.height;
element.attr('fill-opacity', iconColor.getAlpha());
const scale = size / 24;
element.scale(scale);
return element;
}),
@ -110,6 +112,7 @@ const createIconElement = (iconRegistry: MatIconRegistry, icon: string, size: nu
'text-anchor': 'start'
});
textElement.fill('#'+iconColor.toHex());
textElement.attr('fill-opacity', iconColor.getAlpha());
const tspan = textElement.tspan(iconName);
tspan.attr({
'dominant-baseline': 'hanging'

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-key-input.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field class="tb-key-field" subscriptSizing="dynamic"
<mat-form-field class="tb-key-field"
[class]="{'tb-inline-field': inlineField,
'tb-suffix-absolute': (inlineField && !keysFormControl.value?.length)}"
[appearance]="inlineField ? 'fill' : appearance"

19
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-keys.component.html

@ -16,9 +16,11 @@
-->
<mat-form-field [formGroup]="keysListFormGroup" class="mat-block tb-keys-field"
[appearance]="appearance"
[subscriptSizing]="subscriptSizing">
<mat-label>{{ label ? label : placeholder}}</mat-label>
[class]="{'tb-inline-field': inlineField,
'tb-suffix-absolute': (inlineField && !keys?.length)}"
[appearance]="inlineField ? 'fill' : appearance"
[subscriptSizing]="inlineField ? 'dynamic' : subscriptSizing">
<mat-label *ngIf="!inlineField">{{ label ? label : placeholder}}</mat-label>
<mat-chip-grid #chipList formControlName="keys" [required]="required">
<div class="tb-datakeys-container"
[dndDropzone]="[dndId]"
@ -108,6 +110,15 @@
(matChipInputTokenEnd)="add($event)">
</div>
</mat-chip-grid>
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
[matTooltip]="requiredText"
*ngIf="inlineField && requiredText && keysListFormGroup.get('keys').hasError('required')
&& keysListFormGroup.get('keys').touched"
class="tb-error">
warning
</mat-icon>
<mat-autocomplete #keyAutocomplete="matAutocomplete"
class="tb-autocomplete"
panelWidth="fit-content"
@ -166,7 +177,7 @@
</div>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="keysListFormGroup.get('keys').hasError('required')">
<mat-error *ngIf="!inlineField && keysListFormGroup.get('keys').hasError('required')">
{{ requiredText }}
</mat-error>
</mat-form-field>

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/key/data-keys.component.ts

@ -110,6 +110,10 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
@Input()
subscriptSizing: SubscriptSizing = 'fixed';
@Input()
@coerceBoolean()
inlineField = false;
@Input()
@coerceBoolean()
hideDataKeyLabel: boolean;

10
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts

@ -176,6 +176,16 @@ export class MapDataLayerDialogComponent extends DialogComponent<MapDataLayerDia
}
break;
}
const additionalDataKeys: DataKey[] = this.dataLayerFormGroup.get('additionalDataKeys').value;
if (additionalDataKeys?.length) {
let updated = false;
for (const key of additionalDataKeys) {
updated = this.updateDataKeyToNewDsType(key, newDsType) || updated;
}
if (updated) {
this.dataLayerFormGroup.get('additionalDataKeys').patchValue(additionalDataKeys, {emitEvent: false});
}
}
this.updateValidators();
}

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.scss

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../../../../../scss/constants';
.tb-form-table-row.tb-map-data-layer-row {

80
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-source-row.component.html

@ -0,0 +1,80 @@
<!--
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.
-->
<div [formGroup]="dataSourceFormGroup" class="tb-form-table-row tb-map-data-source-row">
<div class="tb-source-field">
<mat-form-field class="tb-ds-type-field tb-inline-field" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="dsType">
<mat-option *ngFor="let type of datasourceTypes" [value]="type">
{{ datasourceTypesTranslations.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field
*ngIf="dataSourceFormGroup.get('dsType').value === DatasourceType.function"
class="tb-label-field tb-inline-field" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="dsLabel" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-entity-autocomplete
class="tb-device-field"
*ngIf="dataSourceFormGroup.get('dsType').value === DatasourceType.device"
required
inlineField
placeholder="{{ 'device.select-device' | translate }}"
[entityType]="EntityType.DEVICE"
formControlName="dsDeviceId">
</tb-entity-autocomplete>
<tb-entity-alias-select
class="tb-entity-alias-field"
*ngIf="dataSourceFormGroup.get('dsType').value === DatasourceType.entity"
inlineField
tbRequired
[aliasController]="context.aliasController"
formControlName="dsEntityAliasId"
[callbacks]="context.callbacks">
</tb-entity-alias-select>
</div>
<tb-data-keys
class="tb-data-keys-field"
formControlName="dataKeys"
inlineField
hideDataKeyColor
hideDataKeyDecimals
hideDataKeyUnits
disableDrag
[required]="true"
[widgetType]="widgetType.latest"
[widget]="context.widget"
[datasourceType]="dataSourceFormGroup.get('dsType').value"
[entityAliasId]="dataSourceFormGroup.get('dsEntityAliasId').value"
[deviceId]="dataSourceFormGroup.get('dsDeviceId').value"
[aliasController]="context.aliasController"
[callbacks]="context.callbacks"
[generateKey]="generateAdditionalDataKey">
</tb-data-keys>
<div class="tb-form-table-row-cell-buttons">
<div class="tb-remove-button">
<button type="button"
mat-icon-button
(click)="dataSourceRemoved.emit()"
matTooltip="{{ 'widgets.maps.data-layer.remove-datasource' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</div>

36
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-source-row.component.scss

@ -0,0 +1,36 @@
/**
* 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-form-table-row.tb-map-data-source-row {
.tb-source-field {
flex: 1 1 50%;
display: flex;
gap: 12px;
.tb-ds-type-field, .tb-label-field, .tb-device-field, .tb-entity-alias-field {
flex: 1;
}
}
.tb-data-keys-field {
flex: 1 1 50%;
}
.tb-remove-button {
width: 40px;
min-width: 40px;
}
}

207
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-source-row.component.ts

@ -0,0 +1,207 @@
///
/// 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 {
ChangeDetectorRef,
Component,
DestroyRef,
EventEmitter,
forwardRef,
Input,
OnInit,
Output,
ViewEncapsulation
} from '@angular/core';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormGroup,
Validators
} from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { AdditionalMapDataSourceSettings } from '@home/components/widget/lib/maps/models/map.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 { genNextLabelForDataKeys } from '@core/utils';
import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models';
@Component({
selector: 'tb-map-data-source-row',
templateUrl: './map-data-source-row.component.html',
styleUrls: ['./map-data-source-row.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MapDataSourceRowComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class MapDataSourceRowComponent implements ControlValueAccessor, OnInit {
DatasourceType = DatasourceType;
DataKeyType = DataKeyType;
EntityType = EntityType;
widgetType = widgetType;
datasourceTypes: Array<DatasourceType> = [];
datasourceTypesTranslations = datasourceTypeTranslationMap;
@Input()
disabled: boolean;
@Input()
context: MapSettingsContext;
@Output()
dataSourceRemoved = new EventEmitter();
dataSourceFormGroup: UntypedFormGroup;
generateAdditionalDataKey = this.generateDataKey.bind(this);
modelValue: AdditionalMapDataSourceSettings;
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder,
private cd: ChangeDetectorRef,
private destroyRef: DestroyRef) {
}
ngOnInit() {
if (this.context.functionsOnly) {
this.datasourceTypes = [DatasourceType.function];
} else {
this.datasourceTypes = [DatasourceType.function, DatasourceType.device, DatasourceType.entity];
}
this.dataSourceFormGroup = this.fb.group({
dsType: [null, [Validators.required]],
dsLabel: [null, []],
dsDeviceId: [null, [Validators.required]],
dsEntityAliasId: [null, [Validators.required]],
dataKeys: [null, [Validators.required]]
});
this.dataSourceFormGroup.valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(
() => this.updateModel()
);
this.dataSourceFormGroup.get('dsType').valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(
(newDsType: DatasourceType) => this.onDsTypeChanged(newDsType)
);
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(_fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.dataSourceFormGroup.disable({emitEvent: false});
} else {
this.dataSourceFormGroup.enable({emitEvent: false});
this.updateValidators();
}
}
writeValue(value: AdditionalMapDataSourceSettings): void {
this.modelValue = value;
this.dataSourceFormGroup.patchValue(
{
dsType: value?.dsType,
dsLabel: value?.dsLabel,
dsDeviceId: value?.dsDeviceId,
dsEntityAliasId: value?.dsEntityAliasId,
dataKeys: value?.dataKeys
}, {emitEvent: false}
);
this.updateValidators();
this.cd.markForCheck();
}
private generateDataKey(key: DataKey): DataKey {
const dataKey = this.context.callbacks.generateDataKey(key.name, key.type, null, false, null);
const dataKeys: DataKey[] = this.dataSourceFormGroup.get('dataKeys').value || [];
dataKey.label = genNextLabelForDataKeys(dataKey.label, dataKeys);
return dataKey;
}
private onDsTypeChanged(newDsType: DatasourceType) {
let updateModel = false;
const dataKeys: DataKey[] = this.dataSourceFormGroup.get('dataKeys').value;
if (dataKeys?.length) {
for (const key of dataKeys) {
updateModel = this.updateDataKeyToNewDsType(key, newDsType) || updateModel;
}
if (updateModel) {
this.dataSourceFormGroup.get('dataKeys').patchValue(dataKeys, {emitEvent: false});
}
}
this.updateValidators();
if (updateModel) {
this.updateModel();
}
}
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.dataSourceFormGroup.get('dsType').value;
if (dsType === DatasourceType.function) {
this.dataSourceFormGroup.get('dsLabel').enable({emitEvent: false});
this.dataSourceFormGroup.get('dsDeviceId').disable({emitEvent: false});
this.dataSourceFormGroup.get('dsEntityAliasId').disable({emitEvent: false});
} else if (dsType === DatasourceType.device) {
this.dataSourceFormGroup.get('dsLabel').disable({emitEvent: false});
this.dataSourceFormGroup.get('dsDeviceId').enable({emitEvent: false});
this.dataSourceFormGroup.get('dsEntityAliasId').disable({emitEvent: false});
} else {
this.dataSourceFormGroup.get('dsLabel').disable({emitEvent: false});
this.dataSourceFormGroup.get('dsDeviceId').disable({emitEvent: false});
this.dataSourceFormGroup.get('dsEntityAliasId').enable({emitEvent: false});
}
}
private updateModel() {
this.modelValue = {...this.modelValue, ...this.dataSourceFormGroup.value};
this.propagateChange(this.modelValue);
}
}

43
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-sources.component.html

@ -0,0 +1,43 @@
<!--
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.
-->
<div class="tb-form-panel no-padding no-border">
<div class="tb-form-table tb-map-data-sources">
<div class="tb-form-table-header no-padding-right">
<div class="tb-form-table-header-cell tb-source-header" translate>widgets.maps.data-layer.source</div>
<div class="tb-form-table-header-cell tb-data-keys-header" translate>widgets.maps.data-layer.data-keys</div>
<div class="tb-form-table-header-cell tb-actions-header"></div>
</div>
<div *ngIf="dataSourcesFormArray().controls.length; else noDataSources" class="tb-form-table-body">
<div *ngFor="let dataSourceControl of dataSourcesFormArray().controls; trackBy: trackByDataSource; let $index = index;">
<tb-map-data-source-row class="flex-1"
[context]="context"
[formControl]="dataSourceControl"
(dataSourceRemoved)="removeDataSource($index)">
</tb-map-data-source-row>
</div>
</div>
</div>
<div>
<button type="button" mat-stroked-button color="primary" (click)="addDataSource()">
{{ 'widgets.maps.data-layer.add-datasource' | translate }}
</button>
</div>
</div>
<ng-template #noDataSources>
<span class="tb-prompt flex items-center justify-center">{{ 'widgets.maps.data-layer.no-datasources' | translate }}</span>
</ng-template>

36
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-sources.component.scss

@ -0,0 +1,36 @@
/**
* 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-sources {
.tb-form-table-header-cell {
&.tb-source-header {
flex: 1 1 50%;
}
&.tb-data-keys-header {
flex: 1 1 50%;
}
&.tb-actions-header {
width: 40px;
min-width: 40px;
}
}
.tb-form-table-body {
tb-map-data-source-row {
overflow: hidden;
}
}
}

147
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-sources.component.ts

@ -0,0 +1,147 @@
///
/// 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, forwardRef, Input, OnInit, ViewEncapsulation } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormArray,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validator
} from '@angular/forms';
import { mergeDeep } from '@core/utils';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
AdditionalMapDataSourceSettings,
additionalMapDataSourceValid,
additionalMapDataSourceValidator,
defaultAdditionalMapDataSourceSettings
} from '@home/components/widget/lib/maps/models/map.models';
import { MapSettingsContext } from '@home/components/widget/lib/settings/common/map/map-settings.component.models';
@Component({
selector: 'tb-map-data-sources',
templateUrl: './map-data-sources.component.html',
styleUrls: ['./map-data-sources.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MapDataSourcesComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => MapDataSourcesComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class MapDataSourcesComponent implements ControlValueAccessor, OnInit, Validator {
@Input()
disabled: boolean;
@Input()
context: MapSettingsContext;
dataSourcesFormGroup: UntypedFormGroup;
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder,
private destroyRef: DestroyRef) {
}
ngOnInit() {
this.dataSourcesFormGroup = this.fb.group({
dataSources: [this.fb.array([]), []]
});
this.dataSourcesFormGroup.valueChanges.pipe(
takeUntilDestroyed(this.destroyRef)
).subscribe(
() => {
let dataSources: AdditionalMapDataSourceSettings[] = this.dataSourcesFormGroup.get('dataSources').value;
if (dataSources) {
dataSources = dataSources.filter(dataSource => additionalMapDataSourceValid(dataSource));
}
this.propagateChange(dataSources);
}
);
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.dataSourcesFormGroup.disable({emitEvent: false});
} else {
this.dataSourcesFormGroup.enable({emitEvent: false});
}
}
writeValue(value: AdditionalMapDataSourceSettings[] | undefined): void {
const dataSources: AdditionalMapDataSourceSettings[] = value || [];
this.dataSourcesFormGroup.setControl('dataSources', this.prepareDataSourcesFormArray(dataSources), {emitEvent: false});
}
public validate(c: UntypedFormControl) {
const valid = this.dataSourcesFormGroup.valid;
return valid ? null : {
dataSources: {
valid: false,
},
};
}
dataSourcesFormArray(): UntypedFormArray {
return this.dataSourcesFormGroup.get('dataSources') as UntypedFormArray;
}
trackByDataSource(index: number, dataSourceControl: AbstractControl): any {
return dataSourceControl;
}
removeDataSource(index: number) {
(this.dataSourcesFormGroup.get('dataSources') as UntypedFormArray).removeAt(index);
}
addDataSource() {
const dataSource = mergeDeep<AdditionalMapDataSourceSettings>({} as AdditionalMapDataSourceSettings,
defaultAdditionalMapDataSourceSettings(this.context.functionsOnly));
const dataSourcesArray = this.dataSourcesFormGroup.get('dataSources') as UntypedFormArray;
const dataSourceControl = this.fb.control(dataSource, [additionalMapDataSourceValidator]);
dataSourcesArray.push(dataSourceControl);
}
private prepareDataSourcesFormArray(dataSources: AdditionalMapDataSourceSettings[]): UntypedFormArray {
const dataSourcesControls: Array<AbstractControl> = [];
dataSources.forEach((dataSource) => {
dataSourcesControls.push(this.fb.control(dataSource, [additionalMapDataSourceValidator]));
});
return this.fb.array(dataSourcesControls);
}
}

8
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html

@ -57,4 +57,12 @@
[context]="context"
[mapType]="mapSettingsFormGroup.get('mapType').value"></tb-map-data-layers>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title">
{{ 'widgets.maps.data-layer.additional-datasources' | translate }}
</div>
<tb-map-data-sources formControlName="additionalDataSources"
[context]="context">
</tb-map-data-sources>
</div>
</ng-container>

6
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts

@ -226,6 +226,10 @@ import { MarkerShapesComponent } from '@home/components/widget/lib/settings/comm
import {
MarkerClusteringSettingsComponent
} from '@home/components/widget/lib/settings/common/map/marker-clustering-settings.component';
import { MapDataSourcesComponent } from '@home/components/widget/lib/settings/common/map/map-data-sources.component';
import {
MapDataSourceRowComponent
} from '@home/components/widget/lib/settings/common/map/map-data-source-row.component';
@NgModule({
declarations: [
@ -310,6 +314,8 @@ import {
MapDataLayerDialogComponent,
MapDataLayerRowComponent,
MapDataLayersComponent,
MapDataSourceRowComponent,
MapDataSourcesComponent,
MapSettingsComponent,
EntityAliasSelectComponent,
FilterSelectComponent,

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

@ -6874,6 +6874,11 @@
"data-layer": {
"source": "Source",
"additional-data-keys": "Additional data keys",
"additional-datasources": "Additional datasources",
"data-keys": "Data keys",
"add-datasource": "Add datasource",
"no-datasources": "No datasources configured",
"remove-datasource": "Remove datasource",
"groups": "Groups",
"color": "Color",
"fill-color": "Fill color",

Loading…
Cancel
Save