mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-27 02:41:53 +00:00
chore: break down form-object by type, part 2
This commit is contained in:
committed by
Aiden McClelland
parent
234258a077
commit
cc9cd3fc14
@@ -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,
|
||||
|
||||
@@ -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']) {
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>()
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }
|
||||
} = {}
|
||||
|
||||
Reference in New Issue
Block a user