chore: break down form-object by type, part 2

This commit is contained in:
waterplea
2023-03-23 17:54:36 +08:00
committed by Aiden McClelland
parent 234258a077
commit cc9cd3fc14
13 changed files with 207 additions and 127 deletions

View File

@@ -19,6 +19,9 @@ import {
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'
@NgModule({
declarations: [
@@ -33,6 +36,9 @@ import { FormWarningDirective } from './form-warning.directive'
FormWarningDirective,
FormFileComponent,
FormInputComponent,
FormSubformComponent,
FormEnumComponent,
FormValueComponent,
],
imports: [
CommonModule,

View File

@@ -14,11 +14,14 @@ import { getElementId } from './form-object/form-object.component'
name: 'getError',
})
export class GetErrorPipe implements PipeTransform {
transform(errors: ValidationErrors, patternDesc?: string): string {
transform(
errors: ValidationErrors,
patternDesc: string = 'Invalid pattern',
): string {
if (errors['required']) {
return 'Required'
} else if (errors['pattern']) {
return patternDesc || 'Invalid pattern'
return patternDesc
} else if (errors['notNumber']) {
return 'Must be a number'
} else if (errors['numberNotInteger']) {

View File

@@ -0,0 +1,21 @@
<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>

View File

@@ -0,0 +1,13 @@
.label {
margin: 16px 0 6px;
}
.list {
white-space: nowrap;
}
.error-message {
margin-top: 2px;
font-size: small;
color: var(--ion-color-danger);
}

View File

@@ -0,0 +1,15 @@
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>()
}

View File

@@ -2,7 +2,6 @@ import { Component, Input, inject, Output, EventEmitter } from '@angular/core'
import { FormControl } from '@angular/forms'
import { ValueSpecOf } from 'start-sdk/types/config-types'
import { THEME } from '@start9labs/shared'
import { FormObjectComponent } from '../../form-object.component'
@Component({
selector: 'form-input',
@@ -19,6 +18,4 @@ export class FormInputComponent {
unmasked = false
readonly theme$ = inject(THEME)
constructor(readonly form: FormObjectComponent) {}
}

View File

@@ -0,0 +1,23 @@
<ion-item-divider
[class.error-border]="control.invalid"
(click)="expanded = !expanded"
>
<form-label
[data]="{
name: spec.name,
description: spec.description,
edited: control.dirty,
newOptions: hasNewOptions
}"
></form-label>
<ion-icon
slot="end"
name="chevron-down"
class="icon"
[class.icon_rotated]="expanded"
[color]="control.invalid ? 'danger' : undefined"
></ion-icon>
</ion-item-divider>
<tui-expand [expanded]="expanded">
<ng-content></ng-content>
</tui-expand>

View File

@@ -0,0 +1,25 @@
ion-item-divider {
cursor: pointer;
text-transform: unset;
border-bottom: 1px solid
var(
--ion-item-border-color,
var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, 0.13)))
);
--padding-top: 18px;
--padding-start: 0;
&.error-border {
border-color: var(--ion-color-danger-shade);
--border-color: var(--ion-color-danger-shade);
}
}
.icon {
transition: transform 0.42s ease-out;
&_rotated {
transform: rotate(180deg);
}
}

View File

@@ -0,0 +1,16 @@
import { Component, Input, Output, EventEmitter } from '@angular/core'
import { AbstractControl } from '@angular/forms'
import { ValueSpecOf } from 'start-sdk/types/config-types'
@Component({
selector: 'form-subform',
templateUrl: './form-subform.component.html',
styleUrls: ['./form-subform.component.scss'],
})
export class FormSubformComponent {
@Input() spec!: ValueSpecOf<'object'>
@Input() control!: AbstractControl
@Input() hasNewOptions = false
expanded = false
}

View File

@@ -0,0 +1,38 @@
<ion-item
*ngIf="spec.type === 'boolean' || spec.type === 'enum'"
style="--padding-start: 0"
>
<form-label
[data]="{
name: spec.name,
description: spec.description,
edited: control.dirty
}"
></form-label>
<!-- boolean -->
<ion-toggle
*ngIf="spec.type === 'boolean'"
formWarning
#warning="formWarning"
slot="end"
[formControl]="control"
(ionChange)="warning.onChange(name, spec, undefined, cancel)"
></ion-toggle>
<!-- enum -->
<!-- class enter-click disables the enter click on the modal behind the select -->
<ion-select
*ngIf="spec.type === 'enum'"
[interfaceOptions]="{
message: spec.warning | toWarningText,
cssClass: 'enter-click'
}"
slot="end"
placeholder="Select"
[formControl]="control"
[selectedText]="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>

View File

@@ -0,0 +1,15 @@
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)
}

View File

@@ -1,6 +1,6 @@
<ion-item-group [formGroup]="formGroup">
<div *ngFor="let entry of formGroup.controls | keyvalue : asIsOrder">
<div *ngIf="objectSpec[entry.key] as spec">
<ng-container *ngFor="let entry of formGroup.controls | keyvalue : asIsOrder">
<ng-container *ngIf="objectSpec[entry.key] as spec">
<!-- file -->
<form-file
*ngIf="spec.type === 'file'"
@@ -16,89 +16,28 @@
(onInputChange)="handleInputChange()"
></form-input>
<!-- boolean or enum -->
<ion-item
<form-value
*ngIf="spec.type === 'boolean' || spec.type === 'enum'"
style="--padding-start: 0"
>
<form-label
[data]="{
name: spec.name,
description: spec.description,
edited: entry.value.dirty
}"
></form-label>
<!-- boolean -->
<ion-toggle
*ngIf="spec.type === 'boolean'"
slot="end"
[formControlName]="entry.key"
(ionChange)="handleBooleanChange(entry.key, spec)"
></ion-toggle>
<!-- enum -->
<!-- class enter-click disables the enter click on the modal behind the select -->
<ion-select
*ngIf="spec.type === 'enum'"
[interfaceOptions]="{
message: spec.warning | toWarningText,
cssClass: 'enter-click'
}"
slot="end"
placeholder="Select"
[formControlName]="entry.key"
[selectedText]="spec['value-names'][entry.value.value]"
>
<ion-select-option
*ngFor="let option of spec.values"
[value]="option"
>
{{ spec['value-names'][option] }}
</ion-select-option>
</ion-select>
</ion-item>
[spec]="spec"
[name]="entry.key"
[control]="$any(entry.value)"
></form-value>
<!-- object -->
<ng-container *ngIf="spec.type === 'object'">
<!-- label -->
<ion-item-divider
style="cursor: pointer"
[class.error-border]="entry.value.invalid"
(click)="toggleExpandObject(entry.key)"
>
<form-label
[data]="{
name: spec.name,
description: spec.description,
edited: entry.value.dirty,
newOptions: objectDisplay[entry.key].hasNewOptions
}"
></form-label>
<ion-icon
slot="end"
name="chevron-up"
[color]="entry.value.invalid ? 'danger' : undefined"
[ngStyle]="{
transform: objectDisplay[entry.key].expanded
? 'rotate(0deg)'
: 'rotate(180deg)',
transition: 'transform 0.42s ease-out'
}"
></ion-icon>
</ion-item-divider>
<!-- body -->
<tui-expand
[expanded]="objectDisplay[entry.key].expanded"
[id]="objectId | toElementId : entry.key"
>
<div class="nested-wrapper">
<form-object
[objectSpec]="spec.spec"
[formGroup]="$any(entry.value)"
[current]="current?.[entry.key]"
[original]="original?.[entry.key]"
(hasNewOptions)="setHasNew(entry.key)"
></form-object>
</div>
</tui-expand>
</ng-container>
<form-subform
*ngIf="spec.type === 'object'"
[spec]="spec"
[control]="entry.value"
[hasNewOptions]="objectDisplay[entry.key].hasNewOptions"
>
<form-object
class="nested-wrapper"
[objectSpec]="spec.spec"
[formGroup]="$any(entry.value)"
[current]="current?.[entry.key]"
[original]="original?.[entry.key]"
(hasNewOptions)="setHasNew(entry.key)"
></form-object>
</form-subform>
<!-- union -->
<form-union
*ngIf="spec.type === 'union'"
@@ -257,42 +196,12 @@
</ng-container>
</ng-container>
<!-- list (enum) -->
<ng-container *ngIf="spec.type === 'list' && spec.subtype === 'enum'">
<ng-container
*ngIf="formGroup.get(entry.key) as formArr"
[formArrayName]="entry.key"
>
<!-- label -->
<p class="input-label">
<form-label
[data]="{
name: spec.name,
description: spec.description,
edited: entry.value.dirty
}"
></form-label>
</p>
<!-- list -->
<ion-item
button
detail="false"
color="dark"
(click)="presentModalEnumList(entry.key, $any(spec), formArr.value)"
>
<ion-label style="white-space: nowrap !important">
<h2>{{ formArr.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="formGroup.get(entry.key)?.errors as errors">
{{ errors | getError }}
</span>
</p>
</ng-container>
</ng-container>
</div>
</div>
<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>

View File

@@ -39,7 +39,6 @@ export class FormObjectComponent {
@Output() onInputChange = new EventEmitter<void>()
@Output() hasNewOptions = new EventEmitter<void>()
warningAck: { [key: string]: boolean } = {}
unmasked: { [key: string]: boolean } = {}
objectDisplay: {
[key: string]: { expanded: boolean; hasNewOptions: boolean }
} = {}