16 changed files with 848 additions and 10 deletions
@ -0,0 +1,224 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 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]="alarmScheduleForm" fxLayout="column"> |
|||
<mat-form-field class="mat-block" hideRequiredMarker floatLabel="always"> |
|||
<mat-label> </mat-label> |
|||
<mat-select formControlName="type" required placeholder="{{ 'device-profile.schedule-type' | translate }}"> |
|||
<mat-option *ngFor="let alarmScheduleType of alarmScheduleTypes" [value]="alarmScheduleType"> |
|||
{{ alarmScheduleTypeTranslate.get(alarmScheduleType) | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
<mat-error *ngIf="alarmScheduleForm.get('type').hasError('required')"> |
|||
{{ 'device-profile.schedule-type-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<div *ngIf="alarmScheduleForm.get('type').value !== alarmScheduleType.ANY_TIME"> |
|||
<tb-timezone-select |
|||
[defaultTimezone]="defaultTimezone" |
|||
required |
|||
formControlName="timezone"> |
|||
</tb-timezone-select> |
|||
<section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.SPECIFIC_TIME"> |
|||
<div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-days</div> |
|||
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap="16px" style="padding-bottom: 16px;"> |
|||
<div fxLayout="row" fxLayoutGap="16px"> |
|||
<mat-checkbox [formControl]="weeklyRepeatControl(0)"> |
|||
{{ 'device-profile.schedule-day.monday' | translate }} |
|||
</mat-checkbox> |
|||
<mat-checkbox [formControl]="weeklyRepeatControl(1)"> |
|||
{{ 'device-profile.schedule-day.tuesday' | translate }} |
|||
</mat-checkbox> |
|||
<mat-checkbox [formControl]="weeklyRepeatControl(2)"> |
|||
{{ 'device-profile.schedule-day.wednesday' | translate }} |
|||
</mat-checkbox> |
|||
<mat-checkbox [formControl]="weeklyRepeatControl(3)"> |
|||
{{ 'device-profile.schedule-day.thursday' | translate }} |
|||
</mat-checkbox> |
|||
</div> |
|||
<div fxLayout="row" fxLayoutGap="16px"> |
|||
<mat-checkbox [formControl]="weeklyRepeatControl(4)"> |
|||
{{ 'device-profile.schedule-day.friday' | translate }} |
|||
</mat-checkbox> |
|||
<mat-checkbox [formControl]="weeklyRepeatControl(5)"> |
|||
{{ 'device-profile.schedule-day.saturday' | translate }} |
|||
</mat-checkbox> |
|||
<mat-checkbox [formControl]="weeklyRepeatControl(6)"> |
|||
{{ 'device-profile.schedule-day.sunday' | translate }} |
|||
</mat-checkbox> |
|||
</div> |
|||
</div> |
|||
<div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-time</div> |
|||
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> |
|||
<mat-form-field fxFlex> |
|||
<mat-label translate>device-profile.schedule-time-from</mat-label> |
|||
<mat-datetimepicker-toggle [for]="startTimePicker" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #startTimePicker type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker"> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex> |
|||
<mat-label translate>device-profile.schedule-time-to</mat-label> |
|||
<mat-datetimepicker-toggle [for]="endTimePicker" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #endTimePicker type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker"> |
|||
</mat-form-field> |
|||
</div> |
|||
</section> |
|||
<section *ngIf="alarmScheduleForm.get('type').value === alarmScheduleType.CUSTOM"> |
|||
<div class="tb-small" style="margin-bottom: 0.5em" translate>device-profile.schedule-days</div> |
|||
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutGap.gt-sm="16px" formArrayName="items"> |
|||
<div fxLayout="column" fxFlex fxFlex.gt-sm="50"> |
|||
<div fxLayout="row" fxLayoutGap="8px" formGroupName="0" fxLayoutAlign="start center"> |
|||
<mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 0)"> |
|||
{{ 'device-profile.schedule-day.monday' | translate }} |
|||
</mat-checkbox> |
|||
<div fxLayout="row" fxLayoutGap="8px" fxFlex> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-from</mat-label> |
|||
<mat-datetimepicker-toggle [for]="startTimePicker1" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #startTimePicker1 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker1"> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-to</mat-label> |
|||
<mat-datetimepicker-toggle [for]="endTimePicker1" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #endTimePicker1 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker1"> |
|||
</mat-form-field> |
|||
</div> |
|||
</div> |
|||
<div fxLayout="row" fxLayoutGap="8px" formGroupName="1" fxLayoutAlign="start center"> |
|||
<mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 1)"> |
|||
{{ 'device-profile.schedule-day.tuesday' | translate }} |
|||
</mat-checkbox> |
|||
<div fxLayout="row" fxLayoutGap="8px" fxFlex> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-from</mat-label> |
|||
<mat-datetimepicker-toggle [for]="startTimePicker2" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #startTimePicker2 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker2"> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-to</mat-label> |
|||
<mat-datetimepicker-toggle [for]="endTimePicker2" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #endTimePicker2 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker2"> |
|||
</mat-form-field> |
|||
</div> |
|||
</div> |
|||
<div fxLayout="row" fxLayoutGap="8px" formGroupName="2" fxLayoutAlign="start center"> |
|||
<mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 2)"> |
|||
{{ 'device-profile.schedule-day.wednesday' | translate }} |
|||
</mat-checkbox> |
|||
<div fxLayout="row" fxLayoutGap="8px" fxFlex> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-from</mat-label> |
|||
<mat-datetimepicker-toggle [for]="startTimePicker3" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #startTimePicker3 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker3"> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-to</mat-label> |
|||
<mat-datetimepicker-toggle [for]="endTimePicker3" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #endTimePicker3 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker3"> |
|||
</mat-form-field> |
|||
</div> |
|||
</div> |
|||
<div fxLayout="row" fxLayoutGap="8px" formGroupName="3" fxLayoutAlign="start center"> |
|||
<mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 3)"> |
|||
{{ 'device-profile.schedule-day.thursday' | translate }} |
|||
</mat-checkbox> |
|||
<div fxLayout="row" fxLayoutGap="8px" fxFlex> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-from</mat-label> |
|||
<mat-datetimepicker-toggle [for]="startTimePicker4" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #startTimePicker4 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker4"> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-to</mat-label> |
|||
<mat-datetimepicker-toggle [for]="endTimePicker4" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #endTimePicker4 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker4"> |
|||
</mat-form-field> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div fxLayout="column" fxFlex fxFlex.gt-sm="50"> |
|||
<div fxLayout="row" fxLayoutGap="8px" formGroupName="4" fxLayoutAlign="start center"> |
|||
<mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 4)"> |
|||
{{ 'device-profile.schedule-day.friday' | translate }} |
|||
</mat-checkbox> |
|||
<div fxLayout="row" fxLayoutGap="8px" fxFlex> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-from</mat-label> |
|||
<mat-datetimepicker-toggle [for]="startTimePicker5" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #startTimePicker5 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker5"> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-to</mat-label> |
|||
<mat-datetimepicker-toggle [for]="endTimePicker5" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #endTimePicker5 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker5"> |
|||
</mat-form-field> |
|||
</div> |
|||
</div> |
|||
<div fxLayout="row" fxLayoutGap="8px" formGroupName="5" fxLayoutAlign="start center"> |
|||
<mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 5)"> |
|||
{{ 'device-profile.schedule-day.saturday' | translate }} |
|||
</mat-checkbox> |
|||
<div fxLayout="row" fxLayoutGap="8px" fxFlex> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-from</mat-label> |
|||
<mat-datetimepicker-toggle [for]="startTimePicker6" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #startTimePicker6 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker6"> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-to</mat-label> |
|||
<mat-datetimepicker-toggle [for]="endTimePicker6" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #endTimePicker6 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker6"> |
|||
</mat-form-field> |
|||
</div> |
|||
</div> |
|||
<div fxLayout="row" fxLayoutGap="8px" formGroupName="6" fxLayoutAlign="start center"> |
|||
<mat-checkbox formControlName="enabled" fxFlex="40" (change)="changeCustomScheduler($event, 6)"> |
|||
{{ 'device-profile.schedule-day.sunday' | translate }} |
|||
</mat-checkbox> |
|||
<div fxLayout="row" fxLayoutGap="8px" fxFlex> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-from</mat-label> |
|||
<mat-datetimepicker-toggle [for]="startTimePicker7" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #startTimePicker7 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="startsOn" [matDatetimepicker]="startTimePicker7"> |
|||
</mat-form-field> |
|||
<mat-form-field fxFlex="100px"> |
|||
<mat-label translate>device-profile.schedule-time-to</mat-label> |
|||
<mat-datetimepicker-toggle [for]="endTimePicker7" matPrefix></mat-datetimepicker-toggle> |
|||
<mat-datetimepicker #endTimePicker7 type="time" openOnFocus="true"></mat-datetimepicker> |
|||
<input required matInput formControlName="endsOn" [matDatetimepicker]="endTimePicker7"> |
|||
</mat-form-field> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</section> |
|||
</div> |
|||
</section> |
|||
@ -0,0 +1,259 @@ |
|||
///
|
|||
/// Copyright © 2016-2020 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, OnInit } from '@angular/core'; |
|||
import { |
|||
ControlValueAccessor, |
|||
FormArray, |
|||
FormBuilder, |
|||
FormControl, |
|||
FormGroup, |
|||
NG_VALIDATORS, |
|||
NG_VALUE_ACCESSOR, |
|||
ValidationErrors, |
|||
Validator, |
|||
Validators |
|||
} from '@angular/forms'; |
|||
import { AlarmSchedule, AlarmScheduleType, AlarmScheduleTypeTranslationMap } from '@shared/models/device.models'; |
|||
import { isDefined, isDefinedAndNotNull } from '@core/utils'; |
|||
import * as _moment from 'moment-timezone'; |
|||
import { MatCheckboxChange } from '@angular/material/checkbox'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-alarm-schedule', |
|||
templateUrl: './alarm-schedule.component.html', |
|||
providers: [{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => AlarmScheduleComponent), |
|||
multi: true |
|||
}, { |
|||
provide: NG_VALIDATORS, |
|||
useExisting: forwardRef(() => AlarmScheduleComponent), |
|||
multi: true |
|||
}] |
|||
}) |
|||
export class AlarmScheduleComponent implements ControlValueAccessor, Validator, OnInit { |
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
alarmScheduleForm: FormGroup; |
|||
|
|||
defaultTimezone = _moment.tz.guess(); |
|||
|
|||
alarmScheduleTypes = Object.keys(AlarmScheduleType); |
|||
alarmScheduleType = AlarmScheduleType; |
|||
alarmScheduleTypeTranslate = AlarmScheduleTypeTranslationMap; |
|||
|
|||
private modelValue: AlarmSchedule; |
|||
|
|||
private defaultItems = Array.from({length: 7}, (value, i) => ({ |
|||
enabled: true, |
|||
dayOfWeek: i |
|||
})); |
|||
|
|||
private propagateChange = (v: any) => { }; |
|||
|
|||
constructor(private fb: FormBuilder) { |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.alarmScheduleForm = this.fb.group({ |
|||
type: [AlarmScheduleType.ANY_TIME, Validators.required], |
|||
timezone: [null, Validators.required], |
|||
daysOfWeek: this.fb.array(new Array(7).fill(false)), |
|||
startsOn: [0, Validators.required], |
|||
endsOn: [0, Validators.required], |
|||
items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i))) |
|||
}); |
|||
this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { |
|||
this.alarmScheduleForm.reset({type, items: this.defaultItems}, {emitEvent: false}); |
|||
this.updateValidators(type, true); |
|||
this.alarmScheduleForm.updateValueAndValidity(); |
|||
}); |
|||
this.alarmScheduleForm.valueChanges.subscribe(() => { |
|||
this.updateModel(); |
|||
}); |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.alarmScheduleForm.disable({emitEvent: false}); |
|||
} else { |
|||
this.alarmScheduleForm.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: AlarmSchedule): void { |
|||
this.modelValue = value; |
|||
if (!isDefinedAndNotNull(this.modelValue)) { |
|||
this.modelValue = { |
|||
type: AlarmScheduleType.ANY_TIME |
|||
}; |
|||
} |
|||
switch (this.modelValue.type) { |
|||
case AlarmScheduleType.SPECIFIC_TIME: |
|||
let daysOfWeek = new Array(7).fill(false); |
|||
if (isDefined(this.modelValue.daysOfWeek)) { |
|||
daysOfWeek = daysOfWeek.map((item, index) => this.modelValue.daysOfWeek.indexOf(index + 1) > -1); |
|||
} |
|||
this.alarmScheduleForm.patchValue({ |
|||
type: this.modelValue.type, |
|||
timezone: this.modelValue.timezone, |
|||
daysOfWeek, |
|||
startsOn: this.timestampToTime(this.modelValue.startsOn), |
|||
endsOn: this.timestampToTime(this.modelValue.endsOn) |
|||
}, {emitEvent: false}); |
|||
break; |
|||
case AlarmScheduleType.CUSTOM: |
|||
if (this.modelValue.items) { |
|||
const alarmDays = []; |
|||
this.modelValue.items |
|||
.sort((a, b) => a.dayOfWeek - b.dayOfWeek) |
|||
.forEach((item, index) => { |
|||
if (item.enabled) { |
|||
this.itemsSchedulerForm.at(index).get('startsOn').enable({emitEvent: false}); |
|||
this.itemsSchedulerForm.at(index).get('endsOn').enable({emitEvent: false}); |
|||
} else { |
|||
this.itemsSchedulerForm.at(index).get('startsOn').disable({emitEvent: false}); |
|||
this.itemsSchedulerForm.at(index).get('endsOn').disable({emitEvent: false}); |
|||
} |
|||
alarmDays.push({ |
|||
enabled: item.enabled, |
|||
startsOn: this.timestampToTime(item.startsOn), |
|||
endsOn: this.timestampToTime(item.endsOn) |
|||
}); |
|||
}); |
|||
this.alarmScheduleForm.patchValue({ |
|||
type: this.modelValue.type, |
|||
timezone: this.modelValue.timezone, |
|||
items: alarmDays |
|||
}, {emitEvent: false}); |
|||
} |
|||
break; |
|||
default: |
|||
this.alarmScheduleForm.patchValue(this.modelValue || undefined, {emitEvent: false}); |
|||
} |
|||
this.updateValidators(this.modelValue.type); |
|||
} |
|||
|
|||
validate(control: FormGroup): ValidationErrors | null { |
|||
return this.alarmScheduleForm.valid ? null : { |
|||
alarmScheduler: { |
|||
valid: false |
|||
} |
|||
}; |
|||
} |
|||
|
|||
weeklyRepeatControl(index: number): FormControl { |
|||
return (this.alarmScheduleForm.get('daysOfWeek') as FormArray).at(index) as FormControl; |
|||
} |
|||
|
|||
private updateValidators(type: AlarmScheduleType, changedType = false){ |
|||
switch (type){ |
|||
case AlarmScheduleType.ANY_TIME: |
|||
this.alarmScheduleForm.get('timezone').disable({emitEvent: false}); |
|||
this.alarmScheduleForm.get('daysOfWeek').disable({emitEvent: false}); |
|||
this.alarmScheduleForm.get('startsOn').disable({emitEvent: false}); |
|||
this.alarmScheduleForm.get('endsOn').disable({emitEvent: false}); |
|||
this.alarmScheduleForm.get('items').disable({emitEvent: false}); |
|||
break; |
|||
case AlarmScheduleType.SPECIFIC_TIME: |
|||
this.alarmScheduleForm.get('timezone').enable({emitEvent: false}); |
|||
this.alarmScheduleForm.get('daysOfWeek').enable({emitEvent: false}); |
|||
this.alarmScheduleForm.get('startsOn').enable({emitEvent: false}); |
|||
this.alarmScheduleForm.get('endsOn').enable({emitEvent: false}); |
|||
this.alarmScheduleForm.get('items').disable({emitEvent: false}); |
|||
break; |
|||
case AlarmScheduleType.CUSTOM: |
|||
this.alarmScheduleForm.get('timezone').enable({emitEvent: false}); |
|||
this.alarmScheduleForm.get('daysOfWeek').disable({emitEvent: false}); |
|||
this.alarmScheduleForm.get('startsOn').disable({emitEvent: false}); |
|||
this.alarmScheduleForm.get('endsOn').disable({emitEvent: false}); |
|||
if (changedType) { |
|||
this.alarmScheduleForm.get('items').enable({emitEvent: false}); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
private updateModel() { |
|||
const value = this.alarmScheduleForm.value; |
|||
if (this.modelValue) { |
|||
if (isDefined(value.daysOfWeek)) { |
|||
value.daysOfWeek = value.daysOfWeek |
|||
.map((day: boolean, index: number) => day ? index + 1 : null) |
|||
.filter(day => !!day); |
|||
} |
|||
if (isDefined(value.startsOn) && value.startsOn !== 0) { |
|||
value.startsOn = this.timeToTimestamp(value.startsOn); |
|||
} |
|||
if (isDefined(value.endsOn) && value.endsOn !== 0) { |
|||
value.endsOn = this.timeToTimestamp(value.endsOn); |
|||
} |
|||
if (isDefined(value.items)){ |
|||
value.items = this.alarmScheduleForm.getRawValue().items; |
|||
value.items = value.items.map((item) => { |
|||
return { ...item, startsOn: this.timeToTimestamp(item.startsOn), endsOn: this.timeToTimestamp(item.endsOn)}; |
|||
}); |
|||
} |
|||
this.modelValue = value; |
|||
this.propagateChange(this.modelValue); |
|||
} |
|||
} |
|||
|
|||
private timeToTimestamp(date: Date | number): number { |
|||
if (typeof date === 'number' || date === null) { |
|||
return 0; |
|||
} |
|||
return _moment.utc([1970, 0, 1, date.getHours(), date.getMinutes(), date.getSeconds(), 0]).valueOf(); |
|||
} |
|||
|
|||
private timestampToTime(time = 0): Date { |
|||
return new Date(time + new Date().getTimezoneOffset() * 60 * 1000); |
|||
} |
|||
|
|||
private defaultItemsScheduler(index): FormGroup { |
|||
return this.fb.group({ |
|||
enabled: [true], |
|||
dayOfWeek: [index], |
|||
startsOn: [0, Validators.required], |
|||
endsOn: [0, Validators.required] |
|||
}); |
|||
} |
|||
|
|||
changeCustomScheduler($event: MatCheckboxChange, index: number) { |
|||
const value = $event.checked; |
|||
if (value) { |
|||
this.itemsSchedulerForm.at(index).get('startsOn').enable({emitEvent: false}); |
|||
this.itemsSchedulerForm.at(index).get('endsOn').enable(); |
|||
} else { |
|||
this.itemsSchedulerForm.at(index).get('startsOn').disable({emitEvent: false}); |
|||
this.itemsSchedulerForm.at(index).get('endsOn').disable(); |
|||
} |
|||
} |
|||
|
|||
private get itemsSchedulerForm(): FormArray { |
|||
return this.alarmScheduleForm.get('items') as FormArray; |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2020 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. |
|||
|
|||
--> |
|||
<mat-form-field [formGroup]="selectTimezoneFormGroup" fxFlex class="mat-block"> |
|||
<mat-label translate>timezone.timezone</mat-label> |
|||
<input matInput type="text" placeholder="{{ 'timezone.select-timezone' | translate }}" |
|||
#timezoneInput |
|||
formControlName="timezone" |
|||
(focusin)="onFocus()" |
|||
[required]="required" |
|||
[matAutocomplete]="timezoneAutocomplete"> |
|||
<button *ngIf="selectTimezoneFormGroup.get('timezone').value && !disabled" |
|||
type="button" style="margin-right: 1px" |
|||
matSuffix mat-button mat-icon-button aria-label="Clear" |
|||
(mousedown)="ignoreClosePanel = true" |
|||
(click)="clear()"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
<mat-autocomplete class="tb-autocomplete" |
|||
#timezoneAutocomplete="matAutocomplete" |
|||
(closed)="onPanelClosed()" |
|||
(optionSelected)="ignoreClosePanel = true" |
|||
[displayWith]="displayTimezoneFn"> |
|||
<mat-option *ngFor="let timezone of filteredTimezones | async" [value]="timezone"> |
|||
<span [innerHTML]="displayTimezoneFn(timezone) | highlight:searchText"></span> |
|||
</mat-option> |
|||
<mat-option *ngIf="!(filteredTimezones | async)?.length" [value]="null"> |
|||
<span> |
|||
{{ translate.get('timezone.no-timezones-matching', {timezone: searchText}) | async }} |
|||
</span> |
|||
</mat-option> |
|||
</mat-autocomplete> |
|||
<mat-error *ngIf="selectTimezoneFormGroup.get('timezone').hasError('required')"> |
|||
{{ 'timezone.timezone-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
@ -0,0 +1,221 @@ |
|||
///
|
|||
/// Copyright © 2016-2020 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 { AfterViewInit, Component, forwardRef, Input, NgZone, OnInit, ViewChild } from '@angular/core'; |
|||
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; |
|||
import { Observable, of } from 'rxjs'; |
|||
import { map, mergeMap, share, tap } from 'rxjs/operators'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@app/core/core.state'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
|||
import * as _moment from 'moment-timezone'; |
|||
import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; |
|||
|
|||
interface TimezoneInfo { |
|||
id: string; |
|||
name: string; |
|||
offset: string; |
|||
nOffset: number; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-timezone-select', |
|||
templateUrl: './timezone-select.component.html', |
|||
styleUrls: [], |
|||
providers: [{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => TimezoneSelectComponent), |
|||
multi: true |
|||
}] |
|||
}) |
|||
export class TimezoneSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit { |
|||
|
|||
selectTimezoneFormGroup: FormGroup; |
|||
|
|||
modelValue: string | null; |
|||
|
|||
defaultTimezoneId: string = null; |
|||
|
|||
defaultTimezoneInfo: TimezoneInfo = null; |
|||
|
|||
timezones: TimezoneInfo[] = _moment.tz.names().map((zoneName) => { |
|||
const tz = _moment.tz(zoneName); |
|||
return { |
|||
id: zoneName, |
|||
name: zoneName.replace(/_/g, ' '), |
|||
offset: `UTC${tz.format('Z')}`, |
|||
nOffset: tz.utcOffset() |
|||
} |
|||
}); |
|||
|
|||
@Input() |
|||
set defaultTimezone(timezone: string) { |
|||
if (this.defaultTimezoneId !== timezone) { |
|||
this.defaultTimezoneId = timezone; |
|||
if (this.defaultTimezoneId) { |
|||
this.defaultTimezoneInfo = |
|||
this.timezones.find((timezoneInfo) => timezoneInfo.id === this.defaultTimezoneId); |
|||
} else { |
|||
this.defaultTimezoneInfo = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private requiredValue: boolean; |
|||
get required(): boolean { |
|||
return this.requiredValue; |
|||
} |
|||
@Input() |
|||
set required(value: boolean) { |
|||
this.requiredValue = coerceBooleanProperty(value); |
|||
} |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
@ViewChild('timezoneInput', {static: true, read: MatAutocompleteTrigger}) timezoneInputTrigger: MatAutocompleteTrigger; |
|||
|
|||
filteredTimezones: Observable<Array<TimezoneInfo>>; |
|||
|
|||
searchText = ''; |
|||
|
|||
ignoreClosePanel = false; |
|||
|
|||
private dirty = false; |
|||
|
|||
private propagateChange = (v: any) => { }; |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
public translate: TranslateService, |
|||
private ngZone: NgZone, |
|||
private fb: FormBuilder) { |
|||
this.selectTimezoneFormGroup = this.fb.group({ |
|||
timezone: [null] |
|||
}); |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.filteredTimezones = this.selectTimezoneFormGroup.get('timezone').valueChanges |
|||
.pipe( |
|||
tap(value => { |
|||
let modelValue; |
|||
if (typeof value === 'string' || !value) { |
|||
modelValue = null; |
|||
} else { |
|||
modelValue = value.id; |
|||
} |
|||
this.updateView(modelValue); |
|||
if (value === null) { |
|||
this.clear(); |
|||
} |
|||
}), |
|||
map(value => value ? (typeof value === 'string' ? value : value.name) : ''), |
|||
mergeMap(name => this.fetchTimezones(name) ), |
|||
share() |
|||
); |
|||
} |
|||
|
|||
ngAfterViewInit(): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.selectTimezoneFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.selectTimezoneFormGroup.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: string | null): void { |
|||
this.searchText = ''; |
|||
let foundTimezone: TimezoneInfo = null; |
|||
if (value !== null) { |
|||
foundTimezone = this.timezones.find(timezoneInfo => timezoneInfo.id === value); |
|||
} |
|||
if (foundTimezone !== null) { |
|||
this.modelValue = value; |
|||
this.selectTimezoneFormGroup.get('timezone').patchValue(foundTimezone, {emitEvent: false}); |
|||
} else { |
|||
if (this.defaultTimezoneInfo) { |
|||
this.selectTimezoneFormGroup.get('timezone').patchValue(this.defaultTimezoneInfo, {emitEvent: false}); |
|||
setTimeout(() => { |
|||
this.updateView(this.defaultTimezoneInfo.id); |
|||
}, 0); |
|||
} else { |
|||
this.modelValue = null; |
|||
this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: false}); |
|||
} |
|||
} |
|||
this.dirty = true; |
|||
} |
|||
|
|||
onFocus() { |
|||
if (this.dirty) { |
|||
this.selectTimezoneFormGroup.get('timezone').updateValueAndValidity({onlySelf: true, emitEvent: true}); |
|||
this.dirty = false; |
|||
} |
|||
} |
|||
|
|||
onPanelClosed() { |
|||
if (this.ignoreClosePanel) { |
|||
this.ignoreClosePanel = false; |
|||
} else { |
|||
if (!this.modelValue && this.defaultTimezoneInfo) { |
|||
this.ngZone.run(() => { |
|||
this.selectTimezoneFormGroup.get('timezone').reset(this.defaultTimezoneInfo, {emitEvent: true}); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
updateView(value: string | null) { |
|||
if (this.modelValue !== value) { |
|||
this.modelValue = value; |
|||
this.propagateChange(this.modelValue); |
|||
} |
|||
} |
|||
|
|||
displayTimezoneFn(timezone?: TimezoneInfo): string | undefined { |
|||
return timezone ? `${timezone.name} (${timezone.offset})` : undefined; |
|||
} |
|||
|
|||
fetchTimezones(searchText?: string): Observable<Array<TimezoneInfo>> { |
|||
this.searchText = searchText; |
|||
let result = this.timezones; |
|||
if (searchText && searchText.length) { |
|||
result = this.timezones.filter((timezoneInfo) => |
|||
timezoneInfo.name.toLowerCase().includes(searchText.toLowerCase())); |
|||
} |
|||
return of(result); |
|||
} |
|||
|
|||
clear() { |
|||
this.selectTimezoneFormGroup.get('timezone').patchValue('', {emitEvent: true}); |
|||
setTimeout(() => { |
|||
this.timezoneInputTrigger.openPanel(); |
|||
}, 0); |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue