@pdfme/ui 5.3.11-dev.1 → 5.3.11-dev.10
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 +9027 -8912
- package/dist/index.umd.js +125 -125
- package/dist/types/src/components/Designer/Canvas/Guides.d.ts +3 -9
- package/dist/types/src/components/Designer/Canvas/Moveable.d.ts +2 -49
- package/dist/types/src/components/Designer/Canvas/Selecto.d.ts +5 -16
- package/dist/types/src/components/Designer/PluginIcon.d.ts +2 -2
- package/dist/types/src/components/Designer/RightSidebar/ListView/Item.d.ts +17 -0
- package/dist/types/src/components/Renderer.d.ts +1 -10
- package/dist/types/src/constants.d.ts +1 -1
- package/dist/types/src/contexts.d.ts +1 -1
- package/dist/types/src/helper.d.ts +3 -4
- package/package.json +1 -1
- package/src/components/AppContextProvider.tsx +17 -10
- package/src/components/CtlBar.tsx +2 -2
- package/src/components/Designer/Canvas/Guides.tsx +4 -13
- package/src/components/Designer/Canvas/Moveable.tsx +15 -74
- package/src/components/Designer/Canvas/Selecto.tsx +9 -24
- package/src/components/Designer/Canvas/index.tsx +51 -72
- package/src/components/Designer/LeftSidebar.tsx +2 -2
- package/src/components/Designer/PluginIcon.tsx +14 -3
- package/src/components/Designer/RightSidebar/DetailView/AlignWidget.tsx +91 -16
- package/src/components/Designer/RightSidebar/DetailView/ButtonGroupWidget.tsx +12 -8
- package/src/components/Designer/RightSidebar/DetailView/index.tsx +159 -48
- package/src/components/Designer/RightSidebar/ListView/Item.tsx +30 -2
- package/src/components/Designer/RightSidebar/ListView/SelectableSortableContainer.tsx +23 -4
- package/src/components/Designer/RightSidebar/ListView/SelectableSortableItem.tsx +20 -4
- package/src/components/Designer/index.tsx +14 -9
- package/src/components/Preview.tsx +5 -5
- package/src/components/Renderer.tsx +42 -36
- package/src/components/Root.tsx +1 -1
- package/src/constants.ts +1 -1
- package/src/contexts.ts +1 -1
- package/src/helper.ts +131 -38
- package/src/types/react-guides.d.ts +0 -22
- package/src/types/react-selecto.d.ts +0 -35
@@ -1,11 +1,12 @@
|
|
1
1
|
import { useForm } from 'form-render';
|
2
2
|
import React, { useRef, useContext, useState, useEffect } from 'react';
|
3
3
|
import type {
|
4
|
-
ChangeSchemaItem,
|
5
4
|
Dict,
|
5
|
+
ChangeSchemaItem,
|
6
6
|
SchemaForUI,
|
7
7
|
PropPanelWidgetProps,
|
8
8
|
PropPanelSchema,
|
9
|
+
Schema,
|
9
10
|
} from '@pdfme/common';
|
10
11
|
import type { SidebarProps } from '../../../../types.js';
|
11
12
|
import { Menu } from 'lucide-react';
|
@@ -44,6 +45,12 @@ const DetailView = (props: DetailViewProps) => {
|
|
44
45
|
const i18n = useContext(I18nContext);
|
45
46
|
const pluginsRegistry = useContext(PluginsRegistry);
|
46
47
|
const options = useContext(OptionsContext);
|
48
|
+
|
49
|
+
// Define a type-safe i18n function that accepts string keys
|
50
|
+
const typedI18n = (key: string): string => {
|
51
|
+
// Use a type assertion to handle the union type constraint
|
52
|
+
return typeof i18n === 'function' ? i18n(key as keyof Dict) : key;
|
53
|
+
};
|
47
54
|
|
48
55
|
const [widgets, setWidgets] = useState<{
|
49
56
|
[key: string]: (props: PropPanelWidgetProps) => React.JSX.Element;
|
@@ -66,7 +73,7 @@ const DetailView = (props: DetailViewProps) => {
|
|
66
73
|
{...props}
|
67
74
|
options={options}
|
68
75
|
theme={token}
|
69
|
-
i18n={
|
76
|
+
i18n={typedI18n}
|
70
77
|
widget={widgetValue}
|
71
78
|
/>
|
72
79
|
);
|
@@ -76,8 +83,11 @@ const DetailView = (props: DetailViewProps) => {
|
|
76
83
|
}, [activeSchema, pluginsRegistry, JSON.stringify(options)]);
|
77
84
|
|
78
85
|
useEffect(() => {
|
79
|
-
|
80
|
-
values
|
86
|
+
// Create a type-safe copy of the schema with editable property
|
87
|
+
const values: Record<string, unknown> = { ...activeSchema };
|
88
|
+
// Safely access and set properties
|
89
|
+
const readOnly = typeof values.readOnly === 'boolean' ? values.readOnly : false;
|
90
|
+
values.editable = !readOnly;
|
81
91
|
form.setValues(values);
|
82
92
|
}, [activeSchema, form]);
|
83
93
|
|
@@ -96,14 +106,26 @@ const DetailView = (props: DetailViewProps) => {
|
|
96
106
|
};
|
97
107
|
}, [schemasList, activeSchema]);
|
98
108
|
|
99
|
-
|
109
|
+
// Reference to a function that validates schema name uniqueness
|
110
|
+
const uniqueSchemaName = useRef(
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
112
|
+
(_unused: string): boolean => true
|
113
|
+
);
|
100
114
|
|
101
|
-
|
102
|
-
|
115
|
+
// Use proper type for validator function parameter
|
116
|
+
const validateUniqueSchemaName = (
|
117
|
+
_: unknown,
|
118
|
+
value: string
|
119
|
+
): boolean => uniqueSchemaName.current(value);
|
103
120
|
|
104
|
-
|
105
|
-
|
106
|
-
|
121
|
+
// Use explicit type for debounce function that matches the expected signature
|
122
|
+
const handleWatch = debounce(function(...args: unknown[]) {
|
123
|
+
const formSchema = args[0] as Record<string, unknown>;
|
124
|
+
const formAndSchemaValuesDiffer = (
|
125
|
+
formValue: unknown,
|
126
|
+
schemaValue: unknown
|
127
|
+
): boolean => {
|
128
|
+
if (typeof formValue === 'object' && formValue !== null) {
|
107
129
|
return JSON.stringify(formValue) !== JSON.stringify(schemaValue);
|
108
130
|
}
|
109
131
|
return formValue !== schemaValue;
|
@@ -114,7 +136,7 @@ const DetailView = (props: DetailViewProps) => {
|
|
114
136
|
if (['id', 'content'].includes(key)) continue;
|
115
137
|
|
116
138
|
let value = formSchema[key];
|
117
|
-
if (formAndSchemaValuesDiffer(value, (activeSchema as
|
139
|
+
if (formAndSchemaValuesDiffer(value, (activeSchema as Record<string, unknown>)[key])) {
|
118
140
|
// FIXME memo: https://github.com/pdfme/pdfme/pull/367#issuecomment-1857468274
|
119
141
|
if (value === null && ['rotate', 'opacity'].includes(key)) {
|
120
142
|
value = undefined;
|
@@ -154,28 +176,98 @@ const DetailView = (props: DetailViewProps) => {
|
|
154
176
|
}
|
155
177
|
}, 100);
|
156
178
|
|
179
|
+
// Find the active plugin with proper type safety
|
157
180
|
const activePlugin = Object.values(pluginsRegistry).find(
|
158
|
-
(plugin) =>
|
159
|
-
|
181
|
+
(plugin) => {
|
182
|
+
if (!plugin || typeof plugin !== 'object') return false;
|
183
|
+
if (!plugin.propPanel || typeof plugin.propPanel !== 'object') return false;
|
184
|
+
if (!plugin.propPanel.defaultSchema || typeof plugin.propPanel.defaultSchema !== 'object') return false;
|
185
|
+
|
186
|
+
const defaultSchema = plugin.propPanel.defaultSchema as Record<string, unknown>;
|
187
|
+
return 'type' in defaultSchema &&
|
188
|
+
typeof defaultSchema.type === 'string' &&
|
189
|
+
defaultSchema.type === activeSchema.type;
|
190
|
+
}
|
191
|
+
);
|
160
192
|
|
161
|
-
|
193
|
+
// Safely access the propPanel schema
|
194
|
+
const activePropPanelSchema = activePlugin?.propPanel?.schema;
|
162
195
|
if (!activePropPanelSchema) {
|
163
196
|
console.error(`[@pdfme/ui] No propPanel.schema for ${activeSchema.type}.
|
164
197
|
Check this document: https://pdfme.com/docs/custom-schemas`);
|
165
198
|
}
|
166
199
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
200
|
+
// Create type-safe options for the type dropdown
|
201
|
+
// Create a type-safe options array for the dropdown
|
202
|
+
const typeOptions: Array<{ label: string; value: string | undefined }> = [];
|
203
|
+
|
204
|
+
// Safely populate the options array
|
205
|
+
Object.entries(pluginsRegistry).forEach(([label, value]) => {
|
206
|
+
// Skip invalid plugins
|
207
|
+
if (!value || typeof value !== 'object') {
|
208
|
+
typeOptions.push({ label, value: undefined });
|
209
|
+
return;
|
210
|
+
}
|
211
|
+
|
212
|
+
if (!('propPanel' in value) ||
|
213
|
+
!value.propPanel ||
|
214
|
+
typeof value.propPanel !== 'object') {
|
215
|
+
typeOptions.push({ label, value: undefined });
|
216
|
+
return;
|
217
|
+
}
|
218
|
+
|
219
|
+
if (!('defaultSchema' in value.propPanel) ||
|
220
|
+
!value.propPanel.defaultSchema ||
|
221
|
+
typeof value.propPanel.defaultSchema !== 'object') {
|
222
|
+
typeOptions.push({ label, value: undefined });
|
223
|
+
return;
|
224
|
+
}
|
225
|
+
|
226
|
+
// Safely extract the type
|
227
|
+
const defaultSchema = value.propPanel.defaultSchema as Record<string, unknown>;
|
228
|
+
let schemaType: string | undefined = undefined;
|
229
|
+
|
230
|
+
if ('type' in defaultSchema && typeof defaultSchema.type === 'string') {
|
231
|
+
schemaType = defaultSchema.type;
|
232
|
+
}
|
233
|
+
|
234
|
+
typeOptions.push({ label, value: schemaType });
|
235
|
+
});
|
236
|
+
// Create a safe empty schema as fallback
|
237
|
+
const emptySchema: Record<string, unknown> = {};
|
238
|
+
|
239
|
+
// Safely access the default schema with proper null checking
|
240
|
+
const defaultSchema: Record<string, unknown> =
|
241
|
+
activePlugin?.propPanel?.defaultSchema ?
|
242
|
+
// Create a safe copy of the schema
|
243
|
+
(() => {
|
244
|
+
// First check if the defaultSchema is an object
|
245
|
+
if (typeof activePlugin.propPanel.defaultSchema !== 'object' ||
|
246
|
+
activePlugin.propPanel.defaultSchema === null) {
|
247
|
+
return emptySchema;
|
248
|
+
}
|
249
|
+
|
250
|
+
// Create a safe copy
|
251
|
+
const result: Record<string, unknown> = {};
|
252
|
+
|
253
|
+
// Only copy properties that exist on the object
|
254
|
+
for (const key in activePlugin.propPanel.defaultSchema) {
|
255
|
+
if (Object.prototype.hasOwnProperty.call(activePlugin.propPanel.defaultSchema, key)) {
|
256
|
+
result[key] = (activePlugin.propPanel.defaultSchema as Record<string, unknown>)[key];
|
257
|
+
}
|
258
|
+
}
|
259
|
+
|
260
|
+
return result;
|
261
|
+
})() :
|
262
|
+
emptySchema;
|
172
263
|
|
264
|
+
// Create a type-safe schema object
|
173
265
|
const propPanelSchema: PropPanelSchema = {
|
174
266
|
type: 'object',
|
175
267
|
column: 2,
|
176
268
|
properties: {
|
177
269
|
type: {
|
178
|
-
title:
|
270
|
+
title: typedI18n('type'),
|
179
271
|
type: 'string',
|
180
272
|
widget: 'select',
|
181
273
|
props: { options: typeOptions },
|
@@ -183,32 +275,32 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
|
|
183
275
|
span: 12,
|
184
276
|
},
|
185
277
|
name: {
|
186
|
-
title:
|
278
|
+
title: typedI18n('fieldName'),
|
187
279
|
type: 'string',
|
188
280
|
required: true,
|
189
281
|
span: 12,
|
190
282
|
rules: [
|
191
283
|
{
|
192
284
|
validator: validateUniqueSchemaName,
|
193
|
-
message:
|
285
|
+
message: typedI18n('validation.uniqueName'),
|
194
286
|
},
|
195
287
|
],
|
196
288
|
props: { autoComplete: 'off' },
|
197
289
|
},
|
198
290
|
editable: {
|
199
|
-
title:
|
291
|
+
title: typedI18n('editable'),
|
200
292
|
type: 'boolean',
|
201
293
|
span: 8,
|
202
|
-
hidden: defaultSchema
|
294
|
+
hidden: typeof defaultSchema.readOnly !== 'undefined',
|
203
295
|
},
|
204
296
|
required: {
|
205
|
-
title:
|
297
|
+
title: typedI18n('required'),
|
206
298
|
type: 'boolean',
|
207
299
|
span: 16,
|
208
300
|
hidden: '{{!formData.editable}}',
|
209
301
|
},
|
210
302
|
'-': { type: 'void', widget: 'Divider' },
|
211
|
-
align: { title:
|
303
|
+
align: { title: typedI18n('align'), type: 'void', widget: 'AlignWidget' },
|
212
304
|
position: {
|
213
305
|
type: 'object',
|
214
306
|
widget: 'card',
|
@@ -218,7 +310,7 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
|
|
218
310
|
},
|
219
311
|
},
|
220
312
|
width: {
|
221
|
-
title:
|
313
|
+
title: typedI18n('width'),
|
222
314
|
type: 'number',
|
223
315
|
widget: 'inputNumber',
|
224
316
|
required: true,
|
@@ -226,7 +318,7 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
|
|
226
318
|
props: { min: 0 },
|
227
319
|
},
|
228
320
|
height: {
|
229
|
-
title:
|
321
|
+
title: typedI18n('height'),
|
230
322
|
type: 'number',
|
231
323
|
widget: 'inputNumber',
|
232
324
|
required: true,
|
@@ -234,46 +326,65 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
|
|
234
326
|
props: { min: 0 },
|
235
327
|
},
|
236
328
|
rotate: {
|
237
|
-
title:
|
329
|
+
title: typedI18n('rotate'),
|
238
330
|
type: 'number',
|
239
331
|
widget: 'inputNumber',
|
240
|
-
disabled: defaultSchema
|
332
|
+
disabled: typeof defaultSchema.rotate === 'undefined',
|
241
333
|
max: 360,
|
242
334
|
props: { min: 0 },
|
243
335
|
span: 6,
|
244
336
|
},
|
245
337
|
opacity: {
|
246
|
-
title:
|
338
|
+
title: typedI18n('opacity'),
|
247
339
|
type: 'number',
|
248
340
|
widget: 'inputNumber',
|
249
|
-
disabled: defaultSchema
|
341
|
+
disabled: typeof defaultSchema.opacity === 'undefined',
|
250
342
|
props: { step: 0.1, min: 0, max: 1 },
|
251
343
|
span: 6,
|
252
344
|
},
|
253
345
|
},
|
254
346
|
};
|
255
347
|
|
348
|
+
// Create a safe copy of the properties
|
349
|
+
const safeProperties = { ...propPanelSchema.properties };
|
350
|
+
|
256
351
|
if (typeof activePropPanelSchema === 'function') {
|
257
|
-
|
352
|
+
// Create a new object without the schemasList property
|
353
|
+
const { size, schemas, pageSize, changeSchemas, activeElements, deselectSchema, activeSchema } = props;
|
354
|
+
const propPanelProps = { size, schemas, pageSize, changeSchemas, activeElements, deselectSchema, activeSchema };
|
258
355
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
356
|
+
// Use the typedI18n function to avoid type issues
|
357
|
+
const functionResult = activePropPanelSchema({
|
358
|
+
...propPanelProps,
|
359
|
+
options,
|
360
|
+
theme: token,
|
361
|
+
i18n: typedI18n,
|
362
|
+
});
|
363
|
+
|
364
|
+
// Safely handle the result
|
365
|
+
const apps = functionResult && typeof functionResult === 'object' ? functionResult : {};
|
366
|
+
|
367
|
+
// Create a divider if needed
|
368
|
+
const dividerObj = Object.keys(apps).length === 0 ? {} : { '--': { type: 'void', widget: 'Divider' } };
|
369
|
+
|
370
|
+
// Assign properties safely - use type assertion to satisfy TypeScript
|
266
371
|
propPanelSchema.properties = {
|
267
|
-
...
|
268
|
-
...(
|
269
|
-
...apps,
|
372
|
+
...safeProperties,
|
373
|
+
...(dividerObj as Record<string, Partial<Schema>>),
|
374
|
+
...(apps as Record<string, Partial<Schema>>),
|
270
375
|
};
|
271
376
|
} else {
|
272
|
-
|
377
|
+
// Handle non-function case
|
378
|
+
const apps = activePropPanelSchema && typeof activePropPanelSchema === 'object' ? activePropPanelSchema : {};
|
379
|
+
|
380
|
+
// Create a divider if needed
|
381
|
+
const dividerObj = Object.keys(apps).length === 0 ? {} : { '--': { type: 'void', widget: 'Divider' } };
|
382
|
+
|
383
|
+
// Assign properties safely - use type assertion to satisfy TypeScript
|
273
384
|
propPanelSchema.properties = {
|
274
|
-
...
|
275
|
-
...(
|
276
|
-
...apps,
|
385
|
+
...safeProperties,
|
386
|
+
...(dividerObj as Record<string, Partial<Schema>>),
|
387
|
+
...(apps as Record<string, Partial<Schema>>),
|
277
388
|
};
|
278
389
|
}
|
279
390
|
|
@@ -292,7 +403,7 @@ Check this document: https://pdfme.com/docs/custom-schemas`);
|
|
292
403
|
icon={<Menu strokeWidth={1.5} size={20} />}
|
293
404
|
/>
|
294
405
|
<Text strong style={{ textAlign: 'center', width: '100%' }}>
|
295
|
-
{
|
406
|
+
{typedI18n('editField')}
|
296
407
|
</Text>
|
297
408
|
</div>
|
298
409
|
<Divider style={{ marginTop: token.marginXS, marginBottom: token.marginXS }} />
|
@@ -6,28 +6,49 @@ import { Button, Typography } from 'antd';
|
|
6
6
|
|
7
7
|
const { Text } = Typography;
|
8
8
|
|
9
|
+
// Define prop types for Item component
|
9
10
|
interface Props {
|
11
|
+
/** Content to display in the item */
|
10
12
|
value: React.ReactNode;
|
13
|
+
/** Optional icon to display */
|
11
14
|
icon?: React.ReactNode;
|
15
|
+
/** Custom styles for the item */
|
12
16
|
style?: React.CSSProperties;
|
17
|
+
/** Status indicator for the item */
|
13
18
|
status?: 'is-warning' | 'is-danger';
|
19
|
+
/** Title attribute for the item */
|
14
20
|
title?: string;
|
21
|
+
/** Whether the item is required */
|
15
22
|
required?: boolean;
|
23
|
+
/** Whether the item is read-only */
|
16
24
|
readOnly?: boolean;
|
25
|
+
/** Whether the item is being dragged as an overlay */
|
17
26
|
dragOverlay?: boolean;
|
27
|
+
/** Click handler for the item */
|
18
28
|
onClick?: () => void;
|
29
|
+
/** Mouse enter handler */
|
19
30
|
onMouseEnter?: () => void;
|
31
|
+
/** Mouse leave handler */
|
20
32
|
onMouseLeave?: () => void;
|
33
|
+
/** Whether the item is currently being dragged */
|
21
34
|
dragging?: boolean;
|
35
|
+
/** Whether items are being sorted */
|
22
36
|
sorting?: boolean;
|
37
|
+
/** CSS transition value */
|
23
38
|
transition?: string;
|
39
|
+
/** Transform data for the item */
|
24
40
|
transform?: { x: number; y: number; scaleX: number; scaleY: number } | null;
|
41
|
+
/** Whether to fade the item in */
|
25
42
|
fadeIn?: boolean;
|
43
|
+
/** Drag listeners from dnd-kit */
|
26
44
|
listeners?: DraggableSyntheticListeners;
|
27
45
|
}
|
46
|
+
// Using React.memo and forwardRef for optimized rendering
|
47
|
+
// Using TypeScript interface for prop validation instead of PropTypes
|
28
48
|
const Item = React.memo(
|
49
|
+
/* eslint-disable react/prop-types */
|
29
50
|
React.forwardRef<HTMLLIElement, Props>(
|
30
|
-
(
|
51
|
+
function Item(
|
31
52
|
{
|
32
53
|
icon,
|
33
54
|
value,
|
@@ -40,16 +61,20 @@ const Item = React.memo(
|
|
40
61
|
onClick,
|
41
62
|
onMouseEnter,
|
42
63
|
onMouseLeave,
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
43
65
|
dragging,
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
44
67
|
fadeIn,
|
45
68
|
listeners,
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
46
70
|
sorting,
|
47
71
|
transition,
|
48
72
|
transform,
|
49
73
|
...props
|
50
74
|
},
|
51
75
|
ref,
|
52
|
-
)
|
76
|
+
) {
|
77
|
+
/* eslint-enable react/prop-types */
|
53
78
|
const i18n = useContext(I18nContext);
|
54
79
|
|
55
80
|
useEffect(() => {
|
@@ -129,4 +154,7 @@ const Item = React.memo(
|
|
129
154
|
),
|
130
155
|
);
|
131
156
|
|
157
|
+
// Set display name for debugging
|
158
|
+
Item.displayName = 'Item';
|
159
|
+
|
132
160
|
export default Item;
|
@@ -59,12 +59,31 @@ const SelectableSortableContainer = (
|
|
59
59
|
};
|
60
60
|
|
61
61
|
const getPluginIcon = (inSchema: string | SchemaForUI): ReactNode => {
|
62
|
+
// Get schema by ID or use directly
|
62
63
|
const thisSchema =
|
63
64
|
typeof inSchema === 'string' ? schemas.find((schema) => schema.id === inSchema) : inSchema;
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
65
|
+
|
66
|
+
if (!thisSchema) return <></>;
|
67
|
+
|
68
|
+
// Safely extract schema type
|
69
|
+
const schemaType = typeof thisSchema.type === 'string' ? thisSchema.type : '';
|
70
|
+
|
71
|
+
// Find matching plugin with type-safe approach
|
72
|
+
const pluginEntry = Object.entries(pluginsRegistry).find(
|
73
|
+
([, plugin]) => {
|
74
|
+
if (!plugin || typeof plugin !== 'object') return false;
|
75
|
+
if (!plugin.propPanel || typeof plugin.propPanel !== 'object') return false;
|
76
|
+
if (!plugin.propPanel.defaultSchema || typeof plugin.propPanel.defaultSchema !== 'object') return false;
|
77
|
+
|
78
|
+
// Use Record<string, unknown> to safely access properties
|
79
|
+
const defaultSchema = plugin.propPanel.defaultSchema as Record<string, unknown>;
|
80
|
+
return 'type' in defaultSchema &&
|
81
|
+
typeof defaultSchema.type === 'string' &&
|
82
|
+
defaultSchema.type === schemaType;
|
83
|
+
}
|
84
|
+
);
|
85
|
+
|
86
|
+
const [pluginLabel, activePlugin] = pluginEntry || ['', undefined];
|
68
87
|
|
69
88
|
if (!activePlugin) {
|
70
89
|
return <></>;
|
@@ -39,12 +39,28 @@ const SelectableSortableItem = ({
|
|
39
39
|
|
40
40
|
const newListeners = {
|
41
41
|
...listeners,
|
42
|
-
onClick: (event:
|
42
|
+
onClick: (event: React.MouseEvent) => onSelect(schema.id, event.shiftKey),
|
43
43
|
};
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
// Safely extract schema type
|
46
|
+
const schemaType = typeof schema.type === 'string' ? schema.type : '';
|
47
|
+
|
48
|
+
// Find matching plugin with type-safe approach
|
49
|
+
const pluginEntry = Object.entries(pluginsRegistry).find(
|
50
|
+
([, plugin]) => {
|
51
|
+
if (!plugin || typeof plugin !== 'object') return false;
|
52
|
+
if (!plugin.propPanel || typeof plugin.propPanel !== 'object') return false;
|
53
|
+
if (!plugin.propPanel.defaultSchema || typeof plugin.propPanel.defaultSchema !== 'object') return false;
|
54
|
+
|
55
|
+
// Use Record<string, unknown> to safely access properties
|
56
|
+
const defaultSchema = plugin.propPanel.defaultSchema as Record<string, unknown>;
|
57
|
+
return 'type' in defaultSchema &&
|
58
|
+
typeof defaultSchema.type === 'string' &&
|
59
|
+
defaultSchema.type === schemaType;
|
60
|
+
}
|
61
|
+
);
|
62
|
+
|
63
|
+
const [pluginLabel, thisPlugin] = pluginEntry || ['', undefined];
|
48
64
|
|
49
65
|
let status: undefined | 'is-warning' | 'is-danger';
|
50
66
|
if (!schema.name) {
|
@@ -24,7 +24,7 @@ import {
|
|
24
24
|
template2SchemasList,
|
25
25
|
getPagesScrollTopByIndex,
|
26
26
|
changeSchemas as _changeSchemas,
|
27
|
-
|
27
|
+
useMaxZoom,
|
28
28
|
} from '../../helper.js';
|
29
29
|
import { useUIPreProcessor, useScrollPageCursor, useInitEvents } from '../../hooks.js';
|
30
30
|
import Root from '../Root.js';
|
@@ -64,7 +64,7 @@ const TemplateEditor = ({
|
|
64
64
|
const i18n = useContext(I18nContext);
|
65
65
|
const pluginsRegistry = useContext(PluginsRegistry);
|
66
66
|
const options = useContext(OptionsContext);
|
67
|
-
const maxZoom =
|
67
|
+
const maxZoom = useMaxZoom();
|
68
68
|
|
69
69
|
const [hoveringSchemaId, setHoveringSchemaId] = useState<string | null>(null);
|
70
70
|
const [activeElements, setActiveElements] = useState<HTMLElement[]>([]);
|
@@ -227,12 +227,13 @@ const TemplateEditor = ({
|
|
227
227
|
onChangeTemplate(newTemplate);
|
228
228
|
await updateTemplate(newTemplate);
|
229
229
|
void refresh(newTemplate);
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
230
|
+
|
231
|
+
// Use setTimeout to update scroll position after render
|
232
|
+
setTimeout(() => {
|
233
|
+
if (canvasRef.current) {
|
234
|
+
canvasRef.current.scrollTop = getPagesScrollTopByIndex(pageSizes, newPageCursor, scale);
|
235
|
+
}
|
236
|
+
}, 0);
|
236
237
|
};
|
237
238
|
|
238
239
|
const handleRemovePage = () => {
|
@@ -262,6 +263,7 @@ const TemplateEditor = ({
|
|
262
263
|
};
|
263
264
|
|
264
265
|
if (error) {
|
266
|
+
// Pass the error directly to ErrorScreen
|
265
267
|
return <ErrorScreen size={size} error={error} />;
|
266
268
|
}
|
267
269
|
const pageManipulation = isBlankPdf(template.basePdf)
|
@@ -309,6 +311,7 @@ const TemplateEditor = ({
|
|
309
311
|
pageNum={schemasList.length}
|
310
312
|
setPageCursor={(p) => {
|
311
313
|
if (!canvasRef.current) return;
|
314
|
+
// Update scroll position and state
|
312
315
|
canvasRef.current.scrollTop = getPagesScrollTopByIndex(pageSizes, p, scale);
|
313
316
|
setPageCursor(p);
|
314
317
|
onEditEnd();
|
@@ -331,7 +334,9 @@ const TemplateEditor = ({
|
|
331
334
|
onSortEnd={onSortEnd}
|
332
335
|
onEdit={(id) => {
|
333
336
|
const editingElem = document.getElementById(id);
|
334
|
-
|
337
|
+
if (editingElem) {
|
338
|
+
onEdit([editingElem]);
|
339
|
+
}
|
335
340
|
}}
|
336
341
|
onEditEnd={onEditEnd}
|
337
342
|
deselectSchema={onEditEnd}
|
@@ -17,10 +17,10 @@ import Paper from './Paper.js';
|
|
17
17
|
import Renderer from './Renderer.js';
|
18
18
|
import { useUIPreProcessor, useScrollPageCursor } from '../hooks.js';
|
19
19
|
import { FontContext } from '../contexts.js';
|
20
|
-
import { template2SchemasList, getPagesScrollTopByIndex,
|
20
|
+
import { template2SchemasList, getPagesScrollTopByIndex, useMaxZoom } from '../helper.js';
|
21
21
|
import { theme } from 'antd';
|
22
22
|
|
23
|
-
const _cache = new Map();
|
23
|
+
const _cache = new Map<string | number, unknown>();
|
24
24
|
|
25
25
|
const Preview = ({
|
26
26
|
template,
|
@@ -34,7 +34,7 @@ const Preview = ({
|
|
34
34
|
const { token } = theme.useToken();
|
35
35
|
|
36
36
|
const font = useContext(FontContext);
|
37
|
-
const maxZoom =
|
37
|
+
const maxZoom = useMaxZoom();
|
38
38
|
|
39
39
|
const containerRef = useRef<HTMLDivElement>(null);
|
40
40
|
const paperRefs = useRef<HTMLDivElement[]>([]);
|
@@ -98,7 +98,7 @@ const Preview = ({
|
|
98
98
|
const handleChangeInput = ({ name, value }: { name: string; value: string }) =>
|
99
99
|
onChangeInput && onChangeInput({ index: unitCursor, name, value });
|
100
100
|
|
101
|
-
const handleOnChangeRenderer = (args: { key: string; value:
|
101
|
+
const handleOnChangeRenderer = (args: { key: string; value: unknown }[], schema: SchemaForUI) => {
|
102
102
|
let isNeedInit = false;
|
103
103
|
args.forEach(({ key: _key, value }) => {
|
104
104
|
if (_key === 'content') {
|
@@ -112,7 +112,7 @@ const Preview = ({
|
|
112
112
|
const targetSchema = schemasList[pageCursor].find((s) => s.id === schema.id) as SchemaForUI;
|
113
113
|
if (!targetSchema) return;
|
114
114
|
|
115
|
-
// @ts-
|
115
|
+
// @ts-expect-error Dynamic property assignment
|
116
116
|
targetSchema[_key] = value as string;
|
117
117
|
}
|
118
118
|
});
|