mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 17:21:41 +10:00
feat: add envelope editor
This commit is contained in:
158
packages/lib/universal/field-renderer/field-renderer.ts
Normal file
158
packages/lib/universal/field-renderer/field-renderer.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import type { Signature } from '@prisma/client';
|
||||
import { type Field } from '@prisma/client';
|
||||
import type Konva from 'konva';
|
||||
|
||||
import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors';
|
||||
|
||||
import type { TFieldMetaSchema } from '../../types/field-meta';
|
||||
|
||||
export const MIN_FIELD_HEIGHT_PX = 12;
|
||||
export const MIN_FIELD_WIDTH_PX = 36;
|
||||
|
||||
export type FieldToRender = Pick<
|
||||
Field,
|
||||
'envelopeItemId' | 'recipientId' | 'type' | 'page' | 'customText' | 'inserted' | 'recipientId'
|
||||
> & {
|
||||
renderId: string; // A unique ID for the field in the render.
|
||||
width: number;
|
||||
height: number;
|
||||
positionX: number;
|
||||
positionY: number;
|
||||
fieldMeta?: TFieldMetaSchema | null;
|
||||
signature?: Signature | null;
|
||||
};
|
||||
|
||||
export type RenderFieldElementOptions = {
|
||||
pageLayer: Konva.Layer;
|
||||
pageWidth: number;
|
||||
pageHeight: number;
|
||||
mode?: 'edit' | 'sign' | 'export';
|
||||
editable?: boolean;
|
||||
color?: TRecipientColor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a fields percentage based values to pixel based values.
|
||||
*/
|
||||
export const calculateFieldPosition = (
|
||||
field: Pick<FieldToRender, 'width' | 'height' | 'positionX' | 'positionY'>,
|
||||
pageWidth: number,
|
||||
pageHeight: number,
|
||||
) => {
|
||||
const fieldWidth = pageWidth * (Number(field.width) / 100);
|
||||
const fieldHeight = pageHeight * (Number(field.height) / 100);
|
||||
|
||||
const fieldX = pageWidth * (Number(field.positionX) / 100);
|
||||
const fieldY = pageHeight * (Number(field.positionY) / 100);
|
||||
|
||||
return { fieldX, fieldY, fieldWidth, fieldHeight };
|
||||
};
|
||||
|
||||
type ConvertPixelToPercentageOptions = {
|
||||
positionX: number;
|
||||
positionY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
pageWidth: number;
|
||||
pageHeight: number;
|
||||
};
|
||||
|
||||
export const convertPixelToPercentage = (options: ConvertPixelToPercentageOptions) => {
|
||||
const { positionX, positionY, width, height, pageWidth, pageHeight } = options;
|
||||
|
||||
const fieldX = (positionX / pageWidth) * 100;
|
||||
const fieldY = (positionY / pageHeight) * 100;
|
||||
|
||||
const fieldWidth = (width / pageWidth) * 100;
|
||||
const fieldHeight = (height / pageHeight) * 100;
|
||||
|
||||
return { fieldX, fieldY, fieldWidth, fieldHeight };
|
||||
};
|
||||
|
||||
type CalculateMultiItemPositionOptions = {
|
||||
/**
|
||||
* The field width in pixels.
|
||||
*/
|
||||
fieldWidth: number;
|
||||
|
||||
/**
|
||||
* The field height in pixels.
|
||||
*/
|
||||
fieldHeight: number;
|
||||
|
||||
/**
|
||||
* Total amount of items that will be rendered.
|
||||
*/
|
||||
itemCount: number;
|
||||
|
||||
/**
|
||||
* The position of the item in the list.
|
||||
*
|
||||
* Starts from 0
|
||||
*/
|
||||
itemIndex: number;
|
||||
|
||||
/**
|
||||
* The size of the item input, example checkbox box, radio button, etc.
|
||||
*/
|
||||
itemSize: number;
|
||||
|
||||
/**
|
||||
* The spacing between the item and text.
|
||||
*/
|
||||
spacingBetweenItemAndText: number;
|
||||
|
||||
/**
|
||||
* The inner padding of the field.
|
||||
*/
|
||||
fieldPadding: number;
|
||||
|
||||
type: 'checkbox' | 'radio';
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate the position of a field item such as Checkbox, Radio.
|
||||
*/
|
||||
export const calculateMultiItemPosition = (options: CalculateMultiItemPositionOptions) => {
|
||||
const {
|
||||
fieldWidth,
|
||||
fieldHeight,
|
||||
itemCount,
|
||||
itemIndex,
|
||||
itemSize,
|
||||
spacingBetweenItemAndText,
|
||||
fieldPadding,
|
||||
type,
|
||||
} = options;
|
||||
|
||||
const innerFieldHeight = fieldHeight - fieldPadding * 2;
|
||||
const innerFieldWidth = fieldWidth - fieldPadding; // This is purposefully not using fullPadding to allow flush text.
|
||||
const innerFieldX = fieldPadding;
|
||||
const innerFieldY = fieldPadding;
|
||||
|
||||
const itemHeight = innerFieldHeight / itemCount;
|
||||
|
||||
const y = itemIndex * itemHeight + innerFieldY;
|
||||
|
||||
let itemInputY = y + itemHeight / 2 - itemSize / 2;
|
||||
let itemInputX = innerFieldX;
|
||||
|
||||
if (type === 'radio') {
|
||||
itemInputX = innerFieldX + itemSize / 2;
|
||||
itemInputY = y + itemHeight / 2;
|
||||
}
|
||||
|
||||
const textX = innerFieldX + itemSize + spacingBetweenItemAndText;
|
||||
const textY = y;
|
||||
const textWidth = innerFieldWidth - itemSize - spacingBetweenItemAndText;
|
||||
const textHeight = itemHeight;
|
||||
|
||||
return {
|
||||
itemInputX,
|
||||
itemInputY,
|
||||
textX,
|
||||
textY,
|
||||
textWidth,
|
||||
textHeight,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user