diff --git a/package-lock.json b/package-lock.json index ad7ab6276..949c3ef66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18475,7 +18475,7 @@ "dev": true, "requires": { "is-ssh": "^1.3.0", - "parse-url": ">=6.0.1" + "parse-url": "^6.0.0" } }, "git-url-parse": { @@ -21478,8 +21478,7 @@ } }, "parse-url": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", + "version": "https://registry.npmjs.org/parse-url/-/parse-url-7.0.2.tgz", "integrity": "sha512-PqO4Z0eCiQ08Wj6QQmrmp5YTTxpYfONdOEamrtvK63AmzXpcavIVQubGHxOEwiIoDZFb8uDOoQFS0NCcjqIYQg==", "dev": true, "requires": { diff --git a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap index df7157dd4..b3bcef090 100644 --- a/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap +++ b/packages/codegen-ui-react/lib/__tests__/__snapshots__/studio-ui-codegen-react-forms.test.ts.snap @@ -53,9 +53,20 @@ export default function CustomDataForm(props) { category, }; const validationResponses = await Promise.all( - Object.keys(validations).map((fieldName) => - runValidationTasks(fieldName, modelFields[fieldName]) - ) + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks(fieldName, item) + ) + ); + return promises; + } + promises.push( + runValidationTasks(fieldName, modelFields[fieldName]) + ); + return promises; + }, []) ); if (validationResponses.some((r) => r.hasError)) { return; @@ -206,7 +217,7 @@ export default function CustomDataForm(props) { children=\\"create\\" type=\\"submit\\" variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e.hasError)} + isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} > @@ -297,9 +308,20 @@ export default function NestedJson(props) { bio, }; const validationResponses = await Promise.all( - Object.keys(validations).map((fieldName) => - runValidationTasks(fieldName, modelFields[fieldName]) - ) + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks(fieldName, item) + ) + ); + return promises; + } + promises.push( + runValidationTasks(fieldName, modelFields[fieldName]) + ); + return promises; + }, []) ); if (validationResponses.some((r) => r.hasError)) { return; @@ -423,7 +445,7 @@ export default function NestedJson(props) { children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e.hasError)} + isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} > @@ -519,9 +541,20 @@ export default function CustomWithSectionalElements(props) { name, }; const validationResponses = await Promise.all( - Object.keys(validations).map((fieldName) => - runValidationTasks(fieldName, modelFields[fieldName]) - ) + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks(fieldName, item) + ) + ); + return promises; + } + promises.push( + runValidationTasks(fieldName, modelFields[fieldName]) + ); + return promises; + }, []) ); if (validationResponses.some((r) => r.hasError)) { return; @@ -613,7 +646,7 @@ export default function CustomWithSectionalElements(props) { children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e.hasError)} + isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} > @@ -706,9 +739,20 @@ export default function MyPostForm(props) { profile_url, }; const validationResponses = await Promise.all( - Object.keys(validations).map((fieldName) => - runValidationTasks(fieldName, modelFields[fieldName]) - ) + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks(fieldName, item) + ) + ); + return promises; + } + promises.push( + runValidationTasks(fieldName, modelFields[fieldName]) + ); + return promises; + }, []) ); if (validationResponses.some((r) => r.hasError)) { return; @@ -756,7 +800,7 @@ export default function MyPostForm(props) { children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e.hasError)} + isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} > @@ -963,9 +1007,20 @@ export default function MyPostForm(props) { post_url, }; const validationResponses = await Promise.all( - Object.keys(validations).map((fieldName) => - runValidationTasks(fieldName, modelFields[fieldName]) - ) + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks(fieldName, item) + ) + ); + return promises; + } + promises.push( + runValidationTasks(fieldName, modelFields[fieldName]) + ); + return promises; + }, []) ); if (validationResponses.some((r) => r.hasError)) { return; @@ -1017,7 +1072,7 @@ export default function MyPostForm(props) { children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e.hasError)} + isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} > @@ -1150,7 +1205,7 @@ export default function MyPostForm(props) { children=\\"Submit\\" type=\\"submit\\" variation=\\"primary\\" - isDisabled={Object.values(errors).some((e) => e.hasError)} + isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} > @@ -1239,6 +1294,7 @@ export default function InputGalleryCreateForm(props) { const [attend, setAttend] = React.useState(undefined); const [maybeSlide, setMaybeSlide] = React.useState(undefined); const [maybeCheck, setMaybeCheck] = React.useState(undefined); + const [arrayTypeField, setArrayTypeField] = React.useState(undefined); const [timestamp, setTimestamp] = React.useState(undefined); const [ippy, setIppy] = React.useState(undefined); const [timeisnow, setTimeisnow] = React.useState(undefined); @@ -1249,6 +1305,7 @@ export default function InputGalleryCreateForm(props) { attend: [{ type: \\"Required\\" }], maybeSlide: [], maybeCheck: [], + arrayTypeField: [], timestamp: [], ippy: [{ type: \\"IpAddress\\" }], timeisnow: [], @@ -1272,14 +1329,26 @@ export default function InputGalleryCreateForm(props) { attend, maybeSlide, maybeCheck, + arrayTypeField, timestamp, ippy, timeisnow, }; const validationResponses = await Promise.all( - Object.keys(validations).map((fieldName) => - runValidationTasks(fieldName, modelFields[fieldName]) - ) + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push( + ...modelFields[fieldName].map((item) => + runValidationTasks(fieldName, item) + ) + ); + return promises; + } + promises.push( + runValidationTasks(fieldName, modelFields[fieldName]) + ); + return promises; + }, []) ); if (validationResponses.some((r) => r.hasError)) { return; @@ -1431,6 +1500,26 @@ export default function InputGalleryCreateForm(props) { rowGap=\\"inherit\\" templateColumns=\\"repeat(1, auto)\\" {...getOverrideProps(overrides, \\"RowGrid5\\")} + > + { + const { value } = e.target; + await runValidationTasks(\\"arrayTypeField\\", value); + setArrayTypeField(value); + }} + errorMessage={errors.arrayTypeField?.errorMessage} + hasError={errors.arrayTypeField?.hasError} + {...getOverrideProps(overrides, \\"arrayTypeField\\")} + > + + e.hasError)} + isDisabled={Object.values(errors).some((e) => e?.hasError)} {...getOverrideProps(overrides, \\"SubmitButton\\")} > @@ -1538,6 +1627,7 @@ export declare type InputGalleryCreateFormInputValues = { attend?: ValidationFunction; maybeSlide?: ValidationFunction; maybeCheck?: ValidationFunction; + arrayTypeField?: ValidationFunction; timestamp?: ValidationFunction; ippy?: ValidationFunction; timeisnow?: ValidationFunction; @@ -1555,10 +1645,12 @@ export declare type InputGalleryCreateFormOverridesProps = { RowGrid4?: GridProps; maybeCheck?: CheckboxFieldProps; RowGrid5?: GridProps; - timestamp?: TextFieldProps; + arrayTypeField?: TextFieldProps; RowGrid6?: GridProps; - ippy?: TextFieldProps; + timestamp?: TextFieldProps; RowGrid7?: GridProps; + ippy?: TextFieldProps; + RowGrid8?: GridProps; timeisnow?: TextFieldProps; } & EscapeHatchProps; export declare type InputGalleryCreateFormProps = React.PropsWithChildren<{ diff --git a/packages/codegen-ui-react/lib/forms/form-renderer-helper.ts b/packages/codegen-ui-react/lib/forms/form-renderer-helper.ts index 2cdf4dc5c..182e4f211 100644 --- a/packages/codegen-ui-react/lib/forms/form-renderer-helper.ts +++ b/packages/codegen-ui-react/lib/forms/form-renderer-helper.ts @@ -39,8 +39,8 @@ import { lowerCaseFirst } from '../helpers'; import { ImportCollection, ImportSource } from '../imports'; import { getActionIdentifier } from '../workflow'; import { buildTargetVariable } from './event-targets'; -import { setFieldState } from './form-state'; import { buildOnValidateType } from './type-helper'; +import { capitalizeFirstLetter, setFieldState } from './form-state'; export const buildMutationBindings = (form: StudioForm) => { const { @@ -258,10 +258,7 @@ export const createValidationExpression = (validationRules: FieldValidationConfi return factory.createArrayLiteralExpression(validateExpressions, true); }; -export const addFormAttributes = ( - { name: componentName, componentType }: StudioComponent | StudioComponentChild, - formMetadata: FormMetadata, -) => { +export const addFormAttributes = (component: StudioComponent | StudioComponentChild, formMetadata: FormMetadata) => { const attributes: JsxAttribute[] = []; /* boolean => RadioGroupField @@ -272,10 +269,11 @@ export const addFormAttributes = ( componentType => SelectField && boolean const value = Boolean(e.target.checked) - + */ - if (componentName in formMetadata.fieldConfigs) { - const fieldConfig = formMetadata.fieldConfigs[componentName]; + + if (component.name in formMetadata.fieldConfigs) { + const fieldConfig = formMetadata.fieldConfigs[component.name]; /* if the componetName is a dotPath we need to change the access expression to the following - bio.user.favorites.Quote => errors['bio.user.favorites.Quote']?.errorMessage @@ -283,16 +281,16 @@ export const addFormAttributes = ( - bio => errors.bio?.errorMessage */ const errorKey = - componentName.split('.').length > 1 + component.name.split('.').length > 1 ? factory.createElementAccessExpression( factory.createIdentifier('errors'), - factory.createStringLiteral(componentName), + factory.createStringLiteral(component.name), ) : factory.createPropertyAccessExpression( factory.createIdentifier('errors'), - factory.createIdentifier(componentName), + factory.createIdentifier(component.name), ); - attributes.push(buildOnChangeStatement(componentName, componentType, fieldConfig)); + attributes.push(buildOnChangeStatement(component, fieldConfig)); attributes.push( factory.createJsxAttribute( factory.createIdentifier('errorMessage'), @@ -317,9 +315,24 @@ export const addFormAttributes = ( ), ), ); + if (fieldConfig.isArray) { + attributes.push( + factory.createJsxAttribute( + factory.createIdentifier('value'), + factory.createJsxExpression( + undefined, + factory.createIdentifier(`current${capitalizeFirstLetter(component.name)}Value`), + ), + ), + factory.createJsxAttribute( + factory.createIdentifier('ref'), + factory.createJsxExpression(undefined, factory.createIdentifier(`${lowerCaseFirst(component.name)}Ref`)), + ), + ); + } } - if (componentName === 'SubmitButton') { + if (component.name === 'SubmitButton') { attributes.push( factory.createJsxAttribute( factory.createIdentifier('isDisabled'), @@ -355,8 +368,9 @@ export const addFormAttributes = ( ], undefined, factory.createToken(SyntaxKind.EqualsGreaterThanToken), - factory.createPropertyAccessExpression( + factory.createPropertyAccessChain( factory.createIdentifier('e'), + factory.createToken(SyntaxKind.QuestionDotToken), factory.createIdentifier('hasError'), ), ), @@ -366,7 +380,7 @@ export const addFormAttributes = ( ), ); } - if (componentName === 'CancelButton') { + if (component.name === 'CancelButton') { attributes.push( factory.createJsxAttribute( factory.createIdentifier('onClick'), @@ -398,7 +412,58 @@ export const addFormAttributes = ( return attributes; }; -export const buildOnChangeStatement = (fieldName: string, fieldType: string, fieldConfig: FieldConfigMetadata) => { +export const buildOnChangeStatement = ( + component: StudioComponent | StudioComponentChild, + fieldConfig: FieldConfigMetadata, +) => { + const { name: fieldName, componentType: fieldType } = component; + const { dataType, isArray } = fieldConfig; + if (isArray) { + return factory.createJsxAttribute( + factory.createIdentifier('onChange'), + factory.createJsxExpression( + undefined, + factory.createArrowFunction( + [factory.createModifier(SyntaxKind.AsyncKeyword)], + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('e'), + undefined, + undefined, + undefined, + ), + ], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createBlock( + [ + buildTargetVariable(fieldType, dataType), + factory.createExpressionStatement( + factory.createAwaitExpression( + factory.createCallExpression(factory.createIdentifier('runValidationTasks'), undefined, [ + factory.createStringLiteral(fieldName), + factory.createIdentifier('value'), + ]), + ), + ), + factory.createExpressionStatement( + factory.createCallExpression( + factory.createIdentifier(`setCurrent${capitalizeFirstLetter(fieldName)}Value`), + undefined, + [factory.createIdentifier('value')], + ), + ), + ], + true, + ), + ), + ), + ); + } return factory.createJsxAttribute( factory.createIdentifier('onChange'), factory.createJsxExpression( @@ -794,9 +859,13 @@ export const buildModelFieldObject = (fieldConfigs: Record - runValidationTasks(fieldName, modelFields[fieldName]) - ) + Object.keys(validations).reduce((promises, fieldName) => { + if (Array.isArray(modelFields[fieldName])) { + promises.push(...modelFields[fieldName].map(item => runValidationTasks(fieldName, item))); + } + promises.push(runValidationTasks(fieldName, modelFields[fieldName])) + return promises + }, []) ); if (validationResponses.some((r) => r.hasError)) { @@ -831,7 +900,7 @@ export const onSubmitValidationRun = [ undefined, [factory.createIdentifier('validations')], ), - factory.createIdentifier('map'), + factory.createIdentifier('reduce'), ), undefined, [ @@ -843,22 +912,122 @@ export const onSubmitValidationRun = [ undefined, undefined, undefined, - factory.createIdentifier('fieldName'), + factory.createIdentifier('promises'), + undefined, + undefined, + ), + factory.createParameterDeclaration( undefined, undefined, undefined, + factory.createIdentifier('fieldName'), + undefined, + undefined, ), ], undefined, factory.createToken(SyntaxKind.EqualsGreaterThanToken), - factory.createCallExpression(factory.createIdentifier('runValidationTasks'), undefined, [ - factory.createIdentifier('fieldName'), - factory.createElementAccessExpression( - factory.createIdentifier('modelFields'), - factory.createIdentifier('fieldName'), - ), - ]), + factory.createBlock( + [ + factory.createIfStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('Array'), + factory.createIdentifier('isArray'), + ), + undefined, + [ + factory.createElementAccessExpression( + factory.createIdentifier('modelFields'), + factory.createIdentifier('fieldName'), + ), + ], + ), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('promises'), + factory.createIdentifier('push'), + ), + undefined, + [ + factory.createSpreadElement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createElementAccessExpression( + factory.createIdentifier('modelFields'), + factory.createIdentifier('fieldName'), + ), + factory.createIdentifier('map'), + ), + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('item'), + undefined, + undefined, + ), + ], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createCallExpression( + factory.createIdentifier('runValidationTasks'), + undefined, + [ + factory.createIdentifier('fieldName'), + factory.createIdentifier('item'), + ], + ), + ), + ], + ), + ), + ], + ), + ), + factory.createReturnStatement(factory.createIdentifier('promises')), + ], + true, + ), + undefined, + ), + factory.createExpressionStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('promises'), + factory.createIdentifier('push'), + ), + undefined, + [ + factory.createCallExpression( + factory.createIdentifier('runValidationTasks'), + undefined, + [ + factory.createIdentifier('fieldName'), + factory.createElementAccessExpression( + factory.createIdentifier('modelFields'), + factory.createIdentifier('fieldName'), + ), + ], + ), + ], + ), + ), + factory.createReturnStatement(factory.createIdentifier('promises')), + ], + true, + ), ), + factory.createArrayLiteralExpression([], false), ], ), ], diff --git a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts index 432c1ca8a..2024c2de8 100644 --- a/packages/codegen-ui-react/lib/forms/react-form-renderer.ts +++ b/packages/codegen-ui-react/lib/forms/react-form-renderer.ts @@ -58,6 +58,7 @@ import { getDeclarationFilename, transpile, } from '../react-studio-template-renderer-helper'; +import { generateArrayFieldComponent } from '../utils/forms/array-field-component'; import { addUseEffectWrapper } from '../utils/generate-react-hooks'; import { RequiredKeys } from '../utils/type-utils'; import { @@ -68,7 +69,7 @@ import { buildValidations, runValidationTasksFunction, } from './form-renderer-helper'; -import { buildUseStateExpression, getUseStateHooks } from './form-state'; +import { buildUseStateExpression, capitalizeFirstLetter, getUseStateHooks } from './form-state'; import { generateOnValidationType, validationFunctionType, validationResponseType } from './type-helper'; export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< @@ -127,6 +128,12 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< importsText += result + EOL; }); + if (this.componentMetadata.formMetadata) { + if (Object.values(this.componentMetadata.formMetadata?.fieldConfigs).some(({ isArray }) => isArray)) { + printer.printNode(EmitHint.Unspecified, generateArrayFieldComponent(), file); + } + } + const wrappedFunction = this.renderFunctionWrapper(this.component.name, variableStatements, jsx, false); const result = printer.printNode(EmitHint.Unspecified, wrappedFunction, file); @@ -168,6 +175,13 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< componentText += propsPrinted; }); + if (this.componentMetadata.formMetadata) { + if (Object.values(this.componentMetadata.formMetadata?.fieldConfigs).some(({ isArray }) => isArray)) { + const arrayFieldComponent = printer.printNode(EmitHint.Unspecified, generateArrayFieldComponent(), file); + componentText += arrayFieldComponent; + } + } + const result = printer.printNode(EmitHint.Unspecified, wrappedFunction, file); componentText += result; @@ -367,6 +381,36 @@ export abstract class ReactFormTemplateRenderer extends StudioTemplateRenderer< } } + this.importCollection.addMappedImport(ImportValue.VALIDATE_FIELD); + // Add value state and ref array type fields in ArrayField wrapper + Object.entries(formMetadata.fieldConfigs).forEach(([field, config]) => { + if (config.isArray) { + statements.push( + buildUseStateExpression(`current${capitalizeFirstLetter(field)}Value`, factory.createStringLiteral('')), + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier(`${field}Ref`), + undefined, + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('React'), + factory.createIdentifier('createRef'), + ), + undefined, + [], + ), + ), + ], + NodeFlags.Const, + ), + ), + ); + } + }); statements.push(buildValidations(formMetadata.fieldConfigs)); statements.push(runValidationTasksFunction); diff --git a/packages/codegen-ui-react/lib/react-component-renderer.ts b/packages/codegen-ui-react/lib/react-component-renderer.ts index 264c0d916..d01a5b80c 100644 --- a/packages/codegen-ui-react/lib/react-component-renderer.ts +++ b/packages/codegen-ui-react/lib/react-component-renderer.ts @@ -36,6 +36,7 @@ import { getStateName, getSetStateName, hasChildrenProp, + isFixedPropertyWithValue, } from './react-component-render-helper'; import { buildOpeningElementControlEvents, @@ -44,6 +45,7 @@ import { } from './workflow'; import { ImportCollection, ImportSource, ImportValue } from './imports'; import { addFormAttributes } from './forms'; +import { renderArrayFieldComponent } from './utils/forms/array-field-component'; export class ReactComponentRenderer extends ComponentRendererBase< TPropIn, @@ -73,6 +75,23 @@ export class ReactComponentRenderer extends ComponentRendererBase< this.importCollection.addImport(ImportSource.UI_REACT, this.component.componentType); + // Add ArrayField wrapper to element if Array type + if (this.componentMetadata.formMetadata?.fieldConfigs[this.component.name]?.isArray) { + this.importCollection.addImport(ImportSource.UI_REACT, 'Icon'); + this.importCollection.addImport(ImportSource.UI_REACT, 'Badge'); + this.importCollection.addImport(ImportSource.UI_REACT, 'ScrollView'); + this.importCollection.addImport(ImportSource.UI_REACT, 'Divider'); + return renderArrayFieldComponent( + this.component.name, + `${ + isFixedPropertyWithValue(this.component.properties.label) + ? this.component.properties.label.value + : this.component.name + }`, + element, + ); + } + return element; } diff --git a/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts b/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts index ac1409c79..94a4ef0f8 100644 --- a/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts +++ b/packages/codegen-ui-react/lib/react-utils-studio-template-renderer.ts @@ -61,6 +61,7 @@ export class ReactUtilsStudioTemplateRenderer extends StudioTemplateRenderer< renderComponentInternal() { const { printer, file } = buildPrinter(this.fileName, this.renderConfig); const utilsStatements: (ts.VariableStatement | ts.TypeAliasDeclaration | ts.FunctionDeclaration)[] = []; + const skipReactImport = true; this.utils.forEach((util) => { if (util === 'validation') { @@ -71,16 +72,29 @@ export class ReactUtilsStudioTemplateRenderer extends StudioTemplateRenderer< utilsStatements.push(getFetchByPathNodeFunction()); } }); + utilsStatements.push(...generateFormatUtil()); - const { componentText } = transpile( - utilsStatements.map((util) => printer.printNode(EmitHint.Unspecified, util, file)).join(EOL), - this.renderConfig, - ); + let componentText = `/* eslint-disable */${EOL}`; + const imports = this.importCollection.buildImportStatements(skipReactImport); + imports.forEach((importStatement) => { + const result = printer.printNode(EmitHint.Unspecified, importStatement, file); + componentText += result + EOL; + }); + componentText += EOL; + + utilsStatements.forEach((util) => { + const result = printer.printNode(EmitHint.Unspecified, util, file); + componentText += result + EOL; + }); + + componentText += EOL; + + const { componentText: transpliedText } = transpile(componentText, this.renderConfig); return { - componentText, + componentText: transpliedText, renderComponentToFilesystem: async (outputPath: string) => { - await this.renderComponentToFilesystem(componentText)(this.fileName)(outputPath); + await this.renderComponentToFilesystem(transpliedText)(this.fileName)(outputPath); }, }; } diff --git a/packages/codegen-ui-react/lib/utils/forms/array-field-component.ts b/packages/codegen-ui-react/lib/utils/forms/array-field-component.ts new file mode 100644 index 000000000..defe73128 --- /dev/null +++ b/packages/codegen-ui-react/lib/utils/forms/array-field-component.ts @@ -0,0 +1,883 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"). + You may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { factory, JsxChild, JsxTagNamePropertyAccess, NodeFlags, SyntaxKind } from 'typescript'; +import { capitalizeFirstLetter } from '../../forms/form-state'; +import { lowerCaseFirst } from '../../helpers'; + +export const generateArrayFieldComponent = () => { + const iconPath = 'M10 10l5.09-5.09L10 10l5.09 5.09L10 10zm0 0L4.91 4.91 10 10l-5.09 5.09L10 10z'; + return factory.createFunctionDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('ArrayField'), + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createObjectBindingPattern([ + factory.createBindingElement( + undefined, + undefined, + factory.createIdentifier('defaultValues'), + factory.createArrayLiteralExpression([], false), + ), + factory.createBindingElement(undefined, undefined, factory.createIdentifier('onChange'), undefined), + factory.createBindingElement(undefined, undefined, factory.createIdentifier('inputFieldRef'), undefined), + factory.createBindingElement(undefined, undefined, factory.createIdentifier('children'), undefined), + factory.createBindingElement(undefined, undefined, factory.createIdentifier('hasError'), undefined), + factory.createBindingElement(undefined, undefined, factory.createIdentifier('setFieldValue'), undefined), + factory.createBindingElement(undefined, undefined, factory.createIdentifier('currentFieldValue'), undefined), + ]), + undefined, + undefined, + undefined, + ), + ], + undefined, + factory.createBlock( + [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createArrayBindingPattern([ + factory.createBindingElement( + undefined, + undefined, + factory.createIdentifier('selectedBadgeIndex'), + undefined, + ), + factory.createBindingElement( + undefined, + undefined, + factory.createIdentifier('setSelectedBadgeIndex'), + undefined, + ), + ]), + undefined, + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('React'), + factory.createIdentifier('useState'), + ), + undefined, + [], + ), + ), + ], + NodeFlags.Const, + ), + ), + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createArrayBindingPattern([ + factory.createBindingElement(undefined, undefined, factory.createIdentifier('items'), undefined), + factory.createBindingElement(undefined, undefined, factory.createIdentifier('setItems'), undefined), + ]), + undefined, + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('React'), + factory.createIdentifier('useState'), + ), + undefined, + [factory.createIdentifier('defaultValues')], + ), + ), + ], + NodeFlags.Const, + ), + ), + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('removeItem'), + undefined, + undefined, + factory.createArrowFunction( + [factory.createModifier(SyntaxKind.AsyncKeyword)], + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('removeIndex'), + undefined, + undefined, + ), + ], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createBlock( + [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('newItems'), + undefined, + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('items'), + factory.createIdentifier('filter'), + ), + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('value'), + undefined, + undefined, + ), + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('index'), + undefined, + undefined, + ), + ], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createBinaryExpression( + factory.createIdentifier('index'), + factory.createToken(SyntaxKind.ExclamationEqualsEqualsToken), + factory.createIdentifier('removeIndex'), + ), + ), + ], + ), + ), + ], + NodeFlags.Const, + ), + ), + factory.createExpressionStatement( + factory.createAwaitExpression( + factory.createCallExpression(factory.createIdentifier('onChange'), undefined, [ + factory.createIdentifier('newItems'), + ]), + ), + ), + factory.createExpressionStatement( + factory.createCallExpression(factory.createIdentifier('setSelectedBadgeIndex'), undefined, [ + factory.createIdentifier('undefined'), + ]), + ), + factory.createExpressionStatement( + factory.createCallExpression(factory.createIdentifier('setItems'), undefined, [ + factory.createIdentifier('newItems'), + ]), + ), + ], + true, + ), + ), + ), + ], + NodeFlags.Const, + ), + ), + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('addItem'), + undefined, + undefined, + factory.createArrowFunction( + [factory.createModifier(SyntaxKind.AsyncKeyword)], + undefined, + [], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createBlock( + [ + factory.createIfStatement( + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('currentFieldValue'), + factory.createIdentifier('length'), + ), + factory.createToken(SyntaxKind.AmpersandAmpersandToken), + factory.createPrefixUnaryExpression( + SyntaxKind.ExclamationToken, + factory.createIdentifier('hasError'), + ), + ), + factory.createBlock( + [ + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('newItems'), + undefined, + undefined, + factory.createArrayLiteralExpression( + [factory.createSpreadElement(factory.createIdentifier('items'))], + false, + ), + ), + ], + NodeFlags.Const, + ), + ), + factory.createIfStatement( + factory.createBinaryExpression( + factory.createIdentifier('selectedBadgeIndex'), + factory.createToken(SyntaxKind.ExclamationEqualsEqualsToken), + factory.createIdentifier('undefined'), + ), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createBinaryExpression( + factory.createElementAccessExpression( + factory.createIdentifier('newItems'), + factory.createIdentifier('selectedBadgeIndex'), + ), + factory.createToken(SyntaxKind.EqualsToken), + factory.createIdentifier('currentFieldValue'), + ), + ), + factory.createExpressionStatement( + factory.createCallExpression(factory.createIdentifier('setItems'), undefined, [ + factory.createIdentifier('newItems'), + ]), + ), + factory.createExpressionStatement( + factory.createCallExpression( + factory.createIdentifier('setSelectedBadgeIndex'), + undefined, + [factory.createIdentifier('undefined')], + ), + ), + ], + true, + ), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('newItems'), + factory.createIdentifier('push'), + ), + undefined, + [factory.createIdentifier('currentFieldValue')], + ), + ), + factory.createExpressionStatement( + factory.createCallExpression(factory.createIdentifier('setItems'), undefined, [ + factory.createIdentifier('newItems'), + ]), + ), + ], + true, + ), + ), + factory.createExpressionStatement( + factory.createAwaitExpression( + factory.createCallExpression(factory.createIdentifier('onChange'), undefined, [ + factory.createIdentifier('newItems'), + ]), + ), + ), + ], + true, + ), + undefined, + ), + ], + true, + ), + ), + ), + ], + NodeFlags.Const, + ), + ), + factory.createReturnStatement( + factory.createParenthesizedExpression( + factory.createJsxElement( + factory.createJsxOpeningElement( + factory.createPropertyAccessExpression( + factory.createIdentifier('React'), + factory.createIdentifier('Fragment'), + ) as JsxTagNamePropertyAccess, + undefined, + factory.createJsxAttributes([]), + ), + [ + factory.createJsxExpression(undefined, factory.createIdentifier('children')), + factory.createJsxElement( + factory.createJsxOpeningElement( + factory.createIdentifier('Flex'), + undefined, + factory.createJsxAttributes([ + factory.createJsxAttribute( + factory.createIdentifier('justifyContent'), + factory.createStringLiteral('flex-end'), + ), + ]), + ), + [ + factory.createJsxElement( + factory.createJsxOpeningElement( + factory.createIdentifier('Button'), + undefined, + factory.createJsxAttributes([ + factory.createJsxAttribute( + factory.createIdentifier('children'), + factory.createStringLiteral('Cancel'), + ), + factory.createJsxAttribute( + factory.createIdentifier('type'), + factory.createStringLiteral('button'), + ), + factory.createJsxAttribute( + factory.createIdentifier('onClick'), + factory.createJsxExpression( + undefined, + factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createCallExpression( + factory.createIdentifier('setFieldValue'), + undefined, + [factory.createStringLiteral('')], + ), + ), + ], + true, + ), + ), + ), + ), + ]), + ), + [], + factory.createJsxClosingElement(factory.createIdentifier('Button')), + ), + factory.createJsxElement( + factory.createJsxOpeningElement( + factory.createIdentifier('Button'), + undefined, + factory.createJsxAttributes([ + factory.createJsxAttribute( + factory.createIdentifier('children'), + factory.createStringLiteral('Save'), + ), + factory.createJsxAttribute( + factory.createIdentifier('variation'), + factory.createStringLiteral('primary'), + ), + factory.createJsxAttribute( + factory.createIdentifier('isDisabled'), + factory.createJsxExpression(undefined, factory.createIdentifier('hasError')), + ), + factory.createJsxAttribute( + factory.createIdentifier('onClick'), + factory.createJsxExpression(undefined, factory.createIdentifier('addItem')), + ), + ]), + ), + [], + factory.createJsxClosingElement(factory.createIdentifier('Button')), + ), + ], + factory.createJsxClosingElement(factory.createIdentifier('Flex')), + ), + factory.createJsxExpression( + undefined, + factory.createBinaryExpression( + factory.createPrefixUnaryExpression( + SyntaxKind.ExclamationToken, + factory.createPrefixUnaryExpression( + SyntaxKind.ExclamationToken, + factory.createPropertyAccessExpression( + factory.createIdentifier('items'), + factory.createIdentifier('length'), + ), + ), + ), + factory.createToken(SyntaxKind.AmpersandAmpersandToken), + factory.createJsxElement( + factory.createJsxOpeningElement( + factory.createIdentifier('ScrollView'), + undefined, + factory.createJsxAttributes([ + factory.createJsxAttribute( + factory.createIdentifier('height'), + factory.createStringLiteral('inherit'), + ), + factory.createJsxAttribute( + factory.createIdentifier('width'), + factory.createStringLiteral('inherit'), + ), + factory.createJsxAttribute( + factory.createIdentifier('maxHeight'), + factory.createJsxExpression(undefined, factory.createStringLiteral('7rem')), + ), + ]), + ), + [ + factory.createJsxExpression( + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('items'), + factory.createIdentifier('map'), + ), + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('value'), + undefined, + undefined, + ), + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('index'), + undefined, + undefined, + ), + ], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createBlock( + [ + factory.createReturnStatement( + factory.createParenthesizedExpression( + factory.createJsxElement( + factory.createJsxOpeningElement( + factory.createIdentifier('Badge'), + undefined, + factory.createJsxAttributes([ + factory.createJsxAttribute( + factory.createIdentifier('key'), + factory.createJsxExpression( + undefined, + factory.createIdentifier('index'), + ), + ), + factory.createJsxAttribute( + factory.createIdentifier('style'), + factory.createJsxExpression( + undefined, + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('cursor'), + factory.createStringLiteral('pointer'), + ), + factory.createPropertyAssignment( + factory.createIdentifier('alignItems'), + factory.createStringLiteral('center'), + ), + factory.createPropertyAssignment( + factory.createIdentifier('marginRight'), + factory.createNumericLiteral('3'), + ), + factory.createPropertyAssignment( + factory.createIdentifier('marginTop'), + factory.createNumericLiteral('3'), + ), + factory.createPropertyAssignment( + factory.createIdentifier('backgroundColor'), + factory.createConditionalExpression( + factory.createBinaryExpression( + factory.createIdentifier('index'), + factory.createToken(SyntaxKind.EqualsEqualsEqualsToken), + factory.createIdentifier('selectedBadgeIndex'), + ), + factory.createToken(SyntaxKind.QuestionToken), + factory.createStringLiteral('#B8CEF9'), + factory.createToken(SyntaxKind.ColonToken), + factory.createStringLiteral(''), + ), + ), + ], + true, + ), + ), + ), + factory.createJsxAttribute( + factory.createIdentifier('onClick'), + factory.createJsxExpression( + undefined, + factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createCallExpression( + factory.createIdentifier('setSelectedBadgeIndex'), + undefined, + [factory.createIdentifier('index')], + ), + ), + factory.createExpressionStatement( + factory.createCallExpression( + factory.createIdentifier('setFieldValue'), + undefined, + [ + factory.createElementAccessExpression( + factory.createIdentifier('items'), + factory.createIdentifier('index'), + ), + ], + ), + ), + factory.createExpressionStatement( + factory.createCallExpression( + factory.createPropertyAccessChain( + factory.createPropertyAccessChain( + factory.createIdentifier('inputFieldRef'), + factory.createToken(SyntaxKind.QuestionDotToken), + factory.createIdentifier('current'), + ), + factory.createToken(SyntaxKind.QuestionDotToken), + factory.createIdentifier('focus'), + ), + undefined, + [], + ), + ), + ], + true, + ), + ), + ), + ), + ]), + ), + [ + factory.createJsxExpression(undefined, factory.createIdentifier('value')), + factory.createJsxSelfClosingElement( + factory.createIdentifier('Icon'), + undefined, + factory.createJsxAttributes([ + factory.createJsxAttribute( + factory.createIdentifier('style'), + factory.createJsxExpression( + undefined, + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('cursor'), + factory.createStringLiteral('pointer'), + ), + factory.createPropertyAssignment( + factory.createIdentifier('paddingLeft'), + factory.createNumericLiteral('3'), + ), + factory.createPropertyAssignment( + factory.createIdentifier('width'), + factory.createNumericLiteral('20'), + ), + factory.createPropertyAssignment( + factory.createIdentifier('height'), + factory.createNumericLiteral('20'), + ), + ], + true, + ), + ), + ), + factory.createJsxAttribute( + factory.createIdentifier('viewBox'), + factory.createJsxExpression( + undefined, + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('width'), + factory.createNumericLiteral('20'), + ), + factory.createPropertyAssignment( + factory.createIdentifier('height'), + factory.createNumericLiteral('20'), + ), + ], + false, + ), + ), + ), + factory.createJsxAttribute( + factory.createIdentifier('paths'), + factory.createJsxExpression( + undefined, + factory.createArrayLiteralExpression( + [ + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('d'), + factory.createStringLiteral(iconPath), + ), + factory.createPropertyAssignment( + factory.createIdentifier('stroke'), + factory.createStringLiteral('black'), + ), + ], + true, + ), + ], + true, + ), + ), + ), + factory.createJsxAttribute( + factory.createIdentifier('ariaLabel'), + factory.createStringLiteral('button'), + ), + factory.createJsxAttribute( + factory.createIdentifier('onClick'), + factory.createJsxExpression( + undefined, + factory.createArrowFunction( + undefined, + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('event'), + undefined, + undefined, + ), + ], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('event'), + factory.createIdentifier('stopPropagation'), + ), + undefined, + [], + ), + ), + factory.createExpressionStatement( + factory.createCallExpression( + factory.createIdentifier('removeItem'), + undefined, + [factory.createIdentifier('index')], + ), + ), + ], + true, + ), + ), + ), + ), + ]), + ), + ], + factory.createJsxClosingElement(factory.createIdentifier('Badge')), + ), + ), + ), + ], + true, + ), + ), + ], + ), + ), + ], + factory.createJsxClosingElement(factory.createIdentifier('ScrollView')), + ), + ), + ), + factory.createJsxSelfClosingElement( + factory.createIdentifier('Divider'), + undefined, + factory.createJsxAttributes([ + factory.createJsxAttribute( + factory.createIdentifier('orientation'), + factory.createStringLiteral('horizontal'), + ), + factory.createJsxAttribute( + factory.createIdentifier('marginTop'), + factory.createJsxExpression(undefined, factory.createNumericLiteral('5')), + ), + ]), + ), + ], + factory.createJsxClosingElement( + factory.createPropertyAccessExpression( + factory.createIdentifier('React'), + factory.createIdentifier('Fragment'), + ) as JsxTagNamePropertyAccess, + ), + ), + ), + ), + ], + true, + ), + ); +}; +/* + { + setModelFields({ ...modelFields, breeds: items }); + setCurrentBreedsValue(''); + }} + currentBreedsValue = { currentBreedsValue } + hasError = { errors.breeds?.hasError } + setFieldValue = { setCurrentBreedsValue } + inputFieldRef={ breedsRef } + > + + + */ + +export const renderArrayFieldComponent = (fieldName: string, label: string, inputField: JsxChild) => + factory.createJsxElement( + factory.createJsxOpeningElement( + factory.createIdentifier('ArrayField'), + undefined, + factory.createJsxAttributes([ + factory.createJsxAttribute(factory.createIdentifier('label'), factory.createStringLiteral(label)), + factory.createJsxAttribute( + factory.createIdentifier('onChange'), + factory.createJsxExpression( + undefined, + factory.createArrowFunction( + [factory.createModifier(SyntaxKind.AsyncKeyword)], + undefined, + [ + factory.createParameterDeclaration( + undefined, + undefined, + undefined, + factory.createIdentifier('items'), + undefined, + undefined, + ), + ], + undefined, + factory.createToken(SyntaxKind.EqualsGreaterThanToken), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createCallExpression( + factory.createIdentifier(`set${capitalizeFirstLetter(fieldName)}`), + undefined, + [factory.createIdentifier('items')], + ), + ), + factory.createExpressionStatement( + factory.createCallExpression( + factory.createIdentifier(`setCurrent${capitalizeFirstLetter(fieldName)}Value`), + undefined, + [factory.createStringLiteral('')], + ), + ), + ], + true, + ), + ), + ), + ), + factory.createJsxAttribute( + factory.createIdentifier(`currentFieldValue`), + factory.createJsxExpression( + undefined, + factory.createIdentifier(`current${capitalizeFirstLetter(fieldName)}Value`), + ), + ), + factory.createJsxAttribute( + factory.createIdentifier('hasError'), + factory.createJsxExpression( + undefined, + factory.createPropertyAccessChain( + factory.createPropertyAccessExpression( + factory.createIdentifier('errors'), + factory.createIdentifier(fieldName), + ), + factory.createToken(SyntaxKind.QuestionDotToken), + factory.createIdentifier('hasError'), + ), + ), + ), + factory.createJsxAttribute( + factory.createIdentifier('setFieldValue'), + factory.createJsxExpression( + undefined, + factory.createIdentifier(`setCurrent${capitalizeFirstLetter(fieldName)}Value`), + ), + ), + factory.createJsxAttribute( + factory.createIdentifier('inputFieldRef'), + factory.createJsxExpression(undefined, factory.createIdentifier(`${lowerCaseFirst(fieldName)}Ref`)), + ), + ]), + ), + [inputField], + factory.createJsxClosingElement(factory.createIdentifier('ArrayField')), + ); diff --git a/packages/codegen-ui/example-schemas/datastore/input-gallery.json b/packages/codegen-ui/example-schemas/datastore/input-gallery.json index 8ae5d3af5..568555922 100644 --- a/packages/codegen-ui/example-schemas/datastore/input-gallery.json +++ b/packages/codegen-ui/example-schemas/datastore/input-gallery.json @@ -3,112 +3,119 @@ "InputGallery": { "name": "InputGallery", "fields": { - "id": { - "name": "id", - "isArray": false, - "type": "ID", - "isRequired": true, - "attributes": [] - }, - "num": { - "name": "num", - "isArray": false, - "type": "Int", - "isRequired": false, - "attributes": [] - }, - "rootbeer": { - "name": "rootbeer", - "isArray": false, - "type": "Float", - "isRequired": false, - "attributes": [] - }, - "attend": { - "name": "attend", - "isArray": false, - "type": "Boolean", - "isRequired": false, - "attributes": [] - }, - "maybeSlide": { - "name": "maybeSlide", - "isArray": false, - "type": "Boolean", - "isRequired": false, - "attributes": [] - }, - "maybeCheck": { - "name": "maybeCheck", - "isArray": false, - "type": "Boolean", - "isRequired": false, - "attributes": [] - }, - "timestamp": { - "name": "timestamp", - "isArray": false, - "type": "AWSTimestamp", - "isRequired": false, - "attributes": [] - }, - "ippy": { - "name": "ippy", - "isArray": false, - "type": "AWSIPAddress", - "isRequired": false, - "attributes": [] - }, - "timeisnow": { - "name": "timeisnow", - "isArray": false, - "type": "AWSTime", - "isRequired": false, - "attributes": [] - }, - "createdAt": { - "name": "createdAt", - "isArray": false, - "type": "AWSDateTime", - "isRequired": false, - "attributes": [], - "isReadOnly": true - }, - "updatedAt": { - "name": "updatedAt", - "isArray": false, - "type": "AWSDateTime", - "isRequired": false, - "attributes": [], - "isReadOnly": true - } + "id": { + "name": "id", + "isArray": false, + "type": "ID", + "isRequired": true, + "attributes": [] + }, + "num": { + "name": "num", + "isArray": false, + "type": "Int", + "isRequired": false, + "attributes": [] + }, + "rootbeer": { + "name": "rootbeer", + "isArray": false, + "type": "Float", + "isRequired": false, + "attributes": [] + }, + "attend": { + "name": "attend", + "isArray": false, + "type": "Boolean", + "isRequired": false, + "attributes": [] + }, + "maybeSlide": { + "name": "maybeSlide", + "isArray": false, + "type": "Boolean", + "isRequired": false, + "attributes": [] + }, + "maybeCheck": { + "name": "maybeCheck", + "isArray": false, + "type": "Boolean", + "isRequired": false, + "attributes": [] + }, + "arrayTypeField": { + "name": "arrayTypeField", + "isArray": true, + "type": "String", + "isRequired": false, + "attributes": [] + }, + "timestamp": { + "name": "timestamp", + "isArray": false, + "type": "AWSTimestamp", + "isRequired": false, + "attributes": [] + }, + "ippy": { + "name": "ippy", + "isArray": false, + "type": "AWSIPAddress", + "isRequired": false, + "attributes": [] + }, + "timeisnow": { + "name": "timeisnow", + "isArray": false, + "type": "AWSTime", + "isRequired": false, + "attributes": [] + }, + "createdAt": { + "name": "createdAt", + "isArray": false, + "type": "AWSDateTime", + "isRequired": false, + "attributes": [], + "isReadOnly": true + }, + "updatedAt": { + "name": "updatedAt", + "isArray": false, + "type": "AWSDateTime", + "isRequired": false, + "attributes": [], + "isReadOnly": true + } }, "syncable": true, "pluralName": "InputGalleries", "attributes": [ - { - "type": "model", - "properties": {} - }, - { - "type": "auth", - "properties": { - "rules": [ - { - "allow": "private", - "provider": "iam", - "operations": [ - "create", - "update", - "delete", - "read" - ] - } - ] + { + "type": "model", + "properties": {} + }, + { + "type": "auth", + "properties": { + "rules": [ + { + "allow": "private", + "provider": "iam", + "operations": [ + "create", + "update", + "delete", + "read" + ] } + ] } + } ] - } + } }, "enums": {}, "nonModels": {}, diff --git a/packages/codegen-ui/lib/__tests__/generate-form-definition/helpers/model-fields-configs.test.ts b/packages/codegen-ui/lib/__tests__/generate-form-definition/helpers/model-fields-configs.test.ts index f2bca94c2..ea8f77bba 100644 --- a/packages/codegen-ui/lib/__tests__/generate-form-definition/helpers/model-fields-configs.test.ts +++ b/packages/codegen-ui/lib/__tests__/generate-form-definition/helpers/model-fields-configs.test.ts @@ -29,7 +29,7 @@ describe('mapModelFieldsConfigs', () => { models: { Dog: { fields: { - name: { dataType: 'String', readOnly: false, required: false, isArray: false }, + name: { dataType: 'String', readOnly: false, required: false, isArray: true }, }, }, }, @@ -41,7 +41,7 @@ describe('mapModelFieldsConfigs', () => { expect(modelFieldsConfigs.name).toStrictEqual({ label: 'Name', dataType: 'String', - inputType: { type: 'TextField', required: false, readOnly: false, name: 'name', value: 'name' }, + inputType: { type: 'TextField', isArray: true, required: false, readOnly: false, name: 'name', value: 'name' }, }); }); @@ -121,6 +121,7 @@ describe('mapModelFieldsConfigs', () => { required: true, type: 'TextField', value: 'id', + isArray: false, }, label: 'Id', }, @@ -155,6 +156,7 @@ describe('mapModelFieldsConfigs', () => { required: false, type: 'TextField', value: 'name', + isArray: false, }, label: 'Name', }, @@ -195,6 +197,7 @@ describe('mapModelFieldsConfigs', () => { required: false, type: 'SelectField', value: 'ownerId', + isArray: false, }, label: 'Owner id', }, @@ -240,6 +243,7 @@ describe('mapModelFieldsConfigs', () => { { value: { value: nonEnglishAlphabetTest }, displayValue: { value: nonEnglishAlphabetTest } }, ], }, + isArray: false, }, label: 'City', }, @@ -298,15 +302,4 @@ describe('getFieldTypeMapKey', () => { }), ).toBe('NonModel'); }); - - it('should return `Array` if isArray is true', () => { - expect( - getFieldTypeMapKey({ - dataType: { nonModel: 'Misc' }, - readOnly: false, - required: false, - isArray: true, - }), - ).toBe('Array'); - }); }); diff --git a/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts b/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts index 162e3b306..9a50b8e34 100644 --- a/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts +++ b/packages/codegen-ui/lib/generate-form-definition/helpers/field-type-map.ts @@ -77,10 +77,6 @@ export const FIELD_TYPE_MAP: { defaultComponent: 'JSONField', supportedComponents: new Set(['TextField', 'JSONField']), }, - Array: { - defaultComponent: 'ArrayField', - supportedComponents: new Set(['ArrayField']), - }, AWSPhone: { defaultComponent: 'PhoneNumberField', supportedComponents: new Set(['PhoneNumberField']), diff --git a/packages/codegen-ui/lib/generate-form-definition/helpers/form-field.ts b/packages/codegen-ui/lib/generate-form-definition/helpers/form-field.ts index 666ea5411..2bc82af98 100644 --- a/packages/codegen-ui/lib/generate-form-definition/helpers/form-field.ts +++ b/packages/codegen-ui/lib/generate-form-definition/helpers/form-field.ts @@ -327,20 +327,6 @@ export function getFormDefinitionInputElement( }, }; break; - case 'ArrayField': - formDefinitionElement = { - componentType: 'ArrayField', - props: { - label: config.label || baseConfig?.label || FORM_DEFINITION_DEFAULTS.field.inputType.label, - descriptiveText: config.inputType?.descriptiveText ?? baseConfig?.inputType?.descriptiveText, - isRequired: isRequiredValue, - isReadOnly: getFirstDefinedValue([config.inputType?.readOnly, baseConfig?.inputType?.readOnly]), - placeholder: config.inputType?.placeholder || baseConfig?.inputType?.placeholder, - defaultValues: defaultStringValue ? [defaultStringValue] : undefined, - }, - }; - break; - default: throw new InvalidInputError(`componentType ${componentType} could not be mapped`); } diff --git a/packages/codegen-ui/lib/generate-form-definition/helpers/model-fields-configs.ts b/packages/codegen-ui/lib/generate-form-definition/helpers/model-fields-configs.ts index 1daa46d7a..a39507af6 100644 --- a/packages/codegen-ui/lib/generate-form-definition/helpers/model-fields-configs.ts +++ b/packages/codegen-ui/lib/generate-form-definition/helpers/model-fields-configs.ts @@ -29,10 +29,6 @@ import { import { FIELD_TYPE_MAP } from './field-type-map'; export function getFieldTypeMapKey(field: GenericDataField): FieldTypeMapKeys { - if (field.isArray) { - return 'Array'; - } - if (typeof field.dataType === 'object' && 'enum' in field.dataType) { return 'Enum'; } @@ -69,6 +65,7 @@ export function getFieldConfigFromModelField({ readOnly: field.readOnly, name: fieldName, value: fieldName, + isArray: field.isArray, }, }; diff --git a/packages/codegen-ui/lib/types/form/form-definition-element.ts b/packages/codegen-ui/lib/types/form/form-definition-element.ts index 5cbc61ee6..f856ff95d 100644 --- a/packages/codegen-ui/lib/types/form/form-definition-element.ts +++ b/packages/codegen-ui/lib/types/form/form-definition-element.ts @@ -154,18 +154,6 @@ export type FormDefinitionPasswordFieldElement = { }; }; -export type FormDefinitionArrayFieldElement = { - componentType: 'ArrayField'; - props: { - label: string; - descriptiveText?: string; - isRequired?: boolean; - isReadOnly?: boolean; - placeholder?: string; - defaultValues?: string[]; - }; -}; - export type FormDefinitionButtonElement = { name: string; componentType: 'Button'; @@ -188,7 +176,6 @@ export type FormDefinitionInputElement = ( | FormDefinitionCheckboxFieldElement | FormDefinitionRadioGroupFieldElement | FormDefinitionPasswordFieldElement - | FormDefinitionArrayFieldElement ) & FormDefinitionInputElementCommon; diff --git a/packages/codegen-ui/lib/types/form/form-definition.ts b/packages/codegen-ui/lib/types/form/form-definition.ts index 4113b0f28..501bc56ab 100644 --- a/packages/codegen-ui/lib/types/form/form-definition.ts +++ b/packages/codegen-ui/lib/types/form/form-definition.ts @@ -55,6 +55,5 @@ export type FieldTypeMapKeys = | 'AWSJSON' | 'AWSPhone' | 'Enum' - | 'Array' | 'Relationship' | 'NonModel'; diff --git a/packages/codegen-ui/lib/types/form/input-config.ts b/packages/codegen-ui/lib/types/form/input-config.ts index 584ac9328..9569d9cc2 100644 --- a/packages/codegen-ui/lib/types/form/input-config.ts +++ b/packages/codegen-ui/lib/types/form/input-config.ts @@ -63,4 +63,6 @@ export type StudioFieldInputConfig = { step?: number; value?: string; + + isArray?: boolean; }; diff --git a/packages/codegen-ui/lib/utils/form-component-metadata.ts b/packages/codegen-ui/lib/utils/form-component-metadata.ts index cfe591039..5a775c54a 100644 --- a/packages/codegen-ui/lib/utils/form-component-metadata.ts +++ b/packages/codegen-ui/lib/utils/form-component-metadata.ts @@ -21,6 +21,7 @@ import { FieldValidationConfiguration, FormDefinitionElement, FormDefinitionInputElement, + StudioFieldInputConfig, } from '../types'; export const getFormFieldStateName = (formName: string) => { @@ -40,7 +41,6 @@ export const mapFormMetadata = (form: StudioForm, formDefinition: FormDefinition const updatedConfigs = configs; const metadata: FieldConfigMetadata = { validationRules: [], - isArray: config.componentType === 'ArrayField', }; if ('validations' in config && config.validations) { metadata.validationRules = config.validations.map((validation) => { @@ -52,6 +52,12 @@ export const mapFormMetadata = (form: StudioForm, formDefinition: FormDefinition if ('dataType' in config && config.dataType) { metadata.dataType = config.dataType; } + if (form.fields[name] && 'inputType' in form.fields[name]) { + const { inputType } = form.fields[name] as { inputType: StudioFieldInputConfig }; + if (inputType.isArray) { + metadata.isArray = inputType.isArray; + } + } updatedConfigs[name] = metadata; return updatedConfigs; }, {}), diff --git a/packages/test-generator/lib/forms/custom-form-create-dog.json b/packages/test-generator/lib/forms/custom-form-create-dog.json index 0e9a91b37..86d0852d5 100644 --- a/packages/test-generator/lib/forms/custom-form-create-dog.json +++ b/packages/test-generator/lib/forms/custom-form-create-dog.json @@ -1,48 +1,48 @@ { - "id": "123", - "name": "CustomFormCreateDog", - "formActionType": "create", - "dataType": { - "dataSourceType": "Custom", - "dataTypeName": "Dog" - }, - "fields": { - "name": { - "label": "Name", - "inputType": { - "type": "TextField" - }, - "validations": [{"type": "GreaterThanChar", "numValues": ["1"], "validationMessage": "Name must be longer than 1 character"}] - }, - "age": { - "label": "Age", - "inputType": { - "type": "NumberField" - }, - "validations": [{"type": "GreaterThanNum","numValues": ["0"], "validationMessage": "Age must be greater than 0"}] - }, - "email": { - "label": "Email", - "inputType": { - "type": "EmailField" - } - }, - "ip": { - "label": "IP Address", - "inputType": { - "type": "IPAddressField", - "required": true - } - } - }, - "sectionalElements": { - "formHeading": { - "type": "Heading", - "position": {"fixed": "first"}, - "text": "Register your dog" - } - }, - "style": {}, - "cta": {}, - "schemaVersion": "1.0" - } \ No newline at end of file + "id": "123", + "name": "CustomFormCreateDog", + "formActionType": "create", + "dataType": { + "dataSourceType": "Custom", + "dataTypeName": "Dog" + }, + "fields": { + "name": { + "label": "Name", + "inputType": { + "type": "TextField" + }, + "validations": [{"type": "GreaterThanChar", "numValues": ["1"], "validationMessage": "Name must be longer than 1 character"}] + }, + "age": { + "label": "Age", + "inputType": { + "type": "NumberField" + }, + "validations": [{"type": "GreaterThanNum","numValues": ["0"], "validationMessage": "Age must be greater than 0"}] + }, + "email": { + "label": "Email", + "inputType": { + "type": "EmailField" + } + }, + "ip": { + "label": "IP Address", + "inputType": { + "type": "IPAddressField", + "required": true + } + } + }, + "sectionalElements": { + "formHeading": { + "type": "Heading", + "position": {"fixed": "first"}, + "text": "Register your dog" + } + }, + "style": {}, + "cta": {}, + "schemaVersion": "1.0" +} \ No newline at end of file diff --git a/packages/test-generator/lib/generators/NodeTestGenerator.ts b/packages/test-generator/lib/generators/NodeTestGenerator.ts index 8c2e89d4d..1cdca03d5 100644 --- a/packages/test-generator/lib/generators/NodeTestGenerator.ts +++ b/packages/test-generator/lib/generators/NodeTestGenerator.ts @@ -141,11 +141,11 @@ export class NodeTestGenerator extends TestGenerator { return indexRenderer.renderComponent(); } - writeUtilsFileToDisk(utils: string[]) { + writeUtilsFileToDisk(utils: UtilTemplateType[]) { this.utilsRendererManager.renderSchemaToTemplate(utils); } - renderUtilsFile(utils: string[]) { + renderUtilsFile(utils: UtilTemplateType[]) { const utilsRenderer = this.utilsRendererFactory.buildRenderer(utils); return utilsRenderer.renderComponent(); } diff --git a/packages/test-generator/lib/generators/TestGenerator.ts b/packages/test-generator/lib/generators/TestGenerator.ts index 19b792875..8d6f08587 100644 --- a/packages/test-generator/lib/generators/TestGenerator.ts +++ b/packages/test-generator/lib/generators/TestGenerator.ts @@ -162,7 +162,7 @@ export abstract class TestGenerator { } }; - const generateUtilsFile = (utils: string[]) => { + const generateUtilsFile = (utils: UtilTemplateType[]) => { try { if (this.params.writeToDisk) { this.writeUtilsFileToDisk(utils);