@servicetitan/dte-pdf-editor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +356 -0
- package/dist/components/data-model-field-type-list.d.ts +11 -0
- package/dist/components/data-model-field-type-list.d.ts.map +1 -0
- package/dist/components/data-model-field-type-list.js +23 -0
- package/dist/components/data-model-field-type-list.js.map +1 -0
- package/dist/components/e-sign-field-type-list.d.ts +9 -0
- package/dist/components/e-sign-field-type-list.d.ts.map +1 -0
- package/dist/components/e-sign-field-type-list.js +12 -0
- package/dist/components/e-sign-field-type-list.js.map +1 -0
- package/dist/components/field-config-panel-overlay.d.ts +13 -0
- package/dist/components/field-config-panel-overlay.d.ts.map +1 -0
- package/dist/components/field-config-panel-overlay.js +8 -0
- package/dist/components/field-config-panel-overlay.js.map +1 -0
- package/dist/components/field-config-panel.d.ts +12 -0
- package/dist/components/field-config-panel.d.ts.map +1 -0
- package/dist/components/field-config-panel.js +38 -0
- package/dist/components/field-config-panel.js.map +1 -0
- package/dist/components/field-sidebar.d.ts +10 -0
- package/dist/components/field-sidebar.d.ts.map +1 -0
- package/dist/components/field-sidebar.js +25 -0
- package/dist/components/field-sidebar.js.map +1 -0
- package/dist/components/field-type.d.ts +9 -0
- package/dist/components/field-type.d.ts.map +1 -0
- package/dist/components/field-type.js +12 -0
- package/dist/components/field-type.js.map +1 -0
- package/dist/components/fillable-field-type-list.d.ts +10 -0
- package/dist/components/fillable-field-type-list.d.ts.map +1 -0
- package/dist/components/fillable-field-type-list.js +17 -0
- package/dist/components/fillable-field-type-list.js.map +1 -0
- package/dist/components/pdf-canvas.d.ts +22 -0
- package/dist/components/pdf-canvas.d.ts.map +1 -0
- package/dist/components/pdf-canvas.js +14 -0
- package/dist/components/pdf-canvas.js.map +1 -0
- package/dist/components/pdf-document-renderer.d.ts +16 -0
- package/dist/components/pdf-document-renderer.d.ts.map +1 -0
- package/dist/components/pdf-document-renderer.js +28 -0
- package/dist/components/pdf-document-renderer.js.map +1 -0
- package/dist/components/pdf-editor.d.ts +21 -0
- package/dist/components/pdf-editor.d.ts.map +1 -0
- package/dist/components/pdf-editor.js +108 -0
- package/dist/components/pdf-editor.js.map +1 -0
- package/dist/components/pdf-field-overlay.d.ts +14 -0
- package/dist/components/pdf-field-overlay.d.ts.map +1 -0
- package/dist/components/pdf-field-overlay.js +32 -0
- package/dist/components/pdf-field-overlay.js.map +1 -0
- package/dist/constants/field.constants.d.ts +16 -0
- package/dist/constants/field.constants.d.ts.map +1 -0
- package/dist/constants/field.constants.js +28 -0
- package/dist/constants/field.constants.js.map +1 -0
- package/dist/constants/index.d.ts +3 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +3 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/constants/pdf-editor.constants.d.ts +2 -0
- package/dist/constants/pdf-editor.constants.d.ts.map +1 -0
- package/dist/constants/pdf-editor.constants.js +2 -0
- package/dist/constants/pdf-editor.constants.js.map +1 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useFieldDrag.d.ts +24 -0
- package/dist/hooks/useFieldDrag.d.ts.map +1 -0
- package/dist/hooks/useFieldDrag.js +34 -0
- package/dist/hooks/useFieldDrag.js.map +1 -0
- package/dist/hooks/useFieldResize.d.ts +17 -0
- package/dist/hooks/useFieldResize.d.ts.map +1 -0
- package/dist/hooks/useFieldResize.js +58 -0
- package/dist/hooks/useFieldResize.js.map +1 -0
- package/dist/hooks/usePdfDocumentRenderer.d.ts +9 -0
- package/dist/hooks/usePdfDocumentRenderer.d.ts.map +1 -0
- package/dist/hooks/usePdfDocumentRenderer.js +13 -0
- package/dist/hooks/usePdfDocumentRenderer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/interface/pdf-editor.d.ts +64 -0
- package/dist/interface/pdf-editor.d.ts.map +1 -0
- package/dist/interface/pdf-editor.js +14 -0
- package/dist/interface/pdf-editor.js.map +1 -0
- package/dist/utils/calculate-drop-coordinates.utils.d.ts +15 -0
- package/dist/utils/calculate-drop-coordinates.utils.d.ts.map +1 -0
- package/dist/utils/calculate-drop-coordinates.utils.js +48 -0
- package/dist/utils/calculate-drop-coordinates.utils.js.map +1 -0
- package/dist/utils/extract-grouped-fields-from-data-model.utils.d.ts +7 -0
- package/dist/utils/extract-grouped-fields-from-data-model.utils.d.ts.map +1 -0
- package/dist/utils/extract-grouped-fields-from-data-model.utils.js +57 -0
- package/dist/utils/extract-grouped-fields-from-data-model.utils.js.map +1 -0
- package/dist/utils/generate-e-sign-path.d.ts +3 -0
- package/dist/utils/generate-e-sign-path.d.ts.map +1 -0
- package/dist/utils/generate-e-sign-path.js +4 -0
- package/dist/utils/generate-e-sign-path.js.map +1 -0
- package/dist/utils/get-page-dimensions.utils.d.ts +12 -0
- package/dist/utils/get-page-dimensions.utils.d.ts.map +1 -0
- package/dist/utils/get-page-dimensions.utils.js +31 -0
- package/dist/utils/get-page-dimensions.utils.js.map +1 -0
- package/dist/utils/get-page-number-from-client-y.utils.d.ts +9 -0
- package/dist/utils/get-page-number-from-client-y.utils.d.ts.map +1 -0
- package/dist/utils/get-page-number-from-client-y.utils.js +24 -0
- package/dist/utils/get-page-number-from-client-y.utils.js.map +1 -0
- package/dist/utils/get-page-position.utils.d.ts +12 -0
- package/dist/utils/get-page-position.utils.d.ts.map +1 -0
- package/dist/utils/get-page-position.utils.js +22 -0
- package/dist/utils/get-page-position.utils.js.map +1 -0
- package/dist/utils/handle-field-drag-start.utils.d.ts +16 -0
- package/dist/utils/handle-field-drag-start.utils.d.ts.map +1 -0
- package/dist/utils/handle-field-drag-start.utils.js +41 -0
- package/dist/utils/handle-field-drag-start.utils.js.map +1 -0
- package/dist/utils/handle-field-drag.utils.d.ts +19 -0
- package/dist/utils/handle-field-drag.utils.d.ts.map +1 -0
- package/dist/utils/handle-field-drag.utils.js +36 -0
- package/dist/utils/handle-field-drag.utils.js.map +1 -0
- package/dist/utils/handle-field-resize.utils.d.ts +35 -0
- package/dist/utils/handle-field-resize.utils.d.ts.map +1 -0
- package/dist/utils/handle-field-resize.utils.js +66 -0
- package/dist/utils/handle-field-resize.utils.js.map +1 -0
- package/dist/utils/index.d.ts +13 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +13 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/is-drag-over-canvas.utils.d.ts +9 -0
- package/dist/utils/is-drag-over-canvas.utils.d.ts.map +1 -0
- package/dist/utils/is-drag-over-canvas.utils.js +26 -0
- package/dist/utils/is-drag-over-canvas.utils.js.map +1 -0
- package/dist/utils/map-colors-to-recipients.d.ts +3 -0
- package/dist/utils/map-colors-to-recipients.d.ts.map +1 -0
- package/dist/utils/map-colors-to-recipients.js +35 -0
- package/dist/utils/map-colors-to-recipients.js.map +1 -0
- package/dist/utils/pdfjs-init.d.ts +6 -0
- package/dist/utils/pdfjs-init.d.ts.map +1 -0
- package/dist/utils/pdfjs-init.js +25 -0
- package/dist/utils/pdfjs-init.js.map +1 -0
- package/package.json +28 -0
- package/src/components/data-model-field-type-list.tsx +58 -0
- package/src/components/e-sign-field-type-list.tsx +27 -0
- package/src/components/field-config-panel-overlay.tsx +51 -0
- package/src/components/field-config-panel.tsx +142 -0
- package/src/components/field-sidebar.tsx +93 -0
- package/src/components/field-type.tsx +28 -0
- package/src/components/fillable-field-type-list.tsx +42 -0
- package/src/components/pdf-canvas.tsx +81 -0
- package/src/components/pdf-document-renderer.tsx +78 -0
- package/src/components/pdf-editor.tsx +216 -0
- package/src/components/pdf-field-overlay.tsx +83 -0
- package/src/constants/field.constants.ts +31 -0
- package/src/constants/index.ts +2 -0
- package/src/constants/pdf-editor.constants.ts +1 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useFieldDrag.ts +56 -0
- package/src/hooks/useFieldResize.ts +95 -0
- package/src/hooks/usePdfDocumentRenderer.ts +21 -0
- package/src/index.ts +2 -0
- package/src/interface/pdf-editor.ts +74 -0
- package/src/styles/field-config-panel-overlay.css +33 -0
- package/src/styles/field-sidebar.css +31 -0
- package/src/styles/field-type-list.css +8 -0
- package/src/styles/field-type.css +10 -0
- package/src/styles/generic.css +3 -0
- package/src/styles/index.css +10 -0
- package/src/styles/pdf-canvas.css +9 -0
- package/src/styles/pdf-document-renderer.css +4 -0
- package/src/styles/pdf-editor.css +14 -0
- package/src/styles/pdf-field-overlay.css +54 -0
- package/src/styles/variables.css +26 -0
- package/src/utils/calculate-drop-coordinates.utils.ts +68 -0
- package/src/utils/extract-grouped-fields-from-data-model.utils.ts +73 -0
- package/src/utils/generate-e-sign-path.ts +5 -0
- package/src/utils/get-page-dimensions.utils.ts +39 -0
- package/src/utils/get-page-number-from-client-y.utils.ts +31 -0
- package/src/utils/get-page-position.utils.ts +30 -0
- package/src/utils/handle-field-drag-start.utils.ts +52 -0
- package/src/utils/handle-field-drag.utils.ts +55 -0
- package/src/utils/handle-field-resize.utils.ts +102 -0
- package/src/utils/index.ts +12 -0
- package/src/utils/is-drag-over-canvas.utils.ts +35 -0
- package/src/utils/map-colors-to-recipients.ts +37 -0
- package/src/utils/pdfjs-init.ts +27 -0
- package/src/vite-env.d.ts +16 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { MouseEvent, RefObject, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { PdfField } from '../interface/pdf-editor';
|
|
3
|
+
import { handleFieldResize, handleFieldResizeStart } from '../utils';
|
|
4
|
+
|
|
5
|
+
interface UseFieldResizeOptions {
|
|
6
|
+
pdfWrapperRef: RefObject<HTMLDivElement>;
|
|
7
|
+
onFieldResize: (
|
|
8
|
+
fieldId: string,
|
|
9
|
+
newWidth: number,
|
|
10
|
+
newHeight: number,
|
|
11
|
+
pageNumber: number,
|
|
12
|
+
) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface UseFieldResizeReturn {
|
|
16
|
+
isResizing: boolean;
|
|
17
|
+
handleResizeStart: (e: MouseEvent<HTMLDivElement>, field: PdfField) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Custom hook for handling field resize operations
|
|
22
|
+
* Manages resize state and coordinates field resizing with proper cleanup
|
|
23
|
+
*/
|
|
24
|
+
export const useFieldResize = ({
|
|
25
|
+
onFieldResize,
|
|
26
|
+
pdfWrapperRef,
|
|
27
|
+
}: UseFieldResizeOptions): UseFieldResizeReturn => {
|
|
28
|
+
const resizeStartDataRef = useRef<{
|
|
29
|
+
field: PdfField;
|
|
30
|
+
startX: number;
|
|
31
|
+
startY: number;
|
|
32
|
+
startWidth: number;
|
|
33
|
+
startHeight: number;
|
|
34
|
+
page: number;
|
|
35
|
+
} | null>(null);
|
|
36
|
+
const onFieldResizeRef = useRef(onFieldResize);
|
|
37
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
onFieldResizeRef.current = onFieldResize;
|
|
41
|
+
}, [onFieldResize]);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!isResizing) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const abortController = new AbortController();
|
|
49
|
+
const { signal } = abortController;
|
|
50
|
+
|
|
51
|
+
const handleMouseMove = (e: globalThis.MouseEvent) => {
|
|
52
|
+
if (!resizeStartDataRef.current || signal.aborted) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { field, ...resizeStartData } = resizeStartDataRef.current;
|
|
57
|
+
handleFieldResize(e, field, resizeStartData, pdfWrapperRef, onFieldResizeRef.current);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleMouseUp = () => {
|
|
61
|
+
if (signal.aborted) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
resizeStartDataRef.current = null;
|
|
65
|
+
setIsResizing(false);
|
|
66
|
+
abortController.abort();
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
document.addEventListener('mousemove', handleMouseMove, { signal });
|
|
70
|
+
document.addEventListener('mouseup', handleMouseUp, { signal });
|
|
71
|
+
|
|
72
|
+
return () => {
|
|
73
|
+
abortController.abort();
|
|
74
|
+
};
|
|
75
|
+
}, [isResizing, pdfWrapperRef]);
|
|
76
|
+
|
|
77
|
+
const handleResizeStart = (e: MouseEvent<HTMLDivElement>, field: PdfField) => {
|
|
78
|
+
e.stopPropagation();
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
|
|
81
|
+
const resizeStartData = handleFieldResizeStart(e, field, pdfWrapperRef);
|
|
82
|
+
if (resizeStartData) {
|
|
83
|
+
resizeStartDataRef.current = {
|
|
84
|
+
field,
|
|
85
|
+
...resizeStartData,
|
|
86
|
+
};
|
|
87
|
+
setIsResizing(true);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
isResizing,
|
|
93
|
+
handleResizeStart,
|
|
94
|
+
};
|
|
95
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UsePdfDocumentRendererReturn {
|
|
4
|
+
pages: number[];
|
|
5
|
+
handleDocumentLoadSuccess: (data: { numPages: number }) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const usePdfDocumentRenderer = (): UsePdfDocumentRendererReturn => {
|
|
9
|
+
const [numPages, setNumPages] = useState<number>(0);
|
|
10
|
+
|
|
11
|
+
const handleDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
|
|
12
|
+
setNumPages(numPages);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const pages = useMemo(() => Array.from({ length: numPages }, (_, i) => i + 1), [numPages]);
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
pages,
|
|
19
|
+
handleDocumentLoadSuccess,
|
|
20
|
+
};
|
|
21
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export enum FieldTypeEnum {
|
|
2
|
+
dataModel = 'data-model',
|
|
3
|
+
eSign = 'e-sign',
|
|
4
|
+
fillable = 'fillable',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export enum ESignFieldType {
|
|
8
|
+
signature = 'signature',
|
|
9
|
+
initials = 'initials',
|
|
10
|
+
dateSigned = 'dateSigned',
|
|
11
|
+
fullName = 'fullName',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type FillableFieldType = 'text' | 'date' | 'checkbox' | 'e-sign';
|
|
15
|
+
|
|
16
|
+
export interface PdfField {
|
|
17
|
+
id: string;
|
|
18
|
+
type: FieldTypeEnum;
|
|
19
|
+
subType?: FillableFieldType | ESignFieldType;
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
page: number;
|
|
23
|
+
label: string;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
required?: boolean;
|
|
27
|
+
path?: string;
|
|
28
|
+
recipient?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface FieldTypeOption {
|
|
32
|
+
label: string;
|
|
33
|
+
type: FieldTypeEnum;
|
|
34
|
+
subType?: FillableFieldType | ESignFieldType;
|
|
35
|
+
path?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DataModelFieldGroup {
|
|
39
|
+
groupName: string;
|
|
40
|
+
fields: FieldTypeOption[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SchemaFieldBaseOptions {
|
|
44
|
+
placeholder?: any;
|
|
45
|
+
description?: any;
|
|
46
|
+
sampleData?: any;
|
|
47
|
+
showInEditor?: boolean;
|
|
48
|
+
isHighlighted?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface SchemaNodeProps {
|
|
52
|
+
title?: string;
|
|
53
|
+
options?: SchemaFieldBaseOptions;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface SchemaObject extends SchemaNodeProps {
|
|
57
|
+
type: 'object';
|
|
58
|
+
properties: Record<string, SchemaNode>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type SchemaNode = SchemaObject | SchemaArray | SchemaSimple;
|
|
62
|
+
|
|
63
|
+
export interface SchemaArray extends SchemaNodeProps {
|
|
64
|
+
type: 'array';
|
|
65
|
+
items: SchemaNode;
|
|
66
|
+
}
|
|
67
|
+
export type SchemaNodeStringSubTypes = 'string' | 'text' | 'html' | 'image';
|
|
68
|
+
|
|
69
|
+
export interface SchemaSimpleString extends SchemaNodeProps {
|
|
70
|
+
type: 'string';
|
|
71
|
+
subType?: SchemaNodeStringSubTypes;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type SchemaSimple = SchemaSimpleString;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
@keyframes dte-slide-in-left {
|
|
2
|
+
from {
|
|
3
|
+
transform: translateX(-100%);
|
|
4
|
+
}
|
|
5
|
+
to {
|
|
6
|
+
transform: translateX(0);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.dte-field-config-panel-overlay {
|
|
11
|
+
position: absolute;
|
|
12
|
+
top: 0;
|
|
13
|
+
left: 0;
|
|
14
|
+
bottom: 0;
|
|
15
|
+
width: 100%;
|
|
16
|
+
border-right: 1px solid var(--border-color);
|
|
17
|
+
background-color: var(--white);
|
|
18
|
+
z-index: 1000;
|
|
19
|
+
overflow-y: auto;
|
|
20
|
+
animation: dte-slide-in-left 0.3s ease-out;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dte-field-config-panel-header {
|
|
26
|
+
padding: var(--spacing-2);
|
|
27
|
+
border-bottom: 1px solid var(--border-color);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.dte-field-config-panel-content {
|
|
31
|
+
padding: var(--spacing-2);
|
|
32
|
+
flex: 1;
|
|
33
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.dte-field-sidebar-container {
|
|
2
|
+
position: relative;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.dte-pdf-editor .dte-field-sidebar-menu {
|
|
6
|
+
width: 85px;
|
|
7
|
+
padding: var(--spacing-2) var(--spacing-half);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.dte-field-sidebar-menu-item {
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.dte-field-sidebar-menu-item-text {
|
|
15
|
+
text-align: center;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.dte-field-sidebar-content {
|
|
19
|
+
border-left: 1px solid var(--border-color);
|
|
20
|
+
border-right: 1px solid var(--border-color);
|
|
21
|
+
padding: var(--spacing-2);
|
|
22
|
+
position: relative;
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
overflow-y: auto;
|
|
26
|
+
width: 305px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dte-field-sidebar-search {
|
|
30
|
+
margin-top: 18px;
|
|
31
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@import './variables.css';
|
|
2
|
+
@import './generic.css';
|
|
3
|
+
@import './pdf-canvas.css';
|
|
4
|
+
@import './field-sidebar.css';
|
|
5
|
+
@import './pdf-field-overlay.css';
|
|
6
|
+
@import './pdf-editor.css';
|
|
7
|
+
@import './field-type-list.css';
|
|
8
|
+
@import './field-config-panel-overlay.css';
|
|
9
|
+
@import './field-type.css';
|
|
10
|
+
@import './pdf-document-renderer.css';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.dte-pdf-editor {
|
|
2
|
+
max-width: 100%;
|
|
3
|
+
border: 1px solid var(--border-color);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.dte-pdf-editor-sidebar-container {
|
|
7
|
+
position: relative;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.dte-pdf-editor-content-container {
|
|
12
|
+
max-width: 100%;
|
|
13
|
+
overflow: auto;
|
|
14
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
.dte-pdf-field-overlay {
|
|
2
|
+
position: absolute;
|
|
3
|
+
top: 0;
|
|
4
|
+
left: 0;
|
|
5
|
+
width: 100%;
|
|
6
|
+
height: 100%;
|
|
7
|
+
pointer-events: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.dte-pdf-field {
|
|
11
|
+
position: absolute;
|
|
12
|
+
border: 1px dashed #999;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
font-size: var(--typescale-1, 14px);
|
|
17
|
+
padding: var(--spacing-half, 4px);
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
pointer-events: auto;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.dte-pdf-field-selected {
|
|
23
|
+
border: 2px solid var(--border-color-active);
|
|
24
|
+
cursor: move;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.dte-pdf-field-unselected {
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.dte-pdf-field-dragging {
|
|
32
|
+
opacity: 0.5;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.dte-pdf-field-path {
|
|
36
|
+
margin-top: 2px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.dte-pdf-field-resize-handle {
|
|
40
|
+
position: absolute;
|
|
41
|
+
bottom: 0;
|
|
42
|
+
right: 0;
|
|
43
|
+
width: 12px;
|
|
44
|
+
height: 12px;
|
|
45
|
+
background-color: var(--border-color-active);
|
|
46
|
+
border: 1px solid var(--border-color-active);
|
|
47
|
+
cursor: nwse-resize;
|
|
48
|
+
z-index: 10;
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.dte-pdf-field-resize-handle:hover {
|
|
53
|
+
background-color: var(--border-color-active);
|
|
54
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
@import '@servicetitan/tokens/dist/tokens.css';
|
|
2
|
+
|
|
3
|
+
.dte-pdf-editor {
|
|
4
|
+
/* Typography */
|
|
5
|
+
--typescale-1: var(--typescale-1, 14px);
|
|
6
|
+
|
|
7
|
+
/* Spacing */
|
|
8
|
+
--spacing-0: 0;
|
|
9
|
+
--spacing-half: 4px;
|
|
10
|
+
--spacing-2: 16px;
|
|
11
|
+
|
|
12
|
+
/* Colors - Neutral */
|
|
13
|
+
--color-neutral-0: var(--color-neutral-0, #ffffff);
|
|
14
|
+
|
|
15
|
+
/* Colors - Semantic */
|
|
16
|
+
--success: #28a745;
|
|
17
|
+
--danger: #dc3545;
|
|
18
|
+
--green: #28a745;
|
|
19
|
+
--red: #dc3545;
|
|
20
|
+
--white: var(--color-neutral-0, #ffffff);
|
|
21
|
+
|
|
22
|
+
/* Border colors*/
|
|
23
|
+
--border-color: #e0e0e0;
|
|
24
|
+
--border-color-hover: #bdbdbd;
|
|
25
|
+
--border-color-active: #007bff;
|
|
26
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { DragEvent, RefObject } from 'react';
|
|
2
|
+
import { FIELD_CONSTANTS } from '../constants';
|
|
3
|
+
|
|
4
|
+
interface DropCoordinates {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Calculates field coordinates from a drop event on a PDF page
|
|
11
|
+
* @param e - The drag event
|
|
12
|
+
* @param pageNumber - The page number where the drop occurred
|
|
13
|
+
* @param pdfWrapperRef - Reference to the PDF wrapper element
|
|
14
|
+
* @returns Drop coordinates (x, y) or null if a drop is invalid
|
|
15
|
+
*/
|
|
16
|
+
export const calculateDropCoordinates = (
|
|
17
|
+
e: DragEvent<HTMLDivElement>,
|
|
18
|
+
pageNumber: number,
|
|
19
|
+
pdfWrapperRef: RefObject<HTMLDivElement>,
|
|
20
|
+
): DropCoordinates | null => {
|
|
21
|
+
if (!pdfWrapperRef.current) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const pageElement = pdfWrapperRef.current.querySelector(`[data-page-number="${pageNumber}"]`);
|
|
26
|
+
|
|
27
|
+
if (!pageElement) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const canvas = pageElement.querySelector('canvas');
|
|
32
|
+
if (!canvas) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const canvasRect = canvas.getBoundingClientRect();
|
|
37
|
+
const pageRect = pageElement.getBoundingClientRect();
|
|
38
|
+
const wrapperRect = pdfWrapperRef.current.getBoundingClientRect();
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* Strictly check if the drop is within the actual PDF canvas boundaries
|
|
42
|
+
* Only allow drops directly on the canvas, not on the page wrapper outside the canvas
|
|
43
|
+
*/
|
|
44
|
+
const isWithinCanvas =
|
|
45
|
+
e.clientX >= canvasRect.left &&
|
|
46
|
+
e.clientX <= canvasRect.right &&
|
|
47
|
+
e.clientY >= canvasRect.top &&
|
|
48
|
+
e.clientY <= canvasRect.bottom;
|
|
49
|
+
|
|
50
|
+
if (!isWithinCanvas) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/*
|
|
55
|
+
* Calculate coordinates relative to the page element (matching the overlay's coordinate system).
|
|
56
|
+
* This matches how pdf-field-overlay.tsx calculates positions: pagePos.left + field.x
|
|
57
|
+
*/
|
|
58
|
+
const pagePosLeft = pageRect.left - wrapperRect.left;
|
|
59
|
+
const pagePosTop = pageRect.top - wrapperRect.top;
|
|
60
|
+
const x = e.clientX - wrapperRect.left - pagePosLeft;
|
|
61
|
+
const y = e.clientY - wrapperRect.top - pagePosTop;
|
|
62
|
+
|
|
63
|
+
// Center the field on the drop point (half of width/height)
|
|
64
|
+
const fieldX = Math.max(0, x - FIELD_CONSTANTS.dropOffsetX);
|
|
65
|
+
const fieldY = Math.max(0, y - FIELD_CONSTANTS.dropOffsetY);
|
|
66
|
+
|
|
67
|
+
return { x: fieldX, y: fieldY };
|
|
68
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DataModelFieldGroup,
|
|
3
|
+
FieldTypeEnum,
|
|
4
|
+
FieldTypeOption,
|
|
5
|
+
SchemaObject,
|
|
6
|
+
} from '../interface/pdf-editor';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Utility function to extract grouped fields from DataModel structure
|
|
10
|
+
* Filters out array types and only processes object properties, generating correct paths for data replacement
|
|
11
|
+
*/
|
|
12
|
+
export const extractGroupedFieldsFromDataModel = (
|
|
13
|
+
dataModel: SchemaObject,
|
|
14
|
+
): DataModelFieldGroup[] => {
|
|
15
|
+
const groups: DataModelFieldGroup[] = [];
|
|
16
|
+
|
|
17
|
+
if (!dataModel?.properties) {
|
|
18
|
+
return groups;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Object.keys(dataModel.properties).forEach(key => {
|
|
22
|
+
const property = dataModel.properties[key];
|
|
23
|
+
|
|
24
|
+
if (property.type === 'object' && property.properties) {
|
|
25
|
+
const fields: FieldTypeOption[] = [];
|
|
26
|
+
extractFieldsRecursive(property.properties, key, fields, key);
|
|
27
|
+
|
|
28
|
+
if (fields.length > 0) {
|
|
29
|
+
groups.push({
|
|
30
|
+
groupName: property.title ?? key,
|
|
31
|
+
fields,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
} else if (property.type === 'array' && property.items) {
|
|
35
|
+
// Skip array types - filter them out from the data model
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return groups;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Recursive function to extract fields from nested structures
|
|
43
|
+
const extractFieldsRecursive = (
|
|
44
|
+
properties: any,
|
|
45
|
+
basePath: string,
|
|
46
|
+
fields: FieldTypeOption[],
|
|
47
|
+
groupName: string,
|
|
48
|
+
): void => {
|
|
49
|
+
Object.keys(properties).forEach(fieldKey => {
|
|
50
|
+
const fieldProperty = properties[fieldKey];
|
|
51
|
+
const currentPath = basePath.includes('[]')
|
|
52
|
+
? `${basePath}.${fieldKey}`
|
|
53
|
+
: `${basePath}.${fieldKey}`;
|
|
54
|
+
|
|
55
|
+
if (
|
|
56
|
+
fieldProperty.type === 'string' ||
|
|
57
|
+
fieldProperty.type === 'number' ||
|
|
58
|
+
fieldProperty.type === 'boolean'
|
|
59
|
+
) {
|
|
60
|
+
// Leaf property - add as a field
|
|
61
|
+
const label = fieldProperty.title ?? fieldKey;
|
|
62
|
+
fields.push({
|
|
63
|
+
label,
|
|
64
|
+
type: FieldTypeEnum.dataModel,
|
|
65
|
+
path: currentPath,
|
|
66
|
+
});
|
|
67
|
+
} else if (fieldProperty.type === 'object' && fieldProperty.properties) {
|
|
68
|
+
extractFieldsRecursive(fieldProperty.properties, currentPath, fields, groupName);
|
|
69
|
+
} else if (fieldProperty.type === 'array' && fieldProperty.items) {
|
|
70
|
+
// Skip array types - filter them out from the data model
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gets the dimensions of a PDF page (width and height)
|
|
5
|
+
* @param pageNumber - The page number to get dimensions for
|
|
6
|
+
* @param pdfWrapperRef - Reference to the PDF wrapper element
|
|
7
|
+
* @returns Dimensions object with width and height, or { width: 0, height: 0 } if not found
|
|
8
|
+
*/
|
|
9
|
+
export const getPageDimensions = (
|
|
10
|
+
pageNumber: number,
|
|
11
|
+
pdfWrapperRef: RefObject<HTMLDivElement>,
|
|
12
|
+
): { width: number; height: number } => {
|
|
13
|
+
if (!pdfWrapperRef.current) {
|
|
14
|
+
return { width: 0, height: 0 };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const pageElement = pdfWrapperRef.current.querySelector(`[data-page-number="${pageNumber}"]`);
|
|
18
|
+
|
|
19
|
+
if (!pageElement) {
|
|
20
|
+
return { width: 0, height: 0 };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Get the canvas element inside the page to get actual PDF dimensions
|
|
24
|
+
const canvas = pageElement.querySelector('canvas');
|
|
25
|
+
if (canvas) {
|
|
26
|
+
const canvasRect = canvas.getBoundingClientRect();
|
|
27
|
+
return {
|
|
28
|
+
width: canvasRect.width,
|
|
29
|
+
height: canvasRect.height,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Fallback to page element dimensions
|
|
34
|
+
const pageRect = pageElement.getBoundingClientRect();
|
|
35
|
+
return {
|
|
36
|
+
width: pageRect.width,
|
|
37
|
+
height: pageRect.height,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Determines which page number a client Y coordinate falls within
|
|
5
|
+
* @param clientY - The client Y coordinate to check
|
|
6
|
+
* @param pdfWrapperRef - Reference to the PDF wrapper element
|
|
7
|
+
* @returns The page number (defaults to 1 if not found or wrapper is null)
|
|
8
|
+
*/
|
|
9
|
+
export const getPageNumberFromClientY = (
|
|
10
|
+
clientY: number,
|
|
11
|
+
pdfWrapperRef: RefObject<HTMLDivElement>,
|
|
12
|
+
): number => {
|
|
13
|
+
if (!pdfWrapperRef.current) {
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const pageElements = pdfWrapperRef.current.querySelectorAll('[data-page-number]');
|
|
18
|
+
let targetPage = 1;
|
|
19
|
+
|
|
20
|
+
pageElements.forEach(pageElement => {
|
|
21
|
+
const rect = pageElement.getBoundingClientRect();
|
|
22
|
+
if (clientY >= rect.top && clientY <= rect.bottom) {
|
|
23
|
+
const pageNumber = pageElement.getAttribute('data-page-number');
|
|
24
|
+
if (pageNumber) {
|
|
25
|
+
targetPage = parseInt(pageNumber, 10);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return targetPage;
|
|
31
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gets the position of a PDF page relative to the wrapper element
|
|
5
|
+
* @param pageNumber - The page number to get the position for
|
|
6
|
+
* @param pdfWrapperRef - Reference to the PDF wrapper element
|
|
7
|
+
* @returns Position object with top and left coordinates, or { top: 0, left: 0 } if not found
|
|
8
|
+
*/
|
|
9
|
+
export const getPagePosition = (
|
|
10
|
+
pageNumber: number,
|
|
11
|
+
pdfWrapperRef: RefObject<HTMLDivElement>,
|
|
12
|
+
): { top: number; left: number } => {
|
|
13
|
+
if (!pdfWrapperRef.current) {
|
|
14
|
+
return { top: 0, left: 0 };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const pageElement = pdfWrapperRef.current.querySelector(`[data-page-number="${pageNumber}"]`);
|
|
18
|
+
|
|
19
|
+
if (!pageElement) {
|
|
20
|
+
return { top: 0, left: 0 };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const wrapperRect = pdfWrapperRef.current.getBoundingClientRect();
|
|
24
|
+
const pageRect = pageElement.getBoundingClientRect();
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
top: pageRect.top - wrapperRect.top,
|
|
28
|
+
left: pageRect.left - wrapperRect.left,
|
|
29
|
+
};
|
|
30
|
+
};
|