From 58c60ec70b60b4a1438f10e21298f70703f8d7f3 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Tue, 27 Feb 2024 10:16:29 -0600 Subject: [PATCH 01/10] Save working in the preview view, but not updating the UI yet --- .../FormManager/FormEdit/FormElementEdit.tsx | 19 ++++++++++++++----- .../src/FormManager/FormEdit/Preview.tsx | 2 +- .../design/src/FormManager/FormEdit/index.tsx | 1 + packages/design/src/FormManager/index.tsx | 2 +- packages/design/src/config/edit/index.ts | 2 +- packages/forms/src/config/elements/input.ts | 14 ++++++++------ packages/forms/src/config/index.ts | 2 ++ packages/forms/src/element.ts | 4 ++++ packages/forms/src/index.ts | 11 ++--------- packages/forms/src/pattern.ts | 2 +- 10 files changed, 35 insertions(+), 24 deletions(-) diff --git a/packages/design/src/FormManager/FormEdit/FormElementEdit.tsx b/packages/design/src/FormManager/FormEdit/FormElementEdit.tsx index a18d5553..0ca6e06d 100644 --- a/packages/design/src/FormManager/FormEdit/FormElementEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/FormElementEdit.tsx @@ -5,7 +5,9 @@ import { FormDefinition, FormElement, FormElementMap, + getFormElementConfig, updateElement, + validateElement, } from '@atj/forms'; import { FormEditUIContext } from '../../config'; @@ -31,13 +33,20 @@ export const FormElementEdit = ({
{ - const updatedForm = updateElement( + const elementConfig = getFormElementConfig( context.config, - initialForm, - formElement.id, - formData + formElement.type ); - //onChange(updatedForm); + const data = formData[formElement.id].data; + const result = elementConfig.parseConfigData(data); + if (!result.success) { + return; + } + const updatedForm = updateElement(initialForm, { + ...formElement, + data: result.data, + }); + onChange(updatedForm); })} > diff --git a/packages/design/src/FormManager/FormEdit/Preview.tsx b/packages/design/src/FormManager/FormEdit/Preview.tsx index 100bcba4..e76dc832 100644 --- a/packages/design/src/FormManager/FormEdit/Preview.tsx +++ b/packages/design/src/FormManager/FormEdit/Preview.tsx @@ -6,6 +6,7 @@ import { type FormElementId, type Pattern, createFormSession, + nullSession, } from '@atj/forms'; import Form, { @@ -26,7 +27,6 @@ export const PreviewForm = ({ uiContext, form }: PreviewFormProps) => { // don't have to regenerate it every time we render the form. components: createPreviewComponents(uiContext.components), }; - console.log('creating session'); const disposable = createFormSession(form); // nullSession instead? return
; }; diff --git a/packages/design/src/FormManager/FormEdit/index.tsx b/packages/design/src/FormManager/FormEdit/index.tsx index f6bee9ce..b68a9fa5 100644 --- a/packages/design/src/FormManager/FormEdit/index.tsx +++ b/packages/design/src/FormManager/FormEdit/index.tsx @@ -80,6 +80,7 @@ const EditForm = ({ formElement={formElement} onChange={function (form: FormDefinition): void { setCurrentForm(form); + onSave(form); }} /> )} diff --git a/packages/design/src/FormManager/index.tsx b/packages/design/src/FormManager/index.tsx index 4a570aa8..03d4f61d 100644 --- a/packages/design/src/FormManager/index.tsx +++ b/packages/design/src/FormManager/index.tsx @@ -37,7 +37,7 @@ export default function FormManager({ } return ( diff --git a/packages/design/src/config/edit/index.ts b/packages/design/src/config/edit/index.ts index 49c9f040..4dba3e26 100644 --- a/packages/design/src/config/edit/index.ts +++ b/packages/design/src/config/edit/index.ts @@ -8,7 +8,7 @@ import { import InputElementEdit from './InputElementEdit'; import SequenceElementEdit from './SequenceElementEdit'; -import { type ComponentForPattern } from 'config/view'; +import { ComponentForPattern } from '../../Form'; export type FormEditUIContext = { config: FormConfig; diff --git a/packages/forms/src/config/elements/input.ts b/packages/forms/src/config/elements/input.ts index 11df6ead..27a6ad55 100644 --- a/packages/forms/src/config/elements/input.ts +++ b/packages/forms/src/config/elements/input.ts @@ -6,12 +6,13 @@ import { type Pattern, type TextInputPattern } from '../../pattern'; import { getFormSessionValue } from '../../session'; import { safeZodParse } from '../../util/zod'; -export type InputElement = FormElement<{ - text: string; - initial: string; - required: boolean; - maxLength: number; -}>; +const configSchema = z.object({ + text: z.string(), + initial: z.string(), + required: z.boolean(), + maxLength: z.coerce.number(), +}); +type InputElement = FormElement>; const createSchema = (data: InputElement['data']) => z.string().max(data.maxLength); @@ -25,6 +26,7 @@ export const inputConfig: FormElementConfig = { maxLength: 128, }, parseData: (elementData, obj) => safeZodParse(createSchema(elementData), obj), + parseConfigData: obj => safeZodParse(configSchema, obj), getChildren() { return []; }, diff --git a/packages/forms/src/config/index.ts b/packages/forms/src/config/index.ts index e710daa3..fbb57c45 100644 --- a/packages/forms/src/config/index.ts +++ b/packages/forms/src/config/index.ts @@ -2,6 +2,7 @@ import { type FormElement, type FormElementId, type ParseFormElementData, + type ParseFormElementConfigData, } from '../element'; import { type CreatePrompt } from '../pattern'; @@ -11,6 +12,7 @@ export type FormElementConfig = { acceptsInput: boolean; initial: ThisFormElement['data']; parseData: ParseFormElementData; + parseConfigData: ParseFormElementConfigData; getChildren: ( element: ThisFormElement, elements: Record diff --git a/packages/forms/src/element.ts b/packages/forms/src/element.ts index 1d706ee9..40bf0753 100644 --- a/packages/forms/src/element.ts +++ b/packages/forms/src/element.ts @@ -27,6 +27,10 @@ export type ParseFormElementData = ( obj: string ) => Result; +export type ParseFormElementConfigData = ( + elementData: T['data'] +) => Result; + export const getFormElement: GetFormElement = (form, elementId) => { return form.elements[elementId]; }; diff --git a/packages/forms/src/index.ts b/packages/forms/src/index.ts index 90ec8e6a..6ce8ee62 100644 --- a/packages/forms/src/index.ts +++ b/packages/forms/src/index.ts @@ -194,21 +194,14 @@ export const updateElements = ( }; export const updateElement = ( - config: FormConfig, form: FormDefinition, - elementId: FormElementId, - data: FormElementMap + formElement: FormElement ): FormDefinition => { - if (form.elements[elementId] === undefined) { - console.error(`Element "${elementId}" does not exist on form.`); - return form; - } - const formElement = data[elementId]; return { ...form, elements: { ...form.elements, - [elementId]: formElement, + [formElement.id]: formElement, }, }; }; diff --git a/packages/forms/src/pattern.ts b/packages/forms/src/pattern.ts index 0d6c52c6..6ec36e73 100644 --- a/packages/forms/src/pattern.ts +++ b/packages/forms/src/pattern.ts @@ -48,7 +48,7 @@ export const createPrompt = ( session: FormSession, options: { validate: boolean } ): Prompt => { - if (sessionIsComplete(config, session)) { + if (options.validate && sessionIsComplete(config, session)) { return { actions: [], parts: [ From 8f12d3833af6f5b08ff9128f5970debf6658651e Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Tue, 27 Feb 2024 11:42:22 -0600 Subject: [PATCH 02/10] Update notion of a "prompt part" to include {prompt, children} --- packages/design/src/Form/index.tsx | 36 +++++++++-- .../FormManager/FormEdit/FormElementEdit.tsx | 1 - .../src/FormManager/FormEdit/Preview.tsx | 1 - .../design/src/FormManager/FormEdit/index.tsx | 3 - .../design/src/config/view/Sequence/index.tsx | 14 +++++ .../view/TextInput/TestInput.stories.tsx | 6 +- packages/design/src/config/view/index.tsx | 3 +- .../src/operations/submit-form.test.ts | 6 +- packages/forms/src/config/elements/input.ts | 9 +-- .../forms/src/config/elements/sequence.ts | 21 +++++-- packages/forms/src/pattern.ts | 59 +++++++++++-------- 11 files changed, 109 insertions(+), 50 deletions(-) create mode 100644 packages/design/src/config/view/Sequence/index.tsx diff --git a/packages/design/src/Form/index.tsx b/packages/design/src/Form/index.tsx index c44d590c..978e07dd 100644 --- a/packages/design/src/Form/index.tsx +++ b/packages/design/src/Form/index.tsx @@ -7,8 +7,9 @@ import { createPrompt, type FormConfig, type FormSession, - type Prompt, type Pattern, + type Prompt, + type PromptPart, } from '@atj/forms'; import ActionBar from './ActionBar'; @@ -26,6 +27,7 @@ export type ComponentForPattern> = Record< export type FormElementComponent> = React.ComponentType<{ prompt: T; + children?: React.ReactNode; }>; const usePrompt = ( @@ -102,13 +104,18 @@ export default function Form({ >
{prompt.parts - .map((pattern, index) => { - if (pattern.type === 'text') { - console.log('skipping', pattern.type); + .map((part, index) => { + if (part.pattern.type === 'text') { + console.log('skipping', part.pattern.type); return null; } - const Component = context.components[pattern.type]; - return ; + return ( + + ); }) .filter(a => a)} {/* Add submit button or other controls as needed */} @@ -118,3 +125,20 @@ export default function Form({ ); } + +const PromptComponent = ({ + context, + promptPart, +}: { + context: FormUIContext; + promptPart: PromptPart; +}) => { + const Component = context.components[promptPart.pattern.type]; + return ( + + {promptPart.children?.map((child, index) => ( + + ))} + + ); +}; diff --git a/packages/design/src/FormManager/FormEdit/FormElementEdit.tsx b/packages/design/src/FormManager/FormEdit/FormElementEdit.tsx index 0ca6e06d..3ea11d6a 100644 --- a/packages/design/src/FormManager/FormEdit/FormElementEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/FormElementEdit.tsx @@ -7,7 +7,6 @@ import { FormElementMap, getFormElementConfig, updateElement, - validateElement, } from '@atj/forms'; import { FormEditUIContext } from '../../config'; diff --git a/packages/design/src/FormManager/FormEdit/Preview.tsx b/packages/design/src/FormManager/FormEdit/Preview.tsx index e76dc832..5683c45f 100644 --- a/packages/design/src/FormManager/FormEdit/Preview.tsx +++ b/packages/design/src/FormManager/FormEdit/Preview.tsx @@ -6,7 +6,6 @@ import { type FormElementId, type Pattern, createFormSession, - nullSession, } from '@atj/forms'; import Form, { diff --git a/packages/design/src/FormManager/FormEdit/index.tsx b/packages/design/src/FormManager/FormEdit/index.tsx index b68a9fa5..056f7722 100644 --- a/packages/design/src/FormManager/FormEdit/index.tsx +++ b/packages/design/src/FormManager/FormEdit/index.tsx @@ -1,12 +1,9 @@ import React, { useState } from 'react'; -import { FormProvider, useForm } from 'react-hook-form'; import { type FormService } from '@atj/form-service'; import { type FormDefinition, - type FormElementMap, getRootFormElement, - updateElements, FormElementId, getFormElement, } from '@atj/forms'; diff --git a/packages/design/src/config/view/Sequence/index.tsx b/packages/design/src/config/view/Sequence/index.tsx new file mode 100644 index 00000000..92e92d75 --- /dev/null +++ b/packages/design/src/config/view/Sequence/index.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +import { type Pattern } from '@atj/forms'; +import { SequenceElement } from '@atj/forms/src/config/elements/sequence'; + +import { FormElementComponent } from '../../../Form'; + +const Sequence: FormElementComponent> = ({ + children, +}) => { + return <>{children}; +}; + +export default Sequence; diff --git a/packages/design/src/config/view/TextInput/TestInput.stories.tsx b/packages/design/src/config/view/TextInput/TestInput.stories.tsx index 63baff4c..ac81709d 100644 --- a/packages/design/src/config/view/TextInput/TestInput.stories.tsx +++ b/packages/design/src/config/view/TextInput/TestInput.stories.tsx @@ -3,7 +3,7 @@ import { FormProvider, useForm } from 'react-hook-form'; import type { Meta, StoryObj } from '@storybook/react'; import TextInput, { TextInputProps } from '.'; -import { Pattern, TextInputPattern } from '@atj/forms'; +import { type Pattern, type TextInputPattern } from '@atj/forms'; export default { title: 'patterns/TextInput', @@ -28,7 +28,7 @@ export const Required = { args: { prompt: { _elementId: '', - type: 'text', + type: 'input', inputId: 'test-prompt', value: '', label: 'Please enter your first name.', @@ -41,7 +41,7 @@ export const NotRequired = { args: { prompt: { _elementId: '', - type: 'text', + type: 'input', inputId: 'test-prompt', value: '', label: 'Please enter your first name.', diff --git a/packages/design/src/config/view/index.tsx b/packages/design/src/config/view/index.tsx index 86af50be..0627c03c 100644 --- a/packages/design/src/config/view/index.tsx +++ b/packages/design/src/config/view/index.tsx @@ -1,4 +1,5 @@ import FormSummary from './FormSummary'; +import Sequence from './Sequence'; import SubmissionConfirmation from './SubmissionConfirmation'; import TextInput from './TextInput'; @@ -6,7 +7,7 @@ import { type ComponentForPattern } from '../../Form'; export const defaultFormElementComponents: ComponentForPattern = { 'form-summary': FormSummary, - sequence: SubmissionConfirmation, + sequence: Sequence, 'submission-confirmation': SubmissionConfirmation, input: TextInput, }; diff --git a/packages/form-service/src/operations/submit-form.test.ts b/packages/form-service/src/operations/submit-form.test.ts index 86a408a4..c27d8038 100644 --- a/packages/form-service/src/operations/submit-form.test.ts +++ b/packages/form-service/src/operations/submit-form.test.ts @@ -4,7 +4,7 @@ import { createForm, createFormSession } from '@atj/forms'; import { createTestFormService } from '../context/test'; describe('submitForm', () => { - it('fails with empty form', async () => { + it('succeeds with empty form', async () => { const service = createTestFormService({ 'test-form': createForm({ title: 'test', description: 'description' }), }); @@ -15,8 +15,8 @@ describe('submitForm', () => { const session = createFormSession(formResult.data); const result = await service.submitForm(session, 'test-form', {}); expect(result).toEqual({ - success: false, - error: 'invalid action', + success: true, + data: [], }); }); }); diff --git a/packages/forms/src/config/elements/input.ts b/packages/forms/src/config/elements/input.ts index 27a6ad55..67db658f 100644 --- a/packages/forms/src/config/elements/input.ts +++ b/packages/forms/src/config/elements/input.ts @@ -30,7 +30,7 @@ export const inputConfig: FormElementConfig = { getChildren() { return []; }, - createPrompt(_, session, element, options): Pattern[] { + createPrompt(_, session, element, options) { const extraAttributes: Record = {}; const sessionValue = getFormSessionValue(session, element.id); if (options.validate) { @@ -39,8 +39,8 @@ export const inputConfig: FormElementConfig = { extraAttributes['error'] = isValidResult.error; } } - return [ - { + return { + pattern: { _elementId: element.id, type: 'input', inputId: element.id, @@ -49,6 +49,7 @@ export const inputConfig: FormElementConfig = { required: element.data.required, ...extraAttributes, } as Pattern, - ]; + children: [], + }; }, }; diff --git a/packages/forms/src/config/elements/sequence.ts b/packages/forms/src/config/elements/sequence.ts index 0fc17e7d..22f63e9a 100644 --- a/packages/forms/src/config/elements/sequence.ts +++ b/packages/forms/src/config/elements/sequence.ts @@ -11,6 +11,10 @@ export type SequenceElement = FormElement<{ const sequenceSchema = z.array(z.string()); +const configSchema = z.object({ + elements: z.array(z.string()), +}); + export const sequenceConfig: FormElementConfig = { acceptsInput: false, initial: { @@ -19,15 +23,22 @@ export const sequenceConfig: FormElementConfig = { parseData: (_, obj) => { return safeZodParse(sequenceSchema, obj); }, + parseConfigData: obj => safeZodParse(configSchema, obj), getChildren(element, elements) { return element.data.elements.map( (elementId: string) => elements[elementId] ); }, - createPrompt(config, session, element, options): Pattern[] { - return element.data.elements.flatMap((elementId: string) => { - const element = session.form.elements[elementId]; - return createPromptForElement(config, session, element, options); - }); + createPrompt(config, session, element, options) { + return { + pattern: { + _elementId: element.id, + type: 'sequence', + }, + children: element.data.elements.flatMap((elementId: string) => { + const element = session.form.elements[elementId]; + return createPromptForElement(config, session, element, options); + }), + }; }, }; diff --git a/packages/forms/src/pattern.ts b/packages/forms/src/pattern.ts index 6ec36e73..8436f2e8 100644 --- a/packages/forms/src/pattern.ts +++ b/packages/forms/src/pattern.ts @@ -38,9 +38,14 @@ export type SubmitAction = { }; export type PromptAction = SubmitAction; +export type PromptPart = { + pattern: Pattern; + children: PromptPart[]; +}; + export type Prompt = { actions: PromptAction[]; - parts: Pattern[]; + parts: PromptPart[]; }; export const createPrompt = ( @@ -53,30 +58,36 @@ export const createPrompt = ( actions: [], parts: [ { - _elementId: 'submission-confirmation', - type: 'submission-confirmation', - table: Object.entries(session.data.values).map( - ([elementId, value]) => { - return { - label: session.form.elements[elementId].id, - value: value, - }; - } - ), - } as Pattern, + pattern: { + _elementId: 'submission-confirmation', + type: 'submission-confirmation', + table: Object.entries(session.data.values).map( + ([elementId, value]) => { + return { + label: session.form.elements[elementId].id, + value: value, + }; + } + ), + } as Pattern, + children: [], + }, ], }; } - const parts: Pattern[] = [ + const parts: PromptPart[] = [ { - _elementId: 'form-summary', - type: 'form-summary', - title: session.form.summary.title, - description: session.form.summary.description, - } as Pattern, + pattern: { + _elementId: 'form-summary', + type: 'form-summary', + title: session.form.summary.title, + description: session.form.summary.description, + } as Pattern, + children: [], + }, ]; const root = getRootFormElement(session.form); - parts.push(...createPromptForElement(config, session, root, options)); + parts.push(createPromptForElement(config, session, root, options)); return { actions: [ { @@ -93,7 +104,7 @@ export type CreatePrompt = ( session: FormSession, element: T, options: { validate: boolean } -) => Pattern[]; +) => PromptPart; export const createPromptForElement: CreatePrompt = ( config, @@ -118,9 +129,11 @@ export const createNullPrompt = ({ }): Prompt => { const formElementConfig = getFormElementConfig(config, element.type); return { - parts: formElementConfig.createPrompt(config, nullSession, element, { - validate: false, - }), + parts: [ + formElementConfig.createPrompt(config, nullSession, element, { + validate: false, + }), + ], actions: [], }; }; From f80fb762d3de0c6f7a52e7a808496a5a360f8272 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Tue, 27 Feb 2024 12:44:23 -0600 Subject: [PATCH 03/10] In-progress --- .../FormManager/DocumentImporter/index.tsx | 3 +- .../FormList/PDFFileSelect/hooks.ts | 26 +++++-- .../src/config/edit/FormSummaryEdit.tsx | 37 ++++++++++ .../edit/SubmissionConfirmationEdit.tsx | 72 +++++++++++++++++++ packages/design/src/config/edit/index.ts | 7 +- .../forms/src/config/elements/form-summary.ts | 38 ++++++++++ packages/forms/src/config/elements/input.ts | 2 +- 7 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 packages/design/src/config/edit/FormSummaryEdit.tsx create mode 100644 packages/design/src/config/edit/SubmissionConfirmationEdit.tsx create mode 100644 packages/forms/src/config/elements/form-summary.ts diff --git a/packages/design/src/FormManager/DocumentImporter/index.tsx b/packages/design/src/FormManager/DocumentImporter/index.tsx index ed61a5d0..3a300059 100644 --- a/packages/design/src/FormManager/DocumentImporter/index.tsx +++ b/packages/design/src/FormManager/DocumentImporter/index.tsx @@ -9,9 +9,8 @@ import { createFormSession, } from '@atj/forms'; +import Form, { FormUIContext } from '../../Form'; import { onFileInputChangeGetFile } from '../FormList/PDFFileSelect/file-input'; -import Form from '../../Form'; -import { FormUIContext } from 'config'; const DocumentImporter = ({ baseUrl, diff --git a/packages/design/src/FormManager/FormList/PDFFileSelect/hooks.ts b/packages/design/src/FormManager/FormList/PDFFileSelect/hooks.ts index 63b4f84b..b3b8bcc8 100644 --- a/packages/design/src/FormManager/FormList/PDFFileSelect/hooks.ts +++ b/packages/design/src/FormManager/FormList/PDFFileSelect/hooks.ts @@ -2,8 +2,9 @@ import { useNavigate } from 'react-router-dom'; import { type Result } from '@atj/common'; import { addDocument } from '@atj/documents'; -import { createForm } from '@atj/forms'; +import { FormElement, createForm } from '@atj/forms'; import { type FormService } from '@atj/form-service'; +import { FormSummary } from '@atj/forms/src/config/elements/form-summary'; export const useDocumentImporter = ( formService: FormService, @@ -65,10 +66,25 @@ export const stepOneSelectPdfByUpload = async ( data: Uint8Array; } ): Promise> => { - const emptyForm = createForm({ - title: fileDetails.name, - description: '', - }); + const emptyForm = createForm( + { + title: fileDetails.name, + description: '', + }, + { + root: 'root', + elements: [ + { + type: 'form-summary', + id: 'form-summary', + data: { + title: fileDetails.name, + description: '', + }, + } as FormElement, + ], + } + ); const { updatedForm } = await addDocument(emptyForm, fileDetails); return ctx.formService.addForm(updatedForm); }; diff --git a/packages/design/src/config/edit/FormSummaryEdit.tsx b/packages/design/src/config/edit/FormSummaryEdit.tsx new file mode 100644 index 00000000..1092e298 --- /dev/null +++ b/packages/design/src/config/edit/FormSummaryEdit.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { useFormContext } from 'react-hook-form'; + +import { type FormSummary } from '@atj/forms/src/config/elements/form-summary'; + +import { FormElementEditComponent } from '..'; + +const FormSummaryEdit: FormElementEditComponent = ({ + element, +}) => { + const { register } = useFormContext(); + return ( +
+
+ +
+
+ +
+
+ ); +}; + +export default FormSummaryEdit; diff --git a/packages/design/src/config/edit/SubmissionConfirmationEdit.tsx b/packages/design/src/config/edit/SubmissionConfirmationEdit.tsx new file mode 100644 index 00000000..db5ec764 --- /dev/null +++ b/packages/design/src/config/edit/SubmissionConfirmationEdit.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { useFormContext } from 'react-hook-form'; + +import { type InputElement } from '@atj/forms/src/config/elements/input'; + +import { FormElementEditComponent } from '..'; + +const InputElementEdit: FormElementEditComponent = ({ + element, +}) => { + const { register } = useFormContext(); + return ( +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+ ); +}; + +export default InputElementEdit; diff --git a/packages/design/src/config/edit/index.ts b/packages/design/src/config/edit/index.ts index 4dba3e26..8f66bdbc 100644 --- a/packages/design/src/config/edit/index.ts +++ b/packages/design/src/config/edit/index.ts @@ -6,9 +6,11 @@ import { type FormElement, } from '@atj/forms'; +import { ComponentForPattern } from '../../Form'; + import InputElementEdit from './InputElementEdit'; import SequenceElementEdit from './SequenceElementEdit'; -import { ComponentForPattern } from '../../Form'; +import SubmissionConfirmationEdit from './SubmissionConfirmationEdit'; export type FormEditUIContext = { config: FormConfig; @@ -27,6 +29,7 @@ export type EditComponentForFormElement = Record>; export const defaultFormElementEditComponents: EditComponentForFormElement = { - sequence: SequenceElementEdit, input: InputElementEdit, + sequence: SequenceElementEdit, + 'submission-confirmation': SubmissionConfirmationEdit, }; diff --git a/packages/forms/src/config/elements/form-summary.ts b/packages/forms/src/config/elements/form-summary.ts new file mode 100644 index 00000000..4056510a --- /dev/null +++ b/packages/forms/src/config/elements/form-summary.ts @@ -0,0 +1,38 @@ +import * as z from 'zod'; + +import { type FormElementConfig } from '..'; +import { type FormElement } from '../../element'; +import { FormSummaryPattern, type Pattern } from '../../pattern'; +import { safeZodParse } from '../../util/zod'; + +const configSchema = z.object({ + title: z.string().max(128), + summary: z.string().max(2024), +}); +export type FormSummary = FormElement>; + +export const formSummaryConfig: FormElementConfig = { + acceptsInput: false, + initial: { + text: '', + initial: '', + required: true, + maxLength: 128, + }, + parseData: obj => safeZodParse(configSchema, obj), // make this optional? + parseConfigData: obj => safeZodParse(configSchema, obj), + getChildren() { + return []; + }, + createPrompt(_, session, element, options) { + return { + pattern: { + _elementId: element.id, + type: 'form-summary', + title: element.data.title, + description: element.data.description, + } as FormSummaryPattern, + children: [], + }; + }, +}; diff --git a/packages/forms/src/config/elements/input.ts b/packages/forms/src/config/elements/input.ts index 67db658f..83756dec 100644 --- a/packages/forms/src/config/elements/input.ts +++ b/packages/forms/src/config/elements/input.ts @@ -12,7 +12,7 @@ const configSchema = z.object({ required: z.boolean(), maxLength: z.coerce.number(), }); -type InputElement = FormElement>; +export type InputElement = FormElement>; const createSchema = (data: InputElement['data']) => z.string().max(data.maxLength); From 69ab8ffe9e413170b0cd61581890089271bb41d2 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Tue, 27 Feb 2024 22:45:59 -0600 Subject: [PATCH 04/10] Add output document to the mock form --- apps/spotlight/src/context.ts | 3 +- packages/documents/src/document.ts | 10 +++++- packages/documents/src/pdf/mock-api.ts | 42 ++++++++++++++++++++++---- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/apps/spotlight/src/context.ts b/apps/spotlight/src/context.ts index 4c0c8c12..4b943864 100644 --- a/apps/spotlight/src/context.ts +++ b/apps/spotlight/src/context.ts @@ -13,6 +13,7 @@ export type AppContext = { github: GithubRepository; formConfig: FormConfig; formService: FormService; + uswdsRoot: `${string}/`; }; let _context: AppContext | null = null; @@ -24,7 +25,7 @@ export const getAppContext = (): AppContext => { return _context; }; -const createAppContext = (env: any) => { +const createAppContext = (env: any): AppContext => { return { github: env.GITHUB, baseUrl: env.BASE_URL, diff --git a/packages/documents/src/document.ts b/packages/documents/src/document.ts index 8776b1f6..bcc0ae25 100644 --- a/packages/documents/src/document.ts +++ b/packages/documents/src/document.ts @@ -21,7 +21,7 @@ export const addDocument = async ( const cachedPdf = await getSuggestedFormElementsFromCache(fileDetails.data); if (cachedPdf) { - const updatedForm = addFormElements( + const withElements = addFormElements( form, [ ...cachedPdf.elements, @@ -39,6 +39,14 @@ export const addDocument = async ( ], 'root' ); + const updatedForm = addFormOutput(withElements, { + data: fileDetails.data, + path: fileDetails.name, + fields: cachedPdf.outputs, + formFields: Object.fromEntries( + Object.keys(fields).map(field => [field, field]) + ), + }); // TODO: add form outputs return { newFields: fields, diff --git a/packages/documents/src/pdf/mock-api.ts b/packages/documents/src/pdf/mock-api.ts index 146960ac..5a2c013b 100644 --- a/packages/documents/src/pdf/mock-api.ts +++ b/packages/documents/src/pdf/mock-api.ts @@ -1,6 +1,6 @@ import * as z from 'zod'; -import { type FormElement } from '@atj/forms'; +import { type DocumentFieldMap, type FormElement } from '@atj/forms'; import { type InputElement } from '@atj/forms/src/config/elements/input'; import json from './al_name_change.json' assert { type: 'json' }; @@ -81,15 +81,45 @@ const ExtractedObject = z.object({ raw_fields: z.discriminatedUnion('type', [RawTxField, RawBtnField]).array(), }); -type ExtractedJsonType = z.infer; +type ExtractedObject = z.infer; -export type ParsedPdf = { elements: FormElement[] }; +export type ParsedPdf = { + elements: FormElement[]; + outputs: DocumentFieldMap; // to populate FormOutput +}; export const parseAlabamaNameChangeForm = (): ParsedPdf => { - const parsedPDF: ExtractedJsonType = ExtractedObject.parse(json); - return { - elements: parsedPDF.elements.flatMap(getElementInputs), + const extracted: ExtractedObject = ExtractedObject.parse(json); + const parsedPdf: ParsedPdf = { + elements: [], + outputs: {}, }; + for (const element of extracted.elements) { + for (const input of element.inputs) { + if (input.input_type === 'Tx') { + const id = input.input_params.output_id.toLowerCase(); + parsedPdf.elements.push({ + type: 'input', + id, + default: {} as unknown as any, + data: { + text: input.input_params.instructions, + instructions: input.input_params.instructions, + }, + required: true, + } satisfies FormElement); + parsedPdf.outputs[id] = { + type: 'TextField', + name: input.input_params.text, + label: input.input_params.instructions, + value: '', + maxLength: 1024, + required: input.input_params.required, + }; + } + } + } + return parsedPdf; }; const getElementInputs = (element: ExtractedElement): FormElement[] => { From 0fb7b1aa6ef60fc42ee184046717db496131e143 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Wed, 28 Feb 2024 15:59:01 -0600 Subject: [PATCH 05/10] - PDF field mappings - working on drag/drop for preview page --- apps/spotlight/src/layouts/Layout.astro | 7 +- packages/design/sass/styles.scss | 22 +-- packages/design/src/Form/index.tsx | 28 ++-- .../FormManager/FormEdit/DraggableList.tsx | 128 ++++++++++++++++++ .../FormManager/FormEdit/FormElementEdit.tsx | 85 ++++++------ .../src/FormManager/FormEdit/Preview.tsx | 63 ++++++--- .../src/FormManager/FormEdit/context.tsx | 52 +++++++ .../design/src/FormManager/FormEdit/index.tsx | 83 ++++-------- .../src/config/edit/InputElementEdit.tsx | 14 ++ .../src/config/view/TextInput/index.tsx | 3 + packages/documents/src/pdf/mock-api.ts | 4 +- .../forms/src/config/elements/paragraph.ts | 9 +- .../forms/src/config/elements/sequence.ts | 11 +- packages/forms/src/element.ts | 16 +++ packages/forms/src/pattern.ts | 1 + 15 files changed, 374 insertions(+), 152 deletions(-) create mode 100644 packages/design/src/FormManager/FormEdit/DraggableList.tsx create mode 100644 packages/design/src/FormManager/FormEdit/context.tsx diff --git a/apps/spotlight/src/layouts/Layout.astro b/apps/spotlight/src/layouts/Layout.astro index bf87c867..4b87ba98 100644 --- a/apps/spotlight/src/layouts/Layout.astro +++ b/apps/spotlight/src/layouts/Layout.astro @@ -45,7 +45,10 @@ const context = getAppContext(); /> - + {title} @@ -54,7 +57,7 @@ const context = getAppContext();
-