@pdfme/ui 4.2.4 → 4.2.5-dev.3
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/dist/index.es.js +3524 -3427
- package/dist/index.umd.js +96 -95
- package/dist/types/class.d.ts +3 -0
- package/dist/types/components/Designer/PluginIcon.d.ts +10 -0
- package/dist/types/components/Designer/RightSidebar/ListView/Item.d.ts +2 -0
- package/dist/types/contexts.d.ts +4 -2
- package/dist/types/helper.d.ts +1 -0
- package/package.json +1 -1
- package/src/Designer.tsx +1 -0
- package/src/components/Designer/LeftSidebar.tsx +6 -14
- package/src/components/Designer/PluginIcon.tsx +47 -0
- package/src/components/Designer/RightSidebar/DetailView/ButtonGroupWidget.tsx +1 -1
- package/src/components/Designer/RightSidebar/DetailView/index.tsx +66 -33
- package/src/components/Designer/RightSidebar/ListView/Item.tsx +7 -1
- package/src/components/Designer/RightSidebar/ListView/SelectableSortableContainer.tsx +25 -5
- package/src/components/Designer/RightSidebar/ListView/SelectableSortableItem.tsx +11 -1
- package/src/components/Designer/index.tsx +13 -2
- package/src/components/Renderer.tsx +10 -0
- package/src/helper.ts +4 -1
- package/src/i18n.ts +33 -11
package/dist/types/class.d.ts
CHANGED
@@ -37,6 +37,7 @@ export declare abstract class BaseUIClass {
|
|
37
37
|
rotate: import("zod").ZodOptional<import("zod").ZodNumber>;
|
38
38
|
opacity: import("zod").ZodOptional<import("zod").ZodNumber>;
|
39
39
|
readOnly: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
40
|
+
required: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
40
41
|
}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
|
41
42
|
type: import("zod").ZodString;
|
42
43
|
content: import("zod").ZodOptional<import("zod").ZodString>;
|
@@ -55,6 +56,7 @@ export declare abstract class BaseUIClass {
|
|
55
56
|
rotate: import("zod").ZodOptional<import("zod").ZodNumber>;
|
56
57
|
opacity: import("zod").ZodOptional<import("zod").ZodNumber>;
|
57
58
|
readOnly: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
59
|
+
required: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
58
60
|
}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
|
59
61
|
type: import("zod").ZodString;
|
60
62
|
content: import("zod").ZodOptional<import("zod").ZodString>;
|
@@ -73,6 +75,7 @@ export declare abstract class BaseUIClass {
|
|
73
75
|
rotate: import("zod").ZodOptional<import("zod").ZodNumber>;
|
74
76
|
opacity: import("zod").ZodOptional<import("zod").ZodNumber>;
|
75
77
|
readOnly: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
78
|
+
required: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
76
79
|
}, import("zod").ZodTypeAny, "passthrough">>>, "many">;
|
77
80
|
basePdf: import("zod").ZodUnion<[import("zod").ZodUnion<[import("zod").ZodString, import("zod").ZodType<ArrayBuffer, import("zod").ZodTypeDef, ArrayBuffer>, import("zod").ZodType<Uint8Array, import("zod").ZodTypeDef, Uint8Array>]>, import("zod").ZodObject<{
|
78
81
|
width: import("zod").ZodNumber;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Plugin } from "@pdfme/common";
|
3
|
+
interface PluginIconProps {
|
4
|
+
plugin: Plugin<any>;
|
5
|
+
label: string;
|
6
|
+
size?: number;
|
7
|
+
styles?: React.CSSProperties;
|
8
|
+
}
|
9
|
+
declare const PluginIcon: (props: PluginIconProps) => React.JSX.Element;
|
10
|
+
export default PluginIcon;
|
@@ -2,9 +2,11 @@ import React from 'react';
|
|
2
2
|
import { DraggableSyntheticListeners } from '@dnd-kit/core';
|
3
3
|
interface Props {
|
4
4
|
value: React.ReactNode;
|
5
|
+
icon?: React.ReactNode;
|
5
6
|
style?: React.CSSProperties;
|
6
7
|
status?: 'is-warning' | 'is-danger';
|
7
8
|
title?: string;
|
9
|
+
required?: boolean;
|
8
10
|
dragOverlay?: boolean;
|
9
11
|
onClick?: () => void;
|
10
12
|
onMouseEnter?: () => void;
|
package/dist/types/contexts.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/// <reference types="react" />
|
2
2
|
import { Plugins, UIOptions } from '@pdfme/common';
|
3
|
-
export declare const I18nContext: import("react").Context<(key: "type" | "width" | "height" | "rotate" | "opacity" | "cancel" | "field" | "fieldName" | "align" | "edit" | "plsInputName" | "fieldMustUniq" | "notUniq" | "noKeyName" | "fieldsList" | "editField" | "errorOccurred" | "errorBulkUpdateFieldName" | "commitBulkUpdateFieldName" | "bulkUpdateFieldName" | "addPageAfter" | "removePage" | "removePageConfirm" | "
|
3
|
+
export declare const I18nContext: import("react").Context<(key: "type" | "width" | "height" | "rotate" | "opacity" | "required" | "cancel" | "field" | "fieldName" | "align" | "edit" | "plsInputName" | "fieldMustUniq" | "notUniq" | "noKeyName" | "fieldsList" | "editField" | "errorOccurred" | "errorBulkUpdateFieldName" | "commitBulkUpdateFieldName" | "bulkUpdateFieldName" | "addPageAfter" | "removePage" | "removePageConfirm" | "validation.uniqueName" | "validation.hexColor" | "schemas.color" | "schemas.borderWidth" | "schemas.borderColor" | "schemas.backgroundColor" | "schemas.textColor" | "schemas.bgColor" | "schemas.horizontal" | "schemas.vertical" | "schemas.left" | "schemas.center" | "schemas.right" | "schemas.top" | "schemas.middle" | "schemas.bottom" | "schemas.padding" | "schemas.text.fontName" | "schemas.text.size" | "schemas.text.spacing" | "schemas.text.textAlign" | "schemas.text.verticalAlign" | "schemas.text.lineHeight" | "schemas.text.min" | "schemas.text.max" | "schemas.text.fit" | "schemas.text.dynamicFontSize" | "schemas.text.format" | "schemas.mvt.typingInstructions" | "schemas.mvt.sampleField" | "schemas.mvt.variablesSampleData" | "schemas.barcodes.barColor" | "schemas.barcodes.includetext" | "schemas.table.alternateBackgroundColor" | "schemas.table.tableStyle" | "schemas.table.headStyle" | "schemas.table.bodyStyle" | "schemas.table.columnStyle", dict?: {
|
4
4
|
cancel: string;
|
5
5
|
field: string;
|
6
6
|
fieldName: string;
|
@@ -10,6 +10,7 @@ export declare const I18nContext: import("react").Context<(key: "type" | "width"
|
|
10
10
|
height: string;
|
11
11
|
rotate: string;
|
12
12
|
edit: string;
|
13
|
+
required: string;
|
13
14
|
plsInputName: string;
|
14
15
|
fieldMustUniq: string;
|
15
16
|
notUniq: string;
|
@@ -24,7 +25,8 @@ export declare const I18nContext: import("react").Context<(key: "type" | "width"
|
|
24
25
|
addPageAfter: string;
|
25
26
|
removePage: string;
|
26
27
|
removePageConfirm: string;
|
27
|
-
|
28
|
+
'validation.uniqueName': string;
|
29
|
+
'validation.hexColor': string;
|
28
30
|
'schemas.color': string;
|
29
31
|
'schemas.borderWidth': string;
|
30
32
|
'schemas.borderColor': string;
|
package/dist/types/helper.d.ts
CHANGED
@@ -36,6 +36,7 @@ export declare const template2SchemasList: (_template: Template) => Promise<{
|
|
36
36
|
key: string;
|
37
37
|
opacity?: number | undefined;
|
38
38
|
rotate?: number | undefined;
|
39
|
+
required?: boolean | undefined;
|
39
40
|
content?: string | undefined;
|
40
41
|
readOnly?: boolean | undefined;
|
41
42
|
}[][]>;
|
package/package.json
CHANGED
package/src/Designer.tsx
CHANGED
@@ -8,7 +8,8 @@ import { theme, Button } from 'antd';
|
|
8
8
|
import { useDraggable } from '@dnd-kit/core';
|
9
9
|
import { CSS } from "@dnd-kit/utilities";
|
10
10
|
import Renderer from '../Renderer';
|
11
|
-
import { PluginsRegistry
|
11
|
+
import { PluginsRegistry } from '../../contexts';
|
12
|
+
import PluginIcon from "./PluginIcon";
|
12
13
|
|
13
14
|
const Draggable = (props: { plugin: Plugin<any>, scale: number, basePdf: BasePdf, children: React.ReactNode }) => {
|
14
15
|
const { scale, basePdf, plugin } = props;
|
@@ -44,8 +45,6 @@ const Draggable = (props: { plugin: Plugin<any>, scale: number, basePdf: BasePdf
|
|
44
45
|
const LeftSidebar = ({ height, scale, basePdf }: { height: number, scale: number, basePdf: BasePdf }) => {
|
45
46
|
const { token } = theme.useToken();
|
46
47
|
const pluginsRegistry = useContext(PluginsRegistry);
|
47
|
-
const options = useContext(OptionsContext);
|
48
|
-
|
49
48
|
const [isDragging, setIsDragging] = useState(false);
|
50
49
|
|
51
50
|
useEffect(() => {
|
@@ -77,7 +76,6 @@ const LeftSidebar = ({ height, scale, basePdf }: { height: number, scale: number
|
|
77
76
|
>
|
78
77
|
{Object.entries(pluginsRegistry).map(([label, plugin]) => {
|
79
78
|
if (!plugin?.propPanel.defaultSchema) return null;
|
80
|
-
const icon = options.icons?.[plugin.propPanel.defaultSchema.type] ?? plugin.icon;
|
81
79
|
|
82
80
|
return <Draggable
|
83
81
|
key={label}
|
@@ -85,16 +83,10 @@ const LeftSidebar = ({ height, scale, basePdf }: { height: number, scale: number
|
|
85
83
|
basePdf={basePdf}
|
86
84
|
plugin={plugin}>
|
87
85
|
<Button
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
}}
|
92
|
-
style={{ width: 35, height: 35, marginTop: '0.25rem', padding: '0.25rem' }}>
|
93
|
-
{icon ?
|
94
|
-
<div dangerouslySetInnerHTML={{ __html: icon }} />
|
95
|
-
:
|
96
|
-
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>{label}</div>
|
97
|
-
}
|
86
|
+
onMouseDown={() => setIsDragging(true)}
|
87
|
+
style={{ width: 35, height: 35, marginTop: '0.25rem', padding: '0.25rem' }}
|
88
|
+
>
|
89
|
+
<PluginIcon plugin={plugin} label={label} />
|
98
90
|
</Button>
|
99
91
|
</Draggable>
|
100
92
|
})}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import React, { useContext } from 'react';
|
2
|
+
import { Plugin } from "@pdfme/common";
|
3
|
+
import { OptionsContext } from '../../contexts';
|
4
|
+
|
5
|
+
interface PluginIconProps {
|
6
|
+
plugin: Plugin<any>;
|
7
|
+
label: string;
|
8
|
+
size?: number;
|
9
|
+
styles?: React.CSSProperties;
|
10
|
+
}
|
11
|
+
|
12
|
+
const getWithModifiedSize = (htmlString: string, label: string, size: number, styles?: React.CSSProperties) => {
|
13
|
+
const parser = new DOMParser();
|
14
|
+
const doc = parser.parseFromString(htmlString, 'text/html');
|
15
|
+
|
16
|
+
const modifyNode = (node: HTMLElement) => {
|
17
|
+
if (node.tagName === 'SVG' || node.tagName === 'svg') {
|
18
|
+
node.setAttribute('width', size.toString());
|
19
|
+
node.setAttribute('height', size.toString());
|
20
|
+
}
|
21
|
+
Array.from(node.children).forEach(child => modifyNode(child as HTMLElement));
|
22
|
+
};
|
23
|
+
|
24
|
+
Array.from(doc.body.children).forEach(child => modifyNode(child as HTMLElement));
|
25
|
+
|
26
|
+
return (
|
27
|
+
<div style={styles} title={label} dangerouslySetInnerHTML={{ __html: doc.body.innerHTML }} />
|
28
|
+
);
|
29
|
+
};
|
30
|
+
|
31
|
+
const PluginIcon = (props: PluginIconProps) => {
|
32
|
+
const { plugin, label, size, styles } = props;
|
33
|
+
const options = useContext(OptionsContext);
|
34
|
+
const icon = options.icons?.[plugin.propPanel.defaultSchema.type] ?? plugin.icon;
|
35
|
+
const iconStyles = { ...styles, display: 'flex', justifyContent: 'center' };
|
36
|
+
|
37
|
+
if (icon) {
|
38
|
+
if (size) {
|
39
|
+
return getWithModifiedSize(icon, label, size, iconStyles);
|
40
|
+
}
|
41
|
+
return <div style={iconStyles} title={label} dangerouslySetInnerHTML={{__html: icon}} />
|
42
|
+
}
|
43
|
+
|
44
|
+
return <div style={{...styles, overflow: 'hidden', fontSize: 10, }}>{label}</div>
|
45
|
+
};
|
46
|
+
|
47
|
+
export default PluginIcon;
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import FormRender, { useForm } from 'form-render';
|
2
|
-
import React, { useContext, useEffect } from 'react';
|
2
|
+
import React, { useRef, useContext, useState, useEffect } from 'react';
|
3
3
|
import type { ChangeSchemaItem, Dict, SchemaForUI, PropPanelWidgetProps, PropPanelSchema } from '@pdfme/common';
|
4
4
|
import type { SidebarProps } from '../../../../types';
|
5
5
|
import { MenuOutlined } from '@ant-design/icons';
|
6
6
|
import { I18nContext, PluginsRegistry, OptionsContext } from '../../../../contexts';
|
7
|
-
import { getSidebarContentHeight } from '../../../../helper';
|
7
|
+
import { getSidebarContentHeight, debounce } from '../../../../helper';
|
8
8
|
import { theme, Typography, Button, Divider } from 'antd';
|
9
9
|
import AlignWidget from './AlignWidget';
|
10
10
|
import WidgetRenderer from './WidgetRenderer';
|
@@ -22,25 +22,72 @@ type DetailViewProps = Pick<SidebarProps,
|
|
22
22
|
const DetailView = (props: DetailViewProps) => {
|
23
23
|
const { token } = theme.useToken();
|
24
24
|
|
25
|
-
const { size, changeSchemas, deselectSchema, activeSchema } = props;
|
25
|
+
const { size, schemas, changeSchemas, deselectSchema, activeSchema } = props;
|
26
26
|
const form = useForm();
|
27
27
|
|
28
28
|
const i18n = useContext(I18nContext);
|
29
29
|
const pluginsRegistry = useContext(PluginsRegistry);
|
30
30
|
const options = useContext(OptionsContext);
|
31
31
|
|
32
|
+
const [widgets, setWidgets] = useState<{
|
33
|
+
[key: string]: (props: PropPanelWidgetProps) => React.JSX.Element;
|
34
|
+
}>({});
|
35
|
+
|
36
|
+
useEffect(() => {
|
37
|
+
const newWidgets: typeof widgets = {
|
38
|
+
AlignWidget: (p) => <AlignWidget {...p} {...props} options={options} />,
|
39
|
+
Divider: () => (
|
40
|
+
<Divider style={{ marginTop: token.marginXS, marginBottom: token.marginXS }} />
|
41
|
+
),
|
42
|
+
ButtonGroup: (p) => <ButtonGroupWidget {...p} {...props} options={options} />,
|
43
|
+
};
|
44
|
+
for (const plugin of Object.values(pluginsRegistry)) {
|
45
|
+
const widgets = plugin?.propPanel.widgets || {};
|
46
|
+
Object.entries(widgets).forEach(([widgetKey, widgetValue]) => {
|
47
|
+
newWidgets[widgetKey] = (p) => (
|
48
|
+
<WidgetRenderer
|
49
|
+
{...p}
|
50
|
+
{...props}
|
51
|
+
options={options}
|
52
|
+
theme={token}
|
53
|
+
i18n={i18n as (key: keyof Dict | string) => string}
|
54
|
+
widget={widgetValue}
|
55
|
+
/>
|
56
|
+
);
|
57
|
+
});
|
58
|
+
}
|
59
|
+
setWidgets(newWidgets);
|
60
|
+
}, [activeSchema, pluginsRegistry, JSON.stringify(options)]);
|
61
|
+
|
32
62
|
useEffect(() => {
|
33
63
|
const values: any = { ...activeSchema };
|
34
64
|
// [position] Change the nested position object into a flat, as a three-column layout is difficult to implement
|
35
65
|
values.x = values.position.x;
|
36
66
|
values.y = values.position.y;
|
37
67
|
delete values.position;
|
68
|
+
|
38
69
|
form.setValues(values);
|
39
70
|
|
40
71
|
}, [activeSchema, form]);
|
41
72
|
|
73
|
+
useEffect(() => form.resetFields(), [activeSchema.id])
|
74
|
+
|
75
|
+
useEffect(() => {
|
76
|
+
uniqueSchemaKey.current = (value: string): boolean => {
|
77
|
+
for (const s of Object.values(schemas)) {
|
78
|
+
if (s.key === value && s.id !== activeSchema.id) {
|
79
|
+
return false;
|
80
|
+
}
|
81
|
+
}
|
82
|
+
return true;
|
83
|
+
};
|
84
|
+
}, [schemas, activeSchema]);
|
85
|
+
|
86
|
+
const uniqueSchemaKey = useRef((value: string): boolean => true);
|
42
87
|
|
43
|
-
const
|
88
|
+
const validateUniqueSchemaKey = (_: any, value: string): boolean => uniqueSchemaKey.current(value)
|
89
|
+
|
90
|
+
const handleWatch = debounce((formSchema: any) => {
|
44
91
|
const formAndSchemaValuesDiffer = (formValue: any, schemaValue: any): boolean => {
|
45
92
|
if (typeof formValue === 'object') {
|
46
93
|
return JSON.stringify(formValue) !== JSON.stringify(schemaValue);
|
@@ -88,7 +135,7 @@ const DetailView = (props: DetailViewProps) => {
|
|
88
135
|
}
|
89
136
|
});
|
90
137
|
}
|
91
|
-
};
|
138
|
+
}, 500);
|
92
139
|
|
93
140
|
const activePlugin = Object.values(pluginsRegistry).find(
|
94
141
|
(plugin) => plugin?.propPanel.defaultSchema.type === activeSchema.type
|
@@ -118,7 +165,18 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
|
|
118
165
|
required: true,
|
119
166
|
span: 12,
|
120
167
|
},
|
121
|
-
key: {
|
168
|
+
key: {
|
169
|
+
title: i18n('fieldName'),
|
170
|
+
type: 'string',
|
171
|
+
required: true,
|
172
|
+
span: 12,
|
173
|
+
rules: [{
|
174
|
+
validator: validateUniqueSchemaKey,
|
175
|
+
message: i18n('validation.uniqueName'),
|
176
|
+
}],
|
177
|
+
props: { autoComplete: "off" }
|
178
|
+
},
|
179
|
+
required: { title: i18n('required'), type: 'boolean', span: 8, hidden: defaultSchema?.readOnly },
|
122
180
|
'-': { type: 'void', widget: 'Divider' },
|
123
181
|
align: { title: i18n('align'), type: 'void', widget: 'AlignWidget' },
|
124
182
|
x: { title: 'X', type: 'number', widget: 'inputNumber', required: true, span: 8, min: 0 },
|
@@ -181,31 +239,6 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
|
|
181
239
|
};
|
182
240
|
}
|
183
241
|
|
184
|
-
const allWidgets: {
|
185
|
-
[key: string]: (props: PropPanelWidgetProps) => React.JSX.Element;
|
186
|
-
} = {
|
187
|
-
AlignWidget: (p) => <AlignWidget {...p} {...props} options={options} />,
|
188
|
-
Divider: () => (
|
189
|
-
<Divider style={{ marginTop: token.marginXS, marginBottom: token.marginXS }} />
|
190
|
-
),
|
191
|
-
ButtonGroup: (p) => <ButtonGroupWidget {...p} {...props} options={options} />,
|
192
|
-
};
|
193
|
-
for (const plugin of Object.values(pluginsRegistry)) {
|
194
|
-
const widgets = plugin?.propPanel.widgets || {};
|
195
|
-
Object.entries(widgets).forEach(([widgetKey, widgetValue]) => {
|
196
|
-
allWidgets[widgetKey] = (p) => (
|
197
|
-
<WidgetRenderer
|
198
|
-
{...p}
|
199
|
-
{...props}
|
200
|
-
options={options}
|
201
|
-
theme={token}
|
202
|
-
i18n={i18n as (key: keyof Dict | string) => string}
|
203
|
-
widget={widgetValue}
|
204
|
-
/>
|
205
|
-
);
|
206
|
-
});
|
207
|
-
}
|
208
|
-
|
209
242
|
return (
|
210
243
|
<div>
|
211
244
|
<div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
|
@@ -235,7 +268,7 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
|
|
235
268
|
<FormRender
|
236
269
|
form={form}
|
237
270
|
schema={propPanelSchema}
|
238
|
-
widgets={
|
271
|
+
widgets={widgets}
|
239
272
|
watch={{ '#': handleWatch }}
|
240
273
|
locale="en-US"
|
241
274
|
/>
|
@@ -245,7 +278,7 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
|
|
245
278
|
};
|
246
279
|
|
247
280
|
const propsAreUnchanged = (prevProps: DetailViewProps, nextProps: DetailViewProps) => {
|
248
|
-
return JSON.stringify(prevProps.activeSchema) == JSON.stringify(nextProps.activeSchema)
|
281
|
+
return JSON.stringify(prevProps.activeSchema) == JSON.stringify(nextProps.activeSchema);
|
249
282
|
};
|
250
283
|
|
251
284
|
export default React.memo(DetailView, propsAreUnchanged);
|
@@ -8,9 +8,11 @@ const { Text } = Typography;
|
|
8
8
|
|
9
9
|
interface Props {
|
10
10
|
value: React.ReactNode;
|
11
|
+
icon?: React.ReactNode;
|
11
12
|
style?: React.CSSProperties;
|
12
13
|
status?: 'is-warning' | 'is-danger';
|
13
14
|
title?: string;
|
15
|
+
required?: boolean;
|
14
16
|
dragOverlay?: boolean;
|
15
17
|
onClick?: () => void;
|
16
18
|
onMouseEnter?: () => void;
|
@@ -26,9 +28,11 @@ const Item = React.memo(
|
|
26
28
|
React.forwardRef<HTMLLIElement, Props>(
|
27
29
|
(
|
28
30
|
{
|
31
|
+
icon,
|
29
32
|
value,
|
30
33
|
status,
|
31
34
|
title,
|
35
|
+
required,
|
32
36
|
style,
|
33
37
|
dragOverlay,
|
34
38
|
onClick,
|
@@ -93,6 +97,7 @@ const Item = React.memo(
|
|
93
97
|
}}
|
94
98
|
icon={<HolderOutlined style={{ cursor: 'grab' }} />}
|
95
99
|
/>
|
100
|
+
{icon}
|
96
101
|
<Text
|
97
102
|
style={{
|
98
103
|
overflow: 'hidden',
|
@@ -105,12 +110,13 @@ const Item = React.memo(
|
|
105
110
|
{status === undefined ? (
|
106
111
|
value
|
107
112
|
) : (
|
108
|
-
<span
|
113
|
+
<span>
|
109
114
|
<ExclamationCircleOutlined width={15} style={{ marginRight: '0.5rem' }} />
|
110
115
|
{status === 'is-warning' ? i18n('noKeyName') : value}
|
111
116
|
{status === 'is-danger' ? i18n('notUniq') : ''}
|
112
117
|
</span>
|
113
118
|
)}
|
119
|
+
{required && <span style={{ color: 'red', marginLeft: '0.5rem' }}>*</span>}
|
114
120
|
</Text>
|
115
121
|
</div>
|
116
122
|
</li>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { useState } from 'react';
|
1
|
+
import React, { useState, useContext, ReactNode } from 'react';
|
2
2
|
import { createPortal } from 'react-dom';
|
3
3
|
import {
|
4
4
|
closestCorners,
|
@@ -17,9 +17,11 @@ import {
|
|
17
17
|
} from '@dnd-kit/sortable';
|
18
18
|
import { SchemaForUI } from '@pdfme/common';
|
19
19
|
import type { SidebarProps } from '../../../../types';
|
20
|
+
import { PluginsRegistry } from '../../../../contexts';
|
20
21
|
import Item from './Item';
|
21
22
|
import SelectableSortableItem from './SelectableSortableItem';
|
22
23
|
import { theme } from 'antd';
|
24
|
+
import PluginIcon from "../../PluginIcon";
|
23
25
|
|
24
26
|
const SelectableSortableContainer = (
|
25
27
|
props: Pick<
|
@@ -28,11 +30,11 @@ const SelectableSortableContainer = (
|
|
28
30
|
>
|
29
31
|
) => {
|
30
32
|
const { token } = theme.useToken();
|
31
|
-
|
32
33
|
const { schemas, onEdit, onSortEnd, hoveringSchemaId, onChangeHoveringSchemaId } = props;
|
33
34
|
const [selectedSchemas, setSelectedSchemas] = useState<SchemaForUI[]>([]);
|
34
|
-
const [
|
35
|
+
const [dragOverlaidItems, setClonedItems] = useState<SchemaForUI[] | null>(null);
|
35
36
|
const [activeId, setActiveId] = useState<string | null>(null);
|
37
|
+
const pluginsRegistry = useContext(PluginsRegistry);
|
36
38
|
const sensors = useSensors(
|
37
39
|
useSensor(PointerSensor, { activationConstraint: { distance: 15 } }),
|
38
40
|
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
@@ -56,6 +58,20 @@ const SelectableSortableContainer = (
|
|
56
58
|
}
|
57
59
|
};
|
58
60
|
|
61
|
+
const getPluginIcon = (inSchema: string|SchemaForUI): ReactNode => {
|
62
|
+
const thisSchema = (typeof inSchema === 'string') ? schemas.find((schema) => schema.id === inSchema) : inSchema;
|
63
|
+
|
64
|
+
const [pluginLabel, activePlugin] = Object.entries(pluginsRegistry).find(
|
65
|
+
([label, plugin]) => plugin?.propPanel.defaultSchema.type === thisSchema?.type
|
66
|
+
)!;
|
67
|
+
|
68
|
+
if (!activePlugin) {
|
69
|
+
return <></>
|
70
|
+
}
|
71
|
+
|
72
|
+
return <PluginIcon plugin={activePlugin} label={pluginLabel} size={20} styles={{marginRight: '0.5rem'}}/>
|
73
|
+
};
|
74
|
+
|
59
75
|
return (
|
60
76
|
<DndContext
|
61
77
|
sensors={sensors}
|
@@ -101,8 +117,8 @@ const SelectableSortableContainer = (
|
|
101
117
|
setActiveId(null);
|
102
118
|
}}
|
103
119
|
onDragCancel={() => {
|
104
|
-
if (
|
105
|
-
onSortEnd(
|
120
|
+
if (dragOverlaidItems) {
|
121
|
+
onSortEnd(dragOverlaidItems);
|
106
122
|
}
|
107
123
|
|
108
124
|
setActiveId(null);
|
@@ -139,7 +155,9 @@ const SelectableSortableContainer = (
|
|
139
155
|
<>
|
140
156
|
<ul style={{ margin: 0, padding: 0, listStyle: 'none' }}>
|
141
157
|
<Item
|
158
|
+
icon={getPluginIcon(activeId)}
|
142
159
|
value={schemas.find((schema) => schema.id === activeId)!.key}
|
160
|
+
required={schemas.find((schema) => schema.id === activeId)!.required}
|
143
161
|
style={{ background: token.colorPrimary }}
|
144
162
|
dragOverlay
|
145
163
|
/>
|
@@ -149,8 +167,10 @@ const SelectableSortableContainer = (
|
|
149
167
|
.filter((item) => item.id !== activeId)
|
150
168
|
.map((item) => (
|
151
169
|
<Item
|
170
|
+
icon={getPluginIcon(item)}
|
152
171
|
key={item.id}
|
153
172
|
value={item.key}
|
173
|
+
required={item.required}
|
154
174
|
style={{ background: token.colorPrimary }}
|
155
175
|
dragOverlay
|
156
176
|
/>
|
@@ -1,10 +1,11 @@
|
|
1
1
|
import React, { useContext } from 'react';
|
2
2
|
import { useSortable } from '@dnd-kit/sortable';
|
3
3
|
import { SchemaForUI } from '@pdfme/common';
|
4
|
-
import { I18nContext } from '../../../../contexts';
|
4
|
+
import { PluginsRegistry, I18nContext } from '../../../../contexts';
|
5
5
|
import Item from './Item';
|
6
6
|
import { useMountStatus } from '../../../../hooks';
|
7
7
|
import { theme } from 'antd';
|
8
|
+
import PluginIcon from "../../PluginIcon";
|
8
9
|
|
9
10
|
interface Props {
|
10
11
|
isSelected: boolean;
|
@@ -29,6 +30,7 @@ const SelectableSortableItem = ({
|
|
29
30
|
const { token } = theme.useToken();
|
30
31
|
|
31
32
|
const i18n = useContext(I18nContext);
|
33
|
+
const pluginsRegistry = useContext(PluginsRegistry);
|
32
34
|
const { setNodeRef, listeners, isDragging, isSorting, transform, transition } = useSortable({
|
33
35
|
id: schema.id,
|
34
36
|
});
|
@@ -40,6 +42,12 @@ const SelectableSortableItem = ({
|
|
40
42
|
onClick: (event: any) => onSelect(schema.id, event.shiftKey),
|
41
43
|
};
|
42
44
|
|
45
|
+
const [pluginLabel, thisPlugin] = Object.entries(pluginsRegistry).find(
|
46
|
+
([label, plugin]) => plugin?.propPanel.defaultSchema.type === schema.type
|
47
|
+
)!;
|
48
|
+
|
49
|
+
const iconStyles = { width: 20, marginRight: '0.5rem' };
|
50
|
+
|
43
51
|
let status: undefined | 'is-warning' | 'is-danger';
|
44
52
|
if (!schema.key) {
|
45
53
|
status = 'is-warning';
|
@@ -64,9 +72,11 @@ const SelectableSortableItem = ({
|
|
64
72
|
onMouseEnter={onMouseEnter}
|
65
73
|
onMouseLeave={onMouseLeave}
|
66
74
|
onClick={() => onEdit(schema.id)}
|
75
|
+
icon={thisPlugin && <PluginIcon plugin={thisPlugin} label={pluginLabel} size={20} styles={iconStyles}/>}
|
67
76
|
value={schema.key}
|
68
77
|
status={status}
|
69
78
|
title={title}
|
79
|
+
required={schema.required}
|
70
80
|
style={{ ...selectedStyle, ...style }}
|
71
81
|
dragging={isDragging}
|
72
82
|
sorting={isSorting}
|
@@ -15,7 +15,7 @@ import RightSidebar from './RightSidebar/index';
|
|
15
15
|
import LeftSidebar from './LeftSidebar';
|
16
16
|
import Canvas from './Canvas/index';
|
17
17
|
import { RULER_HEIGHT, RIGHT_SIDEBAR_WIDTH } from '../../constants';
|
18
|
-
import { I18nContext, PluginsRegistry } from '../../contexts';
|
18
|
+
import { I18nContext, OptionsContext, PluginsRegistry } from '../../contexts';
|
19
19
|
import {
|
20
20
|
schemasList2template,
|
21
21
|
uuid,
|
@@ -62,6 +62,7 @@ const TemplateEditor = ({
|
|
62
62
|
|
63
63
|
const i18n = useContext(I18nContext);
|
64
64
|
const pluginsRegistry = useContext(PluginsRegistry);
|
65
|
+
const options = useContext(OptionsContext);
|
65
66
|
|
66
67
|
const [hoveringSchemaId, setHoveringSchemaId] = useState<string | null>(null);
|
67
68
|
const [activeElements, setActiveElements] = useState<HTMLElement[]>([]);
|
@@ -161,16 +162,26 @@ const TemplateEditor = ({
|
|
161
162
|
const [paddingTop, paddingRight, paddingBottom, paddingLeft] = isBlankPdf(template.basePdf) ? template.basePdf.padding : [0, 0, 0, 0];
|
162
163
|
const pageSize = pageSizes[pageCursor];
|
163
164
|
|
165
|
+
const newSchemaKey = (prefix: string) => {
|
166
|
+
let keyNum = schemasList[pageCursor].length + 1;
|
167
|
+
let newKey = prefix + keyNum;
|
168
|
+
while (schemasList[pageCursor].find((s) => s.key === newKey)) {
|
169
|
+
keyNum++;
|
170
|
+
newKey = prefix + keyNum;
|
171
|
+
}
|
172
|
+
return newKey;
|
173
|
+
};
|
164
174
|
const ensureMiddleValue = (min: number, value: number, max: number) => Math.min(Math.max(min, value), max)
|
165
175
|
|
166
176
|
const s = {
|
167
177
|
id: uuid(),
|
168
|
-
key:
|
178
|
+
key: newSchemaKey(i18n('field')),
|
169
179
|
...defaultSchema,
|
170
180
|
position: {
|
171
181
|
x: ensureMiddleValue(paddingLeft, defaultSchema.position.x, pageSize.width - paddingRight - defaultSchema.width),
|
172
182
|
y: ensureMiddleValue(paddingTop, defaultSchema.position.y, pageSize.height - paddingBottom - defaultSchema.height),
|
173
183
|
},
|
184
|
+
required: defaultSchema.readOnly ? false : options.requiredByDefault || defaultSchema.required || false,
|
174
185
|
} as SchemaForUI;
|
175
186
|
|
176
187
|
if (defaultSchema.position.y === 0) {
|
package/src/helper.ts
CHANGED
@@ -448,7 +448,7 @@ const handleTypeChange = (
|
|
448
448
|
pluginsRegistry: Plugins
|
449
449
|
) => {
|
450
450
|
if (key !== 'type') return;
|
451
|
-
const keysToKeep = ['id', 'key', 'type', 'position'];
|
451
|
+
const keysToKeep = ['id', 'key', 'type', 'position', 'required'];
|
452
452
|
Object.keys(schema).forEach((key) => {
|
453
453
|
if (!keysToKeep.includes(key)) {
|
454
454
|
delete schema[key as keyof typeof schema];
|
@@ -463,6 +463,9 @@ const handleTypeChange = (
|
|
463
463
|
(schema as any)[key] = propPanel?.defaultSchema[key];
|
464
464
|
}
|
465
465
|
});
|
466
|
+
if (schema.readOnly) {
|
467
|
+
schema.required = false;
|
468
|
+
}
|
466
469
|
};
|
467
470
|
|
468
471
|
export const changeSchemas = (args: {
|