mirror of
https://github.com/documenso/documenso.git
synced 2026-07-21 15:33:40 +10:00
fix(pdf): stabilize AcroForm imports
This commit is contained in:
@@ -31,7 +31,7 @@ import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DocumentStatus, FieldType, RecipientRole } from '@prisma/client';
|
||||
import { FileTextIcon, FormInputIcon, PencilIcon, SparklesIcon } from 'lucide-react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useRevalidator, useSearchParams } from 'react-router';
|
||||
import { isDeepEqual } from 'remeda';
|
||||
import { match } from 'ts-pattern';
|
||||
@@ -78,7 +78,8 @@ export const EnvelopeEditorFieldsPage = () => {
|
||||
|
||||
const scrollableContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { envelope, editorFields, navigateToStep, editorConfig } = useCurrentEnvelopeEditor();
|
||||
const { envelope, editorFields, navigateToStep, editorConfig, flushAutosave, syncEnvelope } =
|
||||
useCurrentEnvelopeEditor();
|
||||
|
||||
const { currentEnvelopeItem } = useCurrentEnvelopeRender();
|
||||
|
||||
@@ -86,12 +87,34 @@ export const EnvelopeEditorFieldsPage = () => {
|
||||
|
||||
const [isAiFieldDialogOpen, setIsAiFieldDialogOpen] = useState(false);
|
||||
const [isAiEnableDialogOpen, setIsAiEnableDialogOpen] = useState(false);
|
||||
const [acroFormHasFieldsByItemRevision, setAcroFormHasFieldsByItemRevision] = useState<Record<string, boolean>>({});
|
||||
|
||||
const { revalidate } = useRevalidator();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: importFieldsFromPdf, isPending: isImportingFieldsFromPdf } =
|
||||
trpc.envelope.field.importFromPdf.useMutation();
|
||||
|
||||
const currentEnvelopeItemRevision = currentEnvelopeItem
|
||||
? `${currentEnvelopeItem.id}:${currentEnvelopeItem.documentDataId}`
|
||||
: null;
|
||||
const currentItemHasAcroForm = currentEnvelopeItemRevision
|
||||
? acroFormHasFieldsByItemRevision[currentEnvelopeItemRevision] === true
|
||||
: false;
|
||||
|
||||
const onAcroFormDetected = useCallback(
|
||||
(hasFields: boolean) => {
|
||||
if (!currentEnvelopeItemRevision) {
|
||||
return;
|
||||
}
|
||||
|
||||
setAcroFormHasFieldsByItemRevision((prev) =>
|
||||
prev[currentEnvelopeItemRevision] === hasFields ? prev : { ...prev, [currentEnvelopeItemRevision]: hasFields },
|
||||
);
|
||||
},
|
||||
[currentEnvelopeItemRevision],
|
||||
);
|
||||
|
||||
const envelopeItemPermissions = useMemo(
|
||||
() => getEnvelopeItemPermissions(envelope, envelope.recipients),
|
||||
[envelope, envelope.recipients],
|
||||
@@ -160,6 +183,7 @@ export const EnvelopeEditorFieldsPage = () => {
|
||||
|
||||
const onImportFromPdfClick = async () => {
|
||||
try {
|
||||
await flushAutosave();
|
||||
const result = await importFieldsFromPdf({ envelopeId: envelope.id });
|
||||
|
||||
if (result.fieldsCreated === 0) {
|
||||
@@ -172,7 +196,7 @@ export const EnvelopeEditorFieldsPage = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await revalidate();
|
||||
await syncEnvelope();
|
||||
|
||||
toast({
|
||||
title: _(msg`Fields imported`),
|
||||
@@ -255,6 +279,7 @@ export const EnvelopeEditorFieldsPage = () => {
|
||||
customPageRenderer={EnvelopeEditorFieldsPageRenderer}
|
||||
scrollParentRef={scrollableContainerRef}
|
||||
errorMessage={PDF_VIEWER_ERROR_MESSAGES.editor}
|
||||
onAcroFormDetected={onAcroFormDetected}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-32">
|
||||
@@ -313,23 +338,6 @@ export const EnvelopeEditorFieldsPage = () => {
|
||||
selectedEnvelopeItemId={currentEnvelopeItem?.id ?? null}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="mt-4 w-full"
|
||||
onClick={() => void onImportFromPdfClick()}
|
||||
disabled={envelope.status !== DocumentStatus.DRAFT || isImportingFieldsFromPdf}
|
||||
title={
|
||||
envelope.status !== DocumentStatus.DRAFT
|
||||
? _(msg`You can only import fields in draft envelopes`)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<FormInputIcon className="mr-2 -ml-1 h-4 w-4" />
|
||||
{isImportingFieldsFromPdf ? <Trans>Importing...</Trans> : <Trans>Import from PDF form</Trans>}
|
||||
</Button>
|
||||
|
||||
{editorConfig.fields?.allowAIDetection && (
|
||||
<>
|
||||
<Button
|
||||
@@ -364,6 +372,20 @@ export const EnvelopeEditorFieldsPage = () => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{currentItemHasAcroForm && envelope.status === DocumentStatus.DRAFT && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="mt-4 w-full"
|
||||
onClick={() => void onImportFromPdfClick()}
|
||||
disabled={isImportingFieldsFromPdf}
|
||||
>
|
||||
<FormInputIcon className="mr-2 -ml-1 h-4 w-4" />
|
||||
{isImportingFieldsFromPdf ? <Trans>Importing...</Trans> : <Trans>Import from PDF form</Trans>}
|
||||
</Button>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Field details section. */}
|
||||
|
||||
@@ -16,7 +16,12 @@ export type EnvelopePdfViewerProps = {
|
||||
errorMessage: { title: MessageDescriptor; description: MessageDescriptor } | null;
|
||||
} & Omit<PDFViewerProps, 'data'>;
|
||||
|
||||
export const EnvelopePdfViewer = ({ errorMessage, className, ...props }: EnvelopePdfViewerProps) => {
|
||||
export const EnvelopePdfViewer = ({
|
||||
errorMessage,
|
||||
className,
|
||||
onAcroFormDetected,
|
||||
...props
|
||||
}: EnvelopePdfViewerProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const $el = useRef<HTMLDivElement>(null);
|
||||
@@ -46,10 +51,11 @@ export const EnvelopePdfViewer = ({ errorMessage, className, ...props }: Envelop
|
||||
|
||||
return (
|
||||
<PDFViewerLazy
|
||||
key={`${currentEnvelopeItem.envelopeId}-${currentEnvelopeItem.id}`}
|
||||
key={`${currentEnvelopeItem.envelopeId}-${currentEnvelopeItem.id}-${currentEnvelopeItem.documentDataId}`}
|
||||
{...props}
|
||||
className={cn('h-full w-full max-w-[800px]', className)}
|
||||
data={currentEnvelopeItem.data}
|
||||
onAcroFormDetected={onAcroFormDetected}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ export type PDFViewerProps = {
|
||||
scrollParentRef: ScrollTarget;
|
||||
|
||||
onDocumentLoad?: () => void;
|
||||
onAcroFormDetected?: (hasFields: boolean) => void;
|
||||
|
||||
/**
|
||||
* Additional component to render next to the image, such as a Konva canvas
|
||||
@@ -63,6 +64,7 @@ export default function PDFViewer({
|
||||
data,
|
||||
scrollParentRef,
|
||||
onDocumentLoad,
|
||||
onAcroFormDetected,
|
||||
customPageRenderer,
|
||||
...props
|
||||
}: PDFViewerProps) {
|
||||
@@ -124,6 +126,20 @@ export default function PDFViewer({
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
pdfRef.current = loadedPdf;
|
||||
|
||||
if (onAcroFormDetected) {
|
||||
try {
|
||||
const fieldObjects = await loadedPdf.getFieldObjects();
|
||||
|
||||
if (!isCancelled) {
|
||||
onAcroFormDetected(fieldObjects !== null && Object.keys(fieldObjects).length > 0);
|
||||
}
|
||||
} catch {
|
||||
if (!isCancelled) {
|
||||
onAcroFormDetected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the pages
|
||||
const pages = await pMap(Array.from({ length: loadedPdf.numPages }), async (_, pageIndex) => {
|
||||
const page = await loadedPdf.getPage(pageIndex + 1);
|
||||
@@ -168,7 +184,7 @@ export default function PDFViewer({
|
||||
pdfRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [data]);
|
||||
}, [data, onAcroFormDetected]);
|
||||
|
||||
// Notify when document is loaded
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user