committed by
GitHub
12 changed files with 773 additions and 46 deletions
@ -0,0 +1,39 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<section [formGroup]="persistenceSettingRowForm" class="tb-form-panel stroked no-gap no-padding-bottom"> |
||||
|
<div class="tb-form-panel-title mb-4">{{ title }}</div> |
||||
|
<mat-form-field> |
||||
|
<mat-label translate>rule-node-config.save-time-series.strategy</mat-label> |
||||
|
<mat-select formControlName="type"> |
||||
|
@for (strategy of persistenceStrategies; track strategy) { |
||||
|
<mat-option [value]="strategy">{{ PersistenceTypeTranslationMap.get(strategy) | translate }}</mat-option> |
||||
|
} |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
@if(persistenceSettingRowForm.get('type').value === PersistenceType.DEDUPLICATE) { |
||||
|
<tb-time-unit-input |
||||
|
required |
||||
|
labelText="{{ 'rule-node-config.save-time-series.deduplication-interval' | translate }}" |
||||
|
requiredText="{{ 'rule-node-config.save-time-series.deduplication-interval-required' | translate }}" |
||||
|
minErrorText="{{ 'rule-node-config.save-time-series.deduplication-interval-min-max-range' | translate }}" |
||||
|
maxErrorText="{{ 'rule-node-config.save-time-series.deduplication-interval-min-max-range' | translate }}" |
||||
|
[maxTime]="maxDeduplicateTime" |
||||
|
formControlName="deduplicationIntervalSecs"> |
||||
|
</tb-time-unit-input> |
||||
|
} |
||||
|
</section> |
||||
@ -0,0 +1,114 @@ |
|||||
|
///
|
||||
|
/// 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, forwardRef, Input } from '@angular/core'; |
||||
|
import { |
||||
|
ControlValueAccessor, |
||||
|
FormBuilder, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
ValidationErrors, |
||||
|
Validator |
||||
|
} from '@angular/forms'; |
||||
|
import { |
||||
|
AdvancedPersistenceConfig, |
||||
|
defaultAdvancedPersistenceConfig, |
||||
|
maxDeduplicateTimeSecs, |
||||
|
PersistenceType, |
||||
|
PersistenceTypeTranslationMap |
||||
|
} from '@home/components/rule-node/action/timeseries-config.models'; |
||||
|
import { isDefinedAndNotNull } from '@core/utils'; |
||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-advanced-persistence-setting-row', |
||||
|
templateUrl: './advanced-persistence-setting-row.component.html', |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => AdvancedPersistenceSettingRowComponent), |
||||
|
multi: true |
||||
|
},{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => AdvancedPersistenceSettingRowComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class AdvancedPersistenceSettingRowComponent implements ControlValueAccessor, Validator { |
||||
|
|
||||
|
@Input() |
||||
|
title: string; |
||||
|
|
||||
|
persistenceSettingRowForm = this.fb.group({ |
||||
|
type: [defaultAdvancedPersistenceConfig.type], |
||||
|
deduplicationIntervalSecs: [{value: 60, disabled: true}] |
||||
|
}); |
||||
|
|
||||
|
PersistenceType = PersistenceType; |
||||
|
persistenceStrategies = [PersistenceType.ON_EVERY_MESSAGE, PersistenceType.DEDUPLICATE, PersistenceType.SKIP]; |
||||
|
PersistenceTypeTranslationMap = PersistenceTypeTranslationMap; |
||||
|
|
||||
|
maxDeduplicateTime = maxDeduplicateTimeSecs; |
||||
|
|
||||
|
private propagateChange: (value: any) => void = () => {}; |
||||
|
|
||||
|
constructor(private fb: FormBuilder) { |
||||
|
this.persistenceSettingRowForm.get('type').valueChanges.pipe( |
||||
|
takeUntilDestroyed() |
||||
|
).subscribe(() => this.updatedValidation()); |
||||
|
|
||||
|
this.persistenceSettingRowForm.valueChanges.pipe( |
||||
|
takeUntilDestroyed() |
||||
|
).subscribe((value) => this.propagateChange(value)); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any) { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(_fn: any) { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean) { |
||||
|
if (isDisabled) { |
||||
|
this.persistenceSettingRowForm.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.persistenceSettingRowForm.enable({emitEvent: false}); |
||||
|
this.updatedValidation(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
validate(): ValidationErrors | null { |
||||
|
return this.persistenceSettingRowForm.valid ? null : { |
||||
|
persistenceSettingRow: false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
writeValue(value: AdvancedPersistenceConfig) { |
||||
|
if (isDefinedAndNotNull(value)) { |
||||
|
this.persistenceSettingRowForm.patchValue(value, {emitEvent: false}); |
||||
|
} else { |
||||
|
this.persistenceSettingRowForm.patchValue(defaultAdvancedPersistenceConfig); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private updatedValidation() { |
||||
|
if (this.persistenceSettingRowForm.get('type').value === PersistenceType.DEDUPLICATE) { |
||||
|
this.persistenceSettingRowForm.get('deduplicationIntervalSecs').enable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.persistenceSettingRowForm.get('deduplicationIntervalSecs').disable({emitEvent: false}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<section [formGroup]="persistenceForm" class="tb-form-panel no-border no-padding"> |
||||
|
<tb-advanced-persistence-setting-row |
||||
|
formControlName="timeseries" |
||||
|
title="{{ 'rule-node-config.save-time-series.time-series' | translate }}" |
||||
|
></tb-advanced-persistence-setting-row> |
||||
|
<tb-advanced-persistence-setting-row |
||||
|
formControlName="latest" |
||||
|
title="{{ 'rule-node-config.save-time-series.latest' | translate }}" |
||||
|
></tb-advanced-persistence-setting-row> |
||||
|
<tb-advanced-persistence-setting-row |
||||
|
formControlName="webSockets" |
||||
|
title="{{ 'rule-node-config.save-time-series.web-sockets' | translate }}" |
||||
|
></tb-advanced-persistence-setting-row> |
||||
|
</section> |
||||
@ -0,0 +1,83 @@ |
|||||
|
///
|
||||
|
/// 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 { |
||||
|
ControlValueAccessor, |
||||
|
FormBuilder, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
ValidationErrors, |
||||
|
Validator |
||||
|
} from '@angular/forms'; |
||||
|
import { Component, forwardRef } from '@angular/core'; |
||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
||||
|
import { AdvancedPersistenceStrategy } from '@home/components/rule-node/action/timeseries-config.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-advanced-persistence-settings', |
||||
|
templateUrl: './advanced-persistence-setting.component.html', |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => AdvancedPersistenceSettingComponent), |
||||
|
multi: true |
||||
|
},{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => AdvancedPersistenceSettingComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class AdvancedPersistenceSettingComponent implements ControlValueAccessor, Validator { |
||||
|
|
||||
|
persistenceForm = this.fb.group({ |
||||
|
timeseries: [null], |
||||
|
latest: [null], |
||||
|
webSockets: [null] |
||||
|
}); |
||||
|
|
||||
|
private propagateChange: (value: any) => void = () => {}; |
||||
|
|
||||
|
constructor(private fb: FormBuilder) { |
||||
|
this.persistenceForm.valueChanges.pipe( |
||||
|
takeUntilDestroyed() |
||||
|
).subscribe(value => this.propagateChange(value)); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any) { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(_fn: any) { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean) { |
||||
|
if (isDisabled) { |
||||
|
this.persistenceForm.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.persistenceForm.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
validate(): ValidationErrors | null { |
||||
|
return this.persistenceForm.valid ? null : { |
||||
|
persistenceForm: false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
writeValue(value: AdvancedPersistenceStrategy) { |
||||
|
this.persistenceForm.patchValue(value, {emitEvent: false}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
///
|
||||
|
/// 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 { DAY, SECOND } from '@shared/models/time/time.models'; |
||||
|
|
||||
|
export const maxDeduplicateTimeSecs = DAY / SECOND + 1; |
||||
|
|
||||
|
export interface TimeseriesNodeConfiguration { |
||||
|
persistenceSettings: PersistenceSettings; |
||||
|
defaultTTL: number; |
||||
|
useServerTs: boolean; |
||||
|
} |
||||
|
|
||||
|
export interface TimeseriesNodeConfigurationForm extends Omit<TimeseriesNodeConfiguration, 'persistenceSettings'> { |
||||
|
persistenceSettings: PersistenceSettingsForm |
||||
|
} |
||||
|
|
||||
|
export type PersistenceSettings = BasicPersistenceSettings & Partial<DeduplicatePersistenceStrategy> & Partial<AdvancedPersistenceStrategy>; |
||||
|
|
||||
|
export type PersistenceSettingsForm = Omit<PersistenceSettings, keyof AdvancedPersistenceStrategy> & { |
||||
|
isAdvanced: boolean; |
||||
|
advanced?: Partial<AdvancedPersistenceStrategy>; |
||||
|
type: PersistenceType; |
||||
|
}; |
||||
|
|
||||
|
export enum PersistenceType { |
||||
|
ON_EVERY_MESSAGE = 'ON_EVERY_MESSAGE', |
||||
|
DEDUPLICATE = 'DEDUPLICATE', |
||||
|
WEBSOCKETS_ONLY = 'WEBSOCKETS_ONLY', |
||||
|
ADVANCED = 'ADVANCED', |
||||
|
SKIP = 'SKIP' |
||||
|
} |
||||
|
|
||||
|
export const PersistenceTypeTranslationMap = new Map<PersistenceType, string>([ |
||||
|
[PersistenceType.ON_EVERY_MESSAGE, 'rule-node-config.save-time-series.strategy-type.every-message'], |
||||
|
[PersistenceType.DEDUPLICATE, 'rule-node-config.save-time-series.strategy-type.deduplicate'], |
||||
|
[PersistenceType.WEBSOCKETS_ONLY, 'rule-node-config.save-time-series.strategy-type.web-sockets-only'], |
||||
|
[PersistenceType.SKIP, 'rule-node-config.save-time-series.strategy-type.skip'], |
||||
|
]) |
||||
|
|
||||
|
export interface BasicPersistenceSettings { |
||||
|
type: PersistenceType; |
||||
|
} |
||||
|
|
||||
|
export interface DeduplicatePersistenceStrategy extends BasicPersistenceSettings{ |
||||
|
deduplicationIntervalSecs: number; |
||||
|
} |
||||
|
|
||||
|
export interface AdvancedPersistenceStrategy extends BasicPersistenceSettings{ |
||||
|
timeseries: AdvancedPersistenceConfig; |
||||
|
latest: AdvancedPersistenceConfig; |
||||
|
webSockets: AdvancedPersistenceConfig; |
||||
|
} |
||||
|
|
||||
|
export type AdvancedPersistenceConfig = WithOptional<DeduplicatePersistenceStrategy, 'deduplicationIntervalSecs'>; |
||||
|
|
||||
|
export const defaultAdvancedPersistenceConfig: AdvancedPersistenceConfig = { |
||||
|
type: PersistenceType.ON_EVERY_MESSAGE |
||||
|
} |
||||
|
|
||||
|
export const defaultAdvancedPersistenceStrategy: Omit<AdvancedPersistenceStrategy, 'type'> = { |
||||
|
timeseries: defaultAdvancedPersistenceConfig, |
||||
|
latest: defaultAdvancedPersistenceConfig, |
||||
|
webSockets: defaultAdvancedPersistenceConfig, |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<section [formGroup]="timeInputForm" class="flex gap-4"> |
||||
|
<mat-form-field class="max-w-66% flex-full" subscriptSizing="dynamic"> |
||||
|
<mat-label *ngIf="labelText">{{ labelText }}</mat-label> |
||||
|
<input type="number" min="0" step="1" matInput formControlName="time"> |
||||
|
<div matSuffix> |
||||
|
<ng-content select="[matSuffix]"></ng-content> |
||||
|
</div> |
||||
|
<mat-hint></mat-hint> |
||||
|
<mat-error *ngIf="timeInputForm.get('time').hasError('required') && requiredText"> |
||||
|
{{ requiredText }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="timeInputForm.get('time').hasError('min') && minErrorText"> |
||||
|
{{ minErrorText }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="timeInputForm.get('time').hasError('max') && maxErrorText"> |
||||
|
{{ maxErrorText }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field class="max-w-33% flex-full"> |
||||
|
<mat-label translate>rule-node-config.units</mat-label> |
||||
|
<mat-select formControlName="timeUnit"> |
||||
|
@for (timeUnit of timeUnits; track timeUnit) { |
||||
|
<mat-option [value]="timeUnit">{{ timeUnitTranslations.get(timeUnit) | translate }}</mat-option> |
||||
|
} |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
</section> |
||||
@ -0,0 +1,184 @@ |
|||||
|
///
|
||||
|
/// 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 } from '@angular/core'; |
||||
|
import { |
||||
|
AbstractControl, |
||||
|
ControlValueAccessor, |
||||
|
FormBuilder, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
ValidationErrors, |
||||
|
Validator, Validators |
||||
|
} from '@angular/forms'; |
||||
|
import { TimeUnit, timeUnitTranslations } from '../rule-node-config.models'; |
||||
|
import { isDefinedAndNotNull, isNumeric } from '@core/utils'; |
||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
||||
|
import { coerceBoolean, coerceNumber } from '@shared/decorators/coercion'; |
||||
|
import { DAY, HOUR, MINUTE, SECOND } from '@shared/models/time/time.models'; |
||||
|
|
||||
|
interface TimeUnitInputModel { |
||||
|
time: number; |
||||
|
timeUnit: TimeUnit |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-time-unit-input', |
||||
|
templateUrl: './time-unit-input.component.html', |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => TimeUnitInputComponent), |
||||
|
multi: true |
||||
|
},{ |
||||
|
provide: NG_VALIDATORS, |
||||
|
useExisting: forwardRef(() => TimeUnitInputComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class TimeUnitInputComponent implements ControlValueAccessor, Validator, OnInit { |
||||
|
|
||||
|
@Input() |
||||
|
labelText: string; |
||||
|
|
||||
|
@Input() |
||||
|
@coerceBoolean() |
||||
|
required: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
requiredText: string; |
||||
|
|
||||
|
@Input() |
||||
|
minErrorText: string; |
||||
|
|
||||
|
@Input() |
||||
|
@coerceNumber() |
||||
|
maxTime: number; |
||||
|
|
||||
|
@Input() |
||||
|
maxErrorText: string; |
||||
|
|
||||
|
timeUnits = Object.values(TimeUnit).filter(item => item !== TimeUnit.MILLISECONDS) as TimeUnit[]; |
||||
|
|
||||
|
timeUnitTranslations = timeUnitTranslations; |
||||
|
|
||||
|
timeInputForm = this.fb.group({ |
||||
|
time: [0, Validators.min(0)], |
||||
|
timeUnit: [TimeUnit.SECONDS] |
||||
|
}); |
||||
|
|
||||
|
private timeIntervalsInSec = new Map<TimeUnit, number>([ |
||||
|
[TimeUnit.DAYS, DAY/SECOND], |
||||
|
[TimeUnit.HOURS, HOUR/SECOND], |
||||
|
[TimeUnit.MINUTES, MINUTE/SECOND], |
||||
|
[TimeUnit.SECONDS, SECOND/SECOND], |
||||
|
]); |
||||
|
|
||||
|
private modelValue: number; |
||||
|
|
||||
|
private propagateChange: (value: any) => void = () => {}; |
||||
|
|
||||
|
constructor(private fb: FormBuilder, |
||||
|
private destroyRef: DestroyRef) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
if(this.required || this.maxTime) { |
||||
|
const timeControl = this.timeInputForm.get('time'); |
||||
|
const validators = []; |
||||
|
if (this.required) { |
||||
|
validators.push(Validators.required); |
||||
|
} |
||||
|
if (this.maxTime) { |
||||
|
validators.push((control: AbstractControl) => |
||||
|
Validators.max(Math.floor(this.maxTime / this.timeIntervalsInSec.get(this.timeInputForm.get('timeUnit').value)))(control) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
timeControl.setValidators(validators); |
||||
|
timeControl.updateValueAndValidity({ emitEvent: false }); |
||||
|
} |
||||
|
|
||||
|
this.timeInputForm.get('timeUnit').valueChanges.pipe( |
||||
|
takeUntilDestroyed(this.destroyRef) |
||||
|
).subscribe(() => { |
||||
|
this.timeInputForm.get('time').updateValueAndValidity({onlySelf: true}); |
||||
|
this.timeInputForm.get('time').markAsTouched({onlySelf: true}); |
||||
|
}); |
||||
|
|
||||
|
this.timeInputForm.valueChanges.pipe( |
||||
|
takeUntilDestroyed(this.destroyRef) |
||||
|
).subscribe(value => { |
||||
|
this.updatedModel(value); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any) { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(_fn: any) { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean) { |
||||
|
if (isDisabled) { |
||||
|
this.timeInputForm.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.timeInputForm.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writeValue(sec: number) { |
||||
|
if (sec !== this.modelValue) { |
||||
|
if (isDefinedAndNotNull(sec) && isNumeric(sec) && Number(sec) !== 0) { |
||||
|
this.timeInputForm.patchValue(this.parseTime(sec), {emitEvent: false}); |
||||
|
this.modelValue = sec; |
||||
|
} else { |
||||
|
this.timeInputForm.patchValue({ |
||||
|
time: 0, |
||||
|
timeUnit: TimeUnit.SECONDS |
||||
|
}, {emitEvent: false}); |
||||
|
this.modelValue = 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
validate(): ValidationErrors | null { |
||||
|
return this.timeInputForm.valid ? null : { |
||||
|
timeInput: false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private updatedModel(value: Partial<TimeUnitInputModel>) { |
||||
|
const time = value.time * this.timeIntervalsInSec.get(value.timeUnit); |
||||
|
if (this.modelValue !== time) { |
||||
|
this.modelValue = time; |
||||
|
this.propagateChange(time); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private parseTime(value: number): TimeUnitInputModel { |
||||
|
for (const [timeUnit, timeValue] of this.timeIntervalsInSec) { |
||||
|
const calc = value / timeValue; |
||||
|
if (Number.isInteger(calc)) { |
||||
|
return { |
||||
|
time: calc, |
||||
|
timeUnit: timeUnit |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue