mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
This PR is handles the changes required to support envelopes. The new envelope editor/signing page will be hidden during release. The core changes here is to migrate the documents and templates model to a centralized envelopes model. Even though Documents and Templates are removed, from the user perspective they will still exist as we remap envelopes to documents and templates.
177 lines
4.8 KiB
TypeScript
177 lines
4.8 KiB
TypeScript
import { useEffect, useMemo, useRef } from 'react';
|
|
|
|
import Konva from 'konva';
|
|
import type { Layer } from 'konva/lib/Layer';
|
|
import type { RenderParameters } from 'pdfjs-dist/types/src/display/api';
|
|
import { usePageContext } from 'react-pdf';
|
|
|
|
import type { TLocalField } from '@documenso/lib/client-only/hooks/use-editor-fields';
|
|
import { useCurrentEnvelopeEditor } from '@documenso/lib/client-only/providers/envelope-editor-provider';
|
|
import { useCurrentEnvelopeRender } from '@documenso/lib/client-only/providers/envelope-render-provider';
|
|
import { renderField } from '@documenso/lib/universal/field-renderer/render-field';
|
|
|
|
export default function EnvelopeEditorPagePreviewRenderer() {
|
|
const pageContext = usePageContext();
|
|
|
|
if (!pageContext) {
|
|
throw new Error('Unable to find Page context.');
|
|
}
|
|
|
|
const { _className, page, rotate, scale } = pageContext;
|
|
|
|
if (!page) {
|
|
throw new Error('Attempted to render page canvas, but no page was specified.');
|
|
}
|
|
|
|
const { editorFields, getRecipientColorKey } = useCurrentEnvelopeEditor();
|
|
const { currentEnvelopeItem } = useCurrentEnvelopeRender();
|
|
|
|
const canvasElement = useRef<HTMLCanvasElement>(null);
|
|
const konvaContainer = useRef<HTMLDivElement>(null);
|
|
|
|
const stage = useRef<Konva.Stage | null>(null);
|
|
const pageLayer = useRef<Layer | null>(null);
|
|
|
|
const viewport = useMemo(
|
|
() => page.getViewport({ scale, rotation: rotate }),
|
|
[page, rotate, scale],
|
|
);
|
|
|
|
const localPageFields = useMemo(
|
|
() =>
|
|
editorFields.localFields.filter(
|
|
(field) =>
|
|
field.page === pageContext.pageNumber && field.envelopeItemId === currentEnvelopeItem?.id,
|
|
),
|
|
[editorFields.localFields, pageContext.pageNumber],
|
|
);
|
|
|
|
// Custom renderer from Konva examples.
|
|
useEffect(
|
|
function drawPageOnCanvas() {
|
|
if (!page) {
|
|
return;
|
|
}
|
|
|
|
const { current: canvas } = canvasElement;
|
|
const { current: container } = konvaContainer;
|
|
|
|
if (!canvas || !container) {
|
|
return;
|
|
}
|
|
|
|
const renderContext: RenderParameters = {
|
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
canvasContext: canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D,
|
|
viewport,
|
|
};
|
|
|
|
const cancellable = page.render(renderContext);
|
|
const runningTask = cancellable;
|
|
|
|
cancellable.promise.catch(() => {
|
|
// Intentionally empty
|
|
});
|
|
|
|
void cancellable.promise.then(() => {
|
|
createPageCanvas(container);
|
|
});
|
|
|
|
return () => {
|
|
runningTask.cancel();
|
|
};
|
|
},
|
|
[page, viewport],
|
|
);
|
|
|
|
const renderFieldOnLayer = (field: TLocalField) => {
|
|
if (!pageLayer.current) {
|
|
console.error('Layer not loaded yet');
|
|
return;
|
|
}
|
|
|
|
renderField({
|
|
pageLayer: pageLayer.current,
|
|
field: {
|
|
renderId: field.formId,
|
|
...field,
|
|
customText: '',
|
|
inserted: false,
|
|
fieldMeta: field.fieldMeta,
|
|
},
|
|
pageWidth: viewport.width,
|
|
pageHeight: viewport.height,
|
|
color: getRecipientColorKey(field.recipientId),
|
|
editable: false,
|
|
mode: 'export',
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Create the initial Konva page canvas and initialize all fields and interactions.
|
|
*/
|
|
const createPageCanvas = (container: HTMLDivElement) => {
|
|
stage.current = new Konva.Stage({
|
|
container,
|
|
width: viewport.width,
|
|
height: viewport.height,
|
|
});
|
|
|
|
// Create the main layer for interactive elements.
|
|
pageLayer.current = new Konva.Layer();
|
|
stage.current?.add(pageLayer.current);
|
|
|
|
// Render the fields.
|
|
for (const field of localPageFields) {
|
|
renderFieldOnLayer(field);
|
|
}
|
|
|
|
pageLayer.current.batchDraw();
|
|
};
|
|
|
|
/**
|
|
* Render fields when they are added or removed from the localFields.
|
|
*/
|
|
useEffect(() => {
|
|
if (!pageLayer.current || !stage.current) {
|
|
return;
|
|
}
|
|
|
|
// If doesn't exist in localFields, destroy it since it's been deleted.
|
|
pageLayer.current.find('Group').forEach((group) => {
|
|
if (
|
|
group.name() === 'field-group' &&
|
|
!localPageFields.some((field) => field.formId === group.id())
|
|
) {
|
|
console.log('Field removed, removing from canvas');
|
|
group.destroy();
|
|
}
|
|
});
|
|
|
|
// If it exists, rerender.
|
|
localPageFields.forEach((field) => {
|
|
console.log('Field created/updated, rendering on canvas');
|
|
renderFieldOnLayer(field);
|
|
});
|
|
|
|
pageLayer.current.batchDraw();
|
|
}, [localPageFields]);
|
|
|
|
if (!currentEnvelopeItem) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className="relative" key={`${currentEnvelopeItem.id}-renderer-${pageContext.pageNumber}`}>
|
|
<div className="konva-container absolute inset-0 z-10" ref={konvaContainer}></div>
|
|
|
|
<canvas
|
|
className={`${_className}__canvas z-0`}
|
|
height={viewport.height}
|
|
ref={canvasElement}
|
|
width={viewport.width}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|