mirror of
https://github.com/documenso/documenso.git
synced 2026-07-14 23:07:13 +10:00
fix: refactor how non-s3 pdfs are loaded
This commit is contained in:
@@ -108,10 +108,10 @@ export const DocumentCertificateQRView = ({
|
||||
version="current"
|
||||
envelope={{
|
||||
id: envelopeItems[0].envelopeId,
|
||||
envelopeItems,
|
||||
status: DocumentStatus.COMPLETED,
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
}}
|
||||
envelopeItems={envelopeItems}
|
||||
token={token}
|
||||
>
|
||||
<DocumentCertificateQrV2
|
||||
|
||||
@@ -205,6 +205,7 @@ export const EnvelopeEditorPreviewPage = () => {
|
||||
<EnvelopeRenderProvider
|
||||
version="current"
|
||||
envelope={envelope}
|
||||
envelopeItems={envelope.envelopeItems}
|
||||
token={undefined}
|
||||
fields={fieldsWithPlaceholders}
|
||||
recipients={envelope.recipients.map((recipient) => ({
|
||||
|
||||
@@ -157,6 +157,7 @@ export default function DocumentPage({ params }: Route.ComponentProps) {
|
||||
<EnvelopeRenderProvider
|
||||
version="current"
|
||||
envelope={envelope}
|
||||
envelopeItems={envelope.envelopeItems}
|
||||
token={undefined}
|
||||
fields={envelope.fields}
|
||||
recipients={envelope.recipients}
|
||||
|
||||
@@ -101,6 +101,7 @@ export default function EnvelopeEditorPage({ params }: Route.ComponentProps) {
|
||||
<EnvelopeRenderProvider
|
||||
version="current"
|
||||
envelope={envelope}
|
||||
envelopeItems={envelope.envelopeItems}
|
||||
token={undefined}
|
||||
fields={envelope.fields}
|
||||
recipients={envelope.recipients}
|
||||
|
||||
@@ -176,6 +176,7 @@ export default function TemplatePage({ params }: Route.ComponentProps) {
|
||||
<EnvelopeRenderProvider
|
||||
version="current"
|
||||
envelope={envelope}
|
||||
envelopeItems={envelope.envelopeItems}
|
||||
token={undefined}
|
||||
fields={envelope.fields}
|
||||
recipients={envelope.recipients}
|
||||
|
||||
@@ -246,7 +246,12 @@ const DirectSigningPageV2 = ({ data }: { data: Awaited<ReturnType<typeof handleV
|
||||
recipient={recipient}
|
||||
user={user}
|
||||
>
|
||||
<EnvelopeRenderProvider version="current" envelope={envelope} token={recipient.token}>
|
||||
<EnvelopeRenderProvider
|
||||
version="current"
|
||||
envelope={envelope}
|
||||
envelopeItems={envelope.envelopeItems}
|
||||
token={recipient.token}
|
||||
>
|
||||
<DocumentSigningPageViewV2 />
|
||||
</EnvelopeRenderProvider>
|
||||
</DocumentSigningAuthProvider>
|
||||
|
||||
@@ -494,7 +494,12 @@ const SigningPageV2 = ({ data }: { data: Awaited<ReturnType<typeof handleV2Loade
|
||||
recipient={recipient}
|
||||
user={user}
|
||||
>
|
||||
<EnvelopeRenderProvider version="current" envelope={envelope} token={recipient.token}>
|
||||
<EnvelopeRenderProvider
|
||||
version="current"
|
||||
envelope={envelope}
|
||||
envelopeItems={envelope.envelopeItems}
|
||||
token={recipient.token}
|
||||
>
|
||||
<DocumentSigningPageViewV2 />
|
||||
</EnvelopeRenderProvider>
|
||||
</DocumentSigningAuthProvider>
|
||||
|
||||
@@ -320,7 +320,12 @@ const EmbedDirectTemplatePageV2 = ({
|
||||
user={user}
|
||||
isDirectTemplate={true}
|
||||
>
|
||||
<EnvelopeRenderProvider version="current" envelope={envelope} token={recipient.token}>
|
||||
<EnvelopeRenderProvider
|
||||
version="current"
|
||||
envelope={envelope}
|
||||
envelopeItems={envelope.envelopeItems}
|
||||
token={recipient.token}
|
||||
>
|
||||
<EmbedSignDocumentV2ClientPage
|
||||
hidePoweredBy={hidePoweredBy}
|
||||
allowWhitelabelling={allowEmbedSigningWhitelabel}
|
||||
|
||||
@@ -382,7 +382,12 @@ const EmbedSignDocumentPageV2 = ({
|
||||
recipient={recipient}
|
||||
user={user}
|
||||
>
|
||||
<EnvelopeRenderProvider version="current" envelope={envelope} token={token}>
|
||||
<EnvelopeRenderProvider
|
||||
version="current"
|
||||
envelope={envelope}
|
||||
envelopeItems={envelope.envelopeItems}
|
||||
token={token}
|
||||
>
|
||||
<EmbedSignDocumentV2ClientPage
|
||||
hidePoweredBy={hidePoweredBy}
|
||||
allowWhitelabelling={allowEmbedSigningWhitelabel}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
import DocumentDataSchema from '@documenso/prisma/generated/zod/modelSchema/DocumentDataSchema';
|
||||
@@ -76,6 +77,7 @@ export type TGetEnvelopeItemFileTokenDownloadRequestParams = z.infer<
|
||||
export const ZGetEnvelopeItemMetaSchema = z.object({
|
||||
envelopeItemId: z.string(),
|
||||
documentDataId: z.string(),
|
||||
documentDataType: z.nativeEnum(DocumentDataType),
|
||||
pages: z
|
||||
.object({
|
||||
originalWidth: z.number(),
|
||||
|
||||
@@ -110,10 +110,12 @@ export const handleEnvelopeItemsMetaRequest = async ({
|
||||
const pdfPageMetadata: TDocumentDataMeta['pages'] = await extractAndStorePdfImages(
|
||||
new Uint8Array(pdfBytes).buffer,
|
||||
item.documentData.id,
|
||||
item.documentData.type,
|
||||
);
|
||||
|
||||
pageMetadata = {
|
||||
pages: pdfPageMetadata,
|
||||
documentDataType: item.documentData.type,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -122,6 +124,7 @@ export const handleEnvelopeItemsMetaRequest = async ({
|
||||
return {
|
||||
envelopeItemId: item.id,
|
||||
documentDataId: item.documentData.id,
|
||||
documentDataType: item.documentData.type,
|
||||
pages: pages.map((page) => ({
|
||||
originalWidth: page.originalWidth,
|
||||
originalHeight: page.originalHeight,
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { FieldType } from '@prisma/client';
|
||||
import path from 'node:path';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { createEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/create-embedding-presign-token';
|
||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
||||
import { prefixedId } from '@documenso/lib/universal/id';
|
||||
import { formatDirectTemplatePath } from '@documenso/lib/utils/templates';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import {
|
||||
seedBlankDocument,
|
||||
seedPendingDocumentWithFullFields,
|
||||
} from '@documenso/prisma/seed/documents';
|
||||
import { seedBlankTemplate, seedDirectTemplate } from '@documenso/prisma/seed/templates';
|
||||
import { seedUser } from '@documenso/prisma/seed/users';
|
||||
|
||||
import { apiSignin } from '../fixtures/authentication';
|
||||
|
||||
const PDF_PAGE_SELECTOR = 'img[data-page-number]';
|
||||
|
||||
async function addSecondEnvelopeItem(envelopeId: string) {
|
||||
const firstItem = await prisma.envelopeItem.findFirstOrThrow({
|
||||
where: { envelopeId },
|
||||
orderBy: { order: 'asc' },
|
||||
include: { documentData: true },
|
||||
});
|
||||
|
||||
const newDocumentData = await prisma.documentData.create({
|
||||
data: {
|
||||
type: firstItem.documentData.type,
|
||||
data: firstItem.documentData.data,
|
||||
initialData: firstItem.documentData.initialData,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.envelopeItem.create({
|
||||
data: {
|
||||
id: prefixedId('envelope_item'),
|
||||
title: `${firstItem.title} - Page 2`,
|
||||
documentDataId: newDocumentData.id,
|
||||
order: 2,
|
||||
envelopeId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
test.describe('PDF Viewer Rendering', () => {
|
||||
test.describe('Authenticated Pages', () => {
|
||||
test('should render PDF on all authenticated pages (V1 and V2)', async ({ page }) => {
|
||||
const { user, team } = await seedUser();
|
||||
|
||||
const documentV1 = await seedBlankDocument(user, team.id);
|
||||
const documentV2 = await seedBlankDocument(user, team.id, { internalVersion: 2 });
|
||||
await addSecondEnvelopeItem(documentV2.id);
|
||||
|
||||
const templateV1 = await seedBlankTemplate(user, team.id);
|
||||
const templateV2 = await seedBlankTemplate(user, team.id, {
|
||||
createTemplateOptions: { internalVersion: 2 },
|
||||
});
|
||||
await addSecondEnvelopeItem(templateV2.id);
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: user.email,
|
||||
redirectPath: `/t/${team.url}/documents/${documentV1.id}`,
|
||||
});
|
||||
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(`/t/${team.url}/documents/${documentV2.id}`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('button', { name: /Page 2/ }).click();
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(`/t/${team.url}/templates/${templateV1.id}`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(`/t/${team.url}/templates/${templateV2.id}`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('button', { name: /Page 2/ }).click();
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(`/t/${team.url}/documents/${documentV1.id}/edit`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(`/t/${team.url}/documents/${documentV2.id}/edit?step=addFields`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('button', { name: /Page 2/ }).click();
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(`/t/${team.url}/templates/${templateV1.id}/edit`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(`/t/${team.url}/templates/${templateV2.id}/edit?step=addFields`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('button', { name: /Page 2/ }).click();
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(`/t/${team.url}/documents/${documentV1.id}`);
|
||||
await page.locator(PDF_PAGE_SELECTOR).first().waitFor({ state: 'visible', timeout: 30_000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Recipient Signing', () => {
|
||||
test('should render PDF on signing page (V1 and V2)', async ({ page }) => {
|
||||
const { user, team } = await seedUser();
|
||||
|
||||
const { recipients: recipientsV1 } = await seedPendingDocumentWithFullFields({
|
||||
owner: user,
|
||||
teamId: team.id,
|
||||
recipients: ['signer-v1@test.documenso.com'],
|
||||
fields: [FieldType.SIGNATURE],
|
||||
});
|
||||
|
||||
const { document: documentV2, recipients: recipientsV2 } =
|
||||
await seedPendingDocumentWithFullFields({
|
||||
owner: user,
|
||||
teamId: team.id,
|
||||
recipients: ['signer-v2@test.documenso.com'],
|
||||
fields: [FieldType.SIGNATURE],
|
||||
updateDocumentOptions: { internalVersion: 2 },
|
||||
});
|
||||
await addSecondEnvelopeItem(documentV2.id);
|
||||
|
||||
await page.goto(`/sign/${recipientsV1[0].token}`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(`/sign/${recipientsV2[0].token}`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('button', { name: /Page 2/ }).click();
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Direct Template', () => {
|
||||
test('should render PDF on direct template page (V1 and V2)', async ({ page }) => {
|
||||
const { user, team } = await seedUser();
|
||||
|
||||
const templateV1 = await seedDirectTemplate({
|
||||
title: 'PDF Viewer Test Template V1',
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
});
|
||||
|
||||
const templateV2 = await seedDirectTemplate({
|
||||
title: 'PDF Viewer Test Template V2',
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
internalVersion: 2,
|
||||
});
|
||||
await addSecondEnvelopeItem(templateV2.id);
|
||||
|
||||
await page.goto(formatDirectTemplatePath(templateV1.directLink?.token || ''));
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(formatDirectTemplatePath(templateV2.directLink?.token || ''));
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('button', { name: /Page 2/ }).click();
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Embed Pages', () => {
|
||||
test('should render PDF on embed sign page (V1 and V2)', async ({ page }) => {
|
||||
const { user, team } = await seedUser();
|
||||
|
||||
const { recipients: recipientsV1 } = await seedPendingDocumentWithFullFields({
|
||||
owner: user,
|
||||
teamId: team.id,
|
||||
recipients: ['embed-signer-v1@test.documenso.com'],
|
||||
fields: [FieldType.SIGNATURE],
|
||||
});
|
||||
|
||||
const { document: documentV2, recipients: recipientsV2 } =
|
||||
await seedPendingDocumentWithFullFields({
|
||||
owner: user,
|
||||
teamId: team.id,
|
||||
recipients: ['embed-signer-v2@test.documenso.com'],
|
||||
fields: [FieldType.SIGNATURE],
|
||||
updateDocumentOptions: { internalVersion: 2 },
|
||||
});
|
||||
await addSecondEnvelopeItem(documentV2.id);
|
||||
|
||||
await page.goto(`/embed/sign/${recipientsV1[0].token}`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(`/embed/sign/${recipientsV2[0].token}`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('button', { name: /Page 2/ }).click();
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
|
||||
test('should render PDF on embed direct template page (V1 and V2)', async ({ page }) => {
|
||||
const { user, team } = await seedUser();
|
||||
|
||||
const templateV1 = await seedDirectTemplate({
|
||||
title: 'Embed Direct Template V1',
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
});
|
||||
|
||||
const templateV2 = await seedDirectTemplate({
|
||||
title: 'Embed Direct Template V2',
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
internalVersion: 2,
|
||||
});
|
||||
await addSecondEnvelopeItem(templateV2.id);
|
||||
|
||||
await page.goto(`/embed/direct/${templateV1.directLink?.token || ''}`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await page.goto(`/embed/direct/${templateV2.directLink?.token || ''}`);
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('button', { name: /Page 2/ }).click();
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
|
||||
test('should render PDF on embed authoring document create page', async ({ page }) => {
|
||||
const { user, team } = await seedUser();
|
||||
|
||||
const { token: apiToken } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'pdf-viewer-test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const { token: presignToken } = await createEmbeddingPresignToken({
|
||||
apiToken,
|
||||
});
|
||||
|
||||
const embedParams = { darkModeDisabled: false, features: {} };
|
||||
const hash = btoa(encodeURIComponent(JSON.stringify(embedParams)));
|
||||
|
||||
await page.goto(
|
||||
`${NEXT_PUBLIC_WEBAPP_URL()}/embed/v1/authoring/document/create?token=${presignToken}#${hash}`,
|
||||
);
|
||||
|
||||
await expect(page.getByText('Configure Document')).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
const titleInput = page.getByLabel('Title');
|
||||
await titleInput.click();
|
||||
await titleInput.fill('PDF Viewer E2E Test');
|
||||
|
||||
const emailInput = page.getByPlaceholder('Email').first();
|
||||
await emailInput.click();
|
||||
await emailInput.fill('test-signer@documenso.com');
|
||||
|
||||
const [fileChooser] = await Promise.all([
|
||||
page.waitForEvent('filechooser'),
|
||||
page
|
||||
.locator('input[type=file]')
|
||||
.first()
|
||||
.evaluate((el) => {
|
||||
if (el instanceof HTMLInputElement) {
|
||||
el.click();
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
await fileChooser.setFiles(path.join(__dirname, '../../../../assets/example.pdf'));
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByRole('button', { name: 'Continue' }).click();
|
||||
|
||||
await expect(page.locator(PDF_PAGE_SELECTOR).first()).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -9,8 +9,11 @@ import {
|
||||
} from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import type { Field, Recipient } from '@prisma/client';
|
||||
import { DocumentDataType, type Field, type Recipient } from '@prisma/client';
|
||||
import pMap from 'p-map';
|
||||
|
||||
import { PDF_IMAGE_RENDER_SCALE } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { getEnvelopeItemPdfUrl } from '@documenso/lib/utils/envelope-download';
|
||||
import type { TGetEnvelopeItemsMetaResponse } from '@documenso/remix/server/api/files/files.types';
|
||||
import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors';
|
||||
import { AVAILABLE_RECIPIENT_COLORS } from '@documenso/ui/lib/recipient-colors';
|
||||
@@ -26,6 +29,9 @@ import { getEnvelopeItemMetaUrl, getEnvelopeItemPageImageUrl } from '../../utils
|
||||
*/
|
||||
export const EAGER_LOAD_PAGE_COUNT = 5;
|
||||
|
||||
// Todo: Embeds
|
||||
export const PRESIGNED_ENVELOPE_ITEM_ID_PREFIX = 'PRESIGNED_';
|
||||
|
||||
export type PageRenderData = BasePageRenderData & {
|
||||
scale: number;
|
||||
};
|
||||
@@ -48,7 +54,13 @@ type EnvelopeRenderOverrideSettings = {
|
||||
showRecipientSigningStatus?: boolean;
|
||||
};
|
||||
|
||||
type EnvelopeRenderItem = TEnvelope['envelopeItems'][number];
|
||||
type EnvelopeRenderItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
order: number;
|
||||
envelopeId: string;
|
||||
data?: Uint8Array | null;
|
||||
};
|
||||
|
||||
type EnvelopeRenderProviderValue = {
|
||||
version: DocumentDataVersion;
|
||||
@@ -76,7 +88,12 @@ interface EnvelopeRenderProviderProps {
|
||||
*/
|
||||
version: DocumentDataVersion;
|
||||
|
||||
envelope: Pick<TEnvelope, 'id' | 'envelopeItems' | 'status' | 'type'>;
|
||||
envelope: Pick<TEnvelope, 'id' | 'status' | 'type'>;
|
||||
|
||||
/**
|
||||
* The envelope items to render.
|
||||
*/
|
||||
envelopeItems: EnvelopeRenderItem[];
|
||||
|
||||
/**
|
||||
* Optional fields which are passed down to renderers for custom rendering needs.
|
||||
@@ -100,6 +117,13 @@ interface EnvelopeRenderProviderProps {
|
||||
*/
|
||||
token: string | undefined;
|
||||
|
||||
/**
|
||||
* The presign token to access the envelope.
|
||||
*
|
||||
* If not provided, it will be assumed that the current user can access the document.
|
||||
*/
|
||||
presignToken?: string | undefined;
|
||||
|
||||
/**
|
||||
* Custom override settings for generic page renderers.
|
||||
*/
|
||||
@@ -124,8 +148,10 @@ export const useCurrentEnvelopeRender = () => {
|
||||
export const EnvelopeRenderProvider = ({
|
||||
children,
|
||||
envelope,
|
||||
envelopeItems: envelopeItemsFromProps,
|
||||
fields,
|
||||
token,
|
||||
presignToken,
|
||||
recipients = [],
|
||||
version,
|
||||
overrideSettings,
|
||||
@@ -144,12 +170,12 @@ export const EnvelopeRenderProvider = ({
|
||||
const fetchStartedAtRef = useRef<number>(0);
|
||||
|
||||
const envelopeItems = useMemo(
|
||||
() => envelope.envelopeItems.sort((a, b) => a.order - b.order),
|
||||
[envelope.envelopeItems],
|
||||
() => envelopeItemsFromProps.sort((a, b) => a.order - b.order),
|
||||
[envelopeItemsFromProps],
|
||||
);
|
||||
|
||||
const [currentItem, setCurrentItem] = useState<EnvelopeRenderItem | null>(
|
||||
envelope.envelopeItems[0] ?? null,
|
||||
envelopeItems[0] ?? null,
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -157,10 +183,18 @@ export const EnvelopeRenderProvider = ({
|
||||
*/
|
||||
useEffect(() => {
|
||||
void fetchEnvelopeRenderData();
|
||||
}, [envelope.id, envelope.envelopeItems.length, token, version]);
|
||||
}, [envelope.id, envelopeItems, token, version]);
|
||||
|
||||
const fetchEnvelopeRenderData = useCallback(async () => {
|
||||
if (envelopeItems.length === 0) {
|
||||
setEnvelopeItemsMetaLoadingState('loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle "create" embedding mode since all the files are local in that scenario.
|
||||
if (!envelope.id) {
|
||||
await handleLocalFileFetch();
|
||||
|
||||
const fetchEnvelopeRenderData = async () => {
|
||||
if (!envelope.id || envelope.envelopeItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -175,6 +209,7 @@ export const EnvelopeRenderProvider = ({
|
||||
const metaUrl = getEnvelopeItemMetaUrl({
|
||||
envelopeId: envelope.id,
|
||||
token,
|
||||
presignToken,
|
||||
});
|
||||
|
||||
const response = await fetch(metaUrl);
|
||||
@@ -193,7 +228,61 @@ export const EnvelopeRenderProvider = ({
|
||||
// Build a map of envelope items by ID
|
||||
const metaMap: Record<string, BasePageRenderData[]> = {};
|
||||
|
||||
for (const item of data.envelopeItems) {
|
||||
const s3EnvelopeItems = data.envelopeItems.filter(
|
||||
(item) => item.documentDataType === DocumentDataType.S3_PATH,
|
||||
);
|
||||
|
||||
const localPdfs: { envelopeItemId: string; documentDataId: string; data: Uint8Array }[] = [];
|
||||
|
||||
// Handle local envelope items from embedding flows.
|
||||
for (const item of envelopeItems) {
|
||||
if (item.data) {
|
||||
localPdfs.push({
|
||||
envelopeItemId: item.id,
|
||||
documentDataId: item.id,
|
||||
data: new Uint8Array(item.data as Uint8Array),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const bytes64EnvelopeItems = data.envelopeItems.filter(
|
||||
(item) => item.documentDataType !== DocumentDataType.S3_PATH,
|
||||
);
|
||||
|
||||
// Handle byte64 envelope items from the database.
|
||||
const pdfs = await pMap(
|
||||
bytes64EnvelopeItems,
|
||||
async (item) => {
|
||||
const envelopeItemPdfUrl = getEnvelopeItemPdfUrl({
|
||||
type: 'view',
|
||||
envelopeItem: {
|
||||
id: item.envelopeItemId,
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
token,
|
||||
presignToken,
|
||||
});
|
||||
|
||||
const response = await fetch(envelopeItemPdfUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch envelope item PDF: ${response.status}`);
|
||||
}
|
||||
|
||||
const pdfBytes = await response.arrayBuffer();
|
||||
|
||||
return {
|
||||
envelopeItemId: item.envelopeItemId,
|
||||
documentDataId: item.documentDataId,
|
||||
data: new Uint8Array(pdfBytes),
|
||||
};
|
||||
},
|
||||
{ concurrency: 5 },
|
||||
);
|
||||
|
||||
localPdfs.push(...pdfs);
|
||||
|
||||
for (const item of s3EnvelopeItems) {
|
||||
metaMap[item.envelopeItemId] = item.pages.map((page, pageIndex) => {
|
||||
const imageUrl = getEnvelopeItemPageImageUrl({
|
||||
envelopeId: envelope.id,
|
||||
@@ -201,6 +290,7 @@ export const EnvelopeRenderProvider = ({
|
||||
documentDataId: item.documentDataId,
|
||||
pageIndex,
|
||||
token,
|
||||
presignToken,
|
||||
version,
|
||||
});
|
||||
|
||||
@@ -216,6 +306,46 @@ export const EnvelopeRenderProvider = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (localPdfs.length > 0) {
|
||||
// Dynamically import "pdfToImagesClientSide" function to prevent bundling.
|
||||
const { pdfToImagesClientSide } = await import(
|
||||
'@documenso/lib/server-only/ai/pdf-to-images.client'
|
||||
);
|
||||
|
||||
await pMap(
|
||||
localPdfs,
|
||||
async (item) => {
|
||||
const pdfImages = await pdfToImagesClientSide(item.data, {
|
||||
scale: PDF_IMAGE_RENDER_SCALE,
|
||||
});
|
||||
|
||||
metaMap[item.envelopeItemId] = pdfImages.map((image) => ({
|
||||
envelopeItemId: item.envelopeItemId,
|
||||
documentDataId: item.documentDataId,
|
||||
pageIndex: image.pageIndex,
|
||||
pageNumber: image.pageIndex + 1,
|
||||
pageWidth: image.width,
|
||||
pageHeight: image.height,
|
||||
imageUrl: image.image,
|
||||
}));
|
||||
},
|
||||
{ concurrency: 10 },
|
||||
);
|
||||
}
|
||||
|
||||
// Append all local embedding files.
|
||||
const localFiles = envelopeItems.filter(
|
||||
(item) => item.id.startsWith(PRESIGNED_ENVELOPE_ITEM_ID_PREFIX) && item.data,
|
||||
);
|
||||
|
||||
for (const item of localFiles) {
|
||||
if (!item.data) {
|
||||
throw new Error('Not possible');
|
||||
}
|
||||
|
||||
// Handle local files
|
||||
}
|
||||
|
||||
setEnvelopeItemsMeta(metaMap);
|
||||
|
||||
setEnvelopeItemsMetaLoadingState('loaded');
|
||||
@@ -226,10 +356,51 @@ export const EnvelopeRenderProvider = ({
|
||||
setEnvelopeItemsMetaLoadingState('error');
|
||||
}
|
||||
}
|
||||
}, [envelope.id, envelopeItems, token, version]);
|
||||
|
||||
const handleLocalFileFetch = async () => {
|
||||
setEnvelopeItemsMetaLoadingState('loading');
|
||||
|
||||
try {
|
||||
// Build a map of envelope items by ID
|
||||
const metaMap: Record<string, BasePageRenderData[]> = {};
|
||||
|
||||
// Dynamically import "pdfToImagesClientSide" function to prevent bundling.
|
||||
const { pdfToImagesClientSide } = await import(
|
||||
'@documenso/lib/server-only/ai/pdf-to-images.client'
|
||||
);
|
||||
|
||||
for (const item of envelopeItems) {
|
||||
if (item.data) {
|
||||
// Clone the buffer so PDF.js can transfer it to its worker without detaching the one in state
|
||||
const pdfBytes = new Uint8Array(structuredClone(item.data));
|
||||
|
||||
const pdfImages = await pdfToImagesClientSide(pdfBytes, {
|
||||
scale: PDF_IMAGE_RENDER_SCALE,
|
||||
});
|
||||
|
||||
metaMap[item.id] = pdfImages.map((image) => ({
|
||||
envelopeItemId: item.id,
|
||||
documentDataId: item.id,
|
||||
pageIndex: image.pageIndex,
|
||||
pageNumber: image.pageIndex + 1,
|
||||
pageWidth: image.width,
|
||||
pageHeight: image.height,
|
||||
imageUrl: image.image,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
setEnvelopeItemsMeta(metaMap);
|
||||
setEnvelopeItemsMetaLoadingState('loaded');
|
||||
} catch (error) {
|
||||
console.error('Failed to load envelope data:', error);
|
||||
setEnvelopeItemsMetaLoadingState('error');
|
||||
}
|
||||
};
|
||||
|
||||
const setCurrentEnvelopeItem = (envelopeItemId: string) => {
|
||||
const foundItem = envelope.envelopeItems.find((item) => item.id === envelopeItemId);
|
||||
const foundItem = envelopeItems.find((item) => item.id === envelopeItemId);
|
||||
|
||||
setCurrentItem(foundItem ?? null);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { DocumentDataType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDocumentDataMetaSchema = z.object({
|
||||
// Could store other things such as PDF size, etc here.
|
||||
documentDataType: z.nativeEnum(DocumentDataType),
|
||||
pages: z
|
||||
.object({
|
||||
originalWidth: z.number().describe('Original PDF page width'),
|
||||
|
||||
@@ -49,7 +49,7 @@ export const putPdfFileServerSide = async (file: File) => {
|
||||
|
||||
const newDocumentData = await createDocumentData({ type, data });
|
||||
|
||||
void extractAndStorePdfImages(arrayBuffer, newDocumentData.id).catch((err) => {
|
||||
void extractAndStorePdfImages(arrayBuffer, newDocumentData.id, type).catch((err) => {
|
||||
console.error(`Error extracting and storing PDF images: ${err}`);
|
||||
|
||||
// Do nothing.
|
||||
@@ -64,6 +64,7 @@ export const putPdfFileServerSide = async (file: File) => {
|
||||
export const extractAndStorePdfImages = async (
|
||||
arrayBuffer: ArrayBuffer,
|
||||
documentDataId: string,
|
||||
documentDataType: DocumentDataType,
|
||||
): Promise<TDocumentDataMeta['pages']> => {
|
||||
const images = await pdfToImages(new Uint8Array(arrayBuffer));
|
||||
|
||||
@@ -77,6 +78,7 @@ export const extractAndStorePdfImages = async (
|
||||
|
||||
const documentDataMetadata = ZDocumentDataMetaSchema.parse({
|
||||
pages: pageMetadata,
|
||||
documentDataType,
|
||||
} satisfies TDocumentDataMeta);
|
||||
|
||||
// Only update metadata (page dimensions). Never update type, data, or initialData:
|
||||
@@ -140,7 +142,7 @@ export const putNormalizedPdfFileServerSide = async (
|
||||
data: documentData.data,
|
||||
});
|
||||
|
||||
void extractAndStorePdfImages(normalized, newDocumentData.id).catch((err) => {
|
||||
void extractAndStorePdfImages(normalized, newDocumentData.id, documentData.type).catch((err) => {
|
||||
console.error(`Error extracting and storing PDF images: ${err}`);
|
||||
|
||||
// Do nothing.
|
||||
|
||||
Reference in New Issue
Block a user