-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(module:cron-expression): add cron-expression component (#7677)
* feat(module:cron-expression): add cron-expression component * feat(module:cron-expression): add cron-expression component i18n * feat(module:cron-expression): modify cron-expression i18.interface * feat(module:cron-expression): detail modification * feat(module:cron-expression): cron-expression support spring * feat(module:cron-expression): adjust the layout
- Loading branch information
1 parent
e3103f0
commit 3a638af
Showing
30 changed files
with
954 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
components/cron-expression/cron-expression-input.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/** | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE | ||
*/ | ||
|
||
import { Component, ViewEncapsulation, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core'; | ||
|
||
import { CronChangeType, TimeType } from './typings'; | ||
|
||
@Component({ | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
encapsulation: ViewEncapsulation.None, | ||
selector: 'nz-cron-expression-input', | ||
exportAs: 'nzCronExpression', | ||
template: ` | ||
<div class="ant-cron-expression-input"> | ||
<input | ||
nz-input | ||
[(ngModel)]="value" | ||
[name]="label" | ||
(focus)="focusInputEffect($event)" | ||
(blur)="blurInputEffect()" | ||
(ngModelChange)="setValue()" | ||
/> | ||
</div> | ||
` | ||
}) | ||
export class NzCronExpressionInputComponent { | ||
@Input() value: string = '0'; | ||
@Input() label: TimeType = 'second'; | ||
@Output() readonly focusEffect = new EventEmitter<TimeType>(); | ||
@Output() readonly blurEffect = new EventEmitter<void>(); | ||
@Output() readonly getValue = new EventEmitter<CronChangeType>(); | ||
|
||
constructor() {} | ||
|
||
focusInputEffect(event: FocusEvent): void { | ||
this.focusEffect.emit(this.label); | ||
(event.target as HTMLInputElement).select(); | ||
} | ||
|
||
blurInputEffect(): void { | ||
this.blurEffect.emit(); | ||
} | ||
|
||
setValue(): void { | ||
this.getValue.emit({ label: this.label, value: this.value }); | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
components/cron-expression/cron-expression-label.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/** | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE | ||
*/ | ||
|
||
import { Component, ViewEncapsulation, ChangeDetectionStrategy, Input, OnInit } from '@angular/core'; | ||
|
||
import { NzCronExpressionLabelI18n } from 'ng-zorro-antd/i18n'; | ||
|
||
import { TimeType, TimeTypeError } from './typings'; | ||
|
||
@Component({ | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
encapsulation: ViewEncapsulation.None, | ||
selector: 'nz-cron-expression-label', | ||
exportAs: 'nzCronExpression', | ||
template: ` | ||
<div | ||
class="ant-cron-expression-label" | ||
[class.ant-cron-expression-label-foucs]="labelFocus === type" | ||
[class.ant-cron-expression-error]="!valid" | ||
> | ||
<label nz-tooltip [nzTooltipTitle]="error" [nzTooltipVisible]="!valid" nzTooltipPlacement="bottom"> | ||
{{ locale[type] }} | ||
</label> | ||
</div> | ||
<ng-template #error> | ||
<div class="ant-cron-expression-hint" [innerHTML]="locale[labelError]"></div> | ||
</ng-template> | ||
` | ||
}) | ||
export class NzCronExpressionLabelComponent implements OnInit { | ||
@Input() type: TimeType = 'second'; | ||
@Input() valid: boolean = true; | ||
@Input() locale!: NzCronExpressionLabelI18n; | ||
@Input() labelFocus: string | null = null; | ||
labelError: TimeTypeError = 'secondError'; | ||
|
||
constructor() {} | ||
|
||
ngOnInit(): void { | ||
this.labelError = `${this.type}Error`; | ||
} | ||
} |
267 changes: 267 additions & 0 deletions
267
components/cron-expression/cron-expression.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
/** | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE | ||
*/ | ||
|
||
import { | ||
ChangeDetectionStrategy, | ||
ChangeDetectorRef, | ||
Component, | ||
forwardRef, | ||
Input, | ||
OnDestroy, | ||
OnInit, | ||
TemplateRef, | ||
ViewEncapsulation | ||
} from '@angular/core'; | ||
import { | ||
AsyncValidator, | ||
ControlValueAccessor, | ||
FormControl, | ||
NG_ASYNC_VALIDATORS, | ||
NG_VALUE_ACCESSOR, | ||
UntypedFormBuilder, | ||
UntypedFormGroup, | ||
ValidationErrors, | ||
Validators | ||
} from '@angular/forms'; | ||
import { Observable, of, Subject } from 'rxjs'; | ||
import { takeUntil } from 'rxjs/operators'; | ||
|
||
import { CronExpression, parseExpression } from 'cron-parser'; | ||
|
||
import { NzSafeAny } from 'ng-zorro-antd/core/types'; | ||
import { InputBoolean } from 'ng-zorro-antd/core/util'; | ||
import { NzCronExpressionI18nInterface, NzI18nService } from 'ng-zorro-antd/i18n'; | ||
|
||
import { CronChangeType, CronType, NzCronExpressionSize, TimeType } from './typings'; | ||
|
||
@Component({ | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
encapsulation: ViewEncapsulation.None, | ||
selector: 'nz-cron-expression', | ||
exportAs: 'nzCronExpression', | ||
template: ` | ||
<div class="ant-cron-expression"> | ||
<div class="ant-cron-expression-content"> | ||
<div | ||
class="ant-cron-expression-input-group" | ||
[class.ant-cron-expression-input-group-lg]="nzSize === 'large'" | ||
[class.ant-cron-expression-input-group-sm]="nzSize === 'small'" | ||
[class.ant-cron-expression-input-group-focus]="focus" | ||
[class.ant-cron-expression-input-group-error]="!validateForm.valid" | ||
[class.ant-cron-expression-input-group-error-focus]="!validateForm.valid && focus" | ||
> | ||
<ng-container *ngFor="let label of labels"> | ||
<nz-cron-expression-input | ||
[value]="this.validateForm.controls[label].value" | ||
[label]="label" | ||
(focusEffect)="focusEffect($event)" | ||
(blurEffect)="blurEffect()" | ||
(getValue)="getValue($event)" | ||
></nz-cron-expression-input> | ||
</ng-container> | ||
</div> | ||
<div class="ant-cron-expression-label-group"> | ||
<ng-container *ngFor="let label of labels"> | ||
<nz-cron-expression-label | ||
[type]="label" | ||
[valid]="this.validateForm.controls[label].valid" | ||
[labelFocus]="labelFocus" | ||
[locale]="locale" | ||
></nz-cron-expression-label> | ||
</ng-container> | ||
</div> | ||
<nz-collapse *ngIf="!nzCollapseDisable" [nzBordered]="false"> | ||
<nz-collapse-panel [nzHeader]="nextDate"> | ||
<ng-container *ngIf="validateForm.valid"> | ||
<ul class="ant-cron-expression-preview-date"> | ||
<li style="margin: 0" *ngFor="let dateItem of nextTimeList"> | ||
{{ dateItem | date: 'YYYY-MM-dd HH:mm:ss' }} | ||
</li> | ||
<li><a (click)="loadMorePreview()">···</a></li> | ||
</ul> | ||
</ng-container> | ||
<ng-container *ngIf="!validateForm.valid">{{ locale.cronError }}</ng-container> | ||
</nz-collapse-panel> | ||
</nz-collapse> | ||
</div> | ||
<div class="ant-cron-expression-map" *ngIf="nzExtra"> | ||
<ng-template [ngTemplateOutlet]="nzExtra"></ng-template> | ||
</div> | ||
<ng-template #nextDate> | ||
<ng-container *ngIf="validateForm.valid"> | ||
{{ dateTime | date: 'YYYY-MM-dd HH:mm:ss' }} | ||
</ng-container> | ||
<ng-container *ngIf="!validateForm.valid">{{ locale.cronError }}</ng-container> | ||
</ng-template> | ||
</div> | ||
`, | ||
providers: [ | ||
{ | ||
provide: NG_ASYNC_VALIDATORS, | ||
useExisting: forwardRef(() => NzCronExpressionComponent), | ||
multi: true | ||
}, | ||
{ | ||
provide: NG_VALUE_ACCESSOR, | ||
useExisting: forwardRef(() => NzCronExpressionComponent), | ||
multi: true | ||
} | ||
] | ||
}) | ||
export class NzCronExpressionComponent implements OnInit, ControlValueAccessor, AsyncValidator, OnDestroy { | ||
@Input() nzSize: NzCronExpressionSize = 'default'; | ||
@Input() nzType: 'linux' | 'spring' = 'linux'; | ||
@Input() @InputBoolean() nzCollapseDisable: boolean = false; | ||
@Input() nzExtra?: TemplateRef<void> | null = null; | ||
|
||
locale!: NzCronExpressionI18nInterface; | ||
focus: boolean = false; | ||
labelFocus: TimeType | null = null; | ||
validLabel: string | null = null; | ||
labels: TimeType[] = []; | ||
interval!: CronExpression<false>; | ||
nextTimeList: Date[] = []; | ||
dateTime: Date = new Date(); | ||
private destroy$ = new Subject<void>(); | ||
|
||
validateForm!: UntypedFormGroup; | ||
|
||
onChange: NzSafeAny = () => {}; | ||
onTouch: () => void = () => null; | ||
|
||
convertFormat(value: string): void { | ||
const values = value.split(' '); | ||
const valueObject: CronType = {}; | ||
this.labels.map((a, b) => { | ||
valueObject[a] = values[b]; | ||
}); | ||
this.validateForm.patchValue(valueObject); | ||
} | ||
|
||
writeValue(value: string | null): void { | ||
if (value) { | ||
this.convertFormat(value); | ||
} | ||
} | ||
|
||
registerOnChange(fn: NzSafeAny): void { | ||
this.onChange = fn; | ||
} | ||
|
||
registerOnTouched(fn: NzSafeAny): void { | ||
this.onTouch = fn; | ||
} | ||
|
||
validate(): Observable<ValidationErrors | null> { | ||
if (this.validateForm.valid) { | ||
return of(null); | ||
} else { | ||
return of({ error: true }); | ||
} | ||
} | ||
|
||
constructor(private formBuilder: UntypedFormBuilder, private cdr: ChangeDetectorRef, private i18n: NzI18nService) {} | ||
|
||
ngOnInit(): void { | ||
if (this.nzType === 'spring') { | ||
this.labels = ['second', 'minute', 'hour', 'day', 'month', 'week']; | ||
this.validateForm = this.formBuilder.group({ | ||
second: ['0', Validators.required, this.checkValid], | ||
minute: ['*', Validators.required, this.checkValid], | ||
hour: ['*', Validators.required, this.checkValid], | ||
day: ['*', Validators.required, this.checkValid], | ||
month: ['*', Validators.required, this.checkValid], | ||
week: ['*', Validators.required, this.checkValid] | ||
}); | ||
} else { | ||
this.labels = ['minute', 'hour', 'day', 'month', 'week']; | ||
this.validateForm = this.formBuilder.group({ | ||
minute: ['*', Validators.required, this.checkValid], | ||
hour: ['*', Validators.required, this.checkValid], | ||
day: ['*', Validators.required, this.checkValid], | ||
month: ['*', Validators.required, this.checkValid], | ||
week: ['*', Validators.required, this.checkValid] | ||
}); | ||
} | ||
this.i18n.localeChange.pipe(takeUntil(this.destroy$)).subscribe(() => { | ||
this.locale = this.i18n.getLocaleData('CronExpression'); | ||
this.cdr.markForCheck(); | ||
}); | ||
|
||
this.previewDate(this.validateForm.value); | ||
|
||
this.validateForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(value => { | ||
this.onChange(Object.values(value).join(' ')); | ||
this.previewDate(value); | ||
this.cdr.markForCheck(); | ||
}); | ||
} | ||
|
||
previewDate(value: CronType): void { | ||
try { | ||
this.interval = parseExpression(Object.values(value).join(' ')); | ||
this.dateTime = this.interval.next().toDate(); | ||
this.nextTimeList = [ | ||
this.interval.next().toDate(), | ||
this.interval.next().toDate(), | ||
this.interval.next().toDate(), | ||
this.interval.next().toDate(), | ||
this.interval.next().toDate() | ||
]; | ||
} catch (err: NzSafeAny) { | ||
return; | ||
} | ||
} | ||
|
||
loadMorePreview(): void { | ||
this.nextTimeList = [ | ||
...this.nextTimeList, | ||
this.interval.next().toDate(), | ||
this.interval.next().toDate(), | ||
this.interval.next().toDate(), | ||
this.interval.next().toDate(), | ||
this.interval.next().toDate() | ||
]; | ||
this.cdr.markForCheck(); | ||
} | ||
|
||
focusEffect(value: TimeType): void { | ||
this.focus = true; | ||
this.labelFocus = value; | ||
this.cdr.markForCheck(); | ||
} | ||
|
||
blurEffect(): void { | ||
this.focus = false; | ||
this.labelFocus = null; | ||
this.cdr.markForCheck(); | ||
} | ||
|
||
getValue(item: CronChangeType): void { | ||
this.validLabel = item.label; | ||
this.validateForm.controls[item.label].patchValue(item.value); | ||
this.cdr.markForCheck(); | ||
} | ||
|
||
checkValid = (control: FormControl): Observable<ValidationErrors | null> => { | ||
if (control.value) { | ||
try { | ||
const cron: string[] = []; | ||
this.labels.forEach(label => { | ||
label === this.validLabel ? cron.push(control.value) : cron.push('*'); | ||
}); | ||
parseExpression(cron.join(' ')); | ||
} catch (err: unknown) { | ||
return of({ error: true }); | ||
} | ||
} | ||
return of(null); | ||
}; | ||
|
||
ngOnDestroy(): void { | ||
this.destroy$.next(); | ||
this.destroy$.complete(); | ||
} | ||
} |
Oops, something went wrong.