diff --git a/apps/spotlight/public/sample-documents b/apps/spotlight/public/sample-documents
new file mode 120000
index 00000000..d48f4bfb
--- /dev/null
+++ b/apps/spotlight/public/sample-documents
@@ -0,0 +1 @@
+../../../packages/documents/samples
\ No newline at end of file
diff --git a/apps/spotlight/src/components/react/form-builder/document-importer.tsx b/apps/spotlight/src/components/react/form-builder/document-importer.tsx
index 821f936e..01281df1 100644
--- a/apps/spotlight/src/components/react/form-builder/document-importer.tsx
+++ b/apps/spotlight/src/components/react/form-builder/document-importer.tsx
@@ -1,49 +1,86 @@
import React, { PropsWithChildren, useReducer } from 'react';
-import { extractFormFieldData, suggestFormDetails } from '@atj/documents';
-import { SuggestedForm, UD105_TEST_DATA } from '@atj/documents';
+import {
+ DocumentFieldMap,
+ addDocumentFieldsToForm,
+ getDocumentFieldData,
+ suggestFormDetails,
+} from '@atj/documents';
import { onFileInputChangeGetFile } from '../../../lib/file-input';
import { FormView } from '../form/view';
+import { Form, createFormContext, createPrompt } from '@atj/forms';
+import { saveFormToStorage } from '../../../lib/form-repo';
+import { useNavigate } from 'react-router-dom';
-type State = { page: number; suggestedForm?: SuggestedForm };
+type State = {
+ page: number;
+ form: Form;
+ documentFields?: DocumentFieldMap;
+ previewForm?: Form;
+};
type Action =
- | { type: 'SELECT_PDF'; data: SuggestedForm }
| {
- type: 'SAVE_FORM_FIELDS';
- data: SuggestedForm;
+ type: 'SELECT_PDF';
+ data: DocumentFieldMap;
+ }
+ | {
+ type: 'PREVIEW_FORM';
+ data: DocumentFieldMap;
}
| {
type: 'GOTO_PAGE';
page: number;
};
-export const DocumentImporter = () => {
+export const DocumentImporter = ({
+ formId,
+ form,
+}: {
+ formId: string;
+ form: Form;
+}) => {
+ const navigate = useNavigate();
const [state, dispatch] = useReducer(
(state: State, action: Action) => {
if (action.type === 'SELECT_PDF') {
return {
page: 2,
- suggestedForm: action.data,
+ documentFields: action.data,
+ form: state.form,
};
}
- if (action.type === 'SAVE_FORM_FIELDS') {
+ if (action.type === 'PREVIEW_FORM') {
return {
page: 3,
- suggestedForm: action.data,
+ documentFields: action.data,
+ form: state.form,
};
}
if (action.type === 'GOTO_PAGE') {
return {
...state,
page: action.page,
+ form: state.form,
};
}
return state;
},
- { page: 1 }
+ {
+ page: 1,
+ form: form,
+ }
);
+ const selectDocumentByUrl = async (url: string) => {
+ const response = await fetch(url);
+ const blob = await response.blob();
+ const data = new Uint8Array(await blob.arrayBuffer());
+ const fieldData = await getDocumentFieldData(data);
+ const fieldInfo = suggestFormDetails(fieldData);
+ dispatch({ type: 'SELECT_PDF', data: fieldInfo });
+ };
+
const Step: React.FC<
PropsWithChildren<{ title: string; step: number; current: number }>
> = ({ children, title, step, current }) => {
@@ -103,7 +140,7 @@ export const DocumentImporter = () => {
type="file"
accept=".pdf"
onChange={onFileInputChangeGetFile(async fileDetails => {
- const fieldData = await extractFormFieldData(fileDetails.data);
+ const fieldData = await getDocumentFieldData(fileDetails.data);
const fieldInfo = suggestFormDetails(fieldData);
dispatch({ type: 'SELECT_PDF', data: fieldInfo });
})}
@@ -111,14 +148,26 @@ export const DocumentImporter = () => {
@@ -139,30 +188,42 @@ export const DocumentImporter = () => {
className="usa-form usa-form--large"
onSubmit={event => {
dispatch({
- type: 'SAVE_FORM_FIELDS',
- data: state.suggestedForm as SuggestedForm,
+ type: 'PREVIEW_FORM',
+ data: state.documentFields || {},
});
}}
>
- {/**/}
+ {/**/}
+
+ {Object.values(state.documentFields || {}).map((field, index) => {
+ return - {JSON.stringify(field)}
;
+ })}
+
);
};
const PreviewFormPage = () => {
+ const previewForm = addDocumentFieldsToForm(
+ form,
+ state.documentFields || {}
+ );
+ const formContext = createFormContext(previewForm);
+ const prompt = createPrompt(formContext);
return (
-
+ <>
+
+
+ >
);
};
diff --git a/apps/spotlight/src/components/react/form/import-document.tsx b/apps/spotlight/src/components/react/form/import-document.tsx
index 831d7ad4..b7bcece6 100644
--- a/apps/spotlight/src/components/react/form/import-document.tsx
+++ b/apps/spotlight/src/components/react/form/import-document.tsx
@@ -1,6 +1,5 @@
import React from 'react';
import { getFormFromStorage } from '../../../lib/form-repo';
-import { createFormContext, createPrompt } from '@atj/forms';
import { DocumentImporter } from '../form-builder/document-importer';
export const FormDocumentImport = ({ formId }: { formId: string }) => {
@@ -9,8 +8,5 @@ export const FormDocumentImport = ({ formId }: { formId: string }) => {
if (!form) {
return 'null form retrieved from storage';
}
- const context = createFormContext(form);
- const prompt = createPrompt(context);
-
- return ;
+ return ;
};
diff --git a/packages/documents/samples/alabama-name-change/ps-12.pdf b/packages/documents/samples/alabama-name-change/ps-12.pdf
new file mode 100644
index 00000000..0e6add9c
Binary files /dev/null and b/packages/documents/samples/alabama-name-change/ps-12.pdf differ
diff --git a/packages/documents/src/__tests__/extract.test.ts b/packages/documents/src/__tests__/extract.test.ts
index 42a1c1ae..1414e82f 100644
--- a/packages/documents/src/__tests__/extract.test.ts
+++ b/packages/documents/src/__tests__/extract.test.ts
@@ -1,12 +1,12 @@
import { describe, expect, it } from 'vitest';
-import { extractFormFieldData } from '..';
+import { getDocumentFieldData } from '..';
import { loadSamplePDF } from './sample-data';
describe('PDF form field extraction', () => {
it('extracts data from California UD-105 form', async () => {
const pdfBytes = await loadSamplePDF('ca-unlawful-detainer/ud105.pdf');
- const fields = await extractFormFieldData(pdfBytes);
+ const fields = await getDocumentFieldData(pdfBytes);
expect(fields).toEqual({
'UD-105[0].Page4[0].List4[0].Lia[0].Check47[0]': {
type: 'CheckBox',
diff --git a/packages/documents/src/__tests__/fill-pdf.test.ts b/packages/documents/src/__tests__/fill-pdf.test.ts
index d28b1a61..4c61e7d3 100644
--- a/packages/documents/src/__tests__/fill-pdf.test.ts
+++ b/packages/documents/src/__tests__/fill-pdf.test.ts
@@ -1,6 +1,6 @@
import { beforeAll, describe, expect, it } from 'vitest';
-import { extractFormFieldData, fillPDF } from '..';
+import { getDocumentFieldData, fillPDF } from '..';
import { loadSamplePDF } from './sample-data';
describe('PDF form filler', () => {
@@ -28,7 +28,7 @@ describe('PDF form filler', () => {
Weight: { type: 'TextField', value: 'weightField' },
})) as Success;
expect(result.success).toEqual(true);
- const fields = await extractFormFieldData(result.data);
+ const fields = await getDocumentFieldData(result.data);
expect(fields).toEqual({
'CHARACTER IMAGE': { type: 'not-supported', value: 'not-supported' },
diff --git a/packages/documents/src/__tests__/suggestions.test.ts b/packages/documents/src/__tests__/suggestions.test.ts
index 01241fa0..18222b9c 100644
--- a/packages/documents/src/__tests__/suggestions.test.ts
+++ b/packages/documents/src/__tests__/suggestions.test.ts
@@ -1,7 +1,6 @@
-import { describe, expect, it } from 'vitest';
+import { describe, it } from 'vitest';
-import { extractFormFieldData } from '..';
-import { loadSampleFields, loadSamplePDF } from './sample-data';
+import { loadSampleFields } from './sample-data';
describe('PDF form field extraction', () => {
it('extracts data from California UD-105 form', async () => {
diff --git a/packages/documents/src/document.ts b/packages/documents/src/document.ts
index d5a4b00d..7179eff1 100644
--- a/packages/documents/src/document.ts
+++ b/packages/documents/src/document.ts
@@ -1,3 +1,85 @@
+import { Form, Question, addQuestions } from '@atj/forms';
import { PDFDocument } from './pdf';
export type DocumentTemplate = PDFDocument;
+
+export type DocumentFieldValue =
+ | {
+ type: 'TextField';
+ name: string;
+ label: string;
+ value: string;
+ maxLength?: number;
+ required: boolean;
+ }
+ | {
+ type: 'CheckBox';
+ name: string;
+ label: string;
+ value: boolean;
+ required: boolean;
+ }
+ | {
+ type: 'Dropdown';
+ name: string;
+ label: string;
+ value: string[];
+ required: boolean;
+ }
+ | {
+ type: 'OptionList';
+ name: string;
+ label: string;
+ value: string[];
+ required: boolean;
+ }
+ | {
+ type: 'not-supported';
+ name: string;
+ error: string;
+ };
+
+export type DocumentFieldMap = Record;
+
+export const addDocumentFieldsToForm = (
+ form: Form,
+ fields: DocumentFieldMap
+) => {
+ const questions: Question[] = [];
+ Object.entries(fields).map(([key, field]) => {
+ if (field.type === 'CheckBox') {
+ questions.push({
+ id: field.name,
+ text: field.label,
+ initial: field.value,
+ required: field.required,
+ });
+ } else if (field.type === 'OptionList') {
+ questions.push({
+ id: field.name,
+ text: field.label,
+ initial: field.value,
+ required: field.required,
+ });
+ } else if (field.type === 'Dropdown') {
+ questions.push({
+ id: field.name,
+ text: field.label,
+ initial: field.value,
+ required: field.required,
+ });
+ } else if (field.type === 'TextField') {
+ questions.push({
+ id: field.name,
+ text: field.label,
+ initial: field.value,
+ required: field.required,
+ });
+ } else if (field.type === 'not-supported') {
+ console.error(`Skipping field: ${field.error}`);
+ } else {
+ const _exhaustiveCheck: never = field;
+ }
+ });
+ return addQuestions(form, questions);
+};
diff --git a/packages/documents/src/index.ts b/packages/documents/src/index.ts
index deb22a6c..d846d4a5 100644
--- a/packages/documents/src/index.ts
+++ b/packages/documents/src/index.ts
@@ -1,2 +1,3 @@
+export * from './document';
export * from './pdf';
export * from './suggestions';
diff --git a/packages/documents/src/pdf/extract.ts b/packages/documents/src/pdf/extract.ts
index 32a4ce34..ae7ac08d 100644
--- a/packages/documents/src/pdf/extract.ts
+++ b/packages/documents/src/pdf/extract.ts
@@ -1,8 +1,10 @@
import * as pdfLib from 'pdf-lib';
-import { type PDFFieldType } from '.';
+import { DocumentFieldMap, DocumentFieldValue } from '../document';
-export const extractFormFieldData = async (pdfBytes: Uint8Array) => {
+export const getDocumentFieldData = async (
+ pdfBytes: Uint8Array
+): Promise => {
const pdfDoc = await pdfLib.PDFDocument.load(pdfBytes);
const form = pdfDoc.getForm();
const fields = form.getFields();
@@ -13,33 +15,45 @@ export const extractFormFieldData = async (pdfBytes: Uint8Array) => {
);
};
-const getFieldValue = (
- field: pdfLib.PDFField
-): { type: PDFFieldType | 'not-supported'; value: any } => {
+const getFieldValue = (field: pdfLib.PDFField): DocumentFieldValue => {
if (field instanceof pdfLib.PDFTextField) {
return {
type: 'TextField',
- value: field.getText(),
+ name: field.getName(),
+ label: field.getName(),
+ value: field.getText() || '',
+ maxLength: field.getMaxLength(),
+ required: field.isRequired(),
};
} else if (field instanceof pdfLib.PDFCheckBox) {
return {
type: 'CheckBox',
+ name: field.getName(),
+ label: field.getName(),
value: field.isChecked(),
+ required: field.isRequired(),
};
} else if (field instanceof pdfLib.PDFDropdown) {
return {
type: 'Dropdown',
+ name: field.getName(),
+ label: field.getName(),
value: field.getSelected(),
+ required: field.isRequired(),
};
} else if (field instanceof pdfLib.PDFOptionList) {
return {
type: 'OptionList',
+ name: field.getName(),
+ label: field.getName(),
value: field.getSelected(),
+ required: field.isRequired(),
};
} else {
return {
type: 'not-supported',
- value: 'not-supported',
+ name: field.getName(),
+ error: `unsupported type: ${field.constructor.name}`,
};
}
};
diff --git a/packages/documents/src/pdf/index.ts b/packages/documents/src/pdf/index.ts
index 3bd27f07..670b018e 100644
--- a/packages/documents/src/pdf/index.ts
+++ b/packages/documents/src/pdf/index.ts
@@ -1,4 +1,4 @@
-export { extractFormFieldData } from './extract';
+export { getDocumentFieldData } from './extract';
export { fillPDF } from './generate';
export { generateDummyPDF } from './generate-dummy';
diff --git a/packages/documents/src/suggestions.ts b/packages/documents/src/suggestions.ts
index 6277d50d..cb070b91 100644
--- a/packages/documents/src/suggestions.ts
+++ b/packages/documents/src/suggestions.ts
@@ -1,3 +1,5 @@
+import { DocumentFieldMap } from './document';
+
export type SuggestedForm = {
id: string;
tag: 'input' | 'textarea';
@@ -6,11 +8,15 @@ export type SuggestedForm = {
value?: string;
type?: 'text';
}[];
-export const suggestFormDetails = (docData: any) => {
+export const suggestFormDetails = (
+ docData: DocumentFieldMap
+): DocumentFieldMap => {
+ /*
const cache = getFakeCache();
const hash = getObjectHash(docData);
const data = cache.get(hash);
- return data as SuggestedForm;
+ */
+ return docData;
};
const getFakeCache = () => {
diff --git a/packages/forms/src/index.ts b/packages/forms/src/index.ts
index 9f9266a5..575af691 100644
--- a/packages/forms/src/index.ts
+++ b/packages/forms/src/index.ts
@@ -5,7 +5,7 @@ type QuestionId = string;
export type Question = {
id: QuestionId;
text: string;
- initial: string;
+ initial: string | boolean | string[]; // TODO: create separate types
required: boolean;
};
type QuestionValue = any;