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(null); const konvaContainer = useRef(null); const stage = useRef(null); const pageLayer = useRef(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 (
); }