@pdfme/ui 2.2.0 → 3.0.0-beta.2
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 +33 -35
- package/__mocks__/form-render.js +7 -0
- package/dist/index.js +1 -1
- package/dist/index.js.LICENSE.txt +42 -4
- package/dist/index.js.map +1 -1
- package/dist/types/Designer.d.ts +3 -0
- package/dist/types/builtInPropPanel.d.ts +3 -0
- package/dist/types/builtInRenderer.d.ts +3 -0
- package/dist/types/class.d.ts +18 -38
- package/dist/types/components/CtlBar/Pager.d.ts +3 -2
- package/dist/types/components/CtlBar/Zoom.d.ts +3 -2
- package/dist/types/components/CtlBar/index.d.ts +3 -2
- package/dist/types/components/Designer/{Main → Canvas}/Guides.d.ts +2 -2
- package/dist/types/components/Designer/Canvas/Mask.d.ts +4 -0
- package/dist/types/components/Designer/Canvas/Moveable.d.ts +37 -0
- package/dist/types/components/Designer/{Main → Canvas}/Selecto.d.ts +2 -1
- package/dist/types/components/Designer/{Main → Canvas}/index.d.ts +3 -6
- package/dist/types/components/Designer/Sidebar/DetailView/AlignWidget.d.ts +4 -0
- package/dist/types/components/Designer/Sidebar/DetailView/WidgetRenderer.d.ts +7 -0
- package/dist/types/components/Designer/Sidebar/DetailView/index.d.ts +5 -4
- package/dist/types/components/Designer/Sidebar/ListView/SelectableSortableContainer.d.ts +3 -2
- package/dist/types/components/Designer/Sidebar/ListView/SelectableSortableItem.d.ts +1 -1
- package/dist/types/components/Designer/Sidebar/ListView/index.d.ts +3 -2
- package/dist/types/components/Designer/Sidebar/index.d.ts +3 -23
- package/dist/types/components/Designer/index.d.ts +6 -107
- package/dist/types/components/Divider.d.ts +2 -1
- package/dist/types/components/ErrorScreen.d.ts +7 -0
- package/dist/types/components/Paper.d.ts +3 -2
- package/dist/types/components/Preview.d.ts +10 -2
- package/dist/types/components/Renderer.d.ts +10 -0
- package/dist/types/components/Root.d.ts +1 -1
- package/dist/types/components/Spinner.d.ts +2 -1
- package/dist/types/components/UnitPager.d.ts +3 -2
- package/dist/types/constants.d.ts +3 -3
- package/dist/types/contexts.d.ts +4 -1
- package/dist/types/helper.d.ts +4 -46
- package/dist/types/hooks.d.ts +2 -2
- package/dist/types/i18n.d.ts +4 -2
- package/dist/types/index.d.ts +1 -4
- package/dist/types/types.d.ts +25 -0
- package/package.json +19 -8
- package/src/Designer.tsx +69 -21
- package/src/Form.tsx +18 -14
- package/src/Viewer.tsx +6 -2
- package/src/builtInPropPanel.ts +5 -0
- package/src/builtInRenderer.ts +5 -0
- package/src/class.ts +25 -2
- package/src/components/CtlBar/index.tsx +4 -7
- package/src/components/Designer/{Main → Canvas}/Guides.tsx +2 -2
- package/src/components/Designer/{Main → Canvas}/Moveable.tsx +23 -19
- package/src/components/Designer/{Main → Canvas}/index.tsx +77 -30
- package/src/components/Designer/Sidebar/DetailView/AlignWidget.tsx +182 -0
- package/src/components/Designer/Sidebar/DetailView/WidgetRenderer.tsx +28 -0
- package/src/components/Designer/Sidebar/DetailView/index.tsx +153 -22
- package/src/components/Designer/Sidebar/ListView/Item.tsx +1 -1
- package/src/components/Designer/Sidebar/ListView/SelectableSortableContainer.tsx +6 -6
- package/src/components/Designer/Sidebar/ListView/index.tsx +1 -4
- package/src/components/Designer/Sidebar/index.tsx +26 -60
- package/src/components/Designer/index.tsx +53 -32
- package/src/components/{Error.tsx → ErrorScreen.tsx} +2 -2
- package/src/components/Paper.tsx +35 -9
- package/src/components/Preview.tsx +48 -50
- package/src/components/Renderer.tsx +90 -0
- package/src/components/Root.tsx +5 -1
- package/src/constants.ts +4 -4
- package/src/contexts.ts +7 -0
- package/src/helper.ts +19 -122
- package/src/hooks.ts +6 -5
- package/src/i18n.ts +48 -11
- package/src/index.ts +1 -76
- package/src/types.ts +36 -0
- package/tsconfig.json +2 -1
- package/webpack.config.js +6 -1
- package/dist/types/components/Designer/Main/Mask.d.ts +0 -3
- package/dist/types/components/Designer/Main/Moveable.d.ts +0 -31
- package/dist/types/components/Designer/Sidebar/DetailView/ExampleInputEditor.d.ts +0 -6
- package/dist/types/components/Designer/Sidebar/DetailView/PositionAndSizeEditor.d.ts +0 -6
- package/dist/types/components/Designer/Sidebar/DetailView/TextPropEditor.d.ts +0 -6
- package/dist/types/components/Designer/Sidebar/DetailView/TypeAndKeyEditor.d.ts +0 -6
- package/dist/types/components/Error.d.ts +0 -6
- package/dist/types/components/Schemas/BarcodeSchema.d.ts +0 -15
- package/dist/types/components/Schemas/ImageSchema.d.ts +0 -15
- package/dist/types/components/Schemas/SchemaUI.d.ts +0 -15
- package/dist/types/components/Schemas/TextSchema.d.ts +0 -28
- package/src/components/Designer/Sidebar/DetailView/ExampleInputEditor.tsx +0 -85
- package/src/components/Designer/Sidebar/DetailView/PositionAndSizeEditor.tsx +0 -275
- package/src/components/Designer/Sidebar/DetailView/TextPropEditor.tsx +0 -357
- package/src/components/Designer/Sidebar/DetailView/TypeAndKeyEditor.tsx +0 -87
- package/src/components/Schemas/BarcodeSchema.tsx +0 -124
- package/src/components/Schemas/ImageSchema.tsx +0 -87
- package/src/components/Schemas/SchemaUI.tsx +0 -62
- package/src/components/Schemas/TextSchema.tsx +0 -175
- /package/src/components/Designer/{Main → Canvas}/Mask.tsx +0 -0
- /package/src/components/Designer/{Main → Canvas}/Selecto.tsx +0 -0
@@ -1,31 +1,33 @@
|
|
1
1
|
import React, {
|
2
2
|
Ref,
|
3
|
+
useMemo,
|
4
|
+
useContext,
|
3
5
|
MutableRefObject,
|
4
6
|
useRef,
|
5
7
|
useState,
|
6
8
|
useEffect,
|
7
9
|
forwardRef,
|
8
10
|
useCallback,
|
9
|
-
useContext,
|
10
11
|
} from 'react';
|
11
|
-
import { OnDrag, OnResize, OnClick } from 'react-moveable';
|
12
|
-
import { SchemaForUI, Size } from '@pdfme/common';
|
12
|
+
import { OnDrag, OnResize, OnClick, OnRotate } from 'react-moveable';
|
13
|
+
import { ZOOM, SchemaForUI, Size, ChangeSchemas } from '@pdfme/common';
|
14
|
+
import { PropPanelRegistry } from '../../../contexts';
|
13
15
|
import { XMarkIcon } from '@heroicons/react/24/outline';
|
14
|
-
import {
|
16
|
+
import { RULER_HEIGHT, SIDEBAR_WIDTH } from '../../../constants';
|
15
17
|
import { usePrevious } from '../../../hooks';
|
16
18
|
import { uuid, round, flatten } from '../../../helper';
|
17
19
|
import Paper from '../../Paper';
|
18
|
-
import
|
20
|
+
import Renderer from '../../Renderer';
|
19
21
|
import Selecto from './Selecto';
|
20
22
|
import Moveable from './Moveable';
|
21
23
|
import Guides from './Guides';
|
22
24
|
import Mask from './Mask';
|
23
|
-
import { FontContext } from '../../../contexts';
|
24
25
|
|
25
26
|
const DELETE_BTN_ID = uuid();
|
26
27
|
const fmt4Num = (prop: string) => Number(prop.replace('px', ''));
|
27
28
|
const fmt = (prop: string) => round(fmt4Num(prop) / ZOOM, 2);
|
28
29
|
const isTopLeftResize = (d: string) => d === '-1,-1' || d === '-1,0' || d === '0,-1';
|
30
|
+
const normalizeRotate = (angle: number) => ((angle % 360) + 360) % 360;
|
29
31
|
|
30
32
|
const DeleteButton = ({ activeElements: aes }: { activeElements: HTMLElement[] }) => {
|
31
33
|
const top = Math.min(...aes.map(({ style }) => fmt4Num(style.top)));
|
@@ -78,12 +80,13 @@ interface Props {
|
|
78
80
|
size: Size;
|
79
81
|
activeElements: HTMLElement[];
|
80
82
|
onEdit: (targets: HTMLElement[]) => void;
|
81
|
-
changeSchemas:
|
83
|
+
changeSchemas: ChangeSchemas;
|
82
84
|
removeSchemas: (ids: string[]) => void;
|
83
85
|
paperRefs: MutableRefObject<HTMLDivElement[]>;
|
86
|
+
sidebarOpen: boolean;
|
84
87
|
}
|
85
88
|
|
86
|
-
const
|
89
|
+
const Canvas = (props: Props, ref: Ref<HTMLDivElement>) => {
|
87
90
|
const {
|
88
91
|
pageCursor,
|
89
92
|
scale,
|
@@ -93,14 +96,18 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
93
96
|
activeElements,
|
94
97
|
schemasList,
|
95
98
|
hoveringSchemaId,
|
99
|
+
onEdit,
|
100
|
+
changeSchemas,
|
101
|
+
removeSchemas,
|
102
|
+
onChangeHoveringSchemaId,
|
103
|
+
paperRefs,
|
104
|
+
sidebarOpen,
|
96
105
|
} = props;
|
97
|
-
const
|
106
|
+
const propPanelRegistry = useContext(PropPanelRegistry);
|
98
107
|
|
99
|
-
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);
|
100
108
|
const verticalGuides = useRef<GuidesInterface[]>([]);
|
101
109
|
const horizontalGuides = useRef<GuidesInterface[]>([]);
|
102
110
|
const moveable = useRef<any>(null);
|
103
|
-
const font = useContext(FontContext);
|
104
111
|
|
105
112
|
const [isPressShiftKey, setIsPressShiftKey] = useState(false);
|
106
113
|
const [editing, setEditing] = useState(false);
|
@@ -166,6 +173,26 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
166
173
|
changeSchemas(flatten(arg));
|
167
174
|
};
|
168
175
|
|
176
|
+
const onRotate = ({ target, rotate }: OnRotate) => {
|
177
|
+
target.style.transform = `rotate(${rotate}deg)`;
|
178
|
+
};
|
179
|
+
|
180
|
+
const onRotateEnd = ({ target }: { target: HTMLElement | SVGElement }) => {
|
181
|
+
const { transform } = target.style;
|
182
|
+
const rotate = Number(transform.replace('rotate(', '').replace('deg)', ''));
|
183
|
+
const normalizedRotate = normalizeRotate(rotate);
|
184
|
+
changeSchemas([{ key: 'rotate', value: normalizedRotate, schemaId: target.id }]);
|
185
|
+
};
|
186
|
+
|
187
|
+
const onRotateEnds = ({ targets }: { targets: (HTMLElement | SVGElement)[] }) => {
|
188
|
+
const arg = targets.map(({ style: { transform }, id }) => {
|
189
|
+
const rotate = Number(transform.replace('rotate(', '').replace('deg)', ''));
|
190
|
+
const normalizedRotate = normalizeRotate(rotate);
|
191
|
+
return [{ key: 'rotate', value: normalizedRotate, schemaId: id }];
|
192
|
+
});
|
193
|
+
changeSchemas(flatten(arg));
|
194
|
+
};
|
195
|
+
|
169
196
|
const onResizeEnd = async ({ target }: { target: HTMLElement | SVGElement }) => {
|
170
197
|
const { id, style } = target;
|
171
198
|
const { width, height, top, left } = style;
|
@@ -223,16 +250,29 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
223
250
|
const onClickMoveable = (e: OnClick) => {
|
224
251
|
e.inputEvent.stopPropagation();
|
225
252
|
setEditing(true);
|
226
|
-
const ic = inputRef.current;
|
227
|
-
if (!ic) return;
|
228
|
-
ic.focus();
|
229
|
-
if (ic.type !== 'file') {
|
230
|
-
ic.setSelectionRange(ic.value.length, ic.value.length);
|
231
|
-
}
|
232
253
|
};
|
233
254
|
|
255
|
+
const rotatable = useMemo(() => {
|
256
|
+
const selectedSchemas = schemasList[pageCursor].filter((s) =>
|
257
|
+
activeElements.map((ae) => ae.id).includes(s.id)
|
258
|
+
);
|
259
|
+
const schemaTypes = selectedSchemas.map((s) => s.type);
|
260
|
+
const uniqueSchemaTypes = [...new Set(schemaTypes)];
|
261
|
+
return uniqueSchemaTypes.every(
|
262
|
+
(type) => propPanelRegistry[type]?.defaultSchema?.rotate !== undefined
|
263
|
+
);
|
264
|
+
}, [activeElements, pageCursor, schemasList]);
|
265
|
+
|
234
266
|
return (
|
235
|
-
<div
|
267
|
+
<div
|
268
|
+
style={{
|
269
|
+
position: 'relative',
|
270
|
+
overflow: 'auto',
|
271
|
+
marginRight: sidebarOpen ? SIDEBAR_WIDTH : 0,
|
272
|
+
...size,
|
273
|
+
}}
|
274
|
+
ref={ref}
|
275
|
+
>
|
236
276
|
<Selecto
|
237
277
|
container={paperRefs.current[pageCursor]}
|
238
278
|
continueSelect={isPressShiftKey}
|
@@ -277,6 +317,7 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
277
317
|
schemasList={schemasList}
|
278
318
|
pageSizes={pageSizes}
|
279
319
|
backgrounds={backgrounds}
|
320
|
+
hasRulers={true}
|
280
321
|
renderPaper={({ index, paperSize }) => (
|
281
322
|
<>
|
282
323
|
{!editing && activeElements.length > 0 && (
|
@@ -285,18 +326,17 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
285
326
|
<Guides
|
286
327
|
paperSize={paperSize}
|
287
328
|
horizontalRef={(e) => {
|
288
|
-
if (e)
|
289
|
-
horizontalGuides.current[index] = e;
|
290
|
-
}
|
329
|
+
if (e) horizontalGuides.current[index] = e;
|
291
330
|
}}
|
292
331
|
verticalRef={(e) => {
|
293
|
-
if (e)
|
294
|
-
verticalGuides.current[index] = e;
|
295
|
-
}
|
332
|
+
if (e) verticalGuides.current[index] = e;
|
296
333
|
}}
|
297
334
|
/>
|
298
335
|
{pageCursor !== index ? (
|
299
|
-
<Mask
|
336
|
+
<Mask
|
337
|
+
width={paperSize.width + RULER_HEIGHT}
|
338
|
+
height={paperSize.height + RULER_HEIGHT}
|
339
|
+
/>
|
300
340
|
) : (
|
301
341
|
!editing && (
|
302
342
|
<Moveable
|
@@ -306,9 +346,13 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
306
346
|
horizontalGuidelines={getGuideLines(horizontalGuides.current, index)}
|
307
347
|
verticalGuidelines={getGuideLines(verticalGuides.current, index)}
|
308
348
|
keepRatio={isPressShiftKey}
|
349
|
+
rotatable={rotatable}
|
309
350
|
onDrag={onDrag}
|
310
351
|
onDragEnd={onDragEnd}
|
311
352
|
onDragGroupEnd={onDragEnds}
|
353
|
+
onRotate={onRotate}
|
354
|
+
onRotateEnd={onRotateEnd}
|
355
|
+
onRotateGroupEnd={onRotateEnds}
|
312
356
|
onResize={onResize}
|
313
357
|
onResizeEnd={onResizeEnd}
|
314
358
|
onResizeGroupEnd={onResizeEnds}
|
@@ -319,21 +363,24 @@ const Main = (props: Props, ref: Ref<HTMLDivElement>) => {
|
|
319
363
|
</>
|
320
364
|
)}
|
321
365
|
renderSchema={({ schema }) => (
|
322
|
-
<
|
366
|
+
<Renderer
|
323
367
|
key={schema.id}
|
324
368
|
schema={schema}
|
325
369
|
onChangeHoveringSchemaId={onChangeHoveringSchemaId}
|
326
|
-
|
370
|
+
mode={
|
371
|
+
editing && activeElements.map((ae) => ae.id).includes(schema.id)
|
372
|
+
? 'designer'
|
373
|
+
: 'viewer'
|
374
|
+
}
|
327
375
|
onChange={(value) => {
|
328
376
|
changeSchemas([{ key: 'data', value, schemaId: schema.id }]);
|
329
377
|
}}
|
330
|
-
|
378
|
+
stopEditing={() => setEditing(false)}
|
331
379
|
outline={hoveringSchemaId === schema.id ? '1px solid #18a0fb' : '1px dashed #4af'}
|
332
|
-
ref={inputRef}
|
333
380
|
/>
|
334
381
|
)}
|
335
382
|
/>
|
336
383
|
</div>
|
337
384
|
);
|
338
385
|
};
|
339
|
-
export default forwardRef<HTMLDivElement, Props>(
|
386
|
+
export default forwardRef<HTMLDivElement, Props>(Canvas);
|
@@ -0,0 +1,182 @@
|
|
1
|
+
import { Button, Form } from 'antd';
|
2
|
+
import React from 'react';
|
3
|
+
import type { PropPanelWidgetProps } from '@pdfme/common';
|
4
|
+
import { round } from '../../../../helper';
|
5
|
+
|
6
|
+
const svgBaseProp = {
|
7
|
+
style: { width: '90%', height: '90%' },
|
8
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
9
|
+
enableBackground: 'new 0 0 24 24',
|
10
|
+
height: '24px',
|
11
|
+
viewBox: '0 0 24 24',
|
12
|
+
width: '24px',
|
13
|
+
fill: '#000000',
|
14
|
+
};
|
15
|
+
|
16
|
+
const AlignWidget = ({
|
17
|
+
activeElements,
|
18
|
+
changeSchemas,
|
19
|
+
schemas,
|
20
|
+
pageSize,
|
21
|
+
}: PropPanelWidgetProps) => {
|
22
|
+
const align = (type: 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom') => {
|
23
|
+
const ids = activeElements.map((ae) => ae.id);
|
24
|
+
const ass = schemas.filter((s) => ids.includes(s.id));
|
25
|
+
|
26
|
+
const isVertical = ['left', 'center', 'right'].includes(type);
|
27
|
+
const tgtPos = isVertical ? 'x' : 'y';
|
28
|
+
const tgtSize = isVertical ? 'width' : 'height';
|
29
|
+
const isSingle = ass.length === 1;
|
30
|
+
const root = pageSize[tgtSize];
|
31
|
+
|
32
|
+
const min = isSingle ? 0 : Math.min(...ass.map((as) => as.position[tgtPos]));
|
33
|
+
const max = isSingle ? root : Math.max(...ass.map((as) => as.position[tgtPos] + as[tgtSize]));
|
34
|
+
|
35
|
+
let basePos = min;
|
36
|
+
let adjust = (_: number) => 0;
|
37
|
+
|
38
|
+
if (['center', 'middle'].includes(type)) {
|
39
|
+
basePos = (min + max) / 2;
|
40
|
+
adjust = (num: number) => num / 2;
|
41
|
+
} else if (['right', 'bottom'].includes(type)) {
|
42
|
+
basePos = max;
|
43
|
+
adjust = (num: number) => num;
|
44
|
+
}
|
45
|
+
|
46
|
+
changeSchemas(
|
47
|
+
ass.map((as) => ({
|
48
|
+
key: `position.${tgtPos}`,
|
49
|
+
value: round(basePos - adjust(as[tgtSize]), 2),
|
50
|
+
schemaId: as.id,
|
51
|
+
}))
|
52
|
+
);
|
53
|
+
};
|
54
|
+
|
55
|
+
const distribute = (type: 'vertical' | 'horizontal') => {
|
56
|
+
const ids = activeElements.map((ae) => ae.id);
|
57
|
+
const ass = schemas.filter((s) => ids.includes(s.id));
|
58
|
+
|
59
|
+
const isVertical = type === 'vertical';
|
60
|
+
const tgtPos = isVertical ? 'y' : 'x';
|
61
|
+
const tgtSize = isVertical ? 'height' : 'width';
|
62
|
+
const min = Math.min(...ass.map((as) => as.position[tgtPos]));
|
63
|
+
const max = Math.max(...ass.map((as) => as.position[tgtPos] + as[tgtSize]));
|
64
|
+
|
65
|
+
if (ass.length < 3) return;
|
66
|
+
|
67
|
+
const boxPos = min;
|
68
|
+
const boxSize = max - min;
|
69
|
+
const sum = ass.reduce((acc, cur) => acc + cur[tgtSize], 0);
|
70
|
+
const remain = boxSize - sum;
|
71
|
+
const unit = remain / (ass.length - 1);
|
72
|
+
|
73
|
+
let prev = 0;
|
74
|
+
changeSchemas(
|
75
|
+
ass.map((as, index) => {
|
76
|
+
prev += index === 0 ? 0 : ass[index - 1][tgtSize] + unit;
|
77
|
+
const value = round(boxPos + prev, 2);
|
78
|
+
return { key: `position.${tgtPos}`, value, schemaId: as.id };
|
79
|
+
})
|
80
|
+
);
|
81
|
+
};
|
82
|
+
|
83
|
+
const layoutBtns: { id: string; icon: any; onClick: () => void }[] = [
|
84
|
+
{
|
85
|
+
id: 'left',
|
86
|
+
icon: (
|
87
|
+
<svg {...svgBaseProp}>
|
88
|
+
<rect fill="none" height="24" width="24" />
|
89
|
+
<path d="M4,22H2V2h2V22z M22,7H6v3h16V7z M16,14H6v3h10V14z" />
|
90
|
+
</svg>
|
91
|
+
),
|
92
|
+
onClick: () => align('left'),
|
93
|
+
},
|
94
|
+
{
|
95
|
+
id: 'center',
|
96
|
+
icon: (
|
97
|
+
<svg {...svgBaseProp}>
|
98
|
+
<rect fill="none" height="24" width="24" />
|
99
|
+
<polygon points="11,2 13,2 13,7 21,7 21,10 13,10 13,14 18,14 18,17 13,17 13,22 11,22 11,17 6,17 6,14 11,14 11,10 3,10 3,7 11,7" />
|
100
|
+
</svg>
|
101
|
+
),
|
102
|
+
onClick: () => align('center'),
|
103
|
+
},
|
104
|
+
{
|
105
|
+
id: 'right',
|
106
|
+
icon: (
|
107
|
+
<svg {...svgBaseProp}>
|
108
|
+
<rect fill="none" height="24" width="24" />
|
109
|
+
<path d="M20,2h2v20h-2V2z M2,10h16V7H2V10z M8,17h10v-3H8V17z" />
|
110
|
+
</svg>
|
111
|
+
),
|
112
|
+
onClick: () => align('right'),
|
113
|
+
},
|
114
|
+
{
|
115
|
+
id: 'top',
|
116
|
+
icon: (
|
117
|
+
<svg {...svgBaseProp}>
|
118
|
+
<rect fill="none" height="24" width="24" />
|
119
|
+
<path d="M22,2v2H2V2H22z M7,22h3V6H7V22z M14,16h3V6h-3V16z" />
|
120
|
+
</svg>
|
121
|
+
),
|
122
|
+
onClick: () => align('top'),
|
123
|
+
},
|
124
|
+
{
|
125
|
+
id: 'middle',
|
126
|
+
icon: (
|
127
|
+
<svg {...svgBaseProp}>
|
128
|
+
<rect fill="none" height="24" width="24" />
|
129
|
+
<polygon points="22,11 17,11 17,6 14,6 14,11 10,11 10,3 7,3 7,11 1.84,11 1.84,13 7,13 7,21 10,21 10,13 14,13 14,18 17,18 17,13 22,13" />
|
130
|
+
</svg>
|
131
|
+
),
|
132
|
+
onClick: () => align('middle'),
|
133
|
+
},
|
134
|
+
{
|
135
|
+
id: 'bottom',
|
136
|
+
icon: (
|
137
|
+
<svg {...svgBaseProp}>
|
138
|
+
<rect fill="none" height="24" width="24" />
|
139
|
+
<path d="M22,22H2v-2h20V22z M10,2H7v16h3V2z M17,8h-3v10h3V8z" />
|
140
|
+
</svg>
|
141
|
+
),
|
142
|
+
onClick: () => align('bottom'),
|
143
|
+
},
|
144
|
+
{
|
145
|
+
id: 'vertical',
|
146
|
+
icon: (
|
147
|
+
<svg {...svgBaseProp}>
|
148
|
+
<rect fill="none" height="24" width="24" />
|
149
|
+
<path d="M22,2v2H2V2H22z M7,10.5v3h10v-3H7z M2,20v2h20v-2H2z" />
|
150
|
+
</svg>
|
151
|
+
),
|
152
|
+
onClick: () => distribute('vertical'),
|
153
|
+
},
|
154
|
+
{
|
155
|
+
id: 'horizontal',
|
156
|
+
icon: (
|
157
|
+
<svg {...svgBaseProp}>
|
158
|
+
<rect fill="none" height="24" width="24" />
|
159
|
+
<path d="M4,22H2V2h2V22z M22,2h-2v20h2V2z M13.5,7h-3v10h3V7z" />
|
160
|
+
</svg>
|
161
|
+
),
|
162
|
+
onClick: () => distribute('horizontal'),
|
163
|
+
},
|
164
|
+
];
|
165
|
+
|
166
|
+
return (
|
167
|
+
<Form.Item label="Align">
|
168
|
+
<Button.Group>
|
169
|
+
{layoutBtns.map((btn) => (
|
170
|
+
<Button
|
171
|
+
key={btn.id}
|
172
|
+
style={{ padding: 7 }}
|
173
|
+
disabled={activeElements.length <= 2 && ['vertical', 'horizontal'].includes(btn.id)}
|
174
|
+
{...btn}
|
175
|
+
/>
|
176
|
+
))}
|
177
|
+
</Button.Group>
|
178
|
+
</Form.Item>
|
179
|
+
);
|
180
|
+
};
|
181
|
+
|
182
|
+
export default AlignWidget;
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
2
|
+
import type { PropPanelWidgetProps } from '@pdfme/common';
|
3
|
+
|
4
|
+
type Props = PropPanelWidgetProps & {
|
5
|
+
widget: (props: PropPanelWidgetProps) => void;
|
6
|
+
};
|
7
|
+
|
8
|
+
const WidgetRenderer = (props: Props) => {
|
9
|
+
const { widget, ...otherProps } = props;
|
10
|
+
const ref = useRef<HTMLDivElement>(null);
|
11
|
+
|
12
|
+
useEffect(() => {
|
13
|
+
if (ref.current) {
|
14
|
+
ref.current.innerHTML = '';
|
15
|
+
widget({ ...otherProps, rootElement: ref.current });
|
16
|
+
}
|
17
|
+
|
18
|
+
return () => {
|
19
|
+
if (ref.current) {
|
20
|
+
ref.current.innerHTML = '';
|
21
|
+
}
|
22
|
+
};
|
23
|
+
}, [props.activeSchema]);
|
24
|
+
|
25
|
+
return <div ref={ref} />;
|
26
|
+
};
|
27
|
+
|
28
|
+
export default WidgetRenderer;
|
@@ -1,41 +1,172 @@
|
|
1
|
-
import
|
2
|
-
import {
|
3
|
-
import {
|
1
|
+
import FormRender, { useForm } from 'form-render';
|
2
|
+
import React, { useContext, useEffect, useState } from 'react';
|
3
|
+
import type { SchemaForUI, PropPanelWidgetProps, PropPanelSchema } from '@pdfme/common';
|
4
|
+
import type { SidebarProps } from '../../../../types';
|
5
|
+
import { Bars3Icon } from '@heroicons/react/20/solid';
|
6
|
+
import { I18nContext, PropPanelRegistry, OptionsContext } from '../../../../contexts';
|
7
|
+
import { RULER_HEIGHT } from '../../../../constants';
|
4
8
|
import Divider from '../../../Divider';
|
5
|
-
import
|
6
|
-
import
|
7
|
-
import ExampleInputEditor from './ExampleInputEditor';
|
8
|
-
import PositionAndSizeEditor from './PositionAndSizeEditor';
|
9
|
-
import TypeAndKeyEditor from './TypeAndKeyEditor';
|
9
|
+
import AlignWidget from './AlignWidget';
|
10
|
+
import WidgetRenderer from './WidgetRenderer';
|
10
11
|
|
11
12
|
const DetailView = (
|
12
|
-
props: Pick<
|
13
|
+
props: Pick<
|
14
|
+
SidebarProps,
|
15
|
+
'size' | 'schemas' | 'pageSize' | 'changeSchemas' | 'activeElements' | 'deselectSchema'
|
16
|
+
> & {
|
13
17
|
activeSchema: SchemaForUI;
|
14
18
|
}
|
15
19
|
) => {
|
16
|
-
const { activeSchema } = props;
|
20
|
+
const { size, changeSchemas, deselectSchema, activeSchema, activeElements } = props;
|
21
|
+
const form = useForm();
|
22
|
+
|
17
23
|
const i18n = useContext(I18nContext);
|
24
|
+
const propPanelRegistry = useContext(PropPanelRegistry);
|
25
|
+
const options = useContext(OptionsContext);
|
26
|
+
|
27
|
+
const [widgets, setWidgets] = useState<{
|
28
|
+
[key: string]: (props: PropPanelWidgetProps) => React.JSX.Element;
|
29
|
+
}>({});
|
30
|
+
|
31
|
+
useEffect(() => {
|
32
|
+
const newWidgets: typeof widgets = {
|
33
|
+
AlignWidget: (p) => <AlignWidget {...p} {...props} options={options} />,
|
34
|
+
Divider,
|
35
|
+
};
|
36
|
+
for (const propPanel of Object.values(propPanelRegistry)) {
|
37
|
+
const widgets = propPanel?.widgets || {};
|
38
|
+
Object.entries(widgets).forEach(([widgetKey, widgetValue]) => {
|
39
|
+
newWidgets[widgetKey] = (p) => (
|
40
|
+
<WidgetRenderer {...p} {...props} options={options} widget={widgetValue} />
|
41
|
+
);
|
42
|
+
});
|
43
|
+
}
|
44
|
+
setWidgets(newWidgets);
|
45
|
+
}, [activeSchema, activeElements, propPanelRegistry]);
|
46
|
+
|
47
|
+
useEffect(() => {
|
48
|
+
form.setValues({ ...activeSchema });
|
49
|
+
}, [activeSchema]);
|
50
|
+
|
51
|
+
const handleWatch = (newSchema: any) => {
|
52
|
+
const changes = [];
|
53
|
+
for (const key in newSchema) {
|
54
|
+
if (['id', 'data'].includes(key)) continue;
|
55
|
+
if (newSchema[key] !== (activeSchema as any)[key]) {
|
56
|
+
changes.push({ key, value: newSchema[key], schemaId: activeSchema.id });
|
57
|
+
}
|
58
|
+
}
|
59
|
+
if (changes.length) {
|
60
|
+
changeSchemas(changes);
|
61
|
+
}
|
62
|
+
};
|
63
|
+
|
64
|
+
const activePropPanelRegistry = propPanelRegistry[activeSchema.type];
|
65
|
+
const activePropPanelSchema = activePropPanelRegistry?.propPanelSchema;
|
66
|
+
if (!activePropPanelSchema) {
|
67
|
+
console.error(`[@pdfme/ui] No propPanel.propPanelSchema for ${activeSchema.type}.
|
68
|
+
Check this document: https://pdfme.com/docs/custom-schemas`);
|
69
|
+
}
|
70
|
+
|
71
|
+
const propPanelSchema: PropPanelSchema = {
|
72
|
+
type: 'object',
|
73
|
+
column: 2,
|
74
|
+
properties: {
|
75
|
+
type: {
|
76
|
+
title: 'Type',
|
77
|
+
type: 'string',
|
78
|
+
widget: 'select',
|
79
|
+
props: {
|
80
|
+
options: Object.keys(propPanelRegistry).map((label) => ({ label, value: label })),
|
81
|
+
},
|
82
|
+
},
|
83
|
+
key: { title: 'Name', type: 'string', widget: 'input' },
|
84
|
+
'-': { type: 'void', widget: 'Divider', cellSpan: 2 },
|
85
|
+
align: { title: 'Align', type: 'void', widget: 'AlignWidget', cellSpan: 2 },
|
86
|
+
position: {
|
87
|
+
type: 'object',
|
88
|
+
widget: 'card',
|
89
|
+
properties: {
|
90
|
+
x: { title: 'X', type: 'number', widget: 'inputNumber' },
|
91
|
+
y: { title: 'Y', type: 'number', widget: 'inputNumber' },
|
92
|
+
},
|
93
|
+
},
|
94
|
+
width: { title: 'Width', type: 'number', widget: 'inputNumber', span: 8 },
|
95
|
+
height: { title: 'Height', type: 'number', widget: 'inputNumber', span: 8 },
|
96
|
+
rotate: {
|
97
|
+
title: 'Rotate',
|
98
|
+
type: 'number',
|
99
|
+
widget: 'inputNumber',
|
100
|
+
span: 8,
|
101
|
+
disabled: activePropPanelRegistry?.defaultSchema?.rotate === undefined,
|
102
|
+
max: 360,
|
103
|
+
min: 0,
|
104
|
+
},
|
105
|
+
},
|
106
|
+
};
|
107
|
+
|
108
|
+
if (typeof activePropPanelSchema === 'function') {
|
109
|
+
const apps = activePropPanelSchema({ ...props, options }) || {};
|
110
|
+
propPanelSchema.properties = {
|
111
|
+
...propPanelSchema.properties,
|
112
|
+
...(Object.keys(apps).length === 0
|
113
|
+
? {}
|
114
|
+
: { '--': { type: 'void', widget: 'Divider', cellSpan: 2 } }),
|
115
|
+
...apps,
|
116
|
+
};
|
117
|
+
} else {
|
118
|
+
const apps = activePropPanelSchema || {};
|
119
|
+
propPanelSchema.properties = {
|
120
|
+
...propPanelSchema.properties,
|
121
|
+
...(Object.keys(apps).length === 0
|
122
|
+
? {}
|
123
|
+
: { '--': { type: 'void', widget: 'Divider', cellSpan: 2 } }),
|
124
|
+
...apps,
|
125
|
+
};
|
126
|
+
}
|
18
127
|
|
19
128
|
return (
|
20
129
|
<div>
|
21
130
|
<div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
|
131
|
+
<span
|
132
|
+
style={{
|
133
|
+
position: 'absolute',
|
134
|
+
top: '0.75rem',
|
135
|
+
zIndex: 100,
|
136
|
+
border: 'none',
|
137
|
+
borderRadius: 2,
|
138
|
+
padding: '0.5rem',
|
139
|
+
cursor: 'pointer',
|
140
|
+
background: '#eee',
|
141
|
+
display: 'flex',
|
142
|
+
alignItems: 'center',
|
143
|
+
justifyContent: 'center',
|
144
|
+
maxWidth: 30,
|
145
|
+
maxHeight: 30,
|
146
|
+
}}
|
147
|
+
onClick={deselectSchema}
|
148
|
+
>
|
149
|
+
<Bars3Icon width={15} height={15} />
|
150
|
+
</span>
|
22
151
|
<span style={{ textAlign: 'center', width: '100%', fontWeight: 'bold' }}>
|
23
152
|
{i18n('editField')}
|
24
153
|
</span>
|
25
154
|
</div>
|
26
155
|
<Divider />
|
27
|
-
<div
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
156
|
+
<div
|
157
|
+
style={{
|
158
|
+
height: size.height - RULER_HEIGHT - RULER_HEIGHT / 2 - 145,
|
159
|
+
overflowY: 'auto',
|
160
|
+
overflowX: 'hidden',
|
161
|
+
}}
|
162
|
+
>
|
163
|
+
<FormRender
|
164
|
+
form={form}
|
165
|
+
schema={propPanelSchema}
|
166
|
+
widgets={widgets}
|
167
|
+
watch={{ '#': handleWatch }}
|
168
|
+
locale="en-US"
|
169
|
+
/>
|
39
170
|
</div>
|
40
171
|
</div>
|
41
172
|
);
|
@@ -72,7 +72,7 @@ const Item = React.memo(
|
|
72
72
|
{...listeners}
|
73
73
|
style={{ padding: '0.5rem', background: 'none', border: 'none', display: 'flex' }}
|
74
74
|
>
|
75
|
-
<object style={{ cursor: 'grab' }} width={15}>
|
75
|
+
<object style={{ cursor: 'grab', marginTop: 6 }} width={15}>
|
76
76
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" width="16" height="16">
|
77
77
|
<path d="M10 13a1 1 0 100-2 1 1 0 000 2zm-4 0a1 1 0 100-2 1 1 0 000 2zm1-5a1 1 0 11-2 0 1 1 0 012 0zm3 1a1 1 0 100-2 1 1 0 000 2zm1-5a1 1 0 11-2 0 1 1 0 012 0zM6 5a1 1 0 100-2 1 1 0 000 2z"></path>
|
78
78
|
</svg>
|
@@ -16,9 +16,9 @@ import {
|
|
16
16
|
verticalListSortingStrategy,
|
17
17
|
} from '@dnd-kit/sortable';
|
18
18
|
import { SchemaForUI } from '@pdfme/common';
|
19
|
+
import type { SidebarProps } from '../../../../types';
|
19
20
|
import Item from './Item';
|
20
21
|
import SelectableSortableItem from './SelectableSortableItem';
|
21
|
-
import { SidebarProps } from '../index';
|
22
22
|
|
23
23
|
const SelectableSortableContainer = (
|
24
24
|
props: Pick<
|
@@ -58,16 +58,16 @@ const SelectableSortableContainer = (
|
|
58
58
|
sensors={sensors}
|
59
59
|
collisionDetection={closestCorners}
|
60
60
|
onDragStart={({ active }) => {
|
61
|
-
setActiveId(active.id);
|
61
|
+
setActiveId(String(active.id));
|
62
62
|
setClonedItems(schemas);
|
63
63
|
|
64
|
-
if (!isItemSelected(active.id)) {
|
64
|
+
if (!isItemSelected(String(active.id))) {
|
65
65
|
const newSelectedSchemas: SchemaForUI[] = [];
|
66
66
|
setSelectedSchemas(newSelectedSchemas);
|
67
67
|
} else if (selectedSchemas.length > 0) {
|
68
68
|
onSortEnd(
|
69
69
|
selectedSchemas.reduce((ret, selectedItem) => {
|
70
|
-
if (selectedItem.id === active.id) {
|
70
|
+
if (selectedItem.id === String(active.id)) {
|
71
71
|
return ret;
|
72
72
|
}
|
73
73
|
return ret.filter((schema) => schema !== selectedItem);
|
@@ -78,8 +78,8 @@ const SelectableSortableContainer = (
|
|
78
78
|
onDragEnd={({ active, over }) => {
|
79
79
|
const overId = over?.id || '';
|
80
80
|
|
81
|
-
const activeIndex = schemas.map((i) => i.id).indexOf(active.id);
|
82
|
-
const overIndex = schemas.map((i) => i.id).indexOf(overId);
|
81
|
+
const activeIndex = schemas.map((i) => i.id).indexOf(String(active.id));
|
82
|
+
const overIndex = schemas.map((i) => i.id).indexOf(String(overId));
|
83
83
|
|
84
84
|
if (selectedSchemas.length) {
|
85
85
|
let newSchemas = [...schemas];
|