@servicetitan/dte-pdf-editor 1.24.0 → 1.26.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 +75 -38
- package/dist/components/display-conditions/display-condition.d.ts.map +1 -1
- package/dist/components/display-conditions/display-condition.js +0 -2
- package/dist/components/display-conditions/display-condition.js.map +1 -1
- package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-config-panel.js +3 -2
- package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
- package/dist/components/field-config-panel/table-configs.d.ts +9 -0
- package/dist/components/field-config-panel/table-configs.d.ts.map +1 -0
- package/dist/components/field-config-panel/table-configs.js +78 -0
- package/dist/components/field-config-panel/table-configs.js.map +1 -0
- package/dist/components/field-sidebar/field-menu-group.d.ts +1 -1
- package/dist/components/field-sidebar/field-menu-group.d.ts.map +1 -1
- package/dist/components/field-sidebar/field-menu-group.js +1 -5
- package/dist/components/field-sidebar/field-menu-group.js.map +1 -1
- package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
- package/dist/components/field-sidebar/field-sidebar.js +3 -2
- package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
- package/dist/components/field-sidebar/generic-field-type-list.d.ts +9 -0
- package/dist/components/field-sidebar/generic-field-type-list.d.ts.map +1 -0
- package/dist/components/field-sidebar/generic-field-type-list.js +12 -0
- package/dist/components/field-sidebar/generic-field-type-list.js.map +1 -0
- package/dist/components/pdf-canvas/pdf-canvas.d.ts +1 -0
- package/dist/components/pdf-canvas/pdf-canvas.d.ts.map +1 -1
- package/dist/components/pdf-canvas/pdf-canvas.js +2 -2
- package/dist/components/pdf-canvas/pdf-canvas.js.map +1 -1
- package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
- package/dist/components/pdf-editor/pdf-editor.js +1 -1
- package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.d.ts +1 -0
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js +2 -2
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.d.ts +9 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.d.ts.map +1 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.js +83 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-generic.js.map +1 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts +1 -0
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +6 -3
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-generic.d.ts +16 -0
- package/dist/components/pdf-view/pdf-view-generic.d.ts.map +1 -0
- package/dist/components/pdf-view/pdf-view-generic.js +68 -0
- package/dist/components/pdf-view/pdf-view-generic.js.map +1 -0
- package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view.js +2 -1
- package/dist/components/pdf-view/pdf-view.js.map +1 -1
- package/dist/components/shared/index.d.ts +2 -0
- package/dist/components/shared/index.d.ts.map +1 -0
- package/dist/components/shared/index.js +2 -0
- package/dist/components/shared/index.js.map +1 -0
- package/dist/components/shared/inline-editable.d.ts +8 -0
- package/dist/components/shared/inline-editable.d.ts.map +1 -0
- package/dist/components/shared/inline-editable.js +17 -0
- package/dist/components/shared/inline-editable.js.map +1 -0
- package/dist/constants/field.constants.d.ts +8 -1
- package/dist/constants/field.constants.d.ts.map +1 -1
- package/dist/constants/field.constants.js +23 -0
- package/dist/constants/field.constants.js.map +1 -1
- package/dist/constants/menu-group.d.ts.map +1 -1
- package/dist/constants/menu-group.js +6 -0
- package/dist/constants/menu-group.js.map +1 -1
- package/dist/hooks/usePdfFieldDnD.d.ts.map +1 -1
- package/dist/hooks/usePdfFieldDnD.js +80 -20
- package/dist/hooks/usePdfFieldDnD.js.map +1 -1
- package/dist/interface/types.d.ts +24 -2
- package/dist/interface/types.d.ts.map +1 -1
- package/dist/interface/types.js +1 -0
- package/dist/interface/types.js.map +1 -1
- package/package.json +1 -1
- package/src/components/display-conditions/display-condition.tsx +0 -3
- package/src/components/field-config-panel/field-config-panel.tsx +8 -1
- package/src/components/field-config-panel/table-configs.tsx +136 -0
- package/src/components/field-sidebar/field-menu-group.tsx +16 -27
- package/src/components/field-sidebar/field-sidebar.tsx +5 -1
- package/src/components/field-sidebar/generic-field-type-list.tsx +27 -0
- package/src/components/pdf-canvas/pdf-canvas.tsx +3 -0
- package/src/components/pdf-editor/pdf-editor.tsx +1 -0
- package/src/components/pdf-fields-overlay/pdf-fields-overlay.tsx +3 -0
- package/src/components/pdf-fields-overlay/pdf-overlay-field-generic.tsx +188 -0
- package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +11 -1
- package/src/components/pdf-view/pdf-view-generic.tsx +129 -0
- package/src/components/pdf-view/pdf-view.tsx +4 -0
- package/src/components/shared/index.ts +1 -0
- package/src/components/shared/inline-editable.tsx +47 -0
- package/src/constants/field.constants.ts +27 -0
- package/src/constants/menu-group.ts +6 -0
- package/src/hooks/usePdfFieldDnD.ts +97 -27
- package/src/interface/types.ts +27 -1
- package/src/styles/index.css +1 -0
- package/src/styles/inline-editable.css +20 -0
- package/src/styles/pdf-field-overlay.css +31 -0
- package/src/styles/variables.css +6 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { FC, useMemo, useState } from 'react';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import {
|
|
4
|
+
GenericFieldTableDataType,
|
|
5
|
+
GenericFieldTextDataType,
|
|
6
|
+
PdfField,
|
|
7
|
+
} from '../../interface/types';
|
|
8
|
+
import { InlineEditable } from '../shared';
|
|
9
|
+
|
|
10
|
+
interface PdfOverlayFieldGenericProps {
|
|
11
|
+
field: PdfField;
|
|
12
|
+
onFieldConfigChange(updates: Partial<PdfField>): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const PdfOverlayFieldGeneric: FC<PdfOverlayFieldGenericProps> = ({
|
|
16
|
+
field,
|
|
17
|
+
onFieldConfigChange,
|
|
18
|
+
}) => {
|
|
19
|
+
if (field.subType === 'text') {
|
|
20
|
+
return (
|
|
21
|
+
<GenericFieldText
|
|
22
|
+
data={field.data as GenericFieldTextDataType}
|
|
23
|
+
onChange={data => {
|
|
24
|
+
onFieldConfigChange({
|
|
25
|
+
...field,
|
|
26
|
+
...data,
|
|
27
|
+
});
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<GenericFieldTable
|
|
35
|
+
tableData={field.data as GenericFieldTableDataType}
|
|
36
|
+
onChange={data =>
|
|
37
|
+
onFieldConfigChange({
|
|
38
|
+
...field,
|
|
39
|
+
...data,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
interface GenericFieldTextProps {
|
|
47
|
+
data: GenericFieldTextDataType;
|
|
48
|
+
onChange(updates: Partial<PdfField>): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const GenericFieldText: FC<GenericFieldTextProps> = ({ data, onChange }) => {
|
|
52
|
+
const [isActive, setIsActive] = useState(false);
|
|
53
|
+
const [editingValue, setEditingValue] = useState(data.value ?? '');
|
|
54
|
+
const commit = () => {
|
|
55
|
+
onChange({
|
|
56
|
+
data: {
|
|
57
|
+
value: editingValue,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
setIsActive(false);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return isActive ? (
|
|
64
|
+
<textarea
|
|
65
|
+
style={{ width: '100%', height: '100%' }}
|
|
66
|
+
value={editingValue}
|
|
67
|
+
placeholder="Type here..."
|
|
68
|
+
onChange={e => setEditingValue(e.target.value)}
|
|
69
|
+
onBlur={commit}
|
|
70
|
+
onClick={e => e.stopPropagation()}
|
|
71
|
+
autoFocus
|
|
72
|
+
/>
|
|
73
|
+
) : (
|
|
74
|
+
<div
|
|
75
|
+
className="inline-editable-button"
|
|
76
|
+
role="button"
|
|
77
|
+
tabIndex={0}
|
|
78
|
+
onClick={() => setIsActive(true)}
|
|
79
|
+
onKeyDown={e => {
|
|
80
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
setIsActive(true);
|
|
83
|
+
}
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
<pre className="inline-editable-html-text">{data.value ?? 'Type here...'}</pre>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
interface GenericFieldTableProps {
|
|
92
|
+
tableData: GenericFieldTableDataType;
|
|
93
|
+
onChange(updates: Partial<PdfField>): void;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const GenericFieldTable: FC<GenericFieldTableProps> = ({ onChange, tableData }) => {
|
|
97
|
+
const { body, header, showHeader } = tableData;
|
|
98
|
+
|
|
99
|
+
const headerCellsWithKeys = useMemo(() => {
|
|
100
|
+
return header.cells.map(cell => ({
|
|
101
|
+
cell,
|
|
102
|
+
key: uuidv4(),
|
|
103
|
+
}));
|
|
104
|
+
}, [header.cells]);
|
|
105
|
+
|
|
106
|
+
const bodyRowsWithKeys = useMemo(() => {
|
|
107
|
+
return body.map(row => ({
|
|
108
|
+
key: uuidv4(),
|
|
109
|
+
row,
|
|
110
|
+
}));
|
|
111
|
+
}, [body]);
|
|
112
|
+
|
|
113
|
+
const commitCellValue = (
|
|
114
|
+
section: 'header' | 'body',
|
|
115
|
+
rowIndex: number,
|
|
116
|
+
cellIndex: number,
|
|
117
|
+
value: string,
|
|
118
|
+
) => {
|
|
119
|
+
if (section === 'header') {
|
|
120
|
+
onChange({
|
|
121
|
+
data: {
|
|
122
|
+
...tableData,
|
|
123
|
+
header: {
|
|
124
|
+
...header,
|
|
125
|
+
cells: header.cells.map((c, i) => (i === cellIndex ? { ...c, value } : c)),
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
} else {
|
|
130
|
+
onChange({
|
|
131
|
+
data: {
|
|
132
|
+
...tableData,
|
|
133
|
+
body: body.map((row, ri) =>
|
|
134
|
+
ri === rowIndex
|
|
135
|
+
? {
|
|
136
|
+
...row,
|
|
137
|
+
cells: row.cells.map((c, i) =>
|
|
138
|
+
i === cellIndex ? { ...c, value } : c,
|
|
139
|
+
),
|
|
140
|
+
}
|
|
141
|
+
: row,
|
|
142
|
+
),
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<table className="dte-pdf-field-generic-table">
|
|
150
|
+
{showHeader && (
|
|
151
|
+
<tr
|
|
152
|
+
style={{ height: header.height }}
|
|
153
|
+
className="dte-pdf-field-generic-table-header"
|
|
154
|
+
>
|
|
155
|
+
{headerCellsWithKeys.map(({ cell, key }, cellIndex) => (
|
|
156
|
+
<td key={key}>
|
|
157
|
+
<InlineEditable
|
|
158
|
+
onCommit={value => commitCellValue('header', 0, cellIndex, value)}
|
|
159
|
+
value={cell.value}
|
|
160
|
+
>
|
|
161
|
+
{cell.value}
|
|
162
|
+
</InlineEditable>
|
|
163
|
+
</td>
|
|
164
|
+
))}
|
|
165
|
+
</tr>
|
|
166
|
+
)}
|
|
167
|
+
{bodyRowsWithKeys.map(({ key: rowKey, row }, rowIndex) => (
|
|
168
|
+
<tr key={rowKey} style={{ height: row.height }}>
|
|
169
|
+
{row.cells.map((cell, cellIndex) => {
|
|
170
|
+
const cellKey = `body-${rowIndex}-${cellIndex}`;
|
|
171
|
+
return (
|
|
172
|
+
<td key={cellKey}>
|
|
173
|
+
<InlineEditable
|
|
174
|
+
onCommit={value =>
|
|
175
|
+
commitCellValue('body', rowIndex, cellIndex, value)
|
|
176
|
+
}
|
|
177
|
+
value={cell.value}
|
|
178
|
+
>
|
|
179
|
+
{cell.value}
|
|
180
|
+
</InlineEditable>
|
|
181
|
+
</td>
|
|
182
|
+
);
|
|
183
|
+
})}
|
|
184
|
+
</tr>
|
|
185
|
+
))}
|
|
186
|
+
</table>
|
|
187
|
+
);
|
|
188
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FC, MouseEvent, RefObject } from 'react';
|
|
2
|
+
import { HIDE_FIELD_RESIZE } from '../../constants';
|
|
2
3
|
import { useFieldDrag, useFieldResize } from '../../hooks';
|
|
3
4
|
import { FieldTypeEnum, PdfField } from '../../interface/types';
|
|
4
5
|
import { getFieldBackgroundColor, getPagePosition } from '../../utils';
|
|
@@ -6,6 +7,7 @@ import { PdfOverlayFieldCalculated } from './pdf-overlay-field-calculated';
|
|
|
6
7
|
import { PdfOverlayFieldDataModel } from './pdf-overlay-field-data-model';
|
|
7
8
|
import { PdfOverlayFieldESign } from './pdf-overlay-field-e-sign';
|
|
8
9
|
import { PdfOverlayFieldFillable } from './pdf-overlay-field-fillable';
|
|
10
|
+
import { PdfOverlayFieldGeneric } from './pdf-overlay-field-generic';
|
|
9
11
|
import { ResizeHandle } from './resize-handle';
|
|
10
12
|
|
|
11
13
|
interface PdfOverlayFieldProps {
|
|
@@ -15,6 +17,7 @@ interface PdfOverlayFieldProps {
|
|
|
15
17
|
isSameGroup: boolean;
|
|
16
18
|
recipientsColors: Record<string, string>;
|
|
17
19
|
pdfWrapperRef: RefObject<HTMLDivElement>;
|
|
20
|
+
onFieldConfigChange(updates: Partial<PdfField>): void;
|
|
18
21
|
handleAddNewField(field: PdfField): void;
|
|
19
22
|
onFieldClick(fieldId: string, e: MouseEvent): void;
|
|
20
23
|
onFieldMove(fieldId: string, newX: number, newY: number, pageNumber: number): void;
|
|
@@ -28,6 +31,7 @@ export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
|
|
|
28
31
|
isSameGroup,
|
|
29
32
|
isSelected,
|
|
30
33
|
onFieldClick,
|
|
34
|
+
onFieldConfigChange,
|
|
31
35
|
onFieldMove,
|
|
32
36
|
onFieldResize,
|
|
33
37
|
pdfWrapperRef,
|
|
@@ -74,7 +78,13 @@ export const PdfOverlayField: FC<PdfOverlayFieldProps> = ({
|
|
|
74
78
|
{field.type === FieldTypeEnum.fillable && (
|
|
75
79
|
<PdfOverlayFieldFillable field={field} handleAddNewField={handleAddNewField} />
|
|
76
80
|
)}
|
|
77
|
-
{
|
|
81
|
+
{field.type === FieldTypeEnum.generic && (
|
|
82
|
+
<PdfOverlayFieldGeneric field={field} onFieldConfigChange={onFieldConfigChange} />
|
|
83
|
+
)}
|
|
84
|
+
{isSelected &&
|
|
85
|
+
!(field.subType != null && HIDE_FIELD_RESIZE[field.type]?.[field.subType]) && (
|
|
86
|
+
<ResizeHandle handleResizeStart={e => handleResizeStart(e, field)} />
|
|
87
|
+
)}
|
|
78
88
|
{error && <span className="dte-pdf-field-error-message">{error}</span>}
|
|
79
89
|
</div>
|
|
80
90
|
);
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { type CSSProperties, FC, useMemo } from 'react';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import {
|
|
4
|
+
GenericFieldTableDataType,
|
|
5
|
+
GenericFieldTextDataType,
|
|
6
|
+
PdfField,
|
|
7
|
+
} from '../../interface/types';
|
|
8
|
+
|
|
9
|
+
interface PdfViewGenericProps {
|
|
10
|
+
field: PdfField;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const PdfViewGeneric: FC<PdfViewGenericProps> = ({ field }) => {
|
|
14
|
+
if (field.subType === 'text') {
|
|
15
|
+
return <GenericFieldText data={field.data as GenericFieldTextDataType} />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return <GenericFieldTable data={field.data as GenericFieldTableDataType} />;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
interface GenericFieldTextProps {
|
|
22
|
+
data: GenericFieldTextDataType;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const GenericFieldText: FC<GenericFieldTextProps> = ({ data }) => {
|
|
26
|
+
return (
|
|
27
|
+
<div className="dte-pdf-field-value">
|
|
28
|
+
<pre className="inline-editable-html-text">{data?.value ?? ''}</pre>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
interface GenericFieldTableProps {
|
|
34
|
+
data: GenericFieldTableDataType;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const GenericFieldTable: FC<GenericFieldTableProps> = ({ data }) => {
|
|
38
|
+
const { body, header, showHeader } = data;
|
|
39
|
+
|
|
40
|
+
const headerCellsWithKeys = useMemo(() => {
|
|
41
|
+
return header.cells.map(cell => ({
|
|
42
|
+
cell,
|
|
43
|
+
key: uuidv4(),
|
|
44
|
+
}));
|
|
45
|
+
}, [header.cells]);
|
|
46
|
+
|
|
47
|
+
const bodyRowsWithKeys = useMemo(() => {
|
|
48
|
+
return body.map(row => ({
|
|
49
|
+
key: uuidv4(),
|
|
50
|
+
row,
|
|
51
|
+
cellKeys: row.cells.map(() => uuidv4()),
|
|
52
|
+
}));
|
|
53
|
+
}, [body]);
|
|
54
|
+
|
|
55
|
+
const baseCellStyle: CSSProperties = {
|
|
56
|
+
borderTop: '1px solid #e0e0e0',
|
|
57
|
+
borderLeft: '1px solid #e0e0e0',
|
|
58
|
+
paddingInline: '8px',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const headerCellStyle: CSSProperties = {
|
|
62
|
+
...baseCellStyle,
|
|
63
|
+
backgroundColor: '#f5f5f5',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const bodyRowCount = bodyRowsWithKeys.length;
|
|
67
|
+
const isLastRow = (rowIndex: number) => rowIndex === bodyRowCount - 1;
|
|
68
|
+
const getBodyCellStyle = (
|
|
69
|
+
rowIndex: number,
|
|
70
|
+
cellIndex: number,
|
|
71
|
+
row: { cells: { value: string }[] },
|
|
72
|
+
) => {
|
|
73
|
+
const lastInRow = cellIndex === row.cells.length - 1;
|
|
74
|
+
return {
|
|
75
|
+
...baseCellStyle,
|
|
76
|
+
backgroundColor: '#ffffff',
|
|
77
|
+
...(isLastRow(rowIndex) ? { borderBottom: '1px solid #e0e0e0' } : {}),
|
|
78
|
+
...(lastInRow ? { borderRight: '1px solid #e0e0e0' } : {}),
|
|
79
|
+
} as CSSProperties;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<table
|
|
84
|
+
style={{
|
|
85
|
+
padding: 0,
|
|
86
|
+
margin: 0,
|
|
87
|
+
borderSpacing: 0,
|
|
88
|
+
width: '100%',
|
|
89
|
+
tableLayout: 'fixed',
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
{showHeader && header && (
|
|
93
|
+
<tr style={{ height: header.height, fontWeight: 'bold' }}>
|
|
94
|
+
{headerCellsWithKeys.map(({ cell, key }, idx) => (
|
|
95
|
+
<td
|
|
96
|
+
key={key}
|
|
97
|
+
style={{
|
|
98
|
+
...headerCellStyle,
|
|
99
|
+
...(headerCellsWithKeys.length === 1 ||
|
|
100
|
+
idx === headerCellsWithKeys.length - 1
|
|
101
|
+
? { borderRight: '1px solid #e0e0e0' }
|
|
102
|
+
: {}),
|
|
103
|
+
...(bodyRowsWithKeys.length === 0
|
|
104
|
+
? { borderBottom: '1px solid #e0e0e0' }
|
|
105
|
+
: {}),
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{cell.value}
|
|
109
|
+
</td>
|
|
110
|
+
))}
|
|
111
|
+
</tr>
|
|
112
|
+
)}
|
|
113
|
+
{bodyRowsWithKeys.map(({ cellKeys, key: rowKey, row }, rowIndex) => (
|
|
114
|
+
<tr key={rowKey} style={{ height: row.height }}>
|
|
115
|
+
{row.cells.map((cell, cellIndex) => (
|
|
116
|
+
<td
|
|
117
|
+
key={cellKeys[cellIndex]}
|
|
118
|
+
style={{
|
|
119
|
+
...getBodyCellStyle(rowIndex, cellIndex, row),
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
{cell.value}
|
|
123
|
+
</td>
|
|
124
|
+
))}
|
|
125
|
+
</tr>
|
|
126
|
+
))}
|
|
127
|
+
</table>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
@@ -16,6 +16,7 @@ import { PdfViewDataModel } from './pdf-view-data-model';
|
|
|
16
16
|
import { PdfViewESign } from './pdf-view-e-sign';
|
|
17
17
|
import { PdfViewFieldContainer } from './pdf-view-field-container';
|
|
18
18
|
import { PdfViewFillable } from './pdf-view-fillable';
|
|
19
|
+
import { PdfViewGeneric } from './pdf-view-generic';
|
|
19
20
|
|
|
20
21
|
export interface PdfViewProps {
|
|
21
22
|
fields: PdfField[];
|
|
@@ -116,6 +117,9 @@ export const PdfView: FC<PdfViewProps> = ({
|
|
|
116
117
|
onDataChange={handleDataChange}
|
|
117
118
|
/>
|
|
118
119
|
)}
|
|
120
|
+
{field.type === FieldTypeEnum.generic && (
|
|
121
|
+
<PdfViewGeneric field={field} />
|
|
122
|
+
)}
|
|
119
123
|
</PdfViewFieldContainer>
|
|
120
124
|
))}
|
|
121
125
|
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './inline-editable';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { FC, PropsWithChildren, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface InlineEditableProps {
|
|
4
|
+
value: string;
|
|
5
|
+
onCommit(value: string): void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const InlineEditable: FC<PropsWithChildren<InlineEditableProps>> = ({
|
|
9
|
+
children,
|
|
10
|
+
onCommit,
|
|
11
|
+
value,
|
|
12
|
+
}) => {
|
|
13
|
+
const [isActive, setIsActive] = useState(false);
|
|
14
|
+
const [editingValue, setEditingValue] = useState(value);
|
|
15
|
+
|
|
16
|
+
const commit = () => {
|
|
17
|
+
onCommit(editingValue);
|
|
18
|
+
setIsActive(false);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return isActive ? (
|
|
22
|
+
<input
|
|
23
|
+
type="text"
|
|
24
|
+
className="inline-editable-input"
|
|
25
|
+
value={editingValue}
|
|
26
|
+
onChange={e => setEditingValue(e.target.value)}
|
|
27
|
+
onBlur={commit}
|
|
28
|
+
onClick={e => e.stopPropagation()}
|
|
29
|
+
autoFocus
|
|
30
|
+
/>
|
|
31
|
+
) : (
|
|
32
|
+
<div
|
|
33
|
+
className="inline-editable-button"
|
|
34
|
+
role="button"
|
|
35
|
+
tabIndex={0}
|
|
36
|
+
onClick={() => setIsActive(true)}
|
|
37
|
+
onKeyDown={e => {
|
|
38
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
setIsActive(true);
|
|
41
|
+
}
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
{children ?? value}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
FieldTypeOption,
|
|
5
5
|
PdfFieldSubType,
|
|
6
6
|
} from '../interface/types';
|
|
7
|
+
import { BASE_PAGE_WIDTH } from './pdf-editor.constants';
|
|
7
8
|
|
|
8
9
|
export const FIELD_CONSTANTS = {
|
|
9
10
|
defaultWidth: 200,
|
|
@@ -21,6 +22,13 @@ export const FILLABLE_FIELD_DEFAULT_SIZES = {
|
|
|
21
22
|
checkbox: { width: 20, height: 20 },
|
|
22
23
|
} as Record<PdfFieldSubType, { width: number; height: number }>;
|
|
23
24
|
|
|
25
|
+
export const GENERIC_FIELD_TABLE_ROW_HEIGHT = 40;
|
|
26
|
+
|
|
27
|
+
export const GENERIC_FIELD_DEFAULT_SIZES = {
|
|
28
|
+
text: { width: BASE_PAGE_WIDTH, height: 40 },
|
|
29
|
+
table: { width: BASE_PAGE_WIDTH, height: 2 * GENERIC_FIELD_TABLE_ROW_HEIGHT },
|
|
30
|
+
} as Record<PdfFieldSubType, { width: number; height: number }>;
|
|
31
|
+
|
|
24
32
|
export const E_SIGN_FIELD_TYPE_OPTIONS: { name: string; id: ESignFieldType }[] = [
|
|
25
33
|
{ name: 'Signature', id: ESignFieldType.signature },
|
|
26
34
|
{ name: 'Initials', id: ESignFieldType.initials },
|
|
@@ -50,3 +58,22 @@ export const CALCULATED_FIELD_TYPES: FieldTypeOption[] = [
|
|
|
50
58
|
type: FieldTypeEnum.calculated,
|
|
51
59
|
},
|
|
52
60
|
];
|
|
61
|
+
|
|
62
|
+
export const GENERIC_FIELD_TYPES: FieldTypeOption[] = [
|
|
63
|
+
{
|
|
64
|
+
label: 'Text',
|
|
65
|
+
type: FieldTypeEnum.generic,
|
|
66
|
+
subType: 'text',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
label: 'Table',
|
|
70
|
+
type: FieldTypeEnum.generic,
|
|
71
|
+
subType: 'table',
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
export const HIDE_FIELD_RESIZE = {
|
|
76
|
+
[FieldTypeEnum.generic]: {
|
|
77
|
+
table: true,
|
|
78
|
+
},
|
|
79
|
+
} as Record<FieldTypeEnum, Record<PdfFieldSubType, boolean>>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import IconBorderColor from '@servicetitan/anvil2/assets/icons/material/round/border_color.svg';
|
|
2
2
|
import IconCalculate from '@servicetitan/anvil2/assets/icons/material/round/calculate.svg';
|
|
3
3
|
import IconPhotoSizeSelectSmall from '@servicetitan/anvil2/assets/icons/material/round/photo_size_select_small.svg';
|
|
4
|
+
import IconCommercial from '@servicetitan/anvil2/assets/icons/st/commercial.svg';
|
|
4
5
|
import IconEstimate from '@servicetitan/anvil2/assets/icons/st/estimate.svg';
|
|
5
6
|
import { FieldTypeEnum } from '../interface/types';
|
|
6
7
|
|
|
@@ -23,4 +24,9 @@ export const menuGroups: MenuGroupModel[] = [
|
|
|
23
24
|
label: 'Calculated Fields',
|
|
24
25
|
key: FieldTypeEnum.calculated,
|
|
25
26
|
},
|
|
27
|
+
{
|
|
28
|
+
svgIcon: IconCommercial,
|
|
29
|
+
label: 'Generic Fields',
|
|
30
|
+
key: FieldTypeEnum.generic,
|
|
31
|
+
},
|
|
26
32
|
];
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { DragEvent, RefObject, useCallback, useRef, useState } from 'react';
|
|
2
2
|
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
FIELD_CONSTANTS,
|
|
5
|
+
FILLABLE_FIELD_DEFAULT_SIZES,
|
|
6
|
+
GENERIC_FIELD_DEFAULT_SIZES,
|
|
7
|
+
GENERIC_FIELD_TABLE_ROW_HEIGHT,
|
|
8
|
+
} from '../constants';
|
|
4
9
|
import {
|
|
5
10
|
ESignFieldType,
|
|
6
11
|
FieldTypeEnum,
|
|
@@ -17,6 +22,90 @@ import {
|
|
|
17
22
|
|
|
18
23
|
const DRAG_OVER_THROTTLE_MS = 32; // ~30fps for drop effect updates
|
|
19
24
|
|
|
25
|
+
type InitializeDroppedField = (
|
|
26
|
+
baseField: PdfField,
|
|
27
|
+
option: FieldTypeOption,
|
|
28
|
+
recipientName: string,
|
|
29
|
+
) => PdfField;
|
|
30
|
+
|
|
31
|
+
const initializeDroppedFieldByType: Record<FieldTypeEnum, InitializeDroppedField> = {
|
|
32
|
+
[FieldTypeEnum.eSign]: (base, option, recipientName) => ({
|
|
33
|
+
...base,
|
|
34
|
+
recipient: recipientName,
|
|
35
|
+
path: generateESignPath(recipientName, option.subType as ESignFieldType),
|
|
36
|
+
}),
|
|
37
|
+
[FieldTypeEnum.fillable]: (base, option, recipientName) => {
|
|
38
|
+
const defaultSize = FILLABLE_FIELD_DEFAULT_SIZES[base.subType!];
|
|
39
|
+
return {
|
|
40
|
+
...base,
|
|
41
|
+
recipient: recipientName,
|
|
42
|
+
required: false,
|
|
43
|
+
path: generateFillablePath(recipientName, uuidv4()),
|
|
44
|
+
...(defaultSize && {
|
|
45
|
+
width: defaultSize.width,
|
|
46
|
+
height: defaultSize.height,
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
[FieldTypeEnum.calculated]: base => ({
|
|
51
|
+
...base,
|
|
52
|
+
path: base.id,
|
|
53
|
+
}),
|
|
54
|
+
[FieldTypeEnum.dataModel]: base => base,
|
|
55
|
+
[FieldTypeEnum.generic]: base => {
|
|
56
|
+
const defaultSize = GENERIC_FIELD_DEFAULT_SIZES[base.subType!];
|
|
57
|
+
if (base.subType === 'text') {
|
|
58
|
+
return {
|
|
59
|
+
...base,
|
|
60
|
+
x: 0,
|
|
61
|
+
data: {
|
|
62
|
+
value: 'This is a new Text block.',
|
|
63
|
+
},
|
|
64
|
+
...(defaultSize && {
|
|
65
|
+
width: defaultSize.width,
|
|
66
|
+
height: defaultSize.height,
|
|
67
|
+
}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
...base,
|
|
73
|
+
...(defaultSize && {
|
|
74
|
+
width: defaultSize.width,
|
|
75
|
+
height: defaultSize.height,
|
|
76
|
+
}),
|
|
77
|
+
x: 0,
|
|
78
|
+
data: {
|
|
79
|
+
showHeader: true,
|
|
80
|
+
header: {
|
|
81
|
+
height: GENERIC_FIELD_TABLE_ROW_HEIGHT,
|
|
82
|
+
cells: [
|
|
83
|
+
{
|
|
84
|
+
value: 'id',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
value: 'username',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
body: [
|
|
92
|
+
{
|
|
93
|
+
height: GENERIC_FIELD_TABLE_ROW_HEIGHT,
|
|
94
|
+
cells: [
|
|
95
|
+
{
|
|
96
|
+
value: '124',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
value: 'test@gmail.com',
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
20
109
|
interface UsePdfFieldDnDProps {
|
|
21
110
|
fields: PdfField[];
|
|
22
111
|
recipients: RecipientInfo[];
|
|
@@ -87,7 +176,8 @@ export const usePdfFieldDnD = ({
|
|
|
87
176
|
return;
|
|
88
177
|
}
|
|
89
178
|
|
|
90
|
-
const
|
|
179
|
+
const recipientName = recipients[0]?.name ?? '';
|
|
180
|
+
const baseField: PdfField = {
|
|
91
181
|
id: uuidv4(),
|
|
92
182
|
type: draggedFieldOption.type,
|
|
93
183
|
subType: draggedFieldOption.subType,
|
|
@@ -100,31 +190,11 @@ export const usePdfFieldDnD = ({
|
|
|
100
190
|
path: draggedFieldOption.path,
|
|
101
191
|
};
|
|
102
192
|
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
recipientName,
|
|
109
|
-
draggedFieldOption.subType as ESignFieldType,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (newField.type === FieldTypeEnum.fillable) {
|
|
114
|
-
const defaultSize = FILLABLE_FIELD_DEFAULT_SIZES[newField.subType!];
|
|
115
|
-
newField.recipient = recipientName;
|
|
116
|
-
newField.required = false;
|
|
117
|
-
newField.path = generateFillablePath(recipientName, uuidv4());
|
|
118
|
-
|
|
119
|
-
if (defaultSize) {
|
|
120
|
-
newField.width = defaultSize.width;
|
|
121
|
-
newField.height = defaultSize.height;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (newField.type === FieldTypeEnum.calculated) {
|
|
126
|
-
newField.path = newField.id;
|
|
127
|
-
}
|
|
193
|
+
const newField = initializeDroppedFieldByType[baseField.type](
|
|
194
|
+
baseField,
|
|
195
|
+
draggedFieldOption,
|
|
196
|
+
recipientName,
|
|
197
|
+
);
|
|
128
198
|
|
|
129
199
|
onFieldsChange([...fields, newField]);
|
|
130
200
|
onSelectField(newField.id);
|
package/src/interface/types.ts
CHANGED
|
@@ -3,6 +3,7 @@ export enum FieldTypeEnum {
|
|
|
3
3
|
eSign = 'eSign',
|
|
4
4
|
fillable = 'fillable',
|
|
5
5
|
calculated = 'calculated',
|
|
6
|
+
generic = 'generic',
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export enum ESignFieldType {
|
|
@@ -14,7 +15,31 @@ export enum ESignFieldType {
|
|
|
14
15
|
|
|
15
16
|
export type FillableFieldType = 'text' | 'date' | 'checkbox' | 'radio' | 'number';
|
|
16
17
|
|
|
17
|
-
export type
|
|
18
|
+
export type GenericFieldType = 'table' | 'text';
|
|
19
|
+
|
|
20
|
+
export type PdfFieldSubType = FillableFieldType | ESignFieldType | GenericFieldType;
|
|
21
|
+
|
|
22
|
+
export interface GenericFieldTableDataType {
|
|
23
|
+
showHeader: boolean;
|
|
24
|
+
header: {
|
|
25
|
+
height: number;
|
|
26
|
+
cells: {
|
|
27
|
+
value: string;
|
|
28
|
+
}[];
|
|
29
|
+
};
|
|
30
|
+
body: {
|
|
31
|
+
height: number;
|
|
32
|
+
cells: {
|
|
33
|
+
value: string;
|
|
34
|
+
}[];
|
|
35
|
+
}[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface GenericFieldTextDataType {
|
|
39
|
+
value: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type FieldDataType = GenericFieldTableDataType | GenericFieldTextDataType;
|
|
18
43
|
|
|
19
44
|
export interface DisplayConditionState {
|
|
20
45
|
behavior: 'show' | 'hide';
|
|
@@ -49,6 +74,7 @@ export interface PdfField {
|
|
|
49
74
|
path?: string;
|
|
50
75
|
recipient?: string;
|
|
51
76
|
description?: string;
|
|
77
|
+
data?: FieldDataType;
|
|
52
78
|
formula?: StructuredFormula;
|
|
53
79
|
formulaFormat?: CalculatedFieldFormat;
|
|
54
80
|
displayCondition?: DisplayConditionState | null;
|
package/src/styles/index.css
CHANGED