From f4f9a276888df33682887029771d7a3af0fa9b5d Mon Sep 17 00:00:00 2001 From: Natasha Pierre-Louis Date: Sat, 17 Aug 2024 00:34:46 -0600 Subject: [PATCH 1/5] Added the ability to copy patterns and fieldsets --- apps/spotlight/src/env.d.ts | 1 + .../FormEdit/components/PageEdit.tsx | 2 +- .../PreviewSequencePattern/DraggableList.tsx | 5 +- .../components/common/PatternEditActions.tsx | 29 +- .../components/common/PatternEditForm.tsx | 1 + .../FormEdit/formEditStyles.module.css | 25 +- .../design/src/FormManager/FormEdit/store.ts | 17 + packages/forms/src/builder/builder.test.ts | 407 +++++++++++++++++- packages/forms/src/builder/index.ts | 12 + packages/forms/src/index.ts | 202 ++++++++- 10 files changed, 682 insertions(+), 19 deletions(-) diff --git a/apps/spotlight/src/env.d.ts b/apps/spotlight/src/env.d.ts index f964fe0cf..acef35f17 100644 --- a/apps/spotlight/src/env.d.ts +++ b/apps/spotlight/src/env.d.ts @@ -1 +1,2 @@ +/// /// diff --git a/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx b/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx index bca114689..d53fb9dcc 100644 --- a/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx @@ -37,7 +37,7 @@ export const PageEdit: PatternEditComponent = props => { > ) : (
diff --git a/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx b/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx index 69d6be55b..264e1f35f 100644 --- a/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx +++ b/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx @@ -45,6 +45,7 @@ export const DraggableList: React.FC = ({ return (
{ // Stop onFocus events from bubbling up to parent elements. event.stopPropagation(); @@ -120,7 +121,7 @@ const SortableItemOverlay = ({ return (
{ children; const context = useFormManagerStore(state => state.context); + const focusPatternId = useFormManagerStore(state => state.focus?.pattern.id); const { deleteSelectedPattern } = useFormManagerStore(state => ({ deleteSelectedPattern: state.deleteSelectedPattern, })); + const { copyPattern } = useFormManagerStore(state => ({ + copyPattern: state.copyPattern, + })); + const pages = useFormManagerStore(state => + Object.values(state.session.form.patterns).filter(p => p.type === 'page') + ); + const fieldsets = useFormManagerStore(state => + Object.values(state.session.form.patterns).filter( + p => p.type === 'fieldset' + ) + ); + const currentPageIndex = pages.findIndex(page => + page.data.patterns.includes(focusPatternId || '') + ); + const currentFieldsetIndex = fieldsets.findIndex(fieldset => + fieldset.data.patterns.includes(focusPatternId) + ); + const sourcePagePatternId = pages[currentPageIndex]?.id; + const sourceFieldsetPatternId = fieldsets[currentFieldsetIndex]?.id; + const handleCopyPattern = () => { + if (sourcePagePatternId && focusPatternId) { + copyPattern(sourcePagePatternId, focusPatternId); + } else if (sourceFieldsetPatternId && focusPatternId) { + copyPattern(sourceFieldsetPatternId, focusPatternId); + } + }; return ( <> @@ -31,7 +58,7 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => { className="usa-button--outline usa-button--unstyled" onClick={event => { event.preventDefault(); - alert('Unimplemented'); + handleCopyPattern(); }} >
{ updateActivePattern(formData); })} diff --git a/packages/design/src/FormManager/FormEdit/formEditStyles.module.css b/packages/design/src/FormManager/FormEdit/formEditStyles.module.css index fbec8d0d0..5ad723a78 100644 --- a/packages/design/src/FormManager/FormEdit/formEditStyles.module.css +++ b/packages/design/src/FormManager/FormEdit/formEditStyles.module.css @@ -47,34 +47,39 @@ /* Draggable List */ -.draggableListWrapper legend { +.draggableListItemWrapper legend { padding-left: 1.5rem; } -.draggableListWrapper .radioFormPattern legend { +.draggableListItemWrapper .radioFormPattern legend { padding-left: 0; } -.draggableListWrapper button { +.draggableListItemWrapper button { cursor: pointer; } -.draggableListWrapper:focus-within, -.draggableListWrapper button:not([disabled]):focus { +.draggableListItemWrapper:focus-within, +.draggableListItemWrapper button:not([disabled]):focus { outline: 0.25rem solid #783cb9; } -.draggableListWrapper [tabindex]:focus, -.draggableListWrapper:has(input:focus), -.draggableListWrapper:has(button:focus), -.draggableListWrapper:has(textarea:focus) { +.draggableListItemWrapper [tabindex]:focus, +.draggableListItemWrapper:has(input:focus), +.draggableListItemWrapper:has(button:focus), +.draggableListItemWrapper:has(textarea:focus) { outline: none; } -.draggableListWrapper .dropdownMenu { +.draggableListItemWrapper .dropdownMenu { margin-top: 5px; } +.draggableListItemWrapper p { + margin: 0; + padding: 1em 0; +} + /* Configure and Publish Pages */ .progressPage { min-height: 60vh; diff --git a/packages/design/src/FormManager/FormEdit/store.ts b/packages/design/src/FormManager/FormEdit/store.ts index 7db549c4c..ff432cc16 100644 --- a/packages/design/src/FormManager/FormEdit/store.ts +++ b/packages/design/src/FormManager/FormEdit/store.ts @@ -27,6 +27,7 @@ export type FormEditSlice = { addPattern: (patternType: string) => void; addPatternToFieldset: (patternType: string, targetPattern: PatternId) => void; clearFocus: () => void; + copyPattern: (parentPatternId: PatternId, patternId: PatternId) => void; deletePattern: (id: PatternId) => void; deleteSelectedPattern: () => void; setFocus: (patternId: PatternId) => boolean; @@ -75,6 +76,22 @@ export const createFormEditSlice = }); state.addNotification('success', 'Element added successfully.'); }, + + copyPattern: (parentPatternId, patternId) => { + const state = get(); + const builder = new BlueprintBuilder( + state.context.config, + state.session.form + ); + + const copyPattern = builder.copyPattern(parentPatternId, patternId); + set({ + session: mergeSession(state.session, { form: builder.form }), + focus: { pattern: copyPattern }, + }); + state.addNotification('success', 'Element copied successfully.'); + }, + addPatternToFieldset: (patternType, targetPattern) => { const state = get(); const builder = new BlueprintBuilder( diff --git a/packages/forms/src/builder/builder.test.ts b/packages/forms/src/builder/builder.test.ts index 96f4037bc..b0c123780 100644 --- a/packages/forms/src/builder/builder.test.ts +++ b/packages/forms/src/builder/builder.test.ts @@ -1,11 +1,14 @@ import { describe, expect, it } from 'vitest'; import { BlueprintBuilder } from '.'; -import { createForm, getPattern } from '..'; +import { createForm, getPattern, Pattern } from '..'; import { defaultFormConfig } from '../patterns'; import { type InputPattern } from '../patterns/input'; import { PageSetPattern } from '../patterns/page-set/config'; import { PagePattern } from '../patterns/page/config'; +import { FieldsetPattern } from '../patterns/fieldset'; +import { FormSummary } from '../patterns/form-summary'; +import { RadioGroupPattern } from '../patterns/radio-group'; describe('form builder', () => { it('addPattern adds initial pattern of given type', () => { @@ -28,6 +31,337 @@ describe('form builder', () => { }); }); + it('copy input pattern', () => { + const initial = createTestBlueprintMultipleFieldsets(); + const builder = new BlueprintBuilder(defaultFormConfig, initial); + const parentPattern = getPattern(initial, 'page-1'); + const updatedParentPattern = getPattern( + builder.form, + 'page-1' + ); + const pattern = getPattern(builder.form, 'element-1'); + expect(builder.form.patterns[pattern.id]).toEqual(pattern); + const newPattern = builder.copyPattern(parentPattern.id, pattern.id); + + expect(builder.form).toEqual({ + summary: { title: 'Test form', description: 'Test description' }, + root: 'root', + patterns: { + root: { type: 'page-set', id: 'root', data: { pages: ['page-1'] } }, + 'page-1': { + type: 'page', + id: 'page-1', + data: { + title: 'Page 1', + patterns: [ + 'element-1', + newPattern.id, + 'form-summary-1', + 'fieldset-1', + 'radio-group-1', + ], + }, + }, + 'element-1': { + type: 'input', + id: 'element-1', + data: { + label: 'Input Pattern', + initial: '', + required: true, + maxLength: 128, + }, + }, + 'form-summary-1': { + type: 'form-summary', + id: 'form-summary-1', + data: { + description: 'Form extended description', + title: 'Form title', + }, + }, + 'fieldset-1': { + type: 'fieldset', + id: 'fieldset-1', + data: { + legend: 'Fieldset pattern description', + patterns: ['element-2'], + }, + }, + 'radio-group-1': { + type: 'radio-group', + id: 'radio-group-1', + data: { + label: 'Radio group label', + options: [ + { id: 'option-1', label: 'Option 1' }, + { id: 'option-2', label: 'Option 2' }, + ], + }, + }, + [newPattern.id]: { + type: 'input', + id: newPattern.id, + data: { + label: expect.stringMatching(/^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Input Pattern/), + initial: '', + required: true, + maxLength: 128, + }, + }, + }, + outputs: [], + }); + }); + + it('copy form summary pattern', () => { + const initial = createTestBlueprintMultipleFieldsets(); + const builder = new BlueprintBuilder(defaultFormConfig, initial); + const parentPattern = getPattern(initial, 'page-1'); + const updatedParentPattern = getPattern( + builder.form, + 'page-1' + ); + const pattern = getPattern(builder.form, 'form-summary-1'); + expect(builder.form.patterns[pattern.id]).toEqual(pattern); + const newPattern = builder.copyPattern(parentPattern.id, pattern.id); + + expect(builder.form).toEqual({ + summary: { title: 'Test form', description: 'Test description' }, + root: 'root', + patterns: { + root: { type: 'page-set', id: 'root', data: { pages: ['page-1'] } }, + 'page-1': { + type: 'page', + id: 'page-1', + data: { + title: 'Page 1', + patterns: [ + 'element-1', + 'form-summary-1', + newPattern.id, + 'fieldset-1', + 'radio-group-1', + ], + }, + }, + 'element-1': { + type: 'input', + id: 'element-1', + data: { + label: 'Input Pattern', + initial: '', + required: true, + maxLength: 128, + }, + }, + 'form-summary-1': { + type: 'form-summary', + id: 'form-summary-1', + data: { + description: 'Form extended description', + title: 'Form title', + }, + }, + 'fieldset-1': { + type: 'fieldset', + id: 'fieldset-1', + data: { + legend: 'Fieldset pattern description', + patterns: ['element-2'], + }, + }, + 'radio-group-1': { + type: 'radio-group', + id: 'radio-group-1', + data: { + label: 'Radio group label', + options: [ + { id: 'option-1', label: 'Option 1' }, + { id: 'option-2', label: 'Option 2' }, + ], + }, + }, + [newPattern.id]: { + type: 'form-summary', + id: newPattern.id, + data: { + description: 'Form extended description', + title: expect.stringMatching(/^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Form title/), + }, + }, + }, + outputs: [], + }); + }); + + it('copy fieldset pattern', () => { + const initial = createTestBlueprintMultipleFieldsets(); + const builder = new BlueprintBuilder(defaultFormConfig, initial); + const parentPattern = getPattern(initial, 'page-1'); + const updatedParentPattern = getPattern( + builder.form, + 'page-1' + ); + const pattern = getPattern(builder.form, 'fieldset-1'); + expect(builder.form.patterns[pattern.id]).toEqual(pattern); + const newPattern = builder.copyPattern(parentPattern.id, pattern.id); + + expect(builder.form).toEqual({ + summary: { title: 'Test form', description: 'Test description' }, + root: 'root', + patterns: { + root: { type: 'page-set', id: 'root', data: { pages: ['page-1'] } }, + 'page-1': { + type: 'page', + id: 'page-1', + data: { + title: 'Page 1', + patterns: [ + 'element-1', + 'form-summary-1', + 'fieldset-1', + newPattern.id, + 'radio-group-1', + ], + }, + }, + 'element-1': { + type: 'input', + id: 'element-1', + data: { + label: 'Input Pattern', + initial: '', + required: true, + maxLength: 128, + }, + }, + 'form-summary-1': { + type: 'form-summary', + id: 'form-summary-1', + data: { + description: 'Form extended description', + title: 'Form title', + }, + }, + 'fieldset-1': { + type: 'fieldset', + id: 'fieldset-1', + data: { + legend: 'Fieldset pattern description', + patterns: ['element-2'], + }, + }, + 'radio-group-1': { + type: 'radio-group', + id: 'radio-group-1', + data: { + label: 'Radio group label', + options: [ + { id: 'option-1', label: 'Option 1' }, + { id: 'option-2', label: 'Option 2' }, + ], + }, + }, + [newPattern.id]: { + type: 'fieldset', + id: newPattern.id, + data: { + legend: expect.stringMatching(/^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Fieldset pattern description/), + patterns: ['element-2'], + }, + }, + }, + outputs: [], + }); + }); + + it('copy radio group pattern', () => { + const initial = createTestBlueprintMultipleFieldsets(); + const builder = new BlueprintBuilder(defaultFormConfig, initial); + const parentPattern = getPattern(initial, 'page-1'); + const updatedParentPattern = getPattern( + builder.form, + 'page-1' + ); + const pattern = getPattern(builder.form, 'radio-group-1'); + expect(builder.form.patterns[pattern.id]).toEqual(pattern); + const newPattern = builder.copyPattern(parentPattern.id, pattern.id); + + console.log(JSON.stringify(builder.form)); + + expect(builder.form).toEqual({ + summary: { title: 'Test form', description: 'Test description' }, + root: 'root', + patterns: { + root: { type: 'page-set', id: 'root', data: { pages: ['page-1'] } }, + 'page-1': { + type: 'page', + id: 'page-1', + data: { + title: 'Page 1', + patterns: [ + 'element-1', + 'form-summary-1', + 'fieldset-1', + 'radio-group-1', + newPattern.id, + ], + }, + }, + 'element-1': { + type: 'input', + id: 'element-1', + data: { + label: 'Input Pattern', + initial: '', + required: true, + maxLength: 128, + }, + }, + 'form-summary-1': { + type: 'form-summary', + id: 'form-summary-1', + data: { + description: 'Form extended description', + title: 'Form title', + }, + }, + 'fieldset-1': { + type: 'fieldset', + id: 'fieldset-1', + data: { + legend: 'Fieldset pattern description', + patterns: ['element-2'], + }, + }, + 'radio-group-1': { + type: 'radio-group', + id: 'radio-group-1', + data: { + label: 'Radio group label', + options: [ + { id: 'option-1', label: 'Option 1' }, + { id: 'option-2', label: 'Option 2' }, + ], + }, + }, + [newPattern.id]: { + type: 'radio-group', + id: newPattern.id, + data: { + label: expect.stringMatching(/^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Radio group label/), + options: [ + { id: 'option-1', label: 'Option 1' }, + { id: 'option-2', label: 'Option 2' }, + ], + }, + }, + }, + outputs: [], + }); + }); + it('removePattern removes pattern and sequence reference', () => { const initial = createTestBlueprint(); const builder = new BlueprintBuilder(defaultFormConfig, initial); @@ -108,3 +442,74 @@ export const createTestBlueprint = () => { } ); }; + +export const createTestBlueprintMultipleFieldsets = () => { + return createForm( + { + title: 'Test form', + description: 'Test description', + }, + { + root: 'root', + patterns: [ + { + type: 'page-set', + id: 'root', + data: { + pages: ['page-1'], + }, + } satisfies PageSetPattern, + { + type: 'page', + id: 'page-1', + data: { + title: 'Page 1', + patterns: [ + 'element-1', + 'form-summary-1', + 'fieldset-1', + 'radio-group-1', + ], + }, + } satisfies PagePattern, + { + type: 'input', + id: 'element-1', + data: { + label: 'Input Pattern', + initial: '', + required: true, + maxLength: 128, + }, + } satisfies InputPattern, + { + type: 'form-summary', + id: 'form-summary-1', + data: { + description: 'Form extended description', + title: 'Form title', + }, + } satisfies FormSummary, + { + type: 'fieldset', + id: 'fieldset-1', + data: { + legend: 'Fieldset pattern description', + patterns: ['element-2'], + }, + } satisfies FieldsetPattern, + { + type: 'radio-group', + id: 'radio-group-1', + data: { + label: 'Radio group label', + options: [ + { id: 'option-1', label: 'Option 1' }, + { id: 'option-2', label: 'Option 2' }, + ], + }, + } satisfies RadioGroupPattern, + ], + } + ); +}; diff --git a/packages/forms/src/builder/index.ts b/packages/forms/src/builder/index.ts index 12c297c52..db494406a 100644 --- a/packages/forms/src/builder/index.ts +++ b/packages/forms/src/builder/index.ts @@ -17,6 +17,7 @@ import { updatePatternFromFormData, createOnePageBlueprint, addPatternToFieldset, + copyPattern, } from '..'; import { type PageSetPattern } from '../patterns/page-set/config'; import { FieldsetPattern } from '../patterns/fieldset'; @@ -61,6 +62,17 @@ export class BlueprintBuilder { return pattern; } + copyPattern(parentPatternId: PatternId, patternId: PatternId) { + const pattern = getPattern(this.form, patternId); + const root = this.form.patterns[this.form.root] as PageSetPattern; + if (root.type !== 'page-set') { + throw new Error('expected root to be a page-set'); + } + const results = copyPattern(this.form, parentPatternId, patternId); + this.bp = results.bp; + return results.pattern; + } + addPatternToFieldset(patternType: string, fieldsetPatternId: PatternId) { const pattern = createDefaultPattern(this.config, patternType); const root = this.form.patterns[fieldsetPatternId] as FieldsetPattern; diff --git a/packages/forms/src/index.ts b/packages/forms/src/index.ts index 68edf48e5..c9cf6e76f 100644 --- a/packages/forms/src/index.ts +++ b/packages/forms/src/index.ts @@ -190,12 +190,26 @@ export const addPatterns = ( export const addPatternToPage = ( bp: Blueprint, pagePatternId: PatternId, - pattern: Pattern + pattern: Pattern, + index?: number ): Blueprint => { const pagePattern = bp.patterns[pagePatternId] as PagePattern; if (pagePattern.type !== 'page') { throw new Error('Pattern is not a page.'); } + + let updatedPagePattern: PatternId[]; + + if (index && index !== undefined) { + updatedPagePattern = [ + ...pagePattern.data.patterns.slice(0, index + 1), + pattern.id, + ...pagePattern.data.patterns.slice(index + 1), + ]; + } else { + updatedPagePattern = [...pagePattern.data.patterns, pattern.id]; + } + return { ...bp, patterns: { @@ -204,7 +218,7 @@ export const addPatternToPage = ( ...pagePattern, data: { ...pagePattern.data, - patterns: [...pagePattern.data.patterns, pattern.id], + patterns: updatedPagePattern, }, } satisfies SequencePattern, [pattern.id]: pattern, @@ -212,15 +226,195 @@ export const addPatternToPage = ( }; }; +export const copyPattern = ( + bp: Blueprint, + parentPatternId: PatternId, + patternId: PatternId +): { bp: Blueprint; pattern: Pattern } => { + const pattern = bp.patterns[patternId]; + if (!pattern) { + throw new Error(`Pattern with id ${patternId} not found`); + } + + const copySimplePattern = (pattern: Pattern): Pattern => { + const newId = generatePatternId(); + const currentDate = new Date(); + const dateString = currentDate.toLocaleString(); + const newPattern: Pattern = { + ...pattern, + id: newId, + data: { ...pattern.data }, + }; + + if (newPattern.type === 'form-summary') { + newPattern.data.title = `(Copy ${dateString}) ${newPattern.data.title || ''}`; + } else if ( + newPattern.type === 'input' || + newPattern.type === 'radio-group' || + newPattern.type === 'checkbox' + ) { + newPattern.data.label = `(Copy ${dateString}) ${newPattern.data.label || ''}`; + } else { + newPattern.data.text = `(Copy ${dateString}) ${newPattern.data.text || ''}`; + } + + return newPattern; + }; + + const copyFieldsetPattern = (pattern: Pattern): Pattern => { + const newId = generatePatternId(); + const currentDate = new Date(); + const dateString = currentDate.toLocaleString(); + const newPattern: Pattern = { + ...pattern, + id: newId, + data: { ...pattern.data }, + }; + + if (newPattern.type === 'fieldset') { + newPattern.data.legend = `(Copy ${dateString}) ${newPattern.data.legend || ''}`; + } + + return newPattern; + }; + + const findParentFieldset = ( + bp: Blueprint, + childId: PatternId + ): PatternId | null => { + for (const [id, pattern] of Object.entries(bp.patterns)) { + if ( + pattern.type === 'fieldset' && + pattern.data.patterns.includes(childId) + ) { + return id as PatternId; + } + } + return null; + }; + + const copyFieldsetContents = ( + bp: Blueprint, + originalFieldsetId: PatternId, + newFieldsetId: PatternId + ): Blueprint => { + const originalFieldset = bp.patterns[originalFieldsetId] as FieldsetPattern; + const newFieldset = bp.patterns[newFieldsetId] as FieldsetPattern; + let updatedBp = { ...bp }; + + const idMap = new Map(); + + for (const childId of originalFieldset.data.patterns) { + const childPattern = updatedBp.patterns[childId]; + if (childPattern) { + const newChildPattern = copyFieldsetPattern(childPattern); + idMap.set(childId, newChildPattern.id); + + updatedBp = { + ...updatedBp, + patterns: { + ...updatedBp.patterns, + [newChildPattern.id]: newChildPattern, + }, + }; + + if (childPattern.type === 'fieldset') { + updatedBp = copyFieldsetContents( + updatedBp, + childId, + newChildPattern.id + ); + } + } + } + + newFieldset.data.patterns = originalFieldset.data.patterns.map( + id => idMap.get(id) || id + ); + + updatedBp = { + ...updatedBp, + patterns: { + ...updatedBp.patterns, + [newFieldsetId]: newFieldset, + }, + }; + + return updatedBp; + }; + + let updatedBp = { ...bp }; + let newPattern = pattern; + + if (pattern.type === 'fieldset') { + newPattern = copyFieldsetPattern(pattern); + } else { + newPattern = copySimplePattern(pattern); + } + + const actualParentId = findParentFieldset(bp, patternId) || parentPatternId; + const actualParent = updatedBp.patterns[actualParentId]; + + if ( + !actualParent || + !actualParent.data || + !Array.isArray(actualParent.data.patterns) + ) { + throw new Error(`Invalid parent pattern with id ${actualParentId}`); + } + + const originalIndex = actualParent.data.patterns.indexOf(patternId); + if (originalIndex === -1) { + throw new Error( + `Pattern with id ${patternId} not found in parent's patterns` + ); + } + + actualParent.data.patterns = [ + ...actualParent.data.patterns.slice(0, originalIndex + 1), + newPattern.id, + ...actualParent.data.patterns.slice(originalIndex + 1), + ]; + + updatedBp = { + ...updatedBp, + patterns: { + ...updatedBp.patterns, + [newPattern.id]: newPattern, + [actualParentId]: actualParent, + }, + }; + + if (pattern.type === 'fieldset') { + updatedBp = copyFieldsetContents(updatedBp, patternId, newPattern.id); + } + + return { bp: updatedBp, pattern: newPattern }; +}; + export const addPatternToFieldset = ( bp: Blueprint, fieldsetPatternId: PatternId, - pattern: Pattern + pattern: Pattern, + index?: number ): Blueprint => { const fieldsetPattern = bp.patterns[fieldsetPatternId] as FieldsetPattern; if (fieldsetPattern.type !== 'fieldset') { throw new Error('Pattern is not a page.'); } + + let updatedPagePattern: PatternId[]; + + if (index && index !== undefined) { + updatedPagePattern = [ + ...fieldsetPattern.data.patterns.slice(0, index + 1), + pattern.id, + ...fieldsetPattern.data.patterns.slice(index + 1), + ]; + } else { + updatedPagePattern = [...fieldsetPattern.data.patterns, pattern.id]; + } + return { ...bp, patterns: { @@ -229,7 +423,7 @@ export const addPatternToFieldset = ( ...fieldsetPattern, data: { ...fieldsetPattern.data, - patterns: [...fieldsetPattern.data.patterns, pattern.id], + patterns: updatedPagePattern, }, } satisfies FieldsetPattern, [pattern.id]: pattern, From 0ed8dde2d4d4227bc92be13104d940846d7f8b51 Mon Sep 17 00:00:00 2001 From: Natasha Pierre-Louis Date: Sat, 17 Aug 2024 00:35:25 -0600 Subject: [PATCH 2/5] Updated the builder test --- packages/forms/src/builder/builder.test.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/forms/src/builder/builder.test.ts b/packages/forms/src/builder/builder.test.ts index b0c123780..54a9893e0 100644 --- a/packages/forms/src/builder/builder.test.ts +++ b/packages/forms/src/builder/builder.test.ts @@ -103,7 +103,9 @@ describe('form builder', () => { type: 'input', id: newPattern.id, data: { - label: expect.stringMatching(/^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Input Pattern/), + label: expect.stringMatching( + /^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Input Pattern/ + ), initial: '', required: true, maxLength: 128, @@ -187,7 +189,9 @@ describe('form builder', () => { id: newPattern.id, data: { description: 'Form extended description', - title: expect.stringMatching(/^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Form title/), + title: expect.stringMatching( + /^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Form title/ + ), }, }, }, @@ -219,7 +223,7 @@ describe('form builder', () => { title: 'Page 1', patterns: [ 'element-1', - 'form-summary-1', + 'form-summary-1', 'fieldset-1', newPattern.id, 'radio-group-1', @@ -267,7 +271,9 @@ describe('form builder', () => { type: 'fieldset', id: newPattern.id, data: { - legend: expect.stringMatching(/^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Fieldset pattern description/), + legend: expect.stringMatching( + /^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Fieldset pattern description/ + ), patterns: ['element-2'], }, }, @@ -302,7 +308,7 @@ describe('form builder', () => { title: 'Page 1', patterns: [ 'element-1', - 'form-summary-1', + 'form-summary-1', 'fieldset-1', 'radio-group-1', newPattern.id, @@ -350,7 +356,9 @@ describe('form builder', () => { type: 'radio-group', id: newPattern.id, data: { - label: expect.stringMatching(/^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Radio group label/), + label: expect.stringMatching( + /^\(\s*Copy\s+\d{1,2}\/\d{1,2}\/\d{4},\s+\d{1,2}:\d{2}:\d{2}\s+[AP]M\)\s*Radio group label/ + ), options: [ { id: 'option-1', label: 'Option 1' }, { id: 'option-2', label: 'Option 2' }, From b21de840baba754e0fe73f244200f275e5cda09b Mon Sep 17 00:00:00 2001 From: Natasha Pierre-Louis Date: Mon, 19 Aug 2024 18:44:10 -0600 Subject: [PATCH 3/5] Updated package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b00e2961a..7be8f5837 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "build": "turbo run build --filter=!infra-cdktf", "clean": "turbo run clean", - "dev": "turbo run dev --concurrency 14", + "dev": "turbo run dev --concurrency 18", "format": "prettier --write \"packages/*/src/**/*.{js,jsx,ts,tsx,scss}\"", "lint": "turbo run lint", "pages": "rm -rf node_modules && npm i -g pnpm turbo && pnpm i && pnpm build && ln -sf ./apps/spotlight/dist _site", From c1d30907e7f636c3e6ba1a6649dd143482ef7fec Mon Sep 17 00:00:00 2001 From: Natasha Pierre-Louis Date: Tue, 20 Aug 2024 21:45:23 -0600 Subject: [PATCH 4/5] Made updates per PR review request --- .../components/common/PatternEditActions.tsx | 30 +++++++++++-------- packages/forms/src/index.ts | 4 +-- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx b/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx index 6c4025c88..04292ac15 100644 --- a/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx +++ b/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx @@ -10,13 +10,13 @@ type PatternEditActionsProps = PropsWithChildren<{ export const PatternEditActions = ({ children }: PatternEditActionsProps) => { children; const context = useFormManagerStore(state => state.context); - const focusPatternId = useFormManagerStore(state => state.focus?.pattern.id); const { deleteSelectedPattern } = useFormManagerStore(state => ({ deleteSelectedPattern: state.deleteSelectedPattern, })); const { copyPattern } = useFormManagerStore(state => ({ copyPattern: state.copyPattern, })); + const focusPatternId = useFormManagerStore(state => state.focus?.pattern.id); const pages = useFormManagerStore(state => Object.values(state.session.form.patterns).filter(p => p.type === 'page') ); @@ -25,19 +25,23 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => { p => p.type === 'fieldset' ) ); - const currentPageIndex = pages.findIndex(page => - page.data.patterns.includes(focusPatternId || '') - ); - const currentFieldsetIndex = fieldsets.findIndex(fieldset => - fieldset.data.patterns.includes(focusPatternId) - ); - const sourcePagePatternId = pages[currentPageIndex]?.id; - const sourceFieldsetPatternId = fieldsets[currentFieldsetIndex]?.id; + const handleCopyPattern = () => { - if (sourcePagePatternId && focusPatternId) { - copyPattern(sourcePagePatternId, focusPatternId); - } else if (sourceFieldsetPatternId && focusPatternId) { - copyPattern(sourceFieldsetPatternId, focusPatternId); + const currentPageIndex = pages.findIndex(page => + page.data.patterns.includes(focusPatternId || '') + ); + const currentFieldsetIndex = fieldsets.findIndex(fieldset => + fieldset.data.patterns.includes(focusPatternId) + ); + const sourcePagePatternId = pages[currentPageIndex]?.id; + const sourceFieldsetPatternId = fieldsets[currentFieldsetIndex]?.id; + + if (focusPatternId) { + if (sourcePagePatternId) { + copyPattern(sourcePagePatternId, focusPatternId); + } else { + copyPattern(sourceFieldsetPatternId, focusPatternId); + } } }; diff --git a/packages/forms/src/index.ts b/packages/forms/src/index.ts index c9cf6e76f..231888c09 100644 --- a/packages/forms/src/index.ts +++ b/packages/forms/src/index.ts @@ -200,7 +200,7 @@ export const addPatternToPage = ( let updatedPagePattern: PatternId[]; - if (index && index !== undefined) { + if (index !== undefined) { updatedPagePattern = [ ...pagePattern.data.patterns.slice(0, index + 1), pattern.id, @@ -405,7 +405,7 @@ export const addPatternToFieldset = ( let updatedPagePattern: PatternId[]; - if (index && index !== undefined) { + if (index !== undefined) { updatedPagePattern = [ ...fieldsetPattern.data.patterns.slice(0, index + 1), pattern.id, From 8272a8e7f545ac43f38b9a7f820b51814ef89304 Mon Sep 17 00:00:00 2001 From: Natasha Pierre-Louis Date: Tue, 20 Aug 2024 23:44:48 -0600 Subject: [PATCH 5/5] Fixes for the merge with main branch --- .../design/src/FormManager/FormEdit/components/PageEdit.tsx | 2 +- .../components/PreviewSequencePattern/DraggableList.tsx | 1 - .../FormEdit/components/common/PatternEditActions.tsx | 3 --- .../FormEdit/components/common/PatternEditForm.tsx | 1 - .../design/src/FormManager/FormEdit/formEditStyles.module.css | 4 ++++ packages/forms/src/builder/builder.test.ts | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx b/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx index d53fb9dcc..bca114689 100644 --- a/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx +++ b/packages/design/src/FormManager/FormEdit/components/PageEdit.tsx @@ -37,7 +37,7 @@ export const PageEdit: PatternEditComponent = props => { > ) : (
diff --git a/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx b/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx index 264e1f35f..f2f6db406 100644 --- a/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx +++ b/packages/design/src/FormManager/FormEdit/components/PreviewSequencePattern/DraggableList.tsx @@ -45,7 +45,6 @@ export const DraggableList: React.FC = ({ return (
{ // Stop onFocus events from bubbling up to parent elements. event.stopPropagation(); diff --git a/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx b/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx index a95bb6ae7..faffc4cca 100644 --- a/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx +++ b/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx @@ -28,13 +28,11 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => { p => p.type === 'fieldset' && p.data.patterns.includes(focusPatternId) ); }, [focusPatternId, patterns]); - const isFieldset = focusPatternType === 'fieldset'; const isPagePattern = focusPatternType === 'page'; const { copyPattern } = useFormManagerStore(state => ({ copyPattern: state.copyPattern, })); - const focusPatternId = useFormManagerStore(state => state.focus?.pattern.id); const pages = useFormManagerStore(state => Object.values(state.session.form.patterns).filter(p => p.type === 'page') ); @@ -43,7 +41,6 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => { p => p.type === 'fieldset' ) ); - const handleCopyPattern = () => { const currentPageIndex = pages.findIndex(page => page.data.patterns.includes(focusPatternId || '') diff --git a/packages/design/src/FormManager/FormEdit/components/common/PatternEditForm.tsx b/packages/design/src/FormManager/FormEdit/components/common/PatternEditForm.tsx index 69cf59c4d..2ef7d4317 100644 --- a/packages/design/src/FormManager/FormEdit/components/common/PatternEditForm.tsx +++ b/packages/design/src/FormManager/FormEdit/components/common/PatternEditForm.tsx @@ -38,7 +38,6 @@ export const PatternEditForm = ({ return (
{ updateActivePattern(formData); })} diff --git a/packages/design/src/FormManager/FormEdit/formEditStyles.module.css b/packages/design/src/FormManager/FormEdit/formEditStyles.module.css index 2bbaefdab..d0ffe7b84 100644 --- a/packages/design/src/FormManager/FormEdit/formEditStyles.module.css +++ b/packages/design/src/FormManager/FormEdit/formEditStyles.module.css @@ -159,6 +159,10 @@ max-width: 15.625rem; } +.moveToPageWrapper p { + padding-top: 0.5rem; +} + .movePatternButton { color: #005ea2; } diff --git a/packages/forms/src/builder/builder.test.ts b/packages/forms/src/builder/builder.test.ts index 32eb0ff3b..23a48df63 100644 --- a/packages/forms/src/builder/builder.test.ts +++ b/packages/forms/src/builder/builder.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { BlueprintBuilder } from '.'; -import { createForm, getPattern, Pattern, Pattern } from '..'; +import { createForm, getPattern, Pattern } from '..'; import { defaultFormConfig } from '../patterns'; import { type InputPattern } from '../patterns/input'; import { PageSetPattern } from '../patterns/page-set/config';