implement textarea config value

This commit is contained in:
Matt Hill
2023-03-29 15:04:32 -06:00
committed by Aiden McClelland
parent c7438c4aff
commit 5675fc51a0
15 changed files with 74 additions and 70 deletions

View File

@@ -286,7 +286,6 @@ const CifsSpec: InputSpec = {
nullable: false,
masked: false,
default: null,
textarea: false,
warning: null,
},
path: {
@@ -299,7 +298,6 @@ const CifsSpec: InputSpec = {
nullable: false,
masked: false,
default: null,
textarea: false,
warning: null,
},
username: {
@@ -312,7 +310,6 @@ const CifsSpec: InputSpec = {
nullable: false,
masked: false,
default: null,
textarea: false,
warning: null,
},
password: {
@@ -325,7 +322,6 @@ const CifsSpec: InputSpec = {
nullable: true,
masked: true,
default: null,
textarea: false,
warning: null,
},
}

View File

@@ -9,7 +9,7 @@
></form-label>
<ion-item [color]="(theme$ | async) === 'Light' ? 'light' : 'dark'">
<ion-textarea
*ngIf="spec.type === 'string' && spec.textarea; else notTextArea"
*ngIf="spec.type === 'textarea'; else notTextArea"
formWarning
#warning="formWarning"
[placeholder]="spec.placeholder"

View File

@@ -10,7 +10,7 @@ import { THEME } from '@start9labs/shared'
})
export class FormInputComponent {
@Input() name!: string
@Input() spec!: ValueSpecOf<'string' | 'number'>
@Input() spec!: ValueSpecOf<'string' | 'textarea' | 'number'>
@Input() control!: FormControl
@Output() onInputChange = new EventEmitter<void>()

View File

@@ -9,7 +9,11 @@
></form-file>
<!-- string or number -->
<form-input
*ngIf="spec.type === 'string' || spec.type === 'number'"
*ngIf="
spec.type === 'string' ||
spec.type === 'textarea' ||
spec.type === 'number'
"
[spec]="spec"
[name]="entry.key"
[control]="$any(entry.value)"

View File

@@ -30,6 +30,11 @@
</ion-select-option>
</ion-select>
</ion-item-divider>
<p class="error-message">
<span *ngIf="unionControl?.errors as errors">
{{ errors | getError }}
</span>
</p>
<tui-elastic-container [id]="objectId | toElementId : 'union'" class="indent">
<form-object

View File

@@ -22,3 +22,9 @@ ion-item-divider {
.indent {
margin-left: 24px;
}
.error-message {
margin-top: 2px;
font-size: small;
color: var(--ion-color-danger);
}

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { UntypedFormGroup } from '@angular/forms'
import { AbstractControl, UntypedFormGroup } from '@angular/forms'
import { v4 } from 'uuid'
import { FormService } from 'src/app/services/form.service'
import {
@@ -22,16 +22,20 @@ export class FormUnionComponent {
@Input() current?: Record<string, any>
@Input() original?: Record<string, any>
get unionControl(): AbstractControl | null {
return this.formGroup.get(unionSelectKey)
}
get selectedVariant(): string {
return this.formGroup.get(unionSelectKey)?.value
return this.unionControl?.value || ''
}
get variantName(): string {
return this.spec.variants[this.selectedVariant].name
return this.spec.variants[this.selectedVariant]?.name || ''
}
get variantSpec(): InputSpec {
return this.spec.variants[this.selectedVariant].spec
return this.spec.variants[this.selectedVariant]?.spec || {}
}
get hasNewOptions(): boolean {

View File

@@ -279,7 +279,6 @@ function getMarketplaceValueSpec(): ValueSpecObject {
patternDescription: 'Must be a valid URL',
placeholder: 'e.g. https://example.org',
default: null,
textarea: false,
warning: null,
},
},

View File

@@ -229,7 +229,6 @@ const SAMPLE_CONFIG: InputSpec = {
pattern: '^[a-zA-Z0-9! _]+$',
patternDescription: 'Must be alphanumeric (may contain underscore).',
default: null,
textarea: false,
warning: null,
},
'sample-number': {

View File

@@ -30,7 +30,6 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
pattern: '^([a-z][a-z0-9]*)(-[a-z0-9]+)*$',
patternDescription: 'Must be kebab case',
default: basicInfo?.id || '',
textarea: false,
warning: null,
},
title: {
@@ -43,7 +42,6 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
pattern: null,
patternDescription: null,
default: basicInfo ? basicInfo.title : devData.name,
textarea: false,
warning: null,
},
'service-version-number': {
@@ -57,7 +55,6 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
pattern: '^([0-9]+).([0-9]+).([0-9]+).([0-9]+)$',
patternDescription: 'Must be valid Emver version',
default: basicInfo?.['service-version-number'] || '',
textarea: false,
warning: null,
},
description: {
@@ -74,23 +71,17 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
placeholder: null,
nullable: false,
masked: false,
textarea: true,
default: basicInfo?.description?.short || '',
pattern: '^.{1,320}$',
patternDescription: 'Must be shorter than 320 characters',
warning: null,
},
long: {
type: 'string',
type: 'textarea',
name: 'Long Description',
description: `This description will display with additional details in the service's individual marketplace page`,
placeholder: null,
nullable: false,
masked: false,
textarea: true,
default: basicInfo?.description?.long || '',
pattern: '^.{1,5000}$',
patternDescription: 'Must be shorter than 5000 characters',
warning: null,
},
},
@@ -105,7 +96,6 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
masked: false,
pattern: null,
patternDescription: null,
textarea: true,
default: basicInfo?.['release-notes'] || '',
warning: null,
},
@@ -139,7 +129,6 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
nullable: false,
masked: false,
default: basicInfo?.['wrapper-repo'] || '',
textarea: false,
warning: null,
},
'upstream-repo': {
@@ -152,7 +141,6 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
nullable: true,
masked: false,
default: basicInfo?.['upstream-repo'] || '',
textarea: false,
warning: null,
},
'support-site': {
@@ -165,7 +153,6 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
nullable: true,
masked: false,
default: basicInfo?.['support-site'] || '',
textarea: false,
warning: null,
},
'marketing-site': {
@@ -178,7 +165,6 @@ export function getBasicInfoSpec(devData: DevProjectData): InputSpec {
nullable: true,
masked: false,
default: basicInfo?.['marketing-site'] || '',
textarea: false,
warning: null,
},
}

View File

@@ -365,7 +365,6 @@ function getWifiValueSpec(
nullable: false,
masked: false,
default: ssid || null,
textarea: false,
warning: null,
},
password: {
@@ -378,7 +377,6 @@ function getWifiValueSpec(
pattern: '^.{8,}$',
patternDescription: 'Must be longer than 8 characters',
default: null,
textarea: false,
warning: null,
},
},

View File

@@ -13,6 +13,7 @@ import {
Manifest,
} from '@start9labs/marketplace'
import { Log } from '@start9labs/shared'
import { unionSelectKey } from 'start-sdk/lib/config/config-types'
export module Mock {
export const ServerUpdated: ServerStatusInfo = {
@@ -718,7 +719,7 @@ export module Mock {
description:
'<p>The Bitcoin Core node to connect to over the peer-to-peer (P2P) interface:</p><ul><li><strong>Bitcoin Core</strong>: The Bitcoin Core service installed on this device</li><li><strong>External Node</strong>: A Bitcoin node running on a different device</li></ul>',
warning: null,
default: 'internal',
default: null,
nullable: false,
variants: {
internal: { name: 'Internal', spec: {} },
@@ -734,7 +735,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
default: null,
},
@@ -781,7 +781,6 @@ export module Mock {
pattern: '^[a-zA-Z]+$',
patternDescription: 'must contain only letters.',
masked: false,
textarea: false,
},
rpcuser: {
name: 'RPC Username',
@@ -794,7 +793,6 @@ export module Mock {
pattern: '^[a-zA-Z]+$',
patternDescription: 'must contain only letters.',
masked: false,
textarea: false,
},
rpcpass: {
name: 'RPC User Password',
@@ -810,7 +808,6 @@ export module Mock {
masked: true,
pattern: null,
patternDescription: null,
textarea: false,
},
rpcpass2: {
name: 'RPC User Password',
@@ -826,12 +823,19 @@ export module Mock {
masked: true,
pattern: null,
patternDescription: null,
textarea: false,
},
},
},
},
},
bio: {
name: 'Bio',
type: 'textarea',
description: 'Your personal bio',
placeholder: 'Tell the world about yourself',
warning: null,
nullable: true,
},
testnet: {
name: 'Testnet',
type: 'boolean',
@@ -882,7 +886,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
default: null,
},
@@ -899,7 +902,6 @@ export module Mock {
patternDescription: 'must contain only letters.',
masked: false,
placeholder: null,
textarea: false,
warning: null,
},
age: {
@@ -994,7 +996,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
default: null,
},
@@ -1007,7 +1008,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
default: null,
},
@@ -1038,7 +1038,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
},
rulemakerip: {
@@ -1052,7 +1051,6 @@ export module Mock {
patternDescription: 'may only contain numbers and periods',
masked: false,
placeholder: null,
textarea: false,
warning: null,
},
},
@@ -1068,7 +1066,6 @@ export module Mock {
patternDescription: 'must contain only letters.',
masked: false,
placeholder: null,
textarea: false,
warning: null,
},
rpcpass: {
@@ -1084,7 +1081,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
},
},
@@ -1116,7 +1112,6 @@ export module Mock {
pattern: '^[a-zA-Z]+$',
patternDescription: 'Must contain only letters.',
placeholder: null,
textarea: false,
warning: null,
default: null,
},
@@ -1129,7 +1124,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
default: null,
},
@@ -1145,7 +1139,6 @@ export module Mock {
patternDescription: 'anything',
masked: false,
placeholder: null,
textarea: false,
warning: null,
},
'private-domain': {
@@ -1157,7 +1150,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
default: null,
},
@@ -1188,7 +1180,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
default: null,
},
@@ -1253,7 +1244,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
default: null,
},
@@ -1266,7 +1256,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
default: null,
},
@@ -1279,7 +1268,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
default: null,
},
@@ -1308,7 +1296,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
},
lawagency: {
@@ -1323,7 +1310,6 @@ export module Mock {
'may only contain numbers and periods',
masked: false,
placeholder: null,
textarea: false,
warning: null,
},
},
@@ -1338,7 +1324,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
default: null,
},
@@ -1369,7 +1354,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
},
rulemakerip: {
@@ -1383,7 +1367,6 @@ export module Mock {
patternDescription: 'may only contain numbers and periods',
masked: false,
placeholder: null,
textarea: false,
warning: null,
},
},
@@ -1399,7 +1382,6 @@ export module Mock {
patternDescription: 'must contain only letters.',
masked: false,
placeholder: null,
textarea: false,
warning: null,
},
rpcpass: {
@@ -1415,7 +1397,6 @@ export module Mock {
placeholder: null,
pattern: null,
patternDescription: null,
textarea: false,
warning: null,
},
},
@@ -1455,7 +1436,7 @@ export module Mock {
rulemakers: [],
},
'bitcoin-node': {
type: 'internal',
[unionSelectKey]: 'internal',
},
port: 20,
rpcallowip: undefined,
@@ -1524,7 +1505,6 @@ export module Mock {
masked: false,
pattern: '^[a-zA-Z]+$',
patternDescription: 'Must contain only letters.',
textarea: false,
warning: null,
default: null,
},

View File

@@ -25,6 +25,7 @@ import {
ValueSpecString,
ValueSpecUnion,
unionSelectKey,
ValueSpecTextarea,
} from 'start-sdk/lib/config/config-types'
import { getDefaultString, Range } from '../util/config-utilities'
const Mustache = require('mustache')
@@ -48,7 +49,7 @@ export class FormService {
): UntypedFormGroup {
const { name, description, warning, variants, nullable } = spec
const enumSpec: ValueSpecSelect = {
const selectSpec: ValueSpecSelect = {
type: 'select',
name,
description,
@@ -67,7 +68,7 @@ export class FormService {
const selectedSpec = selection ? variants[selection].spec : {}
return this.getFormGroup({
['selectedVariant']: enumSpec,
[unionSelectKey]: selectSpec,
...selectedSpec,
})
}
@@ -111,6 +112,9 @@ export class FormService {
value = spec.default ? getDefaultString(spec.default) : null
}
return this.formBuilder.control(value, stringValidators(spec))
case 'textarea':
value = currentValue || null
return this.formBuilder.control(value, textareaValidators(spec))
case 'number':
if (currentValue !== undefined) {
value = currentValue
@@ -141,9 +145,11 @@ export class FormService {
isValid ? currentSelection : spec.default,
)
case 'boolean':
case 'select':
value = currentValue === undefined ? spec.default : currentValue
return this.formBuilder.control(value)
case 'select':
value = currentValue === undefined ? spec.default : currentValue
return this.formBuilder.control(value, selectValidators(spec))
case 'multiselect':
value = currentValue === undefined ? spec.default : currentValue
return this.formBuilder.control(value, multiselectValidators(spec))
@@ -177,6 +183,16 @@ function stringValidators(
return validators
}
function textareaValidators(spec: ValueSpecTextarea): ValidatorFn[] {
const validators: ValidatorFn[] = []
if (!spec.nullable) {
validators.push(Validators.required)
}
return validators
}
function numberValidators(
spec: ValueSpecNumber | ListValueSpecNumber,
): ValidatorFn[] {
@@ -197,6 +213,16 @@ function numberValidators(
return validators
}
function selectValidators(spec: ValueSpecSelect): ValidatorFn[] {
const validators: ValidatorFn[] = []
if (!spec.nullable) {
validators.push(Validators.required)
}
return validators
}
function multiselectValidators(spec: ValueSpecMultiselect): ValidatorFn[] {
const validators: ValidatorFn[] = []
validators.push(listInRange(spec.range))
@@ -327,6 +353,7 @@ function listItemEquals(spec: ValueSpecList, val1: any, val2: any): boolean {
function itemEquals(spec: ValueSpec, val1: any, val2: any): boolean {
switch (spec.type) {
case 'string':
case 'textarea':
case 'number':
case 'boolean':
case 'select':
@@ -513,7 +540,7 @@ export function convertValuesRecursive(
control.setValue(
control.value || control.value === 0 ? Number(control.value) : null,
)
} else if (valueSpec.type === 'string') {
} else if (valueSpec.type === 'string' || valueSpec.type === 'textarea') {
if (!control.value) control.setValue(null)
} else if (valueSpec.type === 'object') {
convertValuesRecursive(valueSpec.spec, group.get(key) as UntypedFormGroup)