feat: add datetime spec support (#2264)

This commit is contained in:
Alex Inkin
2023-05-06 01:06:10 +04:00
committed by Aiden McClelland
parent 273b5768c4
commit f6c09109ba
12 changed files with 303 additions and 43 deletions

View File

@@ -2,10 +2,15 @@ import { APP_INITIALIZER, Provider } from '@angular/core'
import { UntypedFormBuilder } from '@angular/forms'
import { Router, RouteReuseStrategy } from '@angular/router'
import { IonicRouteStrategy, IonNav } from '@ionic/angular'
import { TUI_DATE_FORMAT, TUI_DATE_SEPARATOR } from '@taiga-ui/cdk'
import {
tuiButtonOptionsProvider,
tuiNumberFormatProvider,
} from '@taiga-ui/core'
import {
TUI_DATE_TIME_VALUE_TRANSFORMER,
TUI_DATE_VALUE_TRANSFORMER,
} from '@taiga-ui/kit'
import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared'
import { ApiService } from './services/api/embassy-api.service'
import { MockApiService } from './services/api/embassy-mock-api.service'
@@ -14,6 +19,8 @@ import { AuthService } from './services/auth.service'
import { ClientStorageService } from './services/client-storage.service'
import { FilterPackagesPipe } from '../../../marketplace/src/pipes/filter-packages.pipe'
import { ThemeSwitcherService } from './services/theme-switcher.service'
import { DateTransformerService } from './services/date-transformer.service'
import { DatetimeTransformerService } from './services/datetime-transformer.service'
const {
useMocks,
@@ -26,6 +33,22 @@ export const APP_PROVIDERS: Provider[] = [
IonNav,
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
tuiButtonOptionsProvider({ size: 'm' }),
{
provide: TUI_DATE_FORMAT,
useValue: 'MDY',
},
{
provide: TUI_DATE_SEPARATOR,
useValue: '/',
},
{
provide: TUI_DATE_VALUE_TRANSFORMER,
useClass: DateTransformerService,
},
{
provide: TUI_DATE_TIME_VALUE_TRANSFORMER,
useClass: DatetimeTransformerService,
},
{
provide: RouteReuseStrategy,
useClass: IonicRouteStrategy,

View File

@@ -1,5 +1,6 @@
<ng-container [ngSwitch]="spec.type">
<form-color *ngSwitchCase="'color'"></form-color>
<form-datetime *ngSwitchCase="'datetime'"></form-datetime>
<form-file *ngSwitchCase="'file'"></form-file>
<form-multiselect *ngSwitchCase="'multiselect'"></form-multiselect>
<form-number *ngSwitchCase="'number'"></form-number>

View File

@@ -0,0 +1,34 @@
<ng-container [ngSwitch]="spec.inputmode" [tuiHintContent]="spec.description">
<tui-input-time
*ngSwitchCase="'time'"
[pseudoInvalid]="invalid"
[ngModel]="getTime(value)"
(ngModelChange)="value = $event?.toString() || null"
(focusedChange)="onFocus($event)"
>
{{ spec.name }}
<span *ngIf="spec.required">*</span>
</tui-input-time>
<tui-input-date
*ngSwitchCase="'date'"
[pseudoInvalid]="invalid"
[min]="spec.min ? (spec.min | tuiMapper : getLimit)[0] : min"
[max]="spec.max ? (spec.max | tuiMapper : getLimit)[0] : max"
[(ngModel)]="value"
(focusedChange)="onFocus($event)"
>
{{ spec.name }}
<span *ngIf="spec.required">*</span>
</tui-input-date>
<tui-input-date-time
*ngSwitchCase="'datetime-local'"
[pseudoInvalid]="invalid"
[min]="spec.min ? (spec.min | tuiMapper : getLimit) : min"
[max]="spec.max ? (spec.max | tuiMapper : getLimit) : max"
[(ngModel)]="value"
(focusedChange)="onFocus($event)"
>
{{ spec.name }}
<span *ngIf="spec.required">*</span>
</tui-input-date-time>
</ng-container>

View File

@@ -0,0 +1,33 @@
import { Component } from '@angular/core'
import {
TUI_FIRST_DAY,
TUI_LAST_DAY,
TuiDay,
tuiPure,
TuiTime,
} from '@taiga-ui/cdk'
import { ValueSpecDatetime } from 'start-sdk/lib/config/configTypes'
import { Control } from '../control'
@Component({
selector: 'form-datetime',
templateUrl: './form-datetime.component.html',
})
export class FormDatetimeComponent extends Control<ValueSpecDatetime, string> {
readonly min = TUI_FIRST_DAY
readonly max = TUI_LAST_DAY
@tuiPure
getTime(value: string | null) {
return value ? TuiTime.fromString(value) : null
}
getLimit(limit: string): [TuiDay, TuiTime] {
return [
TuiDay.jsonParse(limit.slice(0, 10)),
limit.length === 10
? new TuiTime(0, 0)
: TuiTime.fromString(limit.slice(-5)),
]
}
}

View File

@@ -1,5 +1,9 @@
import { Provider, SkipSelf } from '@angular/core'
import { TUI_ARROW_MODE } from '@taiga-ui/kit'
import {
TUI_ARROW_MODE,
tuiInputDateOptionsProvider,
tuiInputTimeOptionsProvider,
} from '@taiga-ui/kit'
import { TUI_DEFAULT_ERROR_MESSAGE } from '@taiga-ui/core'
import { ControlContainer } from '@angular/forms'
import { identity, of } from 'rxjs'
@@ -21,4 +25,10 @@ export const FORM_GROUP_PROVIDERS: Provider[] = [
disabled: null,
},
},
tuiInputDateOptionsProvider({
nativePicker: true,
}),
tuiInputTimeOptionsProvider({
nativePicker: true,
}),
]

View File

@@ -1,7 +1,8 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { TuiValueChangesModule } from '@taiga-ui/cdk'
import { MaskitoModule } from '@maskito/angular'
import { TuiMapperPipeModule, TuiValueChangesModule } from '@taiga-ui/cdk'
import {
TuiButtonModule,
TuiErrorModule,
@@ -17,9 +18,12 @@ import {
import {
TuiElasticContainerModule,
TuiFieldErrorPipeModule,
TuiInputDateModule,
TuiInputDateTimeModule,
TuiInputFilesModule,
TuiInputModule,
TuiInputNumberModule,
TuiInputTimeModule,
TuiMultiSelectModule,
TuiPromptModule,
TuiSelectModule,
@@ -43,7 +47,7 @@ import { FormControlComponent } from './form-control/form-control.component'
import { MustachePipe } from './mustache.pipe'
import { ControlDirective } from './control.directive'
import { FormColorComponent } from './form-color/form-color.component'
import { MaskitoModule } from '@maskito/angular'
import { FormDatetimeComponent } from './form-datetime/form-datetime.component'
@NgModule({
imports: [
@@ -73,11 +77,16 @@ import { MaskitoModule } from '@maskito/angular'
MaskitoModule,
TuiSvgModule,
TuiWrapperModule,
TuiInputDateModule,
TuiInputTimeModule,
TuiInputDateTimeModule,
TuiMapperPipeModule,
],
declarations: [
FormGroupComponent,
FormControlComponent,
FormColorComponent,
FormDatetimeComponent,
FormTextComponent,
FormToggleComponent,
FormTextareaComponent,

View File

@@ -774,6 +774,50 @@ export module Mock {
required: false,
default: '#000000',
},
chronos: {
name: 'Chronos',
type: 'object',
description: 'Various time related settings',
warning: null,
spec: {
time: {
name: 'Time',
type: 'datetime',
inputmode: 'time',
description: 'Time of day',
warning: null,
required: true,
min: '12:00',
max: '16:00',
step: null,
default: '12:00',
},
date: {
name: 'Date',
type: 'datetime',
inputmode: 'date',
description: 'Just a date',
warning: null,
required: true,
min: '2023-01-01',
max: '2023-12-31',
step: null,
default: '2023-05-01',
},
datetime: {
name: 'Date and time',
type: 'datetime',
inputmode: 'datetime-local',
description: 'Both date and time',
warning: null,
required: true,
min: '2023-01-01T12:00',
max: '2023-12-31T16:00',
step: null,
default: '2023-05-01T18:30',
},
},
},
advanced: {
name: 'Advanced',
type: 'object',

View File

@@ -0,0 +1,19 @@
import { Injectable } from '@angular/core'
import { AbstractTuiValueTransformer, TuiDay } from '@taiga-ui/cdk'
type From = TuiDay | null
type To = string | null
@Injectable()
export class DateTransformerService extends AbstractTuiValueTransformer<
From,
To
> {
fromControlValue(controlValue: To): From {
return controlValue ? TuiDay.jsonParse(controlValue) : null
}
toControlValue(componentValue: From): To {
return componentValue?.toJSON() || null
}
}

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core'
import { AbstractTuiValueTransformer, TuiDay, TuiTime } from '@taiga-ui/cdk'
type From = [TuiDay | null, TuiTime | null] | null
type To = string | null
@Injectable()
export class DatetimeTransformerService extends AbstractTuiValueTransformer<
From,
To
> {
fromControlValue(controlValue: To): From {
if (!controlValue) {
return null
}
const day = TuiDay.jsonParse(controlValue.slice(0, 10))
const time =
controlValue.length === 16
? TuiTime.fromString(controlValue.slice(-5))
: null
return [day, time]
}
toControlValue(componentValue: From): To {
if (!componentValue) {
return null
}
const [day, time] = componentValue
return day?.toJSON() + (time ? `T${time.toString()}` : '')
}
}

View File

@@ -29,6 +29,7 @@ import {
ValueSpecTextarea,
unionValueKey,
ValueSpecColor,
ValueSpecDatetime,
} from 'start-sdk/lib/config/configTypes'
const Mustache = require('mustache')
@@ -131,6 +132,13 @@ export class FormService {
value = spec.default || null
}
return this.formBuilder.control(value, colorValidators(spec))
case 'datetime':
if (currentValue !== undefined) {
value = currentValue
} else {
value = spec.default || null
}
return this.formBuilder.control(value, datetimeValidators(spec))
case 'object':
return this.getFormGroup(spec.spec, [], currentValue)
case 'list':
@@ -216,6 +224,28 @@ function colorValidators({ required }: ValueSpecColor): ValidatorFn[] {
return validators
}
function datetimeValidators({
required,
min,
max,
}: ValueSpecDatetime): ValidatorFn[] {
const validators: ValidatorFn[] = []
if (required) {
validators.push(Validators.required)
}
if (min) {
validators.push(datetimeMin(min))
}
if (max) {
validators.push(datetimeMax(max))
}
return validators
}
function numberValidators(
spec: ValueSpecNumber | ListValueSpecNumber,
): ValidatorFn[] {
@@ -316,6 +346,28 @@ export function listInRange(
}
}
export function datetimeMin(min: string): ValidatorFn {
return ({ value }) => {
if (!value) return null
const date = new Date(value.length === 5 ? `2000-01-01T${value}` : value)
const minDate = new Date(min.length === 5 ? `2000-01-01T${min}` : min)
return date < minDate ? { datetimeMin: `Minimum is ${min}` } : null
}
}
export function datetimeMax(max: string): ValidatorFn {
return ({ value }) => {
if (!value) return null
const date = new Date(value.length === 5 ? `2000-01-01T${value}` : value)
const maxDate = new Date(max.length === 5 ? `2000-01-01T${max}` : max)
return date > maxDate ? { datetimeMin: `Maximum is ${max}` } : null
}
}
export function textLengthInRange(
minLength: number | null,
maxLength: number | null,