@pdfme/ui 0.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 +9 -0
- package/__mocks__/assetsTransformer.js +7 -0
- package/__mocks__/form-render.js +7 -0
- package/__mocks__/lucide-react.js +19 -0
- package/dist/index.es.js +159393 -0
- package/dist/index.umd.js +1041 -0
- package/dist/types/__tests__/assets/helper.d.ts +3 -0
- package/dist/types/__tests__/components/Designer.test.d.ts +1 -0
- package/dist/types/__tests__/components/PluginIcon.test.d.ts +1 -0
- package/dist/types/__tests__/components/Preview.test.d.ts +1 -0
- package/dist/types/__tests__/helper.test.d.ts +1 -0
- package/dist/types/src/Designer.d.ts +21 -0
- package/dist/types/src/Form.d.ts +24 -0
- package/dist/types/src/Viewer.d.ts +15 -0
- package/dist/types/src/class.d.ts +89 -0
- package/dist/types/src/components/AppContextProvider.d.ts +11 -0
- package/dist/types/src/components/CtlBar.d.ts +14 -0
- package/dist/types/src/components/Designer/Canvas/Guides.d.ts +9 -0
- package/dist/types/src/components/Designer/Canvas/Mask.d.ts +4 -0
- package/dist/types/src/components/Designer/Canvas/Moveable.d.ts +37 -0
- package/dist/types/src/components/Designer/Canvas/Padding.d.ts +6 -0
- package/dist/types/src/components/Designer/Canvas/Selecto.d.ts +10 -0
- package/dist/types/src/components/Designer/Canvas/index.d.ts +22 -0
- package/dist/types/src/components/Designer/LeftSidebar.d.ts +8 -0
- package/dist/types/src/components/Designer/PluginIcon.d.ts +10 -0
- package/dist/types/src/components/Designer/RightSidebar/DetailView/AlignWidget.d.ts +4 -0
- package/dist/types/src/components/Designer/RightSidebar/DetailView/ButtonGroupWidget.d.ts +4 -0
- package/dist/types/src/components/Designer/RightSidebar/DetailView/WidgetRenderer.d.ts +7 -0
- package/dist/types/src/components/Designer/RightSidebar/DetailView/index.d.ts +8 -0
- package/dist/types/src/components/Designer/RightSidebar/ListView/Item.d.ts +45 -0
- package/dist/types/src/components/Designer/RightSidebar/ListView/SelectableSortableContainer.d.ts +4 -0
- package/dist/types/src/components/Designer/RightSidebar/ListView/SelectableSortableItem.d.ts +14 -0
- package/dist/types/src/components/Designer/RightSidebar/ListView/index.d.ts +4 -0
- package/dist/types/src/components/Designer/RightSidebar/index.d.ts +4 -0
- package/dist/types/src/components/Designer/RightSidebar/layout.d.ts +15 -0
- package/dist/types/src/components/Designer/index.d.ts +11 -0
- package/dist/types/src/components/ErrorScreen.d.ts +7 -0
- package/dist/types/src/components/Paper.d.ts +20 -0
- package/dist/types/src/components/Preview.d.ts +15 -0
- package/dist/types/src/components/Renderer.d.ts +13 -0
- package/dist/types/src/components/Root.d.ts +9 -0
- package/dist/types/src/components/Spinner.d.ts +3 -0
- package/dist/types/src/components/StaticSchema.d.ts +10 -0
- package/dist/types/src/components/UnitPager.d.ts +10 -0
- package/dist/types/src/constants.d.ts +11 -0
- package/dist/types/src/contexts.d.ts +10 -0
- package/dist/types/src/helper.d.ts +73 -0
- package/dist/types/src/hooks.d.ts +46 -0
- package/dist/types/src/i18n.d.ts +3 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/theme.d.ts +2 -0
- package/dist/types/src/types.d.ts +19 -0
- package/eslint.config.mjs +41 -0
- package/package.json +127 -0
- package/src/Designer.tsx +107 -0
- package/src/Form.tsx +102 -0
- package/src/Viewer.tsx +59 -0
- package/src/class.ts +188 -0
- package/src/components/AppContextProvider.tsx +78 -0
- package/src/components/CtlBar.tsx +183 -0
- package/src/components/Designer/Canvas/Guides.tsx +49 -0
- package/src/components/Designer/Canvas/Mask.tsx +20 -0
- package/src/components/Designer/Canvas/Moveable.tsx +91 -0
- package/src/components/Designer/Canvas/Padding.tsx +56 -0
- package/src/components/Designer/Canvas/Selecto.tsx +45 -0
- package/src/components/Designer/Canvas/index.tsx +536 -0
- package/src/components/Designer/LeftSidebar.tsx +120 -0
- package/src/components/Designer/PluginIcon.tsx +87 -0
- package/src/components/Designer/RightSidebar/DetailView/AlignWidget.tsx +229 -0
- package/src/components/Designer/RightSidebar/DetailView/ButtonGroupWidget.tsx +78 -0
- package/src/components/Designer/RightSidebar/DetailView/WidgetRenderer.tsx +28 -0
- package/src/components/Designer/RightSidebar/DetailView/index.tsx +469 -0
- package/src/components/Designer/RightSidebar/ListView/Item.tsx +158 -0
- package/src/components/Designer/RightSidebar/ListView/SelectableSortableContainer.tsx +204 -0
- package/src/components/Designer/RightSidebar/ListView/SelectableSortableItem.tsx +88 -0
- package/src/components/Designer/RightSidebar/ListView/index.tsx +116 -0
- package/src/components/Designer/RightSidebar/index.tsx +72 -0
- package/src/components/Designer/RightSidebar/layout.tsx +75 -0
- package/src/components/Designer/index.tsx +389 -0
- package/src/components/ErrorScreen.tsx +33 -0
- package/src/components/Paper.tsx +117 -0
- package/src/components/Preview.tsx +220 -0
- package/src/components/Renderer.tsx +165 -0
- package/src/components/Root.tsx +38 -0
- package/src/components/Spinner.tsx +45 -0
- package/src/components/StaticSchema.tsx +50 -0
- package/src/components/UnitPager.tsx +119 -0
- package/src/constants.ts +21 -0
- package/src/contexts.ts +14 -0
- package/src/helper.ts +534 -0
- package/src/hooks.ts +308 -0
- package/src/i18n.ts +903 -0
- package/src/index.ts +5 -0
- package/src/theme.ts +20 -0
- package/src/types.ts +20 -0
- package/tsconfig.json +48 -0
- package/vite.config.mts +22 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import SelectoComponent, {
|
|
3
|
+
OnSelect as SelectoOnSelect,
|
|
4
|
+
OnDragStart as SelectoOnDragStart,
|
|
5
|
+
} from 'react-selecto';
|
|
6
|
+
import { SELECTABLE_CLASSNAME } from '../../../constants.js';
|
|
7
|
+
import { theme } from 'antd';
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
container: HTMLElement | null;
|
|
11
|
+
continueSelect: boolean;
|
|
12
|
+
onDragStart: (e: SelectoOnDragStart) => void;
|
|
13
|
+
onSelect: (e: SelectoOnSelect) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const className = 'pdfme-selecto';
|
|
17
|
+
|
|
18
|
+
const Selecto = (props: Props) => {
|
|
19
|
+
const { token } = theme.useToken();
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const containerElement = document.querySelector('.' + className);
|
|
22
|
+
if (containerElement instanceof HTMLElement) {
|
|
23
|
+
containerElement.style.backgroundColor = token.colorPrimary;
|
|
24
|
+
containerElement.style.opacity = '0.75';
|
|
25
|
+
containerElement.style.borderColor = token.colorPrimaryBorder;
|
|
26
|
+
}
|
|
27
|
+
}, [props.container, token.colorPrimary, token.colorPrimaryBorder]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<SelectoComponent
|
|
31
|
+
className={className}
|
|
32
|
+
selectFromInside={false}
|
|
33
|
+
selectByClick
|
|
34
|
+
preventDefault
|
|
35
|
+
hitRate={0}
|
|
36
|
+
selectableTargets={[`.${SELECTABLE_CLASSNAME}`]}
|
|
37
|
+
container={props.container}
|
|
38
|
+
continueSelect={props.continueSelect}
|
|
39
|
+
onDragStart={props.onDragStart}
|
|
40
|
+
onSelect={props.onSelect}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default Selecto;
|
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
Ref,
|
|
3
|
+
useMemo,
|
|
4
|
+
useContext,
|
|
5
|
+
MutableRefObject,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
useEffect,
|
|
9
|
+
forwardRef,
|
|
10
|
+
useCallback,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { theme, Button } from 'antd';
|
|
13
|
+
import MoveableComponent, { OnDrag, OnRotate, OnResize } from 'react-moveable';
|
|
14
|
+
import {
|
|
15
|
+
ZOOM,
|
|
16
|
+
SchemaForUI,
|
|
17
|
+
Size,
|
|
18
|
+
ChangeSchemas,
|
|
19
|
+
BasePdf,
|
|
20
|
+
isBlankPdf,
|
|
21
|
+
replacePlaceholders,
|
|
22
|
+
} from '@pdfme/common';
|
|
23
|
+
import { PluginsRegistry } from '../../../contexts.js';
|
|
24
|
+
import { X } from 'lucide-react';
|
|
25
|
+
import { RULER_HEIGHT, RIGHT_SIDEBAR_WIDTH, DESIGNER_CLASSNAME } from '../../../constants.js';
|
|
26
|
+
import { usePrevious } from '../../../hooks.js';
|
|
27
|
+
import { round, flatten, uuid } from '../../../helper.js';
|
|
28
|
+
import Paper from '../../Paper.js';
|
|
29
|
+
import Renderer from '../../Renderer.js';
|
|
30
|
+
import Selecto from './Selecto.js';
|
|
31
|
+
import Moveable from './Moveable.js';
|
|
32
|
+
import Guides from './Guides.js';
|
|
33
|
+
import Mask from './Mask.js';
|
|
34
|
+
import Padding from './Padding.js';
|
|
35
|
+
import StaticSchema from '../../StaticSchema.js';
|
|
36
|
+
|
|
37
|
+
const mm2px = (mm: number) => mm * 3.7795275591;
|
|
38
|
+
|
|
39
|
+
const DELETE_BTN_ID = uuid();
|
|
40
|
+
const fmt4Num = (prop: string) => Number(prop.replace('px', ''));
|
|
41
|
+
const fmt = (prop: string) => round(fmt4Num(prop) / ZOOM, 2);
|
|
42
|
+
const isTopLeftResize = (d: string) => d === '-1,-1' || d === '-1,0' || d === '0,-1';
|
|
43
|
+
const normalizeRotate = (angle: number) => ((angle % 360) + 360) % 360;
|
|
44
|
+
|
|
45
|
+
const DeleteButton = ({ activeElements: aes }: { activeElements: HTMLElement[] }) => {
|
|
46
|
+
const { token } = theme.useToken();
|
|
47
|
+
|
|
48
|
+
const size = 26;
|
|
49
|
+
const top = Math.min(...aes.map(({ style }) => fmt4Num(style.top)));
|
|
50
|
+
const left = Math.max(...aes.map(({ style }) => fmt4Num(style.left) + fmt4Num(style.width))) + 10;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Button
|
|
54
|
+
id={DELETE_BTN_ID}
|
|
55
|
+
className={DESIGNER_CLASSNAME + 'delete-button'}
|
|
56
|
+
style={{
|
|
57
|
+
position: 'absolute',
|
|
58
|
+
zIndex: 1,
|
|
59
|
+
top,
|
|
60
|
+
left,
|
|
61
|
+
width: size,
|
|
62
|
+
height: size,
|
|
63
|
+
padding: 2,
|
|
64
|
+
display: 'flex',
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
justifyContent: 'center',
|
|
67
|
+
borderRadius: token.borderRadius,
|
|
68
|
+
color: token.colorWhite,
|
|
69
|
+
background: token.colorPrimary,
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<X style={{ pointerEvents: 'none' }} />
|
|
73
|
+
</Button>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
interface GuidesInterface {
|
|
78
|
+
getGuides(): number[];
|
|
79
|
+
scroll(pos: number): void;
|
|
80
|
+
scrollGuides(pos: number): void;
|
|
81
|
+
loadGuides(guides: number[]): void;
|
|
82
|
+
resize(): void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface Props {
|
|
86
|
+
basePdf: BasePdf;
|
|
87
|
+
height: number;
|
|
88
|
+
hoveringSchemaId: string | null;
|
|
89
|
+
onChangeHoveringSchemaId: (id: string | null) => void;
|
|
90
|
+
pageCursor: number;
|
|
91
|
+
schemasList: SchemaForUI[][];
|
|
92
|
+
scale: number;
|
|
93
|
+
backgrounds: string[];
|
|
94
|
+
pageSizes: Size[];
|
|
95
|
+
size: Size;
|
|
96
|
+
activeElements: HTMLElement[];
|
|
97
|
+
onEdit: (targets: HTMLElement[]) => void;
|
|
98
|
+
changeSchemas: ChangeSchemas;
|
|
99
|
+
removeSchemas: (ids: string[]) => void;
|
|
100
|
+
paperRefs: MutableRefObject<HTMLDivElement[]>;
|
|
101
|
+
sidebarOpen: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const Canvas = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
105
|
+
const {
|
|
106
|
+
basePdf,
|
|
107
|
+
pageCursor,
|
|
108
|
+
scale,
|
|
109
|
+
backgrounds,
|
|
110
|
+
pageSizes,
|
|
111
|
+
size,
|
|
112
|
+
activeElements,
|
|
113
|
+
schemasList,
|
|
114
|
+
hoveringSchemaId,
|
|
115
|
+
onEdit,
|
|
116
|
+
changeSchemas,
|
|
117
|
+
removeSchemas,
|
|
118
|
+
onChangeHoveringSchemaId,
|
|
119
|
+
paperRefs,
|
|
120
|
+
sidebarOpen,
|
|
121
|
+
} = props;
|
|
122
|
+
const { token } = theme.useToken();
|
|
123
|
+
const pluginsRegistry = useContext(PluginsRegistry);
|
|
124
|
+
const verticalGuides = useRef<GuidesInterface[]>([]);
|
|
125
|
+
const horizontalGuides = useRef<GuidesInterface[]>([]);
|
|
126
|
+
const moveable = useRef<MoveableComponent>(null);
|
|
127
|
+
|
|
128
|
+
const [isPressShiftKey, setIsPressShiftKey] = useState(false);
|
|
129
|
+
const [editing, setEditing] = useState(false);
|
|
130
|
+
|
|
131
|
+
const prevSchemas = usePrevious(schemasList[pageCursor]);
|
|
132
|
+
|
|
133
|
+
const onKeydown = (e: KeyboardEvent) => {
|
|
134
|
+
if (e.shiftKey) setIsPressShiftKey(true);
|
|
135
|
+
};
|
|
136
|
+
const onKeyup = (e: KeyboardEvent) => {
|
|
137
|
+
if (e.key === 'Shift' || !e.shiftKey) setIsPressShiftKey(false);
|
|
138
|
+
if (e.key === 'Escape' || e.key === 'Esc') setEditing(false);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const initEvents = useCallback(() => {
|
|
142
|
+
window.addEventListener('keydown', onKeydown);
|
|
143
|
+
window.addEventListener('keyup', onKeyup);
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
const destroyEvents = useCallback(() => {
|
|
147
|
+
window.removeEventListener('keydown', onKeydown);
|
|
148
|
+
window.removeEventListener('keyup', onKeyup);
|
|
149
|
+
}, []);
|
|
150
|
+
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
initEvents();
|
|
153
|
+
|
|
154
|
+
return destroyEvents;
|
|
155
|
+
}, [initEvents, destroyEvents]);
|
|
156
|
+
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
moveable.current?.updateRect();
|
|
159
|
+
if (!prevSchemas) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const prevSchemaKeys = JSON.stringify(prevSchemas[pageCursor] || {});
|
|
164
|
+
const schemaKeys = JSON.stringify(schemasList[pageCursor] || {});
|
|
165
|
+
|
|
166
|
+
if (prevSchemaKeys === schemaKeys) {
|
|
167
|
+
moveable.current?.updateRect();
|
|
168
|
+
}
|
|
169
|
+
}, [pageCursor, schemasList, prevSchemas]);
|
|
170
|
+
|
|
171
|
+
const onDrag = ({ target, top, left }: OnDrag) => {
|
|
172
|
+
const { width: _width, height: _height } = target.style;
|
|
173
|
+
const targetWidth = fmt(_width);
|
|
174
|
+
const targetHeight = fmt(_height);
|
|
175
|
+
const actualTop = top / ZOOM;
|
|
176
|
+
const actualLeft = left / ZOOM;
|
|
177
|
+
const { width: pageWidth, height: pageHeight } = pageSizes[pageCursor];
|
|
178
|
+
let topPadding = 0;
|
|
179
|
+
let rightPadding = 0;
|
|
180
|
+
let bottomPadding = 0;
|
|
181
|
+
let leftPadding = 0;
|
|
182
|
+
|
|
183
|
+
if (isBlankPdf(basePdf)) {
|
|
184
|
+
const [t, r, b, l] = basePdf.padding;
|
|
185
|
+
topPadding = t * ZOOM;
|
|
186
|
+
rightPadding = r;
|
|
187
|
+
bottomPadding = b;
|
|
188
|
+
leftPadding = l * ZOOM;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (actualTop + targetHeight > pageHeight - bottomPadding) {
|
|
192
|
+
target.style.top = `${(pageHeight - targetHeight - bottomPadding) * ZOOM}px`;
|
|
193
|
+
} else {
|
|
194
|
+
target.style.top = `${top < topPadding ? topPadding : top}px`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (actualLeft + targetWidth > pageWidth - rightPadding) {
|
|
198
|
+
target.style.left = `${(pageWidth - targetWidth - rightPadding) * ZOOM}px`;
|
|
199
|
+
} else {
|
|
200
|
+
target.style.left = `${left < leftPadding ? leftPadding : left}px`;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const onDragEnd = ({ target }: { target: HTMLElement | SVGElement }) => {
|
|
205
|
+
const { top, left } = target.style;
|
|
206
|
+
changeSchemas([
|
|
207
|
+
{ key: 'position.y', value: fmt(top), schemaId: target.id },
|
|
208
|
+
{ key: 'position.x', value: fmt(left), schemaId: target.id },
|
|
209
|
+
]);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const onDragEnds = ({ targets }: { targets: (HTMLElement | SVGElement)[] }) => {
|
|
213
|
+
const arg = targets.map(({ style: { top, left }, id }) => [
|
|
214
|
+
{ key: 'position.y', value: fmt(top), schemaId: id },
|
|
215
|
+
{ key: 'position.x', value: fmt(left), schemaId: id },
|
|
216
|
+
]);
|
|
217
|
+
changeSchemas(flatten(arg));
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const onRotate = ({ target, rotate }: OnRotate) => {
|
|
221
|
+
target.style.transform = `rotate(${rotate}deg)`;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const onRotateEnd = ({ target }: { target: HTMLElement | SVGElement }) => {
|
|
225
|
+
const { transform } = target.style;
|
|
226
|
+
const rotate = Number(transform.replace('rotate(', '').replace('deg)', ''));
|
|
227
|
+
const normalizedRotate = normalizeRotate(rotate);
|
|
228
|
+
changeSchemas([{ key: 'rotate', value: normalizedRotate, schemaId: target.id }]);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const onRotateEnds = ({ targets }: { targets: (HTMLElement | SVGElement)[] }) => {
|
|
232
|
+
const arg = targets.map(({ style: { transform }, id }) => {
|
|
233
|
+
const rotate = Number(transform.replace('rotate(', '').replace('deg)', ''));
|
|
234
|
+
const normalizedRotate = normalizeRotate(rotate);
|
|
235
|
+
return [{ key: 'rotate', value: normalizedRotate, schemaId: id }];
|
|
236
|
+
});
|
|
237
|
+
changeSchemas(flatten(arg));
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const onResizeEnd = ({ target }: { target: HTMLElement | SVGElement }) => {
|
|
241
|
+
const { id, style } = target;
|
|
242
|
+
const { width, height, top, left } = style;
|
|
243
|
+
changeSchemas([
|
|
244
|
+
{ key: 'position.x', value: fmt(left), schemaId: id },
|
|
245
|
+
{ key: 'position.y', value: fmt(top), schemaId: id },
|
|
246
|
+
{ key: 'width', value: fmt(width), schemaId: id },
|
|
247
|
+
{ key: 'height', value: fmt(height), schemaId: id },
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
const targetSchema = schemasList[pageCursor].find((schema) => schema.id === id);
|
|
251
|
+
|
|
252
|
+
if (!targetSchema) return;
|
|
253
|
+
|
|
254
|
+
targetSchema.position.x = fmt(left);
|
|
255
|
+
targetSchema.position.y = fmt(top);
|
|
256
|
+
targetSchema.width = fmt(width);
|
|
257
|
+
targetSchema.height = fmt(height);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const onResizeEnds = ({ targets }: { targets: (HTMLElement | SVGElement)[] }) => {
|
|
261
|
+
const arg = targets.map(({ style: { width, height, top, left }, id }) => [
|
|
262
|
+
{ key: 'width', value: fmt(width), schemaId: id },
|
|
263
|
+
{ key: 'height', value: fmt(height), schemaId: id },
|
|
264
|
+
{ key: 'position.y', value: fmt(top), schemaId: id },
|
|
265
|
+
{ key: 'position.x', value: fmt(left), schemaId: id },
|
|
266
|
+
]);
|
|
267
|
+
changeSchemas(flatten(arg));
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const onResize = ({ target, width, height, direction }: OnResize) => {
|
|
271
|
+
if (!target) return;
|
|
272
|
+
let topPadding = 0;
|
|
273
|
+
let rightPadding = 0;
|
|
274
|
+
let bottomPadding = 0;
|
|
275
|
+
let leftPadding = 0;
|
|
276
|
+
|
|
277
|
+
if (isBlankPdf(basePdf)) {
|
|
278
|
+
const [t, r, b, l] = basePdf.padding;
|
|
279
|
+
topPadding = t * ZOOM;
|
|
280
|
+
rightPadding = mm2px(r);
|
|
281
|
+
bottomPadding = mm2px(b);
|
|
282
|
+
leftPadding = l * ZOOM;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const pageWidth = mm2px(pageSizes[pageCursor].width);
|
|
286
|
+
const pageHeight = mm2px(pageSizes[pageCursor].height);
|
|
287
|
+
|
|
288
|
+
const obj: { top?: string; left?: string; width: string; height: string } = {
|
|
289
|
+
width: `${width}px`,
|
|
290
|
+
height: `${height}px`,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const s = target.style;
|
|
294
|
+
let newLeft = fmt4Num(s.left) + (fmt4Num(s.width) - width);
|
|
295
|
+
let newTop = fmt4Num(s.top) + (fmt4Num(s.height) - height);
|
|
296
|
+
if (newLeft < leftPadding) {
|
|
297
|
+
newLeft = leftPadding;
|
|
298
|
+
}
|
|
299
|
+
if (newTop < topPadding) {
|
|
300
|
+
newTop = topPadding;
|
|
301
|
+
}
|
|
302
|
+
if (newLeft + width > pageWidth - rightPadding) {
|
|
303
|
+
obj.width = `${pageWidth - rightPadding - newLeft}px`;
|
|
304
|
+
}
|
|
305
|
+
if (newTop + height > pageHeight - bottomPadding) {
|
|
306
|
+
obj.height = `${pageHeight - bottomPadding - newTop}px`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const d = direction.toString();
|
|
310
|
+
if (isTopLeftResize(d)) {
|
|
311
|
+
obj.top = `${newTop}px`;
|
|
312
|
+
obj.left = `${newLeft}px`;
|
|
313
|
+
} else if (d === '1,-1') {
|
|
314
|
+
obj.top = `${newTop}px`;
|
|
315
|
+
} else if (d === '-1,1') {
|
|
316
|
+
obj.left = `${newLeft}px`;
|
|
317
|
+
}
|
|
318
|
+
Object.assign(s, obj);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const getGuideLines = (guides: GuidesInterface[], index: number) =>
|
|
322
|
+
guides[index] && guides[index].getGuides().map((g) => g * ZOOM);
|
|
323
|
+
|
|
324
|
+
const onClickMoveable = () => {
|
|
325
|
+
// Just set editing to true without trying to access event properties
|
|
326
|
+
setEditing(true);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const rotatable = useMemo(() => {
|
|
330
|
+
const selectedSchemas = (schemasList[pageCursor] || []).filter((s) =>
|
|
331
|
+
activeElements.map((ae) => ae.id).includes(s.id),
|
|
332
|
+
);
|
|
333
|
+
const schemaTypes = selectedSchemas.map((s) => s.type);
|
|
334
|
+
const uniqueSchemaTypes = [...new Set(schemaTypes)];
|
|
335
|
+
|
|
336
|
+
// Create a type-safe array of default schemas
|
|
337
|
+
const defaultSchemas: Record<string, unknown>[] = [];
|
|
338
|
+
|
|
339
|
+
pluginsRegistry.entries().forEach(([, plugin]) => {
|
|
340
|
+
if (plugin.propPanel.defaultSchema) {
|
|
341
|
+
defaultSchemas.push(plugin.propPanel.defaultSchema as Record<string, unknown>);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Check if all schema types have rotate property
|
|
346
|
+
return uniqueSchemaTypes.every((type) => {
|
|
347
|
+
const matchingSchema = defaultSchemas.find((ds) => ds && 'type' in ds && ds.type === type);
|
|
348
|
+
return matchingSchema && 'rotate' in matchingSchema;
|
|
349
|
+
});
|
|
350
|
+
}, [activeElements, pageCursor, schemasList, pluginsRegistry]);
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<div
|
|
354
|
+
className={DESIGNER_CLASSNAME + 'canvas'}
|
|
355
|
+
style={{
|
|
356
|
+
position: 'relative',
|
|
357
|
+
overflow: 'auto',
|
|
358
|
+
marginRight: sidebarOpen ? RIGHT_SIDEBAR_WIDTH : 0,
|
|
359
|
+
...size,
|
|
360
|
+
}}
|
|
361
|
+
ref={ref}
|
|
362
|
+
>
|
|
363
|
+
<Selecto
|
|
364
|
+
container={paperRefs.current[pageCursor]}
|
|
365
|
+
continueSelect={isPressShiftKey}
|
|
366
|
+
onDragStart={(e) => {
|
|
367
|
+
// Use type assertion to safely access inputEvent properties
|
|
368
|
+
const inputEvent = e.inputEvent as MouseEvent | TouchEvent;
|
|
369
|
+
const target = inputEvent.target as Element | null;
|
|
370
|
+
const isMoveableElement = moveable.current?.isMoveableElement(target as Element);
|
|
371
|
+
|
|
372
|
+
if ((inputEvent.type === 'touchstart' && e.isTrusted) || isMoveableElement) {
|
|
373
|
+
e.stop();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (paperRefs.current[pageCursor] === target) {
|
|
377
|
+
onEdit([]);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Check if the target is an HTMLElement and has an id property
|
|
381
|
+
const targetElement = target as HTMLElement | null;
|
|
382
|
+
if (targetElement && targetElement.id === DELETE_BTN_ID) {
|
|
383
|
+
removeSchemas(activeElements.map((ae) => ae.id));
|
|
384
|
+
}
|
|
385
|
+
}}
|
|
386
|
+
onSelect={(e) => {
|
|
387
|
+
// Use type assertions to safely access properties
|
|
388
|
+
const inputEvent = e.inputEvent as MouseEvent | TouchEvent;
|
|
389
|
+
const added = e.added as HTMLElement[];
|
|
390
|
+
const removed = e.removed as HTMLElement[];
|
|
391
|
+
const selected = e.selected as HTMLElement[];
|
|
392
|
+
|
|
393
|
+
const isClick = inputEvent.type === 'mousedown';
|
|
394
|
+
let newActiveElements: HTMLElement[] = isClick ? selected : [];
|
|
395
|
+
|
|
396
|
+
if (!isClick && added.length > 0) {
|
|
397
|
+
newActiveElements = activeElements.concat(added);
|
|
398
|
+
}
|
|
399
|
+
if (!isClick && removed.length > 0) {
|
|
400
|
+
newActiveElements = activeElements.filter((ae) => !removed.includes(ae));
|
|
401
|
+
}
|
|
402
|
+
onEdit(newActiveElements);
|
|
403
|
+
|
|
404
|
+
if (newActiveElements != activeElements) {
|
|
405
|
+
setEditing(false);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// For MacOS CMD+SHIFT+3/4 screenshots where the keydown event is never received, check mouse too
|
|
409
|
+
const mouseEvent = inputEvent as MouseEvent;
|
|
410
|
+
if (mouseEvent && typeof mouseEvent.shiftKey === 'boolean' && !mouseEvent.shiftKey) {
|
|
411
|
+
setIsPressShiftKey(false);
|
|
412
|
+
}
|
|
413
|
+
}}
|
|
414
|
+
/>
|
|
415
|
+
<Paper
|
|
416
|
+
paperRefs={paperRefs}
|
|
417
|
+
scale={scale}
|
|
418
|
+
size={size}
|
|
419
|
+
schemasList={schemasList}
|
|
420
|
+
pageSizes={pageSizes}
|
|
421
|
+
backgrounds={backgrounds}
|
|
422
|
+
hasRulers={true}
|
|
423
|
+
renderPaper={({ index, paperSize }) => (
|
|
424
|
+
<>
|
|
425
|
+
{!editing && activeElements.length > 0 && pageCursor === index && (
|
|
426
|
+
<DeleteButton activeElements={activeElements} />
|
|
427
|
+
)}
|
|
428
|
+
<Padding basePdf={basePdf} />
|
|
429
|
+
<StaticSchema
|
|
430
|
+
template={{ schemas: schemasList, basePdf }}
|
|
431
|
+
input={Object.fromEntries(
|
|
432
|
+
schemasList.flat().map(({ name, content = '' }) => [name, content]),
|
|
433
|
+
)}
|
|
434
|
+
scale={scale}
|
|
435
|
+
totalPages={schemasList.length}
|
|
436
|
+
currentPage={index + 1}
|
|
437
|
+
/>
|
|
438
|
+
<Guides
|
|
439
|
+
paperSize={paperSize}
|
|
440
|
+
horizontalRef={(e) => {
|
|
441
|
+
if (e) horizontalGuides.current[index] = e;
|
|
442
|
+
}}
|
|
443
|
+
verticalRef={(e) => {
|
|
444
|
+
if (e) verticalGuides.current[index] = e;
|
|
445
|
+
}}
|
|
446
|
+
/>
|
|
447
|
+
{pageCursor !== index ? (
|
|
448
|
+
<Mask
|
|
449
|
+
width={paperSize.width + RULER_HEIGHT}
|
|
450
|
+
height={paperSize.height + RULER_HEIGHT}
|
|
451
|
+
/>
|
|
452
|
+
) : (
|
|
453
|
+
!editing && (
|
|
454
|
+
<Moveable
|
|
455
|
+
ref={moveable}
|
|
456
|
+
target={activeElements}
|
|
457
|
+
bounds={{ left: 0, top: 0, bottom: paperSize.height, right: paperSize.width }}
|
|
458
|
+
horizontalGuidelines={getGuideLines(horizontalGuides.current, index)}
|
|
459
|
+
verticalGuidelines={getGuideLines(verticalGuides.current, index)}
|
|
460
|
+
keepRatio={isPressShiftKey}
|
|
461
|
+
rotatable={rotatable}
|
|
462
|
+
onDrag={onDrag}
|
|
463
|
+
onDragEnd={onDragEnd}
|
|
464
|
+
onDragGroupEnd={onDragEnds}
|
|
465
|
+
onRotate={onRotate}
|
|
466
|
+
onRotateEnd={onRotateEnd}
|
|
467
|
+
onRotateGroupEnd={onRotateEnds}
|
|
468
|
+
onResize={onResize}
|
|
469
|
+
onResizeEnd={onResizeEnd}
|
|
470
|
+
onResizeGroupEnd={onResizeEnds}
|
|
471
|
+
onClick={onClickMoveable}
|
|
472
|
+
/>
|
|
473
|
+
)
|
|
474
|
+
)}
|
|
475
|
+
</>
|
|
476
|
+
)}
|
|
477
|
+
renderSchema={({ schema, index }) => {
|
|
478
|
+
const mode =
|
|
479
|
+
editing && activeElements.map((ae) => ae.id).includes(schema.id)
|
|
480
|
+
? 'designer'
|
|
481
|
+
: 'viewer';
|
|
482
|
+
|
|
483
|
+
const content = schema.content || '';
|
|
484
|
+
let value = content;
|
|
485
|
+
|
|
486
|
+
if (mode !== 'designer' && schema.readOnly) {
|
|
487
|
+
const variables = {
|
|
488
|
+
...schemasList.flat().reduce(
|
|
489
|
+
(acc, currSchema) => {
|
|
490
|
+
acc[currSchema.name] = currSchema.content || '';
|
|
491
|
+
return acc;
|
|
492
|
+
},
|
|
493
|
+
{} as Record<string, string>,
|
|
494
|
+
),
|
|
495
|
+
totalPages: schemasList.length,
|
|
496
|
+
currentPage: index + 1,
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
value = replacePlaceholders({ content, variables, schemas: schemasList });
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return (
|
|
503
|
+
<Renderer
|
|
504
|
+
key={schema.id}
|
|
505
|
+
schema={schema}
|
|
506
|
+
basePdf={basePdf}
|
|
507
|
+
value={value}
|
|
508
|
+
onChangeHoveringSchemaId={onChangeHoveringSchemaId}
|
|
509
|
+
mode={mode}
|
|
510
|
+
onChange={
|
|
511
|
+
(schemasList[pageCursor] || []).some((s) => s.id === schema.id)
|
|
512
|
+
? (arg) => {
|
|
513
|
+
// Use type assertion to safely handle the argument
|
|
514
|
+
type ChangeArg = { key: string; value: unknown };
|
|
515
|
+
const args = Array.isArray(arg) ? (arg as ChangeArg[]) : [arg as ChangeArg];
|
|
516
|
+
changeSchemas(
|
|
517
|
+
args.map(({ key, value }) => ({ key, value, schemaId: schema.id })),
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
: undefined
|
|
521
|
+
}
|
|
522
|
+
stopEditing={() => setEditing(false)}
|
|
523
|
+
outline={`1px ${hoveringSchemaId === schema.id ? 'solid' : 'dashed'} ${
|
|
524
|
+
schema.readOnly && hoveringSchemaId !== schema.id
|
|
525
|
+
? 'transparent'
|
|
526
|
+
: token.colorPrimary
|
|
527
|
+
}`}
|
|
528
|
+
scale={scale}
|
|
529
|
+
/>
|
|
530
|
+
);
|
|
531
|
+
}}
|
|
532
|
+
/>
|
|
533
|
+
</div>
|
|
534
|
+
);
|
|
535
|
+
};
|
|
536
|
+
export default forwardRef<HTMLDivElement, Props>(Canvas);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React, { useContext, useState, useEffect } from 'react';
|
|
2
|
+
import { Schema, Plugin, BasePdf, getFallbackFontName } from '@pdfme/common';
|
|
3
|
+
import { theme, Button } from 'antd';
|
|
4
|
+
import { useDraggable } from '@dnd-kit/core';
|
|
5
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
6
|
+
import Renderer from '../Renderer.js';
|
|
7
|
+
import { LEFT_SIDEBAR_WIDTH, DESIGNER_CLASSNAME } from '../../constants.js';
|
|
8
|
+
import { setFontNameRecursively } from '../../helper';
|
|
9
|
+
import { OptionsContext, PluginsRegistry } from '../../contexts.js';
|
|
10
|
+
import PluginIcon from './PluginIcon.js';
|
|
11
|
+
|
|
12
|
+
const Draggable = (props: {
|
|
13
|
+
plugin: Plugin<Schema>;
|
|
14
|
+
scale: number;
|
|
15
|
+
basePdf: BasePdf;
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
}) => {
|
|
18
|
+
const { scale, basePdf, plugin } = props;
|
|
19
|
+
const { token } = theme.useToken();
|
|
20
|
+
const options = useContext(OptionsContext);
|
|
21
|
+
const defaultSchema = plugin.propPanel.defaultSchema;
|
|
22
|
+
if (options.font) {
|
|
23
|
+
const fontName = getFallbackFontName(options.font);
|
|
24
|
+
setFontNameRecursively(defaultSchema, fontName);
|
|
25
|
+
}
|
|
26
|
+
const draggable = useDraggable({ id: defaultSchema.type, data: defaultSchema });
|
|
27
|
+
const { listeners, setNodeRef, attributes, transform, isDragging } = draggable;
|
|
28
|
+
const style = { transform: CSS.Translate.toString(transform) };
|
|
29
|
+
|
|
30
|
+
const renderedSchema = React.useMemo(
|
|
31
|
+
() => (
|
|
32
|
+
<div style={{ transform: `scale(${scale})` }}>
|
|
33
|
+
<Renderer
|
|
34
|
+
schema={{ ...defaultSchema, id: defaultSchema.type }}
|
|
35
|
+
basePdf={basePdf}
|
|
36
|
+
value={defaultSchema.content || ''}
|
|
37
|
+
onChangeHoveringSchemaId={() => {
|
|
38
|
+
void 0;
|
|
39
|
+
}}
|
|
40
|
+
mode={'viewer'}
|
|
41
|
+
outline={`1px solid ${token.colorPrimary}`}
|
|
42
|
+
scale={scale}
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
),
|
|
46
|
+
[defaultSchema, basePdf, scale, token.colorPrimary],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div ref={setNodeRef} style={style} {...listeners} {...attributes}>
|
|
51
|
+
{isDragging && renderedSchema}
|
|
52
|
+
<div style={{ visibility: isDragging ? 'hidden' : 'visible' }}>{props.children}</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const LeftSidebar = ({
|
|
58
|
+
height,
|
|
59
|
+
scale,
|
|
60
|
+
basePdf,
|
|
61
|
+
}: {
|
|
62
|
+
height: number;
|
|
63
|
+
scale: number;
|
|
64
|
+
basePdf: BasePdf;
|
|
65
|
+
}) => {
|
|
66
|
+
const { token } = theme.useToken();
|
|
67
|
+
const pluginsRegistry = useContext(PluginsRegistry);
|
|
68
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const handleMouseUp = () => {
|
|
72
|
+
if (isDragging) {
|
|
73
|
+
setIsDragging(false);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
81
|
+
};
|
|
82
|
+
}, [isDragging]);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
className={DESIGNER_CLASSNAME + 'left-sidebar'}
|
|
87
|
+
style={{
|
|
88
|
+
left: 0,
|
|
89
|
+
right: 0,
|
|
90
|
+
position: 'absolute',
|
|
91
|
+
zIndex: 1,
|
|
92
|
+
height,
|
|
93
|
+
width: LEFT_SIDEBAR_WIDTH,
|
|
94
|
+
background: token.colorBgLayout,
|
|
95
|
+
textAlign: 'center',
|
|
96
|
+
overflow: isDragging ? 'visible' : 'auto',
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
{pluginsRegistry.entries().map(([label, plugin]) => {
|
|
100
|
+
if (!plugin?.propPanel.defaultSchema) return null;
|
|
101
|
+
|
|
102
|
+
const pluginType = plugin.propPanel.defaultSchema.type;
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<Draggable key={label} scale={scale} basePdf={basePdf} plugin={plugin}>
|
|
106
|
+
<Button
|
|
107
|
+
className={DESIGNER_CLASSNAME + 'plugin-' + pluginType}
|
|
108
|
+
onMouseDown={() => setIsDragging(true)}
|
|
109
|
+
style={{ width: 35, height: 35, marginTop: '0.25rem', padding: '0.25rem' }}
|
|
110
|
+
>
|
|
111
|
+
<PluginIcon plugin={plugin} label={label} />
|
|
112
|
+
</Button>
|
|
113
|
+
</Draggable>
|
|
114
|
+
);
|
|
115
|
+
})}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export default LeftSidebar;
|