mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
implement select and multiselect for config
This commit is contained in:
committed by
Aiden McClelland
parent
a657c332b1
commit
4a6a3da36c
@@ -5,7 +5,6 @@ import { IonicModule } from '@ionic/angular'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
import { TuiElasticContainerModule } from '@taiga-ui/kit'
|
||||
import { TuiExpandModule } from '@taiga-ui/core'
|
||||
import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module'
|
||||
import { FormLabelComponent } from './form-label/form-label.component'
|
||||
import { FormObjectComponent } from './form-object/form-object.component'
|
||||
import { FormUnionComponent } from './form-union/form-union.component'
|
||||
@@ -13,15 +12,13 @@ import {
|
||||
GetErrorPipe,
|
||||
ToWarningTextPipe,
|
||||
ToElementIdPipe,
|
||||
ToEnumListDisplayPipe,
|
||||
ToRangePipe,
|
||||
} from './form-object.pipes'
|
||||
import { FormFileComponent } from './form-object/controls/form-file/form-file.component'
|
||||
import { FormInputComponent } from './form-object/controls/form-input/form-input.component'
|
||||
import { FormWarningDirective } from './form-warning.directive'
|
||||
import { FormSubformComponent } from './form-object/controls/form-subform/form-subform.component'
|
||||
import { FormEnumComponent } from './form-object/controls/form-enum/form-enum.component'
|
||||
import { FormValueComponent } from './form-object/controls/form-value/form-value.component'
|
||||
import { FormSelectComponent } from './form-object/controls/form-select/form-select.component'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -30,15 +27,13 @@ import { FormValueComponent } from './form-object/controls/form-value/form-value
|
||||
FormLabelComponent,
|
||||
ToWarningTextPipe,
|
||||
GetErrorPipe,
|
||||
ToEnumListDisplayPipe,
|
||||
ToElementIdPipe,
|
||||
ToRangePipe,
|
||||
FormWarningDirective,
|
||||
FormFileComponent,
|
||||
FormInputComponent,
|
||||
FormSubformComponent,
|
||||
FormEnumComponent,
|
||||
FormValueComponent,
|
||||
FormSelectComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -46,7 +41,6 @@ import { FormValueComponent } from './form-object/controls/form-value/form-value
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
SharedPipesModule,
|
||||
EnumListPageModule,
|
||||
TuiElasticContainerModule,
|
||||
TuiExpandModule,
|
||||
],
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import {
|
||||
AbstractControl,
|
||||
FormGroup,
|
||||
UntypedFormArray,
|
||||
ValidationErrors,
|
||||
} from '@angular/forms'
|
||||
import { ValidationErrors } from '@angular/forms'
|
||||
import { IonicSafeString } from '@ionic/angular'
|
||||
import { ListValueSpecOf } from 'start-sdk/types/config-types'
|
||||
import { Range } from 'src/app/util/config-utilities'
|
||||
import { getElementId } from './form-object/form-object.component'
|
||||
|
||||
@@ -40,15 +34,6 @@ export class GetErrorPipe implements PipeTransform {
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'toEnumListDisplay',
|
||||
})
|
||||
export class ToEnumListDisplayPipe implements PipeTransform {
|
||||
transform(arr: string[], spec: ListValueSpecOf<'enum'>): string {
|
||||
return arr.map((v: string) => spec['value-names'][v]).join(', ')
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'toWarningText',
|
||||
})
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<form-label
|
||||
class="label"
|
||||
[data]="{
|
||||
name: spec.name,
|
||||
description: spec.description,
|
||||
edited: control.dirty
|
||||
}"
|
||||
></form-label>
|
||||
<ion-item button detail="false" color="dark" (click)="edit.emit()">
|
||||
<ion-label class="list">
|
||||
<h2>{{ control.value | toEnumListDisplay : $any(spec.spec) }}</h2>
|
||||
</ion-label>
|
||||
<ion-button slot="end" fill="clear" color="light">
|
||||
<ion-icon slot="icon-only" name="chevron-down"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<p class="error-message">
|
||||
<span *ngIf="control.errors as errors">
|
||||
{{ errors | getError }}
|
||||
</span>
|
||||
</p>
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core'
|
||||
import { AbstractControl } from '@angular/forms'
|
||||
import { ValueSpecOf } from 'start-sdk/types/config-types'
|
||||
|
||||
@Component({
|
||||
selector: 'form-enum',
|
||||
templateUrl: './form-enum.component.html',
|
||||
styleUrls: ['./form-enum.component.scss'],
|
||||
})
|
||||
export class FormEnumComponent {
|
||||
@Input() spec!: ValueSpecOf<'list'>
|
||||
@Input() control!: AbstractControl
|
||||
|
||||
@Output() edit = new EventEmitter<void>()
|
||||
}
|
||||
@@ -13,12 +13,12 @@
|
||||
#warning="formWarning"
|
||||
slot="end"
|
||||
[formControl]="control"
|
||||
(ionChange)="warning.onChange(name, spec, undefined, cancel)"
|
||||
(ionChange)="warning.onChange(name, spec, undefined, cancelBool)"
|
||||
></ion-toggle>
|
||||
<!-- enum -->
|
||||
<!-- class enter-click disables the enter click on the modal behind the select -->
|
||||
<!-- select -->
|
||||
<!-- adding class enter-click disables the enter click on the modal behind the select -->
|
||||
<ion-select
|
||||
*ngIf="spec.type === 'enum'"
|
||||
*ngIf="spec.type === 'select' || spec.type === 'multiselect'"
|
||||
[interfaceOptions]="{
|
||||
header: spec.name,
|
||||
message: spec.warning | toWarningText,
|
||||
@@ -26,11 +26,21 @@
|
||||
}"
|
||||
slot="end"
|
||||
placeholder="Select"
|
||||
[multiple]="spec.type === 'multiselect'"
|
||||
[formControl]="control"
|
||||
[selectedText]="spec['value-names'][control.value]"
|
||||
[selectedText]="
|
||||
spec.type === 'multiselect' && control.value?.length > 1
|
||||
? '[' + control.value.length + ' selected]'
|
||||
: spec['value-names'][control.value]
|
||||
"
|
||||
>
|
||||
<ion-select-option *ngFor="let option of spec.values" [value]="option">
|
||||
{{ spec['value-names'][option] }}
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<p class="error-message">
|
||||
<span *ngIf="control.errors as errors">
|
||||
{{ errors | getError }}
|
||||
</span>
|
||||
</p>
|
||||
@@ -1,11 +1,3 @@
|
||||
.label {
|
||||
margin: 16px 0 6px;
|
||||
}
|
||||
|
||||
.list {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-top: 2px;
|
||||
font-size: small;
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { FormControl } from '@angular/forms'
|
||||
import { ValueSpecOf } from 'start-sdk/types/config-types'
|
||||
|
||||
@Component({
|
||||
selector: 'form-select',
|
||||
templateUrl: './form-select.component.html',
|
||||
styleUrls: ['./form-select.component.scss'],
|
||||
})
|
||||
export class FormSelectComponent {
|
||||
@Input() spec!: ValueSpecOf<'boolean' | 'select' | 'multiselect'>
|
||||
@Input() control!: FormControl
|
||||
@Input() name!: string
|
||||
|
||||
cancelBool = () => this.control.setValue(!this.control.value)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { FormControl } from '@angular/forms'
|
||||
import { ValueSpecOf } from 'start-sdk/types/config-types'
|
||||
|
||||
@Component({
|
||||
selector: 'form-value',
|
||||
templateUrl: './form-value.component.html',
|
||||
})
|
||||
export class FormValueComponent {
|
||||
@Input() spec!: ValueSpecOf<'boolean' | 'enum'>
|
||||
@Input() control!: FormControl
|
||||
@Input() name!: string
|
||||
|
||||
cancel = () => this.control.setValue(!this.control.value)
|
||||
}
|
||||
@@ -15,13 +15,17 @@
|
||||
[control]="$any(entry.value)"
|
||||
(onInputChange)="handleInputChange()"
|
||||
></form-input>
|
||||
<!-- boolean or enum -->
|
||||
<form-value
|
||||
*ngIf="spec.type === 'boolean' || spec.type === 'enum'"
|
||||
<!-- boolean, select or multiselect -->
|
||||
<form-select
|
||||
*ngIf="
|
||||
spec.type === 'boolean' ||
|
||||
spec.type === 'select' ||
|
||||
spec.type === 'multiselect'
|
||||
"
|
||||
[spec]="spec"
|
||||
[name]="entry.key"
|
||||
[control]="$any(entry.value)"
|
||||
></form-value>
|
||||
></form-select>
|
||||
<!-- object -->
|
||||
<form-subform
|
||||
*ngIf="spec.type === 'object'"
|
||||
@@ -46,8 +50,8 @@
|
||||
[current]="current?.[entry.key]"
|
||||
[original]="original?.[entry.key]"
|
||||
></form-union>
|
||||
<!-- list (not enum) -->
|
||||
<ng-container *ngIf="spec.type === 'list' && spec.subtype !== 'enum'">
|
||||
<!-- list -->
|
||||
<ng-container *ngIf="spec.type === 'list'">
|
||||
<ng-container
|
||||
*ngIf="formGroup.get(entry.key) as formArr"
|
||||
[formArrayName]="entry.key"
|
||||
@@ -195,13 +199,6 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<!-- list (enum) -->
|
||||
<form-enum
|
||||
*ngIf="spec.type === 'list' && spec.subtype === 'enum'"
|
||||
[spec]="spec"
|
||||
[control]="entry.value"
|
||||
(edit)="presentModalEnumList(entry.key, $any(spec), entry.value.value)"
|
||||
></form-enum>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ion-item-group>
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
inject,
|
||||
SimpleChanges,
|
||||
} from '@angular/core'
|
||||
import { FormArray, UntypedFormArray, UntypedFormGroup } from '@angular/forms'
|
||||
import { UntypedFormArray, UntypedFormGroup } from '@angular/forms'
|
||||
import { AlertButton, AlertController, ModalController } from '@ionic/angular'
|
||||
import {
|
||||
InputSpec,
|
||||
@@ -15,11 +15,9 @@ import {
|
||||
ValueSpec,
|
||||
ValueSpecBoolean,
|
||||
ValueSpecList,
|
||||
ValueSpecListOf,
|
||||
ValueSpecUnion,
|
||||
} from 'start-sdk/types/config-types'
|
||||
import { FormService } from 'src/app/services/form.service'
|
||||
import { EnumListPage } from 'src/app/modals/enum-list/enum-list.page'
|
||||
import { THEME, pauseFor } from '@start9labs/shared'
|
||||
import { v4 } from 'uuid'
|
||||
import { DOCUMENT } from '@angular/common'
|
||||
@@ -148,28 +146,6 @@ export class FormObjectComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async presentModalEnumList(
|
||||
key: string,
|
||||
spec: ValueSpecListOf<'enum'>,
|
||||
current: string[],
|
||||
) {
|
||||
const modal = await this.modalCtrl.create({
|
||||
componentProps: {
|
||||
key,
|
||||
spec,
|
||||
current,
|
||||
},
|
||||
component: EnumListPage,
|
||||
})
|
||||
|
||||
modal.onWillDismiss<string[]>().then(({ data }) => {
|
||||
if (!data) return
|
||||
this.updateEnumList(key, current, data)
|
||||
})
|
||||
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
async presentAlertChangeWarning<T extends ValueSpec>(
|
||||
key: string,
|
||||
spec: T extends ValueSpecUnion ? never : T,
|
||||
@@ -272,27 +248,6 @@ export class FormObjectComponent {
|
||||
})
|
||||
}
|
||||
|
||||
private updateEnumList(key: string, current: string[], updated: string[]) {
|
||||
const arr = this.formGroup.get(key) as FormArray
|
||||
|
||||
for (let i = current.length - 1; i >= 0; i--) {
|
||||
if (!updated.includes(current[i])) {
|
||||
arr.removeAt(i)
|
||||
}
|
||||
}
|
||||
|
||||
const listSpec = this.objectSpec[key] as ValueSpecList
|
||||
|
||||
updated.forEach(val => {
|
||||
if (!current.includes(val)) {
|
||||
const newItem = this.formService.getListItem(listSpec, val)!
|
||||
arr.insert(arr.length, newItem)
|
||||
}
|
||||
})
|
||||
|
||||
arr.markAsDirty()
|
||||
}
|
||||
|
||||
asIsOrder() {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { EnumListPage } from './enum-list.page'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
|
||||
@NgModule({
|
||||
declarations: [EnumListPage],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
FormsModule,
|
||||
],
|
||||
exports: [EnumListPage],
|
||||
})
|
||||
export class EnumListPageModule { }
|
||||
@@ -1,45 +0,0 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title> {{ spec.name }} </ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="dismiss()">
|
||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-item-group>
|
||||
<ion-item-divider style="padding-bottom: 6px">
|
||||
<ion-buttons slot="end">
|
||||
<ion-button fill="clear" (click)="toggleSelectAll()">
|
||||
<b>{{ selectAll ? 'Select All' : 'Deselect All' }}</b>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-item-divider>
|
||||
<ion-item *ngFor="let option of options | keyvalue : asIsOrder">
|
||||
<ion-label>{{ spec.spec['value-names'][option.key] }}</ion-label>
|
||||
<ion-checkbox
|
||||
slot="end"
|
||||
[(ngModel)]="option.value"
|
||||
(click)="toggleSelected(option.key)"
|
||||
></ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-content>
|
||||
|
||||
<ion-footer>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="end" class="ion-padding-end">
|
||||
<ion-button
|
||||
fill="solid"
|
||||
color="primary"
|
||||
(click)="save()"
|
||||
class="enter-click btn-128"
|
||||
>
|
||||
Done
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
@@ -1,50 +0,0 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { ModalController } from '@ionic/angular'
|
||||
import { ValueSpecListOf } from 'start-sdk/types/config-types'
|
||||
|
||||
@Component({
|
||||
selector: 'enum-list',
|
||||
templateUrl: './enum-list.page.html',
|
||||
styleUrls: ['./enum-list.page.scss'],
|
||||
})
|
||||
export class EnumListPage {
|
||||
@Input() key!: string
|
||||
@Input() spec!: ValueSpecListOf<'enum'>
|
||||
@Input() current: string[] = []
|
||||
|
||||
options: { [option: string]: boolean } = {}
|
||||
selectAll = false
|
||||
|
||||
constructor(private readonly modalCtrl: ModalController) {}
|
||||
|
||||
ngOnInit() {
|
||||
for (let val of this.spec.spec.values || []) {
|
||||
this.options[val] = this.current.includes(val)
|
||||
}
|
||||
// if none are selected, set selectAll to true
|
||||
this.selectAll = Object.values(this.options).some(k => !k)
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
this.modalCtrl.dismiss()
|
||||
}
|
||||
|
||||
save() {
|
||||
this.modalCtrl.dismiss(
|
||||
Object.keys(this.options).filter(key => this.options[key]),
|
||||
)
|
||||
}
|
||||
|
||||
toggleSelectAll() {
|
||||
Object.keys(this.options).forEach(k => (this.options[k] = this.selectAll))
|
||||
this.selectAll = !this.selectAll
|
||||
}
|
||||
|
||||
toggleSelected(key: string) {
|
||||
this.options[key] = !this.options[key]
|
||||
}
|
||||
|
||||
asIsOrder() {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -253,8 +253,8 @@ const SAMPLE_CONFIG: InputSpec = {
|
||||
default: true,
|
||||
warning: null,
|
||||
},
|
||||
'sample-enum': {
|
||||
type: 'enum',
|
||||
'sample-select': {
|
||||
type: 'multiselect',
|
||||
name: 'Example Enum Select',
|
||||
values: ['red', 'blue', 'green'],
|
||||
'value-names': {
|
||||
@@ -263,8 +263,9 @@ const SAMPLE_CONFIG: InputSpec = {
|
||||
green: 'Green',
|
||||
},
|
||||
// optional
|
||||
warning: 'Example warning to display when changing this enum value.',
|
||||
description: 'Example description for enum select',
|
||||
default: 'red',
|
||||
warning: 'Example warning to display when changing this select value.',
|
||||
description: 'Example description for select select',
|
||||
range: '[0, 2)',
|
||||
default: ['red'],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
|
||||
warning: null,
|
||||
},
|
||||
license: {
|
||||
type: 'enum',
|
||||
type: 'select',
|
||||
name: 'License',
|
||||
warning: null,
|
||||
values: [
|
||||
@@ -135,7 +135,7 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
|
||||
'the-unlicense': 'The Unlicense',
|
||||
custom: 'Custom',
|
||||
},
|
||||
description: 'Example description for enum select',
|
||||
description: 'Example description for select',
|
||||
default: 'mit',
|
||||
},
|
||||
'wrapper-repo': {
|
||||
|
||||
@@ -963,7 +963,7 @@ export module Mock {
|
||||
},
|
||||
'favorite-flower': {
|
||||
name: 'Favorite Flower',
|
||||
type: 'enum',
|
||||
type: 'select',
|
||||
description: 'Select your favorite flower',
|
||||
warning: null,
|
||||
'value-names': {
|
||||
@@ -989,19 +989,34 @@ export module Mock {
|
||||
'unique-by': 'preference',
|
||||
},
|
||||
},
|
||||
'random-enum': {
|
||||
name: 'Random Enum',
|
||||
type: 'enum',
|
||||
'random-select': {
|
||||
name: 'Random Select',
|
||||
type: 'select',
|
||||
'value-names': {
|
||||
null: 'Null',
|
||||
option1: 'One 1',
|
||||
option2: 'Two 2',
|
||||
option3: 'Three 3',
|
||||
hello: 'Hello',
|
||||
goodbye: 'Goodbye',
|
||||
sup: 'Sup',
|
||||
},
|
||||
default: 'null',
|
||||
default: 'sup',
|
||||
description: 'This is not even real.',
|
||||
warning: 'Be careful changing this!',
|
||||
values: ['null', 'option1', 'option2', 'option3'],
|
||||
values: ['hello', 'goodbye', 'sup'],
|
||||
},
|
||||
notifications: {
|
||||
name: 'Notification Preferences',
|
||||
type: 'multiselect',
|
||||
description: 'how you want to be notified',
|
||||
warning: null,
|
||||
range: '(1,3]',
|
||||
'value-names': {
|
||||
email: 'EEEEmail',
|
||||
text: 'Texxxt',
|
||||
call: 'Ccccall',
|
||||
push: 'PuuuusH',
|
||||
webhook: 'WebHooookkeee',
|
||||
},
|
||||
values: ['email', 'text', 'call', 'push', 'webhook'],
|
||||
default: ['email'],
|
||||
},
|
||||
'favorite-number': {
|
||||
name: 'Favorite Number',
|
||||
@@ -1292,25 +1307,6 @@ export module Mock {
|
||||
description: 'Advanced settings',
|
||||
warning: null,
|
||||
spec: {
|
||||
notifications: {
|
||||
name: 'Notification Preferences',
|
||||
type: 'list',
|
||||
subtype: 'enum',
|
||||
description: 'how you want to be notified',
|
||||
warning: null,
|
||||
range: '[1,3]',
|
||||
default: ['email'],
|
||||
spec: {
|
||||
'value-names': {
|
||||
email: 'EEEEmail',
|
||||
text: 'Texxxt',
|
||||
call: 'Ccccall',
|
||||
push: 'PuuuusH',
|
||||
webhook: 'WebHooookkeee',
|
||||
},
|
||||
values: ['email', 'text', 'call', 'push', 'webhook'],
|
||||
},
|
||||
},
|
||||
rpcsettings: {
|
||||
name: 'RPC Settings',
|
||||
type: 'object',
|
||||
@@ -1525,7 +1521,7 @@ export module Mock {
|
||||
},
|
||||
],
|
||||
'union-list': undefined,
|
||||
'random-enum': 'option2',
|
||||
'random-select': ['goodbye'],
|
||||
'favorite-number': 0,
|
||||
rpcsettings: {
|
||||
laws: {
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
ListValueSpecUnion,
|
||||
UniqueBy,
|
||||
ValueSpec,
|
||||
ValueSpecEnum,
|
||||
ValueSpecSelect,
|
||||
ValueSpecMultiselect,
|
||||
ValueSpecFile,
|
||||
ValueSpecList,
|
||||
ValueSpecNumber,
|
||||
@@ -49,8 +50,8 @@ export class FormService {
|
||||
const { variants, tag } = spec
|
||||
const { name, description, warning, 'variant-names': variantNames } = tag
|
||||
|
||||
const enumSpec: ValueSpecEnum = {
|
||||
type: 'enum',
|
||||
const enumSpec: ValueSpecSelect = {
|
||||
type: 'select',
|
||||
name,
|
||||
description,
|
||||
warning,
|
||||
@@ -71,8 +72,6 @@ export class FormService {
|
||||
return this.formBuilder.control(entry, listItemValidators)
|
||||
} else if (isValueSpecListOf(spec, 'number')) {
|
||||
return this.formBuilder.control(entry, listItemValidators)
|
||||
} else if (isValueSpecListOf(spec, 'enum')) {
|
||||
return this.formBuilder.control(entry)
|
||||
} else if (isValueSpecListOf(spec, 'object')) {
|
||||
return this.getFormGroup(spec.spec.spec, listItemValidators, entry)
|
||||
} else if (isValueSpecListOf(spec, 'union')) {
|
||||
@@ -99,7 +98,6 @@ export class FormService {
|
||||
spec: ValueSpec,
|
||||
currentValue?: any,
|
||||
): UntypedFormGroup | UntypedFormArray | UntypedFormControl {
|
||||
let validators: ValidatorFn[]
|
||||
let value: any
|
||||
switch (spec.type) {
|
||||
case 'string':
|
||||
@@ -140,9 +138,12 @@ export class FormService {
|
||||
isValid ? currentValue : undefined,
|
||||
)
|
||||
case 'boolean':
|
||||
case 'enum':
|
||||
case 'select':
|
||||
value = currentValue === undefined ? spec.default : currentValue
|
||||
return this.formBuilder.control(value)
|
||||
case 'multiselect':
|
||||
value = currentValue === undefined ? spec.default : currentValue
|
||||
return this.formBuilder.control(value, multiselectValidators(spec))
|
||||
default:
|
||||
return this.formBuilder.control(null)
|
||||
}
|
||||
@@ -193,17 +194,16 @@ function numberValidators(
|
||||
return validators
|
||||
}
|
||||
|
||||
function multiselectValidators(spec: ValueSpecMultiselect): ValidatorFn[] {
|
||||
const validators: ValidatorFn[] = []
|
||||
validators.push(listInRange(spec.range))
|
||||
return validators
|
||||
}
|
||||
|
||||
function listValidators(spec: ValueSpecList): ValidatorFn[] {
|
||||
const validators: ValidatorFn[] = []
|
||||
|
||||
validators.push(listInRange(spec.range))
|
||||
|
||||
validators.push(listItemIssue())
|
||||
|
||||
if (!isValueSpecListOf(spec, 'enum')) {
|
||||
validators.push(listUnique(spec))
|
||||
}
|
||||
|
||||
return validators
|
||||
}
|
||||
|
||||
@@ -314,7 +314,6 @@ function listItemEquals(spec: ValueSpecList, val1: any, val2: any): boolean {
|
||||
switch (spec.subtype) {
|
||||
case 'string':
|
||||
case 'number':
|
||||
case 'enum':
|
||||
return val1 == val2
|
||||
case 'object':
|
||||
const obj: ListValueSpecObject = spec.spec as any
|
||||
@@ -334,7 +333,7 @@ function itemEquals(spec: ValueSpec, val1: any, val2: any): boolean {
|
||||
case 'string':
|
||||
case 'number':
|
||||
case 'boolean':
|
||||
case 'enum':
|
||||
case 'select':
|
||||
return val1 == val2
|
||||
case 'object':
|
||||
// TODO: 'unique-by' does not exist on ValueSpecObject, fix types
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ValueSpec, DefaultString } from 'start-sdk/types/config-types'
|
||||
import { DefaultString } from 'start-sdk/types/config-types'
|
||||
|
||||
export class Range {
|
||||
min?: number
|
||||
@@ -31,95 +31,21 @@ export class Range {
|
||||
}
|
||||
}
|
||||
|
||||
hasMin(): this is Range & { min: number } {
|
||||
private hasMin(): this is Range & { min: number } {
|
||||
return this.min !== undefined
|
||||
}
|
||||
|
||||
hasMax(): this is Range & { max: number } {
|
||||
private hasMax(): this is Range & { max: number } {
|
||||
return this.max !== undefined
|
||||
}
|
||||
|
||||
minMessage(): string {
|
||||
private minMessage(): string {
|
||||
return `greater than${this.minInclusive ? ' or equal to' : ''} ${this.min}`
|
||||
}
|
||||
|
||||
maxMessage(): string {
|
||||
private maxMessage(): string {
|
||||
return `less than${this.maxInclusive ? ' or equal to' : ''} ${this.max}`
|
||||
}
|
||||
|
||||
description(): string {
|
||||
let message = 'Value can be any number.'
|
||||
|
||||
if (this.hasMin() || this.hasMax()) {
|
||||
message = 'Value must be'
|
||||
}
|
||||
|
||||
if (this.hasMin() && this.hasMax()) {
|
||||
message = `${message} ${this.minMessage()} AND ${this.maxMessage()}.`
|
||||
} else if (this.hasMin() && !this.hasMax()) {
|
||||
message = `${message} ${this.minMessage()}.`
|
||||
} else if (!this.hasMin() && this.hasMax()) {
|
||||
message = `${message} ${this.maxMessage()}.`
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
integralMin(): number | undefined {
|
||||
if (this.min) {
|
||||
const ceil = Math.ceil(this.min)
|
||||
if (this.minInclusive) {
|
||||
return ceil
|
||||
} else {
|
||||
if (ceil === this.min) {
|
||||
return ceil + 1
|
||||
} else {
|
||||
return ceil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
integralMax(): number | undefined {
|
||||
if (this.max) {
|
||||
const floor = Math.floor(this.max)
|
||||
if (this.maxInclusive) {
|
||||
return floor
|
||||
} else {
|
||||
if (floor === this.max) {
|
||||
return floor - 1
|
||||
} else {
|
||||
return floor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultDescription(spec: ValueSpec): string {
|
||||
let toReturn: string | undefined
|
||||
switch (spec.type) {
|
||||
case 'string':
|
||||
if (typeof spec.default === 'string') {
|
||||
toReturn = spec.default
|
||||
} else if (typeof spec.default === 'object') {
|
||||
toReturn = 'random'
|
||||
}
|
||||
break
|
||||
case 'number':
|
||||
if (typeof spec.default === 'number') {
|
||||
toReturn = String(spec.default)
|
||||
}
|
||||
break
|
||||
case 'boolean':
|
||||
toReturn = spec.default === true ? 'True' : 'False'
|
||||
break
|
||||
case 'enum':
|
||||
toReturn = spec['value-names'][spec.default]
|
||||
break
|
||||
}
|
||||
|
||||
return toReturn || ''
|
||||
}
|
||||
|
||||
export function getDefaultString(defaultSpec: DefaultString): string {
|
||||
@@ -136,7 +62,7 @@ export function getDefaultString(defaultSpec: DefaultString): string {
|
||||
}
|
||||
|
||||
// a,g,h,A-Z,,,,-
|
||||
export function getRandomCharInSet(charset: string): string {
|
||||
function getRandomCharInSet(charset: string): string {
|
||||
const set = stringToCharSet(charset)
|
||||
let charIdx = Math.floor(Math.random() * set.len)
|
||||
for (let range of set.ranges) {
|
||||
|
||||
Reference in New Issue
Block a user