132 changed files with 4933 additions and 3051 deletions
@ -0,0 +1,71 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2025 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]="limitForm" class="tb-form-table-row overflow-hidden items-start"> |
|||
<div class="flex-shrink capitalize" [class]="isPanelView ? 'basis-10' : 'basis-1/5'"> |
|||
{{ labelKey | translate}} |
|||
</div> |
|||
<div class="flex-grow flex-shrink flex flex-col gap-2 gt-sm:flex-row gt-sm:gap-3" [class]="isPanelView ? 'basis-1/2' : 'basis-40%'"> |
|||
<mat-form-field class="tb-inline-field flex-1" appearance="outline" subscriptSizing="dynamic"> |
|||
<mat-select formControlName="type"> |
|||
<mat-option *ngFor="let type of ValueSourceTypes" [value]="type"> |
|||
{{ ValueSourceTypeTranslation.get(type) | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<tb-entity-alias-input |
|||
required |
|||
class="flex-1" |
|||
*ngIf="limitForm.get('type').value === ValueSourceType.entity" |
|||
[aliasController]="aliasController" |
|||
formControlName="entityAlias"> |
|||
</tb-entity-alias-input> |
|||
</div> |
|||
<div class="flex-grow flex-shrink max-w-5/12" [class]="isPanelView ? 'basis-1/2' : 'basis-40%'"> |
|||
<mat-form-field *ngIf="limitForm.get('type').value === ValueSourceType.constant" |
|||
appearance="outline" class="tb-inline-field !w-full number" subscriptSizing="dynamic" |
|||
[class.tb-suffix-absolute]="!limitForm.get('value').value"> |
|||
<input matInput formControlName="value" type="number" placeholder="{{ 'widgets.chart.chart-axis.scale-auto' | translate }}"> |
|||
</mat-form-field> |
|||
<tb-data-key-input |
|||
required |
|||
*ngIf="limitForm.get('type').value === ValueSourceType.latestKey" |
|||
requiredText="widgets.chart.chart-axis.key-required" |
|||
[datasourceType]="datasource?.type" |
|||
[entityAliasId]="datasource?.entityAliasId" |
|||
[deviceId]="datasource?.deviceId" |
|||
[aliasController]="aliasController" |
|||
[dataKeyType]="datasource?.type === DatasourceType.function ? DataKeyType.function : null" |
|||
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" |
|||
[callbacks]="callbacks" |
|||
[editable]="false" |
|||
[formControl]="latestKeyFormControl"> |
|||
</tb-data-key-input> |
|||
<tb-data-key-input |
|||
required |
|||
*ngIf="limitForm.get('type').value === ValueSourceType.entity" |
|||
requiredText="widgets.chart.chart-axis.entity-key-required" |
|||
[datasourceType]="DatasourceType.entity" |
|||
[aliasController]="aliasController" |
|||
[entityAlias]="limitForm.get('entityAlias').value" |
|||
[dataKeyTypes]="[DataKeyType.attribute, DataKeyType.timeseries]" |
|||
[callbacks]="callbacks" |
|||
[editable]="false" |
|||
[formControl]="entityKeyFormControl"> |
|||
</tb-data-key-input> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,209 @@ |
|||
///
|
|||
/// Copyright © 2016-2025 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 } from '@angular/core'; |
|||
import { |
|||
ControlValueAccessor, NG_VALIDATORS, |
|||
NG_VALUE_ACCESSOR, |
|||
UntypedFormBuilder, UntypedFormControl, |
|||
UntypedFormGroup, ValidationErrors, Validator, |
|||
Validators, |
|||
} from '@angular/forms'; |
|||
import { |
|||
ValueSourceConfig, |
|||
ValueSourceType, |
|||
ValueSourceTypes, |
|||
ValueSourceTypeTranslation |
|||
} from '@shared/models/widget-settings.models'; |
|||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
|||
import { DataKey, Datasource, DatasourceType, } from '@shared/models/widget.models'; |
|||
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; |
|||
import { IAliasController } from '@core/api/widget-api.models'; |
|||
import { DataKeysCallbacks } from '@home/components/widget/lib/settings/common/key/data-keys.component.models'; |
|||
import { merge } from 'rxjs'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-axis-scale-row', |
|||
templateUrl: './axis-scale-row.component.html', |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => AxisScaleRowComponent), |
|||
multi: true |
|||
}, |
|||
{ |
|||
provide: NG_VALIDATORS, |
|||
useExisting: forwardRef(() => AxisScaleRowComponent), |
|||
multi: true |
|||
}, |
|||
] |
|||
}) |
|||
export class AxisScaleRowComponent implements ControlValueAccessor, OnInit, Validator { |
|||
|
|||
@Input() |
|||
isPanelView = false; |
|||
|
|||
@Input() |
|||
aliasController: IAliasController; |
|||
|
|||
@Input() |
|||
callbacks: DataKeysCallbacks; |
|||
|
|||
@Input() |
|||
datasource: Datasource; |
|||
|
|||
@Input() |
|||
labelKey: string; |
|||
|
|||
ValueSourceType = ValueSourceType; |
|||
|
|||
DataKeyType = DataKeyType; |
|||
|
|||
DatasourceType = DatasourceType; |
|||
|
|||
ValueSourceTypeTranslation = ValueSourceTypeTranslation; |
|||
|
|||
ValueSourceTypes = ValueSourceTypes; |
|||
|
|||
limitForm: UntypedFormGroup; |
|||
|
|||
latestKeyFormControl: UntypedFormControl; |
|||
|
|||
entityKeyFormControl: UntypedFormControl; |
|||
|
|||
private propagateChanges: (value: any) => void = () => {}; |
|||
|
|||
private modelValue: ValueSourceConfig | null = null; |
|||
|
|||
constructor(private fb: UntypedFormBuilder, |
|||
private destroyRef: DestroyRef) { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.limitForm = this.fb.group({ |
|||
type: [ValueSourceType.constant], |
|||
value: [null], |
|||
entityAlias: [null] |
|||
}); |
|||
this.latestKeyFormControl = this.fb.control(null, [Validators.required]); |
|||
this.entityKeyFormControl = this.fb.control(null, [Validators.required]); |
|||
merge( |
|||
this.latestKeyFormControl.valueChanges, |
|||
this.entityKeyFormControl.valueChanges, |
|||
this.limitForm.valueChanges |
|||
).pipe(takeUntilDestroyed(this.destroyRef)) |
|||
.subscribe(() => { |
|||
this.updateValidators(); |
|||
this.updateModel(); |
|||
}); |
|||
} |
|||
|
|||
writeValue(value: ValueSourceConfig) { |
|||
this.modelValue = value; |
|||
this.limitForm.patchValue( |
|||
{ |
|||
type: value.type || ValueSourceType.constant, |
|||
value: value.value, |
|||
entityAlias: value.entityAlias, |
|||
}, {emitEvent: false} |
|||
); |
|||
if (value.type === ValueSourceType.latestKey) { |
|||
this.latestKeyFormControl.patchValue({ |
|||
type: value.latestKeyType, |
|||
name: value.latestKey |
|||
}, {emitEvent: false}); |
|||
} else if (value.type === ValueSourceType.entity) { |
|||
this.entityKeyFormControl.patchValue({ |
|||
type: value.entityKeyType, |
|||
name: value.entityKey |
|||
}, {emitEvent: false}); |
|||
} |
|||
|
|||
this.updateValidators(); |
|||
this.limitForm.markAllAsTouched(); |
|||
} |
|||
|
|||
registerOnChange(fn: any) { |
|||
this.propagateChanges = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any) { |
|||
} |
|||
|
|||
validate(): ValidationErrors | null { |
|||
const type = this.limitForm.get('type')?.value; |
|||
const errors: any = {}; |
|||
|
|||
if (this.limitForm.invalid) { |
|||
errors.form = false; |
|||
} |
|||
|
|||
if (type === ValueSourceType.latestKey) { |
|||
if (!this.latestKeyFormControl.value || this.latestKeyFormControl.invalid) { |
|||
errors.latestKey = false; |
|||
} |
|||
} else if (type === ValueSourceType.entity) { |
|||
if (!this.limitForm.get('entityAlias')?.value) { |
|||
errors.entityAlias = false; |
|||
} |
|||
if (!this.entityKeyFormControl.value || this.entityKeyFormControl.invalid) { |
|||
errors.entityKey = false; |
|||
} |
|||
} |
|||
|
|||
return Object.keys(errors).length ? { axisLimitForm: errors } : null; |
|||
} |
|||
|
|||
private updateValidators() { |
|||
const axisTypeControl = this.limitForm.get('type'); |
|||
if (axisTypeControl && this.entityKeyFormControl && this.latestKeyFormControl) { |
|||
const type = axisTypeControl.value; |
|||
if (type === ValueSourceType.latestKey) { |
|||
this.latestKeyFormControl.setValidators([Validators.required]); |
|||
this.entityKeyFormControl.clearValidators(); |
|||
} else if (type === ValueSourceType.entity) { |
|||
this.latestKeyFormControl.clearValidators(); |
|||
this.limitForm.get('entityAlias').setValidators([Validators.required]); |
|||
this.entityKeyFormControl.setValidators([Validators.required]); |
|||
} else { |
|||
this.latestKeyFormControl.clearValidators(); |
|||
this.entityKeyFormControl.clearValidators(); |
|||
} |
|||
this.latestKeyFormControl.updateValueAndValidity({ emitEvent: false }); |
|||
this.entityKeyFormControl.updateValueAndValidity({ emitEvent: false }); |
|||
} |
|||
} |
|||
|
|||
private updateModel() { |
|||
const value = this.limitForm.value; |
|||
const type = value.type; |
|||
let updates: Partial<ValueSourceConfig> = { type }; |
|||
if (type === ValueSourceType.latestKey) { |
|||
const latestKey: DataKey = this.latestKeyFormControl.value; |
|||
updates.latestKey = latestKey?.name; |
|||
updates.latestKeyType = latestKey?.type as any; |
|||
} else if (type === ValueSourceType.entity) { |
|||
const entityKey: DataKey = this.entityKeyFormControl.value; |
|||
updates.entityKey = entityKey?.name; |
|||
updates.entityKeyType = entityKey?.type as any; |
|||
updates.entityAlias = value?.entityAlias; |
|||
} else { |
|||
updates.value = value?.value; |
|||
} |
|||
this.modelValue = updates as ValueSourceConfig; |
|||
this.propagateChanges(this.modelValue); |
|||
} |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2025 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
@import '../../../../../scss/constants'; |
|||
|
|||
:host { |
|||
display: flex; |
|||
flex: 1 1 0; |
|||
.tb-request-password-reset-content { |
|||
background-color: #eee; |
|||
.tb-request-password-reset-card { |
|||
@media #{$mat-gt-xs} { |
|||
width: 450px !important; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2025 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
@import '../../../../../scss/constants'; |
|||
|
|||
:host { |
|||
display: flex; |
|||
flex: 1 1 0; |
|||
.tb-reset-password-content { |
|||
background-color: #eee; |
|||
.tb-reset-password-card { |
|||
@media #{$mat-gt-sm} { |
|||
width: 450px !important; |
|||
} |
|||
} |
|||
|
|||
.tb-card-title{ |
|||
padding-top: 0; |
|||
padding-bottom: 0; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
///
|
|||
/// Copyright © 2016-2025 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 { OverlayModule } from '@angular/cdk/overlay'; |
|||
import { NgModule } from '@angular/core'; |
|||
import { DEFAULT_DIALOG_CONFIG, DialogConfig, DialogModule } from '@angular/cdk/dialog'; |
|||
import { MatDialogModule } from '@angular/material/dialog'; |
|||
import { DynamicDialog, DynamicMatDialog } from './dynamic-dialog'; |
|||
import { DynamicOverlay } from './dynamic-overlay'; |
|||
import { DynamicOverlayContainer } from './dynamic-overlay-container'; |
|||
|
|||
export const DYNAMIC_MAT_DIALOG_PROVIDERS = [ |
|||
DynamicOverlayContainer, |
|||
DynamicOverlay, |
|||
DynamicDialog, |
|||
DynamicMatDialog, |
|||
{ |
|||
provide: DEFAULT_DIALOG_CONFIG, |
|||
useValue: { |
|||
...new DialogConfig() |
|||
} |
|||
} |
|||
]; |
|||
|
|||
@NgModule( { |
|||
imports: [ |
|||
OverlayModule, |
|||
DialogModule, |
|||
MatDialogModule |
|||
], |
|||
providers: DYNAMIC_MAT_DIALOG_PROVIDERS |
|||
} ) |
|||
export class DynamicMatDialogModule { |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
///
|
|||
/// Copyright © 2016-2025 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 { Location } from "@angular/common"; |
|||
import { Inject, Injectable, Injector, Optional, SkipSelf, TemplateRef } from '@angular/core'; |
|||
import { |
|||
MAT_DIALOG_DEFAULT_OPTIONS, |
|||
MAT_DIALOG_SCROLL_STRATEGY, |
|||
MatDialog, |
|||
MatDialogConfig, MatDialogRef |
|||
} from '@angular/material/dialog'; |
|||
import { DynamicOverlay } from "./dynamic-overlay"; |
|||
import { DynamicOverlayContainer } from '@shared/components/dialog/dynamic/dynamic-overlay-container'; |
|||
import { ComponentType, ScrollStrategy } from '@angular/cdk/overlay'; |
|||
import { DEFAULT_DIALOG_CONFIG, Dialog, DialogConfig } from '@angular/cdk/dialog'; |
|||
|
|||
export interface DynamicMatDialogConfig<D> extends MatDialogConfig<D> { |
|||
containerElement?: HTMLElement; |
|||
} |
|||
|
|||
@Injectable() |
|||
export class DynamicMatDialog extends MatDialog { |
|||
|
|||
private _customOverlay: DynamicOverlay; |
|||
|
|||
constructor( _overlay: DynamicOverlay, |
|||
_injector: Injector, |
|||
@Optional() location: Location, |
|||
@Inject( MAT_DIALOG_DEFAULT_OPTIONS ) _defaultOptions: MatDialogConfig, |
|||
@Inject( MAT_DIALOG_SCROLL_STRATEGY ) _scrollStrategy: ScrollStrategy, |
|||
@Optional() @SkipSelf() _parentDialog:DynamicMatDialog, |
|||
_overlayContainer: DynamicOverlayContainer) { |
|||
|
|||
super( _overlay, _injector, location, _defaultOptions, _scrollStrategy, _parentDialog, _overlayContainer ); |
|||
this._dialog = _injector.get(DynamicDialog); |
|||
this._customOverlay = _overlay; |
|||
} |
|||
|
|||
public open<T, D = any, R = any>(component: ComponentType<T> | TemplateRef<T>, config?: DynamicMatDialogConfig<D>): MatDialogRef<T, R> { |
|||
if (config?.containerElement) { |
|||
config.containerElement.style.transform = 'translateZ(0)'; |
|||
this._customOverlay.setContainerElement( config.containerElement ); |
|||
} |
|||
const ref = super.open(component, config); |
|||
if (config?.containerElement) { |
|||
ref.afterClosed().subscribe( |
|||
{ |
|||
next: () => { |
|||
this._customOverlay.setContainerElement(null); |
|||
}, |
|||
error: () => { |
|||
this._customOverlay.setContainerElement(null); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
return ref; |
|||
} |
|||
} |
|||
|
|||
@Injectable() |
|||
export class DynamicDialog extends Dialog { |
|||
constructor( _overlay: DynamicOverlay, |
|||
_injector: Injector, |
|||
@Inject( DEFAULT_DIALOG_CONFIG ) _defaultOptions: DialogConfig, |
|||
@Inject( MAT_DIALOG_SCROLL_STRATEGY ) _scrollStrategy: ScrollStrategy, |
|||
@Optional() @SkipSelf() _parentDialog: DynamicDialog, |
|||
_overlayContainer: DynamicOverlayContainer) { |
|||
|
|||
super( _overlay, _injector, _defaultOptions, _parentDialog, _overlayContainer, _scrollStrategy ); |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
///
|
|||
/// Copyright © 2016-2025 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 { OverlayContainer } from "@angular/cdk/overlay"; |
|||
import { Injectable } from "@angular/core"; |
|||
|
|||
@Injectable() |
|||
export class DynamicOverlayContainer extends OverlayContainer { |
|||
|
|||
public setContainerElement( containerElement:HTMLElement ):void { |
|||
|
|||
this._containerElement = containerElement; |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue