mirror of
https://github.com/documenso/documenso.git
synced 2026-07-13 22:37:24 +10:00
fix: performance improvements (#2581)
This commit is contained in:
@@ -29,7 +29,7 @@ import { Sheet, SheetContent, SheetTrigger } from '@documenso/ui/primitives/shee
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { FieldAdvancedSettingsDrawer } from '~/components/embed/authoring/field-advanced-settings-drawer';
|
||||
import { PDFViewer } from '~/components/general/pdf-viewer/pdf-viewer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
|
||||
import type { TConfigureEmbedFormSchema } from './configure-document-view.types';
|
||||
import type { TConfigureFieldsFormSchema } from './configure-fields-view.types';
|
||||
@@ -549,7 +549,7 @@ export const ConfigureFieldsView = ({
|
||||
<Form {...form}>
|
||||
<div>
|
||||
{normalizedDocumentData && (
|
||||
<PDFViewer data={normalizedDocumentData} scrollParentRef="window" />
|
||||
<PDFViewerLazy data={normalizedDocumentData} scrollParentRef="window" />
|
||||
)}
|
||||
|
||||
<ElementVisible target={PDF_VIEWER_PAGE_SELECTOR}>
|
||||
|
||||
@@ -41,7 +41,7 @@ import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signa
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { BrandingLogo } from '~/components/general/branding-logo';
|
||||
import { PDFViewer } from '~/components/general/pdf-viewer/pdf-viewer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
import { injectCss } from '~/utils/css-vars';
|
||||
|
||||
import type { DirectTemplateLocalField } from '../general/direct-template/direct-template-signing-form';
|
||||
@@ -340,7 +340,7 @@ export const EmbedDirectTemplateClientPage = ({
|
||||
<div className="relative flex w-full flex-col gap-x-6 gap-y-12 md:flex-row">
|
||||
{/* Viewer */}
|
||||
<div className="flex-1">
|
||||
<PDFViewer
|
||||
<PDFViewerLazy
|
||||
data={getDocumentDataUrlForPdfViewer({
|
||||
envelopeId: envelopeItems[0]?.envelopeId,
|
||||
envelopeItemId: envelopeItems[0]?.id,
|
||||
|
||||
@@ -30,7 +30,7 @@ import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signa
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { BrandingLogo } from '~/components/general/branding-logo';
|
||||
import { PDFViewer } from '~/components/general/pdf-viewer/pdf-viewer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
import { injectCss } from '~/utils/css-vars';
|
||||
|
||||
import { DocumentSigningAttachmentsPopover } from '../general/document-signing/document-signing-attachments-popover';
|
||||
@@ -288,7 +288,7 @@ export const EmbedSignDocumentV1ClientPage = ({
|
||||
<div className="embed--DocumentContainer relative flex w-full flex-col gap-x-6 gap-y-12 md:flex-row">
|
||||
{/* Viewer */}
|
||||
<div className="embed--DocumentViewer flex-1">
|
||||
<PDFViewer
|
||||
<PDFViewerLazy
|
||||
data={getDocumentDataUrlForPdfViewer({
|
||||
envelopeId: envelopeItems[0]?.envelopeId,
|
||||
envelopeItemId: envelopeItems[0]?.id,
|
||||
|
||||
@@ -27,7 +27,7 @@ import { Label } from '@documenso/ui/primitives/label';
|
||||
import { SignaturePadDialog } from '@documenso/ui/primitives/signature-pad/signature-pad-dialog';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { PDFViewer } from '~/components/general/pdf-viewer/pdf-viewer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
|
||||
import { useRequiredDocumentSigningContext } from '../../general/document-signing/document-signing-provider';
|
||||
import { DocumentSigningRejectDialog } from '../../general/document-signing/document-signing-reject-dialog';
|
||||
@@ -181,8 +181,8 @@ export const MultiSignDocumentSigningView = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen overflow-hidden bg-background">
|
||||
<div id="document-field-portal-root" className="relative h-full w-full overflow-y-auto p-8">
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="relative h-full w-full p-8">
|
||||
{match({ isLoading, document })
|
||||
.with({ isLoading: true }, () => (
|
||||
<div className="flex min-h-[400px] w-full items-center justify-center">
|
||||
@@ -229,7 +229,7 @@ export const MultiSignDocumentSigningView = ({
|
||||
'md:mx-auto md:max-w-2xl': document.status === DocumentStatus.COMPLETED,
|
||||
})}
|
||||
>
|
||||
<PDFViewer
|
||||
<PDFViewerLazy
|
||||
data={getDocumentDataUrlForPdfViewer({
|
||||
envelopeId: document.envelopeId,
|
||||
envelopeItemId: document.envelopeItems[0]?.id,
|
||||
@@ -369,35 +369,39 @@ export const MultiSignDocumentSigningView = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasDocumentLoaded && showPendingFieldTooltip && pendingFields.length > 0 && (
|
||||
<ElementVisible
|
||||
target={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${pendingFields[0].page}"]`}
|
||||
>
|
||||
<FieldToolTip
|
||||
key={pendingFields[0].id}
|
||||
field={pendingFields[0]}
|
||||
color="warning"
|
||||
>
|
||||
<Trans>Click to insert field</Trans>
|
||||
</FieldToolTip>
|
||||
</ElementVisible>
|
||||
)}
|
||||
|
||||
{/* Fields */}
|
||||
{hasDocumentLoaded && (
|
||||
<EmbedDocumentFields
|
||||
fields={pendingFields}
|
||||
metadata={document.documentMeta}
|
||||
onSignField={onSignField}
|
||||
onUnsignField={onUnsignField}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Completed fields */}
|
||||
{document.status !== DocumentStatus.COMPLETED && (
|
||||
<DocumentReadOnlyFields
|
||||
documentMeta={document.documentMeta ?? undefined}
|
||||
fields={completedFields}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasDocumentLoaded && showPendingFieldTooltip && pendingFields.length > 0 && (
|
||||
<ElementVisible
|
||||
target={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${pendingFields[0].page}"]`}
|
||||
>
|
||||
<FieldToolTip key={pendingFields[0].id} field={pendingFields[0]} color="warning">
|
||||
<Trans>Click to insert field</Trans>
|
||||
</FieldToolTip>
|
||||
</ElementVisible>
|
||||
)}
|
||||
|
||||
{/* Fields */}
|
||||
{hasDocumentLoaded && (
|
||||
<EmbedDocumentFields
|
||||
fields={pendingFields}
|
||||
metadata={document.documentMeta}
|
||||
onSignField={onSignField}
|
||||
onUnsignField={onUnsignField}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Completed fields */}
|
||||
{document.status !== DocumentStatus.COMPLETED && (
|
||||
<DocumentReadOnlyFields
|
||||
documentMeta={document.documentMeta ?? undefined}
|
||||
fields={completedFields}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
))
|
||||
.otherwise(() => null)}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { useRequiredDocumentSigningAuthContext } from '~/components/general/document-signing/document-signing-auth-provider';
|
||||
import { useRequiredDocumentSigningContext } from '~/components/general/document-signing/document-signing-provider';
|
||||
import { PDFViewer } from '~/components/general/pdf-viewer/pdf-viewer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
|
||||
import {
|
||||
DirectTemplateConfigureForm,
|
||||
@@ -152,7 +152,7 @@ export const DirectTemplatePageView = ({
|
||||
gradient
|
||||
>
|
||||
<CardContent className="p-2">
|
||||
<PDFViewer
|
||||
<PDFViewerLazy
|
||||
key={template.id}
|
||||
data={getDocumentDataUrlForPdfViewer({
|
||||
envelopeId: template.envelopeId,
|
||||
|
||||
+2
-2
@@ -46,7 +46,7 @@ import { DocumentSigningRadioField } from '~/components/general/document-signing
|
||||
import { DocumentSigningRejectDialog } from '~/components/general/document-signing/document-signing-reject-dialog';
|
||||
import { DocumentSigningSignatureField } from '~/components/general/document-signing/document-signing-signature-field';
|
||||
import { DocumentSigningTextField } from '~/components/general/document-signing/document-signing-text-field';
|
||||
import { PDFViewer } from '~/components/general/pdf-viewer/pdf-viewer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
|
||||
import { useRequiredDocumentSigningAuthContext } from './document-signing-auth-provider';
|
||||
import { DocumentSigningCompleteDialog } from './document-signing-complete-dialog';
|
||||
@@ -273,7 +273,7 @@ export const DocumentSigningPageViewV1 = ({
|
||||
<div className="flex-1">
|
||||
<Card className="rounded-xl before:rounded-xl" gradient>
|
||||
<CardContent className="p-2">
|
||||
<PDFViewer
|
||||
<PDFViewerLazy
|
||||
key={document.envelopeItems[0]?.id}
|
||||
data={getDocumentDataUrlForPdfViewer({
|
||||
envelopeId: document.envelopeId,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { type DocumentData, DocumentStatus, type EnvelopeItem, EnvelopeType } from '@prisma/client';
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
|
||||
import { EnvelopeDownloadDialog } from '~/components/dialogs/envelope-download-dialog';
|
||||
import { EnvelopePdfViewer } from '~/components/general/pdf-viewer/envelope-pdf-viewer';
|
||||
import { PDFViewer } from '~/components/general/pdf-viewer/pdf-viewer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
|
||||
import { EnvelopeRendererFileSelector } from '../envelope-editor/envelope-file-selector';
|
||||
import { EnvelopeGenericPageRenderer } from '../envelope-editor/envelope-generic-page-renderer';
|
||||
@@ -150,7 +150,7 @@ export const DocumentCertificateQRView = ({
|
||||
</div>
|
||||
|
||||
<div className="mt-12 w-full">
|
||||
<PDFViewer
|
||||
<PDFViewerLazy
|
||||
key={envelopeItems[0]?.id}
|
||||
data={getDocumentDataUrlForPdfViewer({
|
||||
envelopeId: envelopeItems[0]?.envelopeId,
|
||||
@@ -184,8 +184,6 @@ const DocumentCertificateQrV2 = ({
|
||||
}: DocumentCertificateQrV2Props) => {
|
||||
const { envelopeItems } = useCurrentEnvelopeRender();
|
||||
|
||||
const scrollableContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-start">
|
||||
<div className="flex w-full flex-col justify-between gap-4 md:flex-row md:items-end">
|
||||
@@ -216,12 +214,12 @@ const DocumentCertificateQrV2 = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 max-h-[80vh] w-full overflow-y-auto" ref={scrollableContainerRef}>
|
||||
<div className="mt-12 w-full">
|
||||
<EnvelopeRendererFileSelector className="mb-4 p-0" fields={[]} secondaryOverride={''} />
|
||||
|
||||
<EnvelopePdfViewer
|
||||
scrollParentRef="window"
|
||||
customPageRenderer={EnvelopeGenericPageRenderer}
|
||||
scrollParentRef={scrollableContainerRef}
|
||||
errorMessage={PDF_VIEWER_ERROR_MESSAGES.preview}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@ import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/ty
|
||||
import { Stepper } from '@documenso/ui/primitives/stepper';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { PDFViewer } from '~/components/general/pdf-viewer/pdf-viewer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
export type DocumentEditFormProps = {
|
||||
@@ -441,7 +441,7 @@ export const DocumentEditForm = ({
|
||||
gradient
|
||||
>
|
||||
<CardContent className="p-2">
|
||||
<PDFViewer
|
||||
<PDFViewerLazy
|
||||
key={document.envelopeItems[0]?.id}
|
||||
data={getDocumentDataUrlForPdfViewer({
|
||||
envelopeId: document.envelopeId,
|
||||
|
||||
@@ -152,9 +152,12 @@ export const EnvelopeEditorFieldsPage = () => {
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full">
|
||||
<div className="flex h-full w-full flex-col overflow-y-auto" ref={scrollableContainerRef}>
|
||||
<div
|
||||
className="flex h-full w-full flex-col overflow-y-auto px-2"
|
||||
ref={scrollableContainerRef}
|
||||
>
|
||||
{/* Horizontal envelope item selector */}
|
||||
<EnvelopeRendererFileSelector fields={editorFields.localFields} />
|
||||
<EnvelopeRendererFileSelector className="px-0" fields={editorFields.localFields} />
|
||||
|
||||
{/* Document View */}
|
||||
<div className="mt-4 flex h-full flex-col items-center justify-center">
|
||||
|
||||
+31
-15
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { faker } from '@faker-js/faker/locale/en';
|
||||
import type { Faker } from '@faker-js/faker';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { FieldType, SigningStatus } from '@prisma/client';
|
||||
import { FileTextIcon } from 'lucide-react';
|
||||
@@ -26,9 +26,8 @@ import { EnvelopePdfViewer } from '~/components/general/pdf-viewer/envelope-pdf-
|
||||
|
||||
import { EnvelopeRendererFileSelector } from './envelope-file-selector';
|
||||
|
||||
// Todo: Envelopes - Dynamically import faker
|
||||
export const EnvelopeEditorPreviewPage = () => {
|
||||
const { envelope, editorFields } = useCurrentEnvelopeEditor();
|
||||
const { envelope, editorFields, editorConfig } = useCurrentEnvelopeEditor();
|
||||
|
||||
const { currentEnvelopeItem, fields } = useCurrentEnvelopeRender();
|
||||
|
||||
@@ -38,7 +37,20 @@ export const EnvelopeEditorPreviewPage = () => {
|
||||
'recipient',
|
||||
);
|
||||
|
||||
const [fakerInstance, setFakerInstance] = useState<Faker | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
void import('@faker-js/faker/locale/en').then((mod) => {
|
||||
setFakerInstance(mod.faker);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const fieldsWithPlaceholders = useMemo(() => {
|
||||
if (!fakerInstance) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const faker = fakerInstance;
|
||||
return fields.map((field) => {
|
||||
const fieldMeta = ZFieldAndMetaSchema.parse(field);
|
||||
|
||||
@@ -189,7 +201,7 @@ export const EnvelopeEditorPreviewPage = () => {
|
||||
.exhaustive(),
|
||||
};
|
||||
});
|
||||
}, [fields, envelope, envelope.recipients, envelope.documentMeta]);
|
||||
}, [fields, envelope, envelope.recipients, envelope.documentMeta, fakerInstance]);
|
||||
|
||||
/**
|
||||
* Set the selected recipient to the first recipient in the envelope.
|
||||
@@ -210,26 +222,30 @@ export const EnvelopeEditorPreviewPage = () => {
|
||||
...recipient,
|
||||
signingStatus: SigningStatus.SIGNED,
|
||||
}))}
|
||||
presignToken={editorConfig?.embedded?.presignToken}
|
||||
overrideSettings={{
|
||||
mode: 'export',
|
||||
}}
|
||||
>
|
||||
<div className="relative flex h-full">
|
||||
<div className="flex w-full flex-col overflow-y-auto" ref={scrollableContainerRef}>
|
||||
<div
|
||||
className="flex h-full w-full flex-col overflow-y-auto px-2"
|
||||
ref={scrollableContainerRef}
|
||||
>
|
||||
{/* Horizontal envelope item selector */}
|
||||
<EnvelopeRendererFileSelector fields={editorFields.localFields} />
|
||||
<EnvelopeRendererFileSelector className="px-0" fields={editorFields.localFields} />
|
||||
|
||||
<Alert variant="warning" className="mx-auto max-w-[800px]">
|
||||
<AlertTitle>
|
||||
<Trans>Preview Mode</Trans>
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
<Trans>Preview what the signed document will look like with placeholder data</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{/* Document View */}
|
||||
<div className="mt-4 flex h-full flex-col items-center justify-center">
|
||||
<Alert variant="warning" className="mb-4 max-w-[800px]">
|
||||
<AlertTitle>
|
||||
<Trans>Preview Mode</Trans>
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
<Trans>Preview what the signed document will look like with placeholder data</Trans>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{currentEnvelopeItem !== null ? (
|
||||
<EnvelopePdfViewer
|
||||
customPageRenderer={EnvelopeGenericPageRenderer}
|
||||
|
||||
@@ -62,7 +62,12 @@ export const EnvelopeRendererFileSelector = ({
|
||||
const { envelopeItems, currentEnvelopeItem, setCurrentEnvelopeItem } = useCurrentEnvelopeRender();
|
||||
|
||||
return (
|
||||
<div className={cn('flex h-fit flex-shrink-0 space-x-2 overflow-x-auto p-4', className)}>
|
||||
<div
|
||||
className={cn(
|
||||
'scrollbar-hidden flex h-fit flex-shrink-0 space-x-2 overflow-x-auto p-4',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{envelopeItems.map((doc, i) => (
|
||||
<EnvelopeItemSelector
|
||||
key={doc.id}
|
||||
|
||||
@@ -8,7 +8,8 @@ import { PDF_VIEWER_ERROR_MESSAGES } from '@documenso/lib/constants/pdf-viewer-i
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
||||
|
||||
import { PDFViewer, type PDFViewerProps } from './pdf-viewer';
|
||||
import type { PDFViewerProps } from './pdf-viewer';
|
||||
import PDFViewerLazy from './pdf-viewer-lazy';
|
||||
|
||||
export type EnvelopePdfViewerProps = {
|
||||
/**
|
||||
@@ -52,7 +53,8 @@ export const EnvelopePdfViewer = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<PDFViewer
|
||||
<PDFViewerLazy
|
||||
key={`${currentEnvelopeItem.envelopeId}-${currentEnvelopeItem.id}`}
|
||||
{...props}
|
||||
className={cn('h-full w-full max-w-[800px]', className)}
|
||||
data={currentEnvelopeItem.data}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* We need to double wrap the PDFViewer to avoid the following errors:
|
||||
*
|
||||
* 1. Lazy prevents the pdfjs worker from being bundled
|
||||
* 2. onMount guard prevents "Worker not defined"
|
||||
*/
|
||||
import { Suspense, lazy, useEffect, useState } from 'react';
|
||||
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
import type { PDFViewerProps } from './pdf-viewer';
|
||||
import { PdfViewerLoadingState } from './pdf-viewer-states';
|
||||
|
||||
const PDFViewer = lazy(async () => import('./pdf-viewer'));
|
||||
|
||||
export default function PDFViewerLazy(props: PDFViewerProps) {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
const fallback = (
|
||||
<div className={cn('h-full w-full', props.className)}>
|
||||
<PdfViewerLoadingState />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!isClient) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return <Suspense fallback={fallback}>{PDFViewer && <PDFViewer {...props} />}</Suspense>;
|
||||
}
|
||||
@@ -28,7 +28,14 @@ export const PdfViewerPageImage = ({ imageLoadingState, imageProps }: PdfViewerP
|
||||
)}
|
||||
|
||||
{/* The PDF image. */}
|
||||
{imageProps.src && <img {...imageProps} className={cn(imageProps.className, '')} alt="" />}
|
||||
{imageProps.src && (
|
||||
<img
|
||||
{...imageProps}
|
||||
className={cn(imageProps.className, 'select-none')}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -62,14 +62,14 @@ export type PDFViewerProps = {
|
||||
customPageRenderer?: React.FunctionComponent<{ pageData: PageRenderData }>;
|
||||
} & React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const PDFViewer = ({
|
||||
export default function PDFViewer({
|
||||
className,
|
||||
data,
|
||||
scrollParentRef,
|
||||
onDocumentLoad,
|
||||
customPageRenderer,
|
||||
...props
|
||||
}: PDFViewerProps) => {
|
||||
}: PDFViewerProps) {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -77,7 +77,7 @@ export const PDFViewer = ({
|
||||
|
||||
const [loadingState, setLoadingState] = useState<LoadingState>('loading');
|
||||
|
||||
const [pdf, setPdf] = useState<pdfjsLib.PDFDocumentProxy | null>(null);
|
||||
const pdfRef = useRef<pdfjsLib.PDFDocumentProxy | null>(null);
|
||||
|
||||
const [pages, setPages] = useState<PageMeta[]>([]);
|
||||
|
||||
@@ -86,11 +86,17 @@ export const PDFViewer = ({
|
||||
return;
|
||||
}
|
||||
|
||||
let isCancelled = false;
|
||||
|
||||
const fetchMetadata = async () => {
|
||||
try {
|
||||
setLoadingState('loading');
|
||||
setPages([]);
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let result: Uint8Array | null = typeof data === 'string' ? null : new Uint8Array(data);
|
||||
|
||||
if (typeof data === 'string') {
|
||||
@@ -103,13 +109,24 @@ export const PDFViewer = ({
|
||||
result = new Uint8Array(await response.arrayBuffer());
|
||||
}
|
||||
|
||||
const loadedPdf = await pdfjsLib.getDocument({ data: result! }).promise;
|
||||
|
||||
if (pdf) {
|
||||
await pdf.destroy();
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPdf(loadedPdf);
|
||||
const loadedPdf = await pdfjsLib.getDocument({ data: result! }).promise;
|
||||
|
||||
if (isCancelled) {
|
||||
await loadedPdf.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy previous PDF if it exists
|
||||
if (pdfRef.current) {
|
||||
await pdfRef.current.destroy();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
pdfRef.current = loadedPdf;
|
||||
|
||||
// Fetch the pages
|
||||
const pages = await pMap(
|
||||
@@ -125,10 +142,18 @@ export const PDFViewer = ({
|
||||
},
|
||||
);
|
||||
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPages(pages);
|
||||
|
||||
setLoadingState('loaded');
|
||||
} catch (err) {
|
||||
if (isCancelled) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
setLoadingState('error');
|
||||
|
||||
@@ -143,8 +168,11 @@ export const PDFViewer = ({
|
||||
void fetchMetadata();
|
||||
|
||||
return () => {
|
||||
if (pdf) {
|
||||
void pdf.destroy();
|
||||
isCancelled = true;
|
||||
|
||||
if (pdfRef.current) {
|
||||
void pdfRef.current.destroy();
|
||||
pdfRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [data]);
|
||||
@@ -178,19 +206,19 @@ export const PDFViewer = ({
|
||||
{hasError && <PdfViewerErrorState />}
|
||||
|
||||
{/* Loaded State */}
|
||||
{loadingState === 'loaded' && pages.length > 0 && pdf && (
|
||||
{loadingState === 'loaded' && pages.length > 0 && pdfRef.current && (
|
||||
<VirtualizedPageList
|
||||
scrollParentRef={scrollParentRef}
|
||||
constraintRef={$el}
|
||||
numPages={pages.length}
|
||||
pages={pages}
|
||||
pdf={pdf}
|
||||
pdf={pdfRef.current}
|
||||
customPageRenderer={customPageRenderer}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
type VirtualizedPageListProps = {
|
||||
scrollParentRef: ScrollTarget;
|
||||
|
||||
@@ -28,7 +28,7 @@ import { AddTemplateSettingsFormPartial } from '@documenso/ui/primitives/templat
|
||||
import type { TAddTemplateSettingsFormSchema } from '@documenso/ui/primitives/template-flow/add-template-settings.types';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { PDFViewer } from '~/components/general/pdf-viewer/pdf-viewer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
export type TemplateEditFormProps = {
|
||||
@@ -313,7 +313,7 @@ export const TemplateEditForm = ({
|
||||
gradient
|
||||
>
|
||||
<CardContent className="p-2">
|
||||
<PDFViewer
|
||||
<PDFViewerLazy
|
||||
key={template.envelopeItems[0]?.id}
|
||||
data={getDocumentDataUrlForPdfViewer({
|
||||
envelopeId: template.envelopeId,
|
||||
|
||||
@@ -37,7 +37,7 @@ import { EnvelopeRendererFileSelector } from '~/components/general/envelope-edit
|
||||
import { EnvelopeGenericPageRenderer } from '~/components/general/envelope-editor/envelope-generic-page-renderer';
|
||||
import { GenericErrorLayout } from '~/components/general/generic-error-layout';
|
||||
import { EnvelopePdfViewer } from '~/components/general/pdf-viewer/envelope-pdf-viewer';
|
||||
import { PDFViewer } from '~/components/general/pdf-viewer/pdf-viewer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
import { StackAvatarsWithTooltip } from '~/components/general/stack-avatars-with-tooltip';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
|
||||
@@ -199,7 +199,7 @@ export default function DocumentPage({ params }: Route.ComponentProps) {
|
||||
/>
|
||||
)}
|
||||
|
||||
<PDFViewer
|
||||
<PDFViewerLazy
|
||||
data={getDocumentDataUrlForPdfViewer({
|
||||
envelopeId: envelope.id,
|
||||
envelopeItemId: envelope.envelopeItems[0]?.id,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Trans } from '@lingui/react/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { ChevronLeftIcon } from 'lucide-react';
|
||||
import { Link, Outlet, isRouteErrorResponse, redirect } from 'react-router';
|
||||
import type { ShouldRevalidateFunctionArgs } from 'react-router';
|
||||
|
||||
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
@@ -14,6 +15,10 @@ import { GenericErrorLayout } from '~/components/general/generic-error-layout';
|
||||
|
||||
import type { Route } from './+types/settings._layout';
|
||||
|
||||
export const shouldRevalidate = ({ currentParams, nextParams }: ShouldRevalidateFunctionArgs) => {
|
||||
return currentParams.id !== nextParams.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* This file is very similar for templates as well. Any changes here should also be adjusted there as well.
|
||||
*
|
||||
|
||||
@@ -24,7 +24,7 @@ import { EnvelopeRendererFileSelector } from '~/components/general/envelope-edit
|
||||
import { EnvelopeGenericPageRenderer } from '~/components/general/envelope-editor/envelope-generic-page-renderer';
|
||||
import { GenericErrorLayout } from '~/components/general/generic-error-layout';
|
||||
import { EnvelopePdfViewer } from '~/components/general/pdf-viewer/envelope-pdf-viewer';
|
||||
import { PDFViewer } from '~/components/general/pdf-viewer/pdf-viewer';
|
||||
import PDFViewerLazy from '~/components/general/pdf-viewer/pdf-viewer-lazy';
|
||||
import { TemplateDirectLinkBadge } from '~/components/general/template/template-direct-link-badge';
|
||||
import { TemplatePageViewDocumentsTable } from '~/components/general/template/template-page-view-documents-table';
|
||||
import { TemplatePageViewInformation } from '~/components/general/template/template-page-view-information';
|
||||
@@ -210,7 +210,7 @@ export default function TemplatePage({ params }: Route.ComponentProps) {
|
||||
documentMeta={mockedDocumentMeta}
|
||||
/>
|
||||
|
||||
<PDFViewer
|
||||
<PDFViewerLazy
|
||||
data={getDocumentDataUrlForPdfViewer({
|
||||
envelopeId: envelope.id,
|
||||
envelopeItemId: envelope.envelopeItems[0]?.id,
|
||||
|
||||
@@ -2,7 +2,13 @@ import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { ChevronLeftIcon } from 'lucide-react';
|
||||
import { Link, Outlet, isRouteErrorResponse, redirect } from 'react-router';
|
||||
import {
|
||||
Link,
|
||||
Outlet,
|
||||
type ShouldRevalidateFunctionArgs,
|
||||
isRouteErrorResponse,
|
||||
redirect,
|
||||
} from 'react-router';
|
||||
|
||||
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
@@ -14,6 +20,10 @@ import { GenericErrorLayout } from '~/components/general/generic-error-layout';
|
||||
|
||||
import type { Route } from './+types/settings._layout';
|
||||
|
||||
export const shouldRevalidate = ({ currentParams, nextParams }: ShouldRevalidateFunctionArgs) => {
|
||||
return currentParams.id !== nextParams.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* This file is very similar for documents as well. Any changes here should also be adjusted there as well.
|
||||
*
|
||||
|
||||
@@ -20,6 +20,8 @@ import { injectCss } from '~/utils/css-vars';
|
||||
|
||||
import type { Route } from './+types/_layout';
|
||||
|
||||
export const shouldRevalidate = () => false;
|
||||
|
||||
export const loader = async ({ request }: Route.LoaderArgs) => {
|
||||
const url = new URL(request.url);
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader';
|
||||
|
||||
import type { Route } from './+types/envelope.create._index';
|
||||
|
||||
export const shouldRevalidate = () => false;
|
||||
|
||||
export const loader = async ({ request }: Route.LoaderArgs) => {
|
||||
const url = new URL(request.url);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
import { CheckCircle2Icon } from 'lucide-react';
|
||||
import { redirect } from 'react-router';
|
||||
import { type ShouldRevalidateFunctionArgs, redirect } from 'react-router';
|
||||
|
||||
import { EnvelopeEditorProvider } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
||||
import type { SupportedLanguageCodes } from '@documenso/lib/constants/i18n';
|
||||
@@ -30,6 +30,10 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader';
|
||||
|
||||
import type { Route } from './+types/envelope.edit.$id';
|
||||
|
||||
export const shouldRevalidate = ({ currentParams, nextParams }: ShouldRevalidateFunctionArgs) => {
|
||||
return currentParams.id !== nextParams.id;
|
||||
};
|
||||
|
||||
export const loader = async ({ request, params }: Route.LoaderArgs) => {
|
||||
const url = new URL(request.url);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session
|
||||
import { verifyEmbeddingPresignToken } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
|
||||
import { getTeamById } from '@documenso/lib/server-only/team/get-team';
|
||||
import type { DocumentDataVersion } from '@documenso/lib/types/document';
|
||||
import { sha256 } from '@documenso/lib/universal/crypto';
|
||||
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@@ -122,6 +123,12 @@ export const handleEnvelopeItemPdfRequest = async ({
|
||||
const documentDataToUse =
|
||||
version === 'current' ? envelopeItem.documentData.data : envelopeItem.documentData.initialData;
|
||||
|
||||
const etag = Buffer.from(sha256(documentDataToUse)).toString('hex');
|
||||
|
||||
if (c.req.header('If-None-Match') === etag) {
|
||||
return c.status(304);
|
||||
}
|
||||
|
||||
const file = await getFileServerSide({
|
||||
type: envelopeItem.documentData.type,
|
||||
data: documentDataToUse,
|
||||
@@ -137,6 +144,7 @@ export const handleEnvelopeItemPdfRequest = async ({
|
||||
|
||||
// Note: Only set these headers on success.
|
||||
c.header('Content-Type', 'application/pdf');
|
||||
c.header('ETag', etag);
|
||||
c.header('Cache-Control', `${cacheStrategy}, max-age=31536000, immutable`);
|
||||
|
||||
return c.body(file);
|
||||
|
||||
@@ -58,6 +58,7 @@ export default defineConfig({
|
||||
'playwright',
|
||||
'playwright-core',
|
||||
'@playwright/browser-chromium',
|
||||
'pdfjs-dist',
|
||||
],
|
||||
},
|
||||
optimizeDeps: {
|
||||
|
||||
@@ -197,7 +197,7 @@ test.describe('Template Field Prefill API v1', () => {
|
||||
|
||||
// 7. Navigate to the template
|
||||
await page.goto(
|
||||
`${WEBAPP_BASE_URL}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
|
||||
`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
|
||||
);
|
||||
|
||||
// 8. Create a document from the template with prefilled fields
|
||||
@@ -488,7 +488,7 @@ test.describe('Template Field Prefill API v1', () => {
|
||||
|
||||
// 7. Navigate to the template
|
||||
await page.goto(
|
||||
`${WEBAPP_BASE_URL}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
|
||||
`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
|
||||
);
|
||||
|
||||
// 8. Create a document from the template without prefilled fields
|
||||
|
||||
@@ -197,7 +197,7 @@ test.describe('Template Field Prefill API v2', () => {
|
||||
|
||||
// 7. Navigate to the template
|
||||
await page.goto(
|
||||
`${WEBAPP_BASE_URL}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
|
||||
`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
|
||||
);
|
||||
|
||||
// 8. Create a document from the template with prefilled fields using v2 API
|
||||
@@ -485,7 +485,7 @@ test.describe('Template Field Prefill API v2', () => {
|
||||
|
||||
// 7. Navigate to the template
|
||||
await page.goto(
|
||||
`${WEBAPP_BASE_URL}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
|
||||
`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
|
||||
);
|
||||
|
||||
// 8. Create a document from the template without prefilled fields using v2 API
|
||||
|
||||
@@ -124,7 +124,10 @@ export function EnvelopeFieldToolTip({
|
||||
<Tooltip delayDuration={0} open={!field.inserted || !field.fieldMeta}>
|
||||
<TooltipTrigger className="absolute inset-0 w-full"></TooltipTrigger>
|
||||
|
||||
<TooltipContent className={tooltipVariants({ color, className })} sideOffset={2}>
|
||||
<TooltipContent
|
||||
className={tooltipVariants({ color, className: cn(className, 'z-40') })}
|
||||
sideOffset={2}
|
||||
>
|
||||
{children}
|
||||
<TooltipArrow />
|
||||
</TooltipContent>
|
||||
|
||||
@@ -65,7 +65,7 @@ export function FieldToolTip({ children, color, className = '', field }: FieldTo
|
||||
<TooltipTrigger className="absolute inset-0 w-full"></TooltipTrigger>
|
||||
|
||||
<TooltipContent
|
||||
className={tooltipVariants({ color, className })}
|
||||
className={tooltipVariants({ color, className: cn(className, 'z-40') })}
|
||||
sideOffset={2}
|
||||
onClick={onTooltipContentClick}
|
||||
>
|
||||
|
||||
@@ -234,6 +234,15 @@
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.scrollbar-hidden {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.scrollbar-hidden::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* .custom-scrollbar::-webkit-scrollbar-track {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user