mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 22:39:46 +00:00
implement select and multiselect for config
This commit is contained in:
committed by
Aiden McClelland
parent
a657c332b1
commit
4a6a3da36c
8
frontend/package-lock.json
generated
8
frontend/package-lock.json
generated
@@ -49,7 +49,7 @@
|
|||||||
"patch-db-client": "file: ../../../patch-db/client",
|
"patch-db-client": "file: ../../../patch-db/client",
|
||||||
"pbkdf2": "^3.1.2",
|
"pbkdf2": "^3.1.2",
|
||||||
"rxjs": "^7.5.6",
|
"rxjs": "^7.5.6",
|
||||||
"start-sdk": "^0.4.0-lib0.alpha8",
|
"start-sdk": "^0.4.0-lib0.alpha9",
|
||||||
"swiper": "^8.2.4",
|
"swiper": "^8.2.4",
|
||||||
"ts-matches": "^5.2.1",
|
"ts-matches": "^5.2.1",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
@@ -13771,9 +13771,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/start-sdk": {
|
"node_modules/start-sdk": {
|
||||||
"version": "0.4.0-lib0.alpha8",
|
"version": "0.4.0-lib0.alpha9",
|
||||||
"resolved": "https://registry.npmjs.org/start-sdk/-/start-sdk-0.4.0-lib0.alpha8.tgz",
|
"resolved": "https://registry.npmjs.org/start-sdk/-/start-sdk-0.4.0-lib0.alpha9.tgz",
|
||||||
"integrity": "sha512-qErlv8ikV8nYqyCxxSN856dUwddGK5OOwTXk62IiJPxY3si03P1NQ3MnFch6Vx3NXiOmKeNNqw4/bj26TdUWRA==",
|
"integrity": "sha512-F+ekxjVEKgNv7SU5XCe1rn7PqbKsbqCSS7pecMxcKQmbNlic4iyo3+lIS9JuPBSyTTjJlzJFzkdaxGwf4h83mg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
"patch-db-client": "file: ../../../patch-db/client",
|
"patch-db-client": "file: ../../../patch-db/client",
|
||||||
"pbkdf2": "^3.1.2",
|
"pbkdf2": "^3.1.2",
|
||||||
"rxjs": "^7.5.6",
|
"rxjs": "^7.5.6",
|
||||||
"start-sdk": "^0.4.0-lib0.alpha8",
|
"start-sdk": "^0.4.0-lib0.alpha9",
|
||||||
"swiper": "^8.2.4",
|
"swiper": "^8.2.4",
|
||||||
"ts-matches": "^5.2.1",
|
"ts-matches": "^5.2.1",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { IonicModule } from '@ionic/angular'
|
|||||||
import { SharedPipesModule } from '@start9labs/shared'
|
import { SharedPipesModule } from '@start9labs/shared'
|
||||||
import { TuiElasticContainerModule } from '@taiga-ui/kit'
|
import { TuiElasticContainerModule } from '@taiga-ui/kit'
|
||||||
import { TuiExpandModule } from '@taiga-ui/core'
|
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 { FormLabelComponent } from './form-label/form-label.component'
|
||||||
import { FormObjectComponent } from './form-object/form-object.component'
|
import { FormObjectComponent } from './form-object/form-object.component'
|
||||||
import { FormUnionComponent } from './form-union/form-union.component'
|
import { FormUnionComponent } from './form-union/form-union.component'
|
||||||
@@ -13,15 +12,13 @@ import {
|
|||||||
GetErrorPipe,
|
GetErrorPipe,
|
||||||
ToWarningTextPipe,
|
ToWarningTextPipe,
|
||||||
ToElementIdPipe,
|
ToElementIdPipe,
|
||||||
ToEnumListDisplayPipe,
|
|
||||||
ToRangePipe,
|
ToRangePipe,
|
||||||
} from './form-object.pipes'
|
} from './form-object.pipes'
|
||||||
import { FormFileComponent } from './form-object/controls/form-file/form-file.component'
|
import { FormFileComponent } from './form-object/controls/form-file/form-file.component'
|
||||||
import { FormInputComponent } from './form-object/controls/form-input/form-input.component'
|
import { FormInputComponent } from './form-object/controls/form-input/form-input.component'
|
||||||
import { FormWarningDirective } from './form-warning.directive'
|
import { FormWarningDirective } from './form-warning.directive'
|
||||||
import { FormSubformComponent } from './form-object/controls/form-subform/form-subform.component'
|
import { FormSubformComponent } from './form-object/controls/form-subform/form-subform.component'
|
||||||
import { FormEnumComponent } from './form-object/controls/form-enum/form-enum.component'
|
import { FormSelectComponent } from './form-object/controls/form-select/form-select.component'
|
||||||
import { FormValueComponent } from './form-object/controls/form-value/form-value.component'
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@@ -30,15 +27,13 @@ import { FormValueComponent } from './form-object/controls/form-value/form-value
|
|||||||
FormLabelComponent,
|
FormLabelComponent,
|
||||||
ToWarningTextPipe,
|
ToWarningTextPipe,
|
||||||
GetErrorPipe,
|
GetErrorPipe,
|
||||||
ToEnumListDisplayPipe,
|
|
||||||
ToElementIdPipe,
|
ToElementIdPipe,
|
||||||
ToRangePipe,
|
ToRangePipe,
|
||||||
FormWarningDirective,
|
FormWarningDirective,
|
||||||
FormFileComponent,
|
FormFileComponent,
|
||||||
FormInputComponent,
|
FormInputComponent,
|
||||||
FormSubformComponent,
|
FormSubformComponent,
|
||||||
FormEnumComponent,
|
FormSelectComponent,
|
||||||
FormValueComponent,
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -46,7 +41,6 @@ import { FormValueComponent } from './form-object/controls/form-value/form-value
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
SharedPipesModule,
|
SharedPipesModule,
|
||||||
EnumListPageModule,
|
|
||||||
TuiElasticContainerModule,
|
TuiElasticContainerModule,
|
||||||
TuiExpandModule,
|
TuiExpandModule,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
import {
|
import { ValidationErrors } from '@angular/forms'
|
||||||
AbstractControl,
|
|
||||||
FormGroup,
|
|
||||||
UntypedFormArray,
|
|
||||||
ValidationErrors,
|
|
||||||
} from '@angular/forms'
|
|
||||||
import { IonicSafeString } from '@ionic/angular'
|
import { IonicSafeString } from '@ionic/angular'
|
||||||
import { ListValueSpecOf } from 'start-sdk/types/config-types'
|
|
||||||
import { Range } from 'src/app/util/config-utilities'
|
import { Range } from 'src/app/util/config-utilities'
|
||||||
import { getElementId } from './form-object/form-object.component'
|
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({
|
@Pipe({
|
||||||
name: 'toWarningText',
|
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"
|
#warning="formWarning"
|
||||||
slot="end"
|
slot="end"
|
||||||
[formControl]="control"
|
[formControl]="control"
|
||||||
(ionChange)="warning.onChange(name, spec, undefined, cancel)"
|
(ionChange)="warning.onChange(name, spec, undefined, cancelBool)"
|
||||||
></ion-toggle>
|
></ion-toggle>
|
||||||
<!-- enum -->
|
<!-- select -->
|
||||||
<!-- class enter-click disables the enter click on the modal behind the select -->
|
<!-- adding class enter-click disables the enter click on the modal behind the select -->
|
||||||
<ion-select
|
<ion-select
|
||||||
*ngIf="spec.type === 'enum'"
|
*ngIf="spec.type === 'select' || spec.type === 'multiselect'"
|
||||||
[interfaceOptions]="{
|
[interfaceOptions]="{
|
||||||
header: spec.name,
|
header: spec.name,
|
||||||
message: spec.warning | toWarningText,
|
message: spec.warning | toWarningText,
|
||||||
@@ -26,11 +26,21 @@
|
|||||||
}"
|
}"
|
||||||
slot="end"
|
slot="end"
|
||||||
placeholder="Select"
|
placeholder="Select"
|
||||||
|
[multiple]="spec.type === 'multiselect'"
|
||||||
[formControl]="control"
|
[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">
|
<ion-select-option *ngFor="let option of spec.values" [value]="option">
|
||||||
{{ spec['value-names'][option] }}
|
{{ spec['value-names'][option] }}
|
||||||
</ion-select-option>
|
</ion-select-option>
|
||||||
</ion-select>
|
</ion-select>
|
||||||
</ion-item>
|
</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 {
|
.error-message {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
font-size: small;
|
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)"
|
[control]="$any(entry.value)"
|
||||||
(onInputChange)="handleInputChange()"
|
(onInputChange)="handleInputChange()"
|
||||||
></form-input>
|
></form-input>
|
||||||
<!-- boolean or enum -->
|
<!-- boolean, select or multiselect -->
|
||||||
<form-value
|
<form-select
|
||||||
*ngIf="spec.type === 'boolean' || spec.type === 'enum'"
|
*ngIf="
|
||||||
|
spec.type === 'boolean' ||
|
||||||
|
spec.type === 'select' ||
|
||||||
|
spec.type === 'multiselect'
|
||||||
|
"
|
||||||
[spec]="spec"
|
[spec]="spec"
|
||||||
[name]="entry.key"
|
[name]="entry.key"
|
||||||
[control]="$any(entry.value)"
|
[control]="$any(entry.value)"
|
||||||
></form-value>
|
></form-select>
|
||||||
<!-- object -->
|
<!-- object -->
|
||||||
<form-subform
|
<form-subform
|
||||||
*ngIf="spec.type === 'object'"
|
*ngIf="spec.type === 'object'"
|
||||||
@@ -46,8 +50,8 @@
|
|||||||
[current]="current?.[entry.key]"
|
[current]="current?.[entry.key]"
|
||||||
[original]="original?.[entry.key]"
|
[original]="original?.[entry.key]"
|
||||||
></form-union>
|
></form-union>
|
||||||
<!-- list (not enum) -->
|
<!-- list -->
|
||||||
<ng-container *ngIf="spec.type === 'list' && spec.subtype !== 'enum'">
|
<ng-container *ngIf="spec.type === 'list'">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="formGroup.get(entry.key) as formArr"
|
*ngIf="formGroup.get(entry.key) as formArr"
|
||||||
[formArrayName]="entry.key"
|
[formArrayName]="entry.key"
|
||||||
@@ -195,13 +199,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</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>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
inject,
|
inject,
|
||||||
SimpleChanges,
|
SimpleChanges,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { FormArray, UntypedFormArray, UntypedFormGroup } from '@angular/forms'
|
import { UntypedFormArray, UntypedFormGroup } from '@angular/forms'
|
||||||
import { AlertButton, AlertController, ModalController } from '@ionic/angular'
|
import { AlertButton, AlertController, ModalController } from '@ionic/angular'
|
||||||
import {
|
import {
|
||||||
InputSpec,
|
InputSpec,
|
||||||
@@ -15,11 +15,9 @@ import {
|
|||||||
ValueSpec,
|
ValueSpec,
|
||||||
ValueSpecBoolean,
|
ValueSpecBoolean,
|
||||||
ValueSpecList,
|
ValueSpecList,
|
||||||
ValueSpecListOf,
|
|
||||||
ValueSpecUnion,
|
ValueSpecUnion,
|
||||||
} from 'start-sdk/types/config-types'
|
} from 'start-sdk/types/config-types'
|
||||||
import { FormService } from 'src/app/services/form.service'
|
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 { THEME, pauseFor } from '@start9labs/shared'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import { DOCUMENT } from '@angular/common'
|
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>(
|
async presentAlertChangeWarning<T extends ValueSpec>(
|
||||||
key: string,
|
key: string,
|
||||||
spec: T extends ValueSpecUnion ? never : T,
|
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() {
|
asIsOrder() {
|
||||||
return 0
|
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,
|
default: true,
|
||||||
warning: null,
|
warning: null,
|
||||||
},
|
},
|
||||||
'sample-enum': {
|
'sample-select': {
|
||||||
type: 'enum',
|
type: 'multiselect',
|
||||||
name: 'Example Enum Select',
|
name: 'Example Enum Select',
|
||||||
values: ['red', 'blue', 'green'],
|
values: ['red', 'blue', 'green'],
|
||||||
'value-names': {
|
'value-names': {
|
||||||
@@ -263,8 +263,9 @@ const SAMPLE_CONFIG: InputSpec = {
|
|||||||
green: 'Green',
|
green: 'Green',
|
||||||
},
|
},
|
||||||
// optional
|
// optional
|
||||||
warning: 'Example warning to display when changing this enum value.',
|
warning: 'Example warning to display when changing this select value.',
|
||||||
description: 'Example description for enum select',
|
description: 'Example description for select select',
|
||||||
default: 'red',
|
range: '[0, 2)',
|
||||||
|
default: ['red'],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
|
|||||||
warning: null,
|
warning: null,
|
||||||
},
|
},
|
||||||
license: {
|
license: {
|
||||||
type: 'enum',
|
type: 'select',
|
||||||
name: 'License',
|
name: 'License',
|
||||||
warning: null,
|
warning: null,
|
||||||
values: [
|
values: [
|
||||||
@@ -135,7 +135,7 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
|
|||||||
'the-unlicense': 'The Unlicense',
|
'the-unlicense': 'The Unlicense',
|
||||||
custom: 'Custom',
|
custom: 'Custom',
|
||||||
},
|
},
|
||||||
description: 'Example description for enum select',
|
description: 'Example description for select',
|
||||||
default: 'mit',
|
default: 'mit',
|
||||||
},
|
},
|
||||||
'wrapper-repo': {
|
'wrapper-repo': {
|
||||||
|
|||||||
@@ -963,7 +963,7 @@ export module Mock {
|
|||||||
},
|
},
|
||||||
'favorite-flower': {
|
'favorite-flower': {
|
||||||
name: 'Favorite Flower',
|
name: 'Favorite Flower',
|
||||||
type: 'enum',
|
type: 'select',
|
||||||
description: 'Select your favorite flower',
|
description: 'Select your favorite flower',
|
||||||
warning: null,
|
warning: null,
|
||||||
'value-names': {
|
'value-names': {
|
||||||
@@ -989,19 +989,34 @@ export module Mock {
|
|||||||
'unique-by': 'preference',
|
'unique-by': 'preference',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'random-enum': {
|
'random-select': {
|
||||||
name: 'Random Enum',
|
name: 'Random Select',
|
||||||
type: 'enum',
|
type: 'select',
|
||||||
'value-names': {
|
'value-names': {
|
||||||
null: 'Null',
|
hello: 'Hello',
|
||||||
option1: 'One 1',
|
goodbye: 'Goodbye',
|
||||||
option2: 'Two 2',
|
sup: 'Sup',
|
||||||
option3: 'Three 3',
|
|
||||||
},
|
},
|
||||||
default: 'null',
|
default: 'sup',
|
||||||
description: 'This is not even real.',
|
description: 'This is not even real.',
|
||||||
warning: 'Be careful changing this!',
|
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': {
|
'favorite-number': {
|
||||||
name: 'Favorite Number',
|
name: 'Favorite Number',
|
||||||
@@ -1292,25 +1307,6 @@ export module Mock {
|
|||||||
description: 'Advanced settings',
|
description: 'Advanced settings',
|
||||||
warning: null,
|
warning: null,
|
||||||
spec: {
|
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: {
|
rpcsettings: {
|
||||||
name: 'RPC Settings',
|
name: 'RPC Settings',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -1525,7 +1521,7 @@ export module Mock {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
'union-list': undefined,
|
'union-list': undefined,
|
||||||
'random-enum': 'option2',
|
'random-select': ['goodbye'],
|
||||||
'favorite-number': 0,
|
'favorite-number': 0,
|
||||||
rpcsettings: {
|
rpcsettings: {
|
||||||
laws: {
|
laws: {
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import {
|
|||||||
ListValueSpecUnion,
|
ListValueSpecUnion,
|
||||||
UniqueBy,
|
UniqueBy,
|
||||||
ValueSpec,
|
ValueSpec,
|
||||||
ValueSpecEnum,
|
ValueSpecSelect,
|
||||||
|
ValueSpecMultiselect,
|
||||||
ValueSpecFile,
|
ValueSpecFile,
|
||||||
ValueSpecList,
|
ValueSpecList,
|
||||||
ValueSpecNumber,
|
ValueSpecNumber,
|
||||||
@@ -49,8 +50,8 @@ export class FormService {
|
|||||||
const { variants, tag } = spec
|
const { variants, tag } = spec
|
||||||
const { name, description, warning, 'variant-names': variantNames } = tag
|
const { name, description, warning, 'variant-names': variantNames } = tag
|
||||||
|
|
||||||
const enumSpec: ValueSpecEnum = {
|
const enumSpec: ValueSpecSelect = {
|
||||||
type: 'enum',
|
type: 'select',
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
warning,
|
warning,
|
||||||
@@ -71,8 +72,6 @@ export class FormService {
|
|||||||
return this.formBuilder.control(entry, listItemValidators)
|
return this.formBuilder.control(entry, listItemValidators)
|
||||||
} else if (isValueSpecListOf(spec, 'number')) {
|
} else if (isValueSpecListOf(spec, 'number')) {
|
||||||
return this.formBuilder.control(entry, listItemValidators)
|
return this.formBuilder.control(entry, listItemValidators)
|
||||||
} else if (isValueSpecListOf(spec, 'enum')) {
|
|
||||||
return this.formBuilder.control(entry)
|
|
||||||
} else if (isValueSpecListOf(spec, 'object')) {
|
} else if (isValueSpecListOf(spec, 'object')) {
|
||||||
return this.getFormGroup(spec.spec.spec, listItemValidators, entry)
|
return this.getFormGroup(spec.spec.spec, listItemValidators, entry)
|
||||||
} else if (isValueSpecListOf(spec, 'union')) {
|
} else if (isValueSpecListOf(spec, 'union')) {
|
||||||
@@ -99,7 +98,6 @@ export class FormService {
|
|||||||
spec: ValueSpec,
|
spec: ValueSpec,
|
||||||
currentValue?: any,
|
currentValue?: any,
|
||||||
): UntypedFormGroup | UntypedFormArray | UntypedFormControl {
|
): UntypedFormGroup | UntypedFormArray | UntypedFormControl {
|
||||||
let validators: ValidatorFn[]
|
|
||||||
let value: any
|
let value: any
|
||||||
switch (spec.type) {
|
switch (spec.type) {
|
||||||
case 'string':
|
case 'string':
|
||||||
@@ -140,9 +138,12 @@ export class FormService {
|
|||||||
isValid ? currentValue : undefined,
|
isValid ? currentValue : undefined,
|
||||||
)
|
)
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
case 'enum':
|
case 'select':
|
||||||
value = currentValue === undefined ? spec.default : currentValue
|
value = currentValue === undefined ? spec.default : currentValue
|
||||||
return this.formBuilder.control(value)
|
return this.formBuilder.control(value)
|
||||||
|
case 'multiselect':
|
||||||
|
value = currentValue === undefined ? spec.default : currentValue
|
||||||
|
return this.formBuilder.control(value, multiselectValidators(spec))
|
||||||
default:
|
default:
|
||||||
return this.formBuilder.control(null)
|
return this.formBuilder.control(null)
|
||||||
}
|
}
|
||||||
@@ -193,17 +194,16 @@ function numberValidators(
|
|||||||
return validators
|
return validators
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function multiselectValidators(spec: ValueSpecMultiselect): ValidatorFn[] {
|
||||||
|
const validators: ValidatorFn[] = []
|
||||||
|
validators.push(listInRange(spec.range))
|
||||||
|
return validators
|
||||||
|
}
|
||||||
|
|
||||||
function listValidators(spec: ValueSpecList): ValidatorFn[] {
|
function listValidators(spec: ValueSpecList): ValidatorFn[] {
|
||||||
const validators: ValidatorFn[] = []
|
const validators: ValidatorFn[] = []
|
||||||
|
|
||||||
validators.push(listInRange(spec.range))
|
validators.push(listInRange(spec.range))
|
||||||
|
|
||||||
validators.push(listItemIssue())
|
validators.push(listItemIssue())
|
||||||
|
|
||||||
if (!isValueSpecListOf(spec, 'enum')) {
|
|
||||||
validators.push(listUnique(spec))
|
|
||||||
}
|
|
||||||
|
|
||||||
return validators
|
return validators
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +314,6 @@ function listItemEquals(spec: ValueSpecList, val1: any, val2: any): boolean {
|
|||||||
switch (spec.subtype) {
|
switch (spec.subtype) {
|
||||||
case 'string':
|
case 'string':
|
||||||
case 'number':
|
case 'number':
|
||||||
case 'enum':
|
|
||||||
return val1 == val2
|
return val1 == val2
|
||||||
case 'object':
|
case 'object':
|
||||||
const obj: ListValueSpecObject = spec.spec as any
|
const obj: ListValueSpecObject = spec.spec as any
|
||||||
@@ -334,7 +333,7 @@ function itemEquals(spec: ValueSpec, val1: any, val2: any): boolean {
|
|||||||
case 'string':
|
case 'string':
|
||||||
case 'number':
|
case 'number':
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
case 'enum':
|
case 'select':
|
||||||
return val1 == val2
|
return val1 == val2
|
||||||
case 'object':
|
case 'object':
|
||||||
// TODO: 'unique-by' does not exist on ValueSpecObject, fix types
|
// 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 {
|
export class Range {
|
||||||
min?: number
|
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
|
return this.min !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMax(): this is Range & { max: number } {
|
private hasMax(): this is Range & { max: number } {
|
||||||
return this.max !== undefined
|
return this.max !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
minMessage(): string {
|
private minMessage(): string {
|
||||||
return `greater than${this.minInclusive ? ' or equal to' : ''} ${this.min}`
|
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}`
|
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 {
|
export function getDefaultString(defaultSpec: DefaultString): string {
|
||||||
@@ -136,7 +62,7 @@ export function getDefaultString(defaultSpec: DefaultString): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// a,g,h,A-Z,,,,-
|
// a,g,h,A-Z,,,,-
|
||||||
export function getRandomCharInSet(charset: string): string {
|
function getRandomCharInSet(charset: string): string {
|
||||||
const set = stringToCharSet(charset)
|
const set = stringToCharSet(charset)
|
||||||
let charIdx = Math.floor(Math.random() * set.len)
|
let charIdx = Math.floor(Math.random() * set.len)
|
||||||
for (let range of set.ranges) {
|
for (let range of set.ranges) {
|
||||||
|
|||||||
Reference in New Issue
Block a user