@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,220 @@
|
|
|
1
|
+
import React, { useRef, useState, useEffect, useContext } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Template,
|
|
4
|
+
SchemaForUI,
|
|
5
|
+
PreviewProps,
|
|
6
|
+
Size,
|
|
7
|
+
getDynamicTemplate,
|
|
8
|
+
replacePlaceholders,
|
|
9
|
+
} from '@pdfme/common';
|
|
10
|
+
import { getDynamicHeightsForTable } from '@pdfme/schemas/utils';
|
|
11
|
+
import UnitPager from './UnitPager.js';
|
|
12
|
+
import Root from './Root.js';
|
|
13
|
+
import StaticSchema from './StaticSchema.js';
|
|
14
|
+
import ErrorScreen from './ErrorScreen.js';
|
|
15
|
+
import CtlBar from './CtlBar.js';
|
|
16
|
+
import Paper from './Paper.js';
|
|
17
|
+
import Renderer from './Renderer.js';
|
|
18
|
+
import { useUIPreProcessor, useScrollPageCursor } from '../hooks.js';
|
|
19
|
+
import { FontContext, OptionsContext } from '../contexts.js';
|
|
20
|
+
import { template2SchemasList, getPagesScrollTopByIndex, useMaxZoom } from '../helper.js';
|
|
21
|
+
import { theme } from 'antd';
|
|
22
|
+
|
|
23
|
+
const _cache = new Map<string | number, unknown>();
|
|
24
|
+
|
|
25
|
+
const Preview = ({
|
|
26
|
+
template,
|
|
27
|
+
inputs,
|
|
28
|
+
size,
|
|
29
|
+
onChangeInput,
|
|
30
|
+
onPageChange,
|
|
31
|
+
}: Omit<PreviewProps, 'domContainer'> & {
|
|
32
|
+
onChangeInput?: (args: { index: number; value: string; name: string }) => void;
|
|
33
|
+
onPageChange?: (pageInfo: { currentPage: number; totalPages: number }) => void;
|
|
34
|
+
size: Size;
|
|
35
|
+
}) => {
|
|
36
|
+
const { token } = theme.useToken();
|
|
37
|
+
|
|
38
|
+
const font = useContext(FontContext);
|
|
39
|
+
const options = useContext(OptionsContext);
|
|
40
|
+
const maxZoom = useMaxZoom();
|
|
41
|
+
|
|
42
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
43
|
+
const paperRefs = useRef<HTMLDivElement[]>([]);
|
|
44
|
+
|
|
45
|
+
const [unitCursor, setUnitCursor] = useState(0);
|
|
46
|
+
const [pageCursor, setPageCursor] = useState(0);
|
|
47
|
+
const [zoomLevel, setZoomLevel] = useState(options.zoomLevel ?? 1);
|
|
48
|
+
const [schemasList, setSchemasList] = useState<SchemaForUI[][]>([[]] as SchemaForUI[][]);
|
|
49
|
+
|
|
50
|
+
const { backgrounds, pageSizes, scale, error, refresh } = useUIPreProcessor({
|
|
51
|
+
template,
|
|
52
|
+
size,
|
|
53
|
+
zoomLevel,
|
|
54
|
+
maxZoom,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const isForm = Boolean(onChangeInput);
|
|
58
|
+
|
|
59
|
+
const input = inputs[unitCursor];
|
|
60
|
+
|
|
61
|
+
const init = (template: Template) => {
|
|
62
|
+
const options = { font };
|
|
63
|
+
getDynamicTemplate({
|
|
64
|
+
template,
|
|
65
|
+
input,
|
|
66
|
+
options,
|
|
67
|
+
_cache,
|
|
68
|
+
getDynamicHeights: (value, args) => {
|
|
69
|
+
switch (args.schema.type) {
|
|
70
|
+
case 'table':
|
|
71
|
+
return getDynamicHeightsForTable(value, args);
|
|
72
|
+
default:
|
|
73
|
+
return Promise.resolve([args.schema.height]);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
.then(async (dynamicTemplate) => {
|
|
78
|
+
const sl = await template2SchemasList(dynamicTemplate);
|
|
79
|
+
setSchemasList(sl);
|
|
80
|
+
await refresh(dynamicTemplate);
|
|
81
|
+
})
|
|
82
|
+
.catch((err) => console.error(`[@pdfme/ui] `, err));
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Update component state only when _options_ changes
|
|
86
|
+
// Ignore exhaustive useEffect dependency warnings here
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (typeof options.zoomLevel === 'number' && options.zoomLevel !== zoomLevel) {
|
|
89
|
+
setZoomLevel(options.zoomLevel);
|
|
90
|
+
}
|
|
91
|
+
// eslint-disable-next-line
|
|
92
|
+
}, [options]);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (unitCursor > inputs.length - 1) {
|
|
96
|
+
setUnitCursor(inputs.length - 1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
init(template);
|
|
100
|
+
}, [template, inputs, size]);
|
|
101
|
+
|
|
102
|
+
useScrollPageCursor({
|
|
103
|
+
ref: containerRef,
|
|
104
|
+
pageSizes,
|
|
105
|
+
scale,
|
|
106
|
+
pageCursor,
|
|
107
|
+
onChangePageCursor: (p) => {
|
|
108
|
+
setPageCursor(p);
|
|
109
|
+
if (onPageChange) {
|
|
110
|
+
onPageChange({ currentPage: p, totalPages: schemasList.length });
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const handleChangeInput = ({ name, value }: { name: string; value: string }) =>
|
|
116
|
+
onChangeInput && onChangeInput({ index: unitCursor, name, value });
|
|
117
|
+
|
|
118
|
+
const handleOnChangeRenderer = (args: { key: string; value: unknown }[], schema: SchemaForUI) => {
|
|
119
|
+
let isNeedInit = false;
|
|
120
|
+
args.forEach(({ key: _key, value }) => {
|
|
121
|
+
if (_key === 'content') {
|
|
122
|
+
const newValue = value as string;
|
|
123
|
+
const oldValue = (input?.[schema.name] as string) || '';
|
|
124
|
+
if (newValue === oldValue) return;
|
|
125
|
+
handleChangeInput({ name: schema.name, value: newValue });
|
|
126
|
+
// TODO Improve this to allow schema types to determine whether the execution of getDynamicTemplate is required.
|
|
127
|
+
if (schema.type === 'table') isNeedInit = true;
|
|
128
|
+
} else {
|
|
129
|
+
const targetSchema = schemasList[pageCursor].find((s) => s.id === schema.id) as SchemaForUI;
|
|
130
|
+
if (!targetSchema) return;
|
|
131
|
+
|
|
132
|
+
// @ts-expect-error Dynamic property assignment
|
|
133
|
+
targetSchema[_key] = value as string;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
if (isNeedInit) {
|
|
137
|
+
init(template);
|
|
138
|
+
}
|
|
139
|
+
setSchemasList([...schemasList]);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (error) {
|
|
143
|
+
return <ErrorScreen size={size} error={error} />;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<Root size={size} scale={scale}>
|
|
148
|
+
<CtlBar
|
|
149
|
+
size={size}
|
|
150
|
+
pageCursor={pageCursor}
|
|
151
|
+
pageNum={schemasList.length}
|
|
152
|
+
setPageCursor={(p) => {
|
|
153
|
+
if (!containerRef.current) return;
|
|
154
|
+
containerRef.current.scrollTop = getPagesScrollTopByIndex(pageSizes, p, scale);
|
|
155
|
+
setPageCursor(p);
|
|
156
|
+
if (onPageChange) {
|
|
157
|
+
onPageChange({ currentPage: p, totalPages: schemasList.length });
|
|
158
|
+
}
|
|
159
|
+
}}
|
|
160
|
+
zoomLevel={zoomLevel}
|
|
161
|
+
setZoomLevel={setZoomLevel}
|
|
162
|
+
/>
|
|
163
|
+
<UnitPager
|
|
164
|
+
size={size}
|
|
165
|
+
unitCursor={unitCursor}
|
|
166
|
+
unitNum={inputs.length}
|
|
167
|
+
setUnitCursor={setUnitCursor}
|
|
168
|
+
/>
|
|
169
|
+
<div ref={containerRef} style={{ ...size, position: 'relative', overflow: 'auto' }}>
|
|
170
|
+
<Paper
|
|
171
|
+
paperRefs={paperRefs}
|
|
172
|
+
scale={scale}
|
|
173
|
+
size={size}
|
|
174
|
+
schemasList={schemasList}
|
|
175
|
+
pageSizes={pageSizes}
|
|
176
|
+
backgrounds={backgrounds}
|
|
177
|
+
renderSchema={({ schema, index }) => {
|
|
178
|
+
const value = schema.readOnly
|
|
179
|
+
? replacePlaceholders({
|
|
180
|
+
content: schema.content || '',
|
|
181
|
+
variables: { ...input, totalPages: schemasList.length, currentPage: index + 1 },
|
|
182
|
+
schemas: schemasList,
|
|
183
|
+
})
|
|
184
|
+
: String((input && input[schema.name]) || '');
|
|
185
|
+
return (
|
|
186
|
+
<Renderer
|
|
187
|
+
key={schema.id}
|
|
188
|
+
schema={schema}
|
|
189
|
+
basePdf={template.basePdf}
|
|
190
|
+
value={value}
|
|
191
|
+
mode={isForm ? 'form' : 'viewer'}
|
|
192
|
+
placeholder={schema.content}
|
|
193
|
+
tabIndex={index + 100}
|
|
194
|
+
onChange={(arg) => {
|
|
195
|
+
const args = Array.isArray(arg) ? arg : [arg];
|
|
196
|
+
handleOnChangeRenderer(args, schema);
|
|
197
|
+
}}
|
|
198
|
+
outline={
|
|
199
|
+
isForm && !schema.readOnly ? `1px dashed ${token.colorPrimary}` : 'transparent'
|
|
200
|
+
}
|
|
201
|
+
scale={scale}
|
|
202
|
+
/>
|
|
203
|
+
);
|
|
204
|
+
}}
|
|
205
|
+
renderPaper={({ index }) => (
|
|
206
|
+
<StaticSchema
|
|
207
|
+
template={template}
|
|
208
|
+
scale={scale}
|
|
209
|
+
input={input}
|
|
210
|
+
totalPages={schemasList.length}
|
|
211
|
+
currentPage={index + 1}
|
|
212
|
+
/>
|
|
213
|
+
)}
|
|
214
|
+
/>
|
|
215
|
+
</div>
|
|
216
|
+
</Root>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export default Preview;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import React, { useEffect, useContext, ReactNode, useRef, useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Mode,
|
|
4
|
+
ZOOM,
|
|
5
|
+
UIRenderProps,
|
|
6
|
+
SchemaForUI,
|
|
7
|
+
BasePdf,
|
|
8
|
+
Schema,
|
|
9
|
+
Plugin,
|
|
10
|
+
UIOptions,
|
|
11
|
+
cloneDeep,
|
|
12
|
+
} from '@pdfme/common';
|
|
13
|
+
import { theme as antdTheme } from 'antd';
|
|
14
|
+
import { SELECTABLE_CLASSNAME } from '../constants.js';
|
|
15
|
+
import { PluginsRegistry, OptionsContext, I18nContext, CacheContext } from '../contexts.js';
|
|
16
|
+
|
|
17
|
+
type RendererProps = Omit<
|
|
18
|
+
UIRenderProps<Schema>,
|
|
19
|
+
'schema' | 'rootElement' | 'options' | 'theme' | 'i18n' | '_cache'
|
|
20
|
+
> & {
|
|
21
|
+
basePdf: BasePdf;
|
|
22
|
+
schema: SchemaForUI;
|
|
23
|
+
value: string;
|
|
24
|
+
outline: string;
|
|
25
|
+
onChangeHoveringSchemaId?: (id: string | null) => void;
|
|
26
|
+
scale: number;
|
|
27
|
+
selectable?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type ReRenderCheckProps = {
|
|
31
|
+
plugin?: Plugin<Schema>;
|
|
32
|
+
value: string;
|
|
33
|
+
mode: Mode;
|
|
34
|
+
scale: number;
|
|
35
|
+
schema: SchemaForUI;
|
|
36
|
+
options: UIOptions;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const useRerenderDependencies = (arg: ReRenderCheckProps) => {
|
|
40
|
+
const { plugin, value, mode, scale, schema, options } = arg;
|
|
41
|
+
const _options = cloneDeep(options);
|
|
42
|
+
if (_options.font) {
|
|
43
|
+
Object.values(_options.font).forEach((fontObj) => {
|
|
44
|
+
(fontObj as { data: string }).data = '...';
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const optionStr = JSON.stringify(_options);
|
|
48
|
+
|
|
49
|
+
return useMemo(() => {
|
|
50
|
+
if (plugin?.uninterruptedEditMode && mode === 'designer') {
|
|
51
|
+
return [mode];
|
|
52
|
+
} else {
|
|
53
|
+
return [value, mode, scale, JSON.stringify(schema), optionStr];
|
|
54
|
+
}
|
|
55
|
+
}, [value, mode, scale, schema, optionStr, plugin]);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const Wrapper = ({
|
|
59
|
+
children,
|
|
60
|
+
outline,
|
|
61
|
+
onChangeHoveringSchemaId,
|
|
62
|
+
schema,
|
|
63
|
+
selectable = true,
|
|
64
|
+
}: RendererProps & { children: ReactNode }) => (
|
|
65
|
+
<div
|
|
66
|
+
title={schema.name}
|
|
67
|
+
onMouseEnter={() => onChangeHoveringSchemaId && onChangeHoveringSchemaId(schema.id)}
|
|
68
|
+
onMouseLeave={() => onChangeHoveringSchemaId && onChangeHoveringSchemaId(null)}
|
|
69
|
+
className={selectable ? SELECTABLE_CLASSNAME : ''}
|
|
70
|
+
id={schema.id}
|
|
71
|
+
style={{
|
|
72
|
+
position: 'absolute',
|
|
73
|
+
cursor: schema.readOnly ? 'initial' : 'pointer',
|
|
74
|
+
height: schema.height * ZOOM,
|
|
75
|
+
width: schema.width * ZOOM,
|
|
76
|
+
top: schema.position.y * ZOOM,
|
|
77
|
+
left: schema.position.x * ZOOM,
|
|
78
|
+
transform: `rotate(${schema.rotate ?? 0}deg)`,
|
|
79
|
+
opacity: schema.opacity ?? 1,
|
|
80
|
+
outline,
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
{schema.required && (
|
|
84
|
+
<span
|
|
85
|
+
style={{
|
|
86
|
+
color: 'red',
|
|
87
|
+
position: 'absolute',
|
|
88
|
+
top: -12,
|
|
89
|
+
left: -12,
|
|
90
|
+
fontSize: 18,
|
|
91
|
+
fontWeight: 700,
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
*
|
|
95
|
+
</span>
|
|
96
|
+
)}
|
|
97
|
+
{children}
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const Renderer = (props: RendererProps) => {
|
|
102
|
+
const { schema, basePdf, value, mode, onChange, stopEditing, tabIndex, placeholder, scale } =
|
|
103
|
+
props;
|
|
104
|
+
|
|
105
|
+
const pluginsRegistry = useContext(PluginsRegistry);
|
|
106
|
+
const options = useContext(OptionsContext);
|
|
107
|
+
const i18n = useContext(I18nContext) as (key: string) => string;
|
|
108
|
+
const { token: theme } = antdTheme.useToken();
|
|
109
|
+
|
|
110
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
111
|
+
const _cache = useContext(CacheContext);
|
|
112
|
+
const plugin = pluginsRegistry.findByType(schema.type);
|
|
113
|
+
|
|
114
|
+
const reRenderDependencies = useRerenderDependencies({
|
|
115
|
+
plugin,
|
|
116
|
+
value,
|
|
117
|
+
mode,
|
|
118
|
+
scale,
|
|
119
|
+
schema,
|
|
120
|
+
options,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!plugin?.ui || !ref.current || !schema.type) return;
|
|
125
|
+
|
|
126
|
+
ref.current.innerHTML = '';
|
|
127
|
+
const render = plugin.ui;
|
|
128
|
+
|
|
129
|
+
void render({
|
|
130
|
+
value,
|
|
131
|
+
schema,
|
|
132
|
+
basePdf,
|
|
133
|
+
rootElement: ref.current,
|
|
134
|
+
mode,
|
|
135
|
+
onChange,
|
|
136
|
+
stopEditing,
|
|
137
|
+
tabIndex,
|
|
138
|
+
placeholder,
|
|
139
|
+
options,
|
|
140
|
+
theme,
|
|
141
|
+
i18n,
|
|
142
|
+
scale,
|
|
143
|
+
_cache,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return () => {
|
|
147
|
+
if (ref.current) {
|
|
148
|
+
ref.current.innerHTML = '';
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}, reRenderDependencies);
|
|
152
|
+
|
|
153
|
+
if (!plugin) {
|
|
154
|
+
console.error(`[@pdfme/ui] Renderer for type ${schema.type} not found.
|
|
155
|
+
Check this document: https://pdfme.com/docs/custom-schemas`);
|
|
156
|
+
return <></>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<Wrapper {...props}>
|
|
161
|
+
<div style={{ height: '100%', width: '100%' }} ref={ref} />
|
|
162
|
+
</Wrapper>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
export default Renderer;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { useContext, forwardRef, ReactNode, Ref, useEffect } from 'react';
|
|
2
|
+
import { Size } from '@pdfme/common';
|
|
3
|
+
import { FontContext } from '../contexts.js';
|
|
4
|
+
import { BACKGROUND_COLOR, DESIGNER_CLASSNAME } from '../constants.js';
|
|
5
|
+
import Spinner from './Spinner.js';
|
|
6
|
+
|
|
7
|
+
type Props = { size: Size; scale: number; children: ReactNode };
|
|
8
|
+
|
|
9
|
+
const Root = ({ size, scale, children }: Props, ref: Ref<HTMLDivElement>) => {
|
|
10
|
+
const font = useContext(FontContext);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!document || !document.fonts) return;
|
|
14
|
+
const fontFaces = Object.entries(font).map(
|
|
15
|
+
([key, { data }]) =>
|
|
16
|
+
new FontFace(key, typeof data === 'string' ? `url(${data})` : (data as BufferSource), {
|
|
17
|
+
display: 'swap',
|
|
18
|
+
}),
|
|
19
|
+
);
|
|
20
|
+
const newFontFaces = fontFaces.filter((fontFace) => !document.fonts.has(fontFace));
|
|
21
|
+
|
|
22
|
+
void Promise.allSettled(newFontFaces.map((f) => f.load())).then((loadedFontFaces) => {
|
|
23
|
+
loadedFontFaces.forEach((loadedFontFace) => {
|
|
24
|
+
if (loadedFontFace.status === 'fulfilled') {
|
|
25
|
+
document.fonts.add(loadedFontFace.value);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}, [font]);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className={DESIGNER_CLASSNAME + 'root'} ref={ref} style={{ position: 'relative', background: BACKGROUND_COLOR, ...size }}>
|
|
33
|
+
<div className={DESIGNER_CLASSNAME + 'background'} style={{ margin: '0 auto', ...size }}>{scale === 0 ? <Spinner /> : children}</div>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default forwardRef<HTMLDivElement, Props>(Root);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { LoaderCircle } from 'lucide-react';
|
|
3
|
+
import { theme } from 'antd';
|
|
4
|
+
|
|
5
|
+
const Spinner: React.FC = () => {
|
|
6
|
+
const { token } = theme.useToken();
|
|
7
|
+
|
|
8
|
+
const containerStyle: React.CSSProperties = {
|
|
9
|
+
position: 'relative',
|
|
10
|
+
width: '100%',
|
|
11
|
+
height: '100%',
|
|
12
|
+
overflow: 'hidden',
|
|
13
|
+
display: 'flex',
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
justifyContent: 'center',
|
|
16
|
+
borderRadius: '50%',
|
|
17
|
+
color: token.colorPrimary,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const loaderStyle: React.CSSProperties = {
|
|
21
|
+
animation: 'spin 1s linear infinite',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const keyframes = `
|
|
25
|
+
@keyframes spin {
|
|
26
|
+
0% {
|
|
27
|
+
transform: rotate(0deg);
|
|
28
|
+
}
|
|
29
|
+
100% {
|
|
30
|
+
transform: rotate(360deg);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<style>{keyframes}</style>
|
|
38
|
+
<div style={containerStyle}>
|
|
39
|
+
<LoaderCircle size={50} style={loaderStyle} />
|
|
40
|
+
</div>
|
|
41
|
+
</>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default Spinner;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { isBlankPdf, replacePlaceholders, Template } from '@pdfme/common';
|
|
3
|
+
import Renderer from './Renderer.js';
|
|
4
|
+
import { uuid } from '../helper.js';
|
|
5
|
+
|
|
6
|
+
const StaticSchema = (props: {
|
|
7
|
+
template: Template;
|
|
8
|
+
input: Record<string, string>;
|
|
9
|
+
scale: number;
|
|
10
|
+
totalPages: number;
|
|
11
|
+
currentPage: number;
|
|
12
|
+
}) => {
|
|
13
|
+
const {
|
|
14
|
+
template: { schemas, basePdf },
|
|
15
|
+
input,
|
|
16
|
+
scale,
|
|
17
|
+
totalPages,
|
|
18
|
+
currentPage,
|
|
19
|
+
} = props;
|
|
20
|
+
if (!isBlankPdf(basePdf) || !basePdf.staticSchema) return null;
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
{basePdf.staticSchema.map((schema) => (
|
|
24
|
+
<Renderer
|
|
25
|
+
key={schema.name}
|
|
26
|
+
schema={{ ...schema, id: uuid() }}
|
|
27
|
+
basePdf={basePdf}
|
|
28
|
+
value={
|
|
29
|
+
schema.readOnly
|
|
30
|
+
? replacePlaceholders({
|
|
31
|
+
content: schema.content || '',
|
|
32
|
+
variables: { ...input, totalPages, currentPage },
|
|
33
|
+
schemas,
|
|
34
|
+
})
|
|
35
|
+
: schema.content || ''
|
|
36
|
+
}
|
|
37
|
+
onChangeHoveringSchemaId={() => {
|
|
38
|
+
void 0;
|
|
39
|
+
}}
|
|
40
|
+
mode={'viewer'}
|
|
41
|
+
outline={`none`}
|
|
42
|
+
scale={scale}
|
|
43
|
+
selectable={false}
|
|
44
|
+
/>
|
|
45
|
+
))}
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default StaticSchema;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Size } from '@pdfme/common';
|
|
3
|
+
import { theme, Typography, Button } from 'antd';
|
|
4
|
+
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
const { Text } = Typography;
|
|
7
|
+
|
|
8
|
+
type UnitButtonProps = {
|
|
9
|
+
type: 'left' | 'right' | 'doubleLeft' | 'doubleRight';
|
|
10
|
+
onClick: () => void;
|
|
11
|
+
disabled: boolean;
|
|
12
|
+
textStyle: { color: string; fontSize: number; margin: number };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const icons = {
|
|
16
|
+
left: ChevronLeft,
|
|
17
|
+
right: ChevronRight,
|
|
18
|
+
doubleLeft: ChevronsLeft,
|
|
19
|
+
doubleRight: ChevronsRight,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const UnitButton: React.FC<UnitButtonProps> = ({ type, onClick, disabled, textStyle }) => {
|
|
23
|
+
const Icon = icons[type];
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Button type="text" onClick={onClick} disabled={disabled}>
|
|
27
|
+
<Icon style={{ color: textStyle.color }} />
|
|
28
|
+
</Button>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type Props = {
|
|
33
|
+
size: Size;
|
|
34
|
+
unitCursor: number;
|
|
35
|
+
unitNum: number;
|
|
36
|
+
setUnitCursor: (page: number) => void;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const UnitPager = ({ size, unitCursor, unitNum, setUnitCursor }: Props) => {
|
|
40
|
+
if (unitNum <= 1) return null;
|
|
41
|
+
|
|
42
|
+
const { token } = theme.useToken();
|
|
43
|
+
|
|
44
|
+
const buttonWrapStyle: React.CSSProperties = {
|
|
45
|
+
pointerEvents: 'initial',
|
|
46
|
+
position: 'sticky',
|
|
47
|
+
zIndex: 1,
|
|
48
|
+
display: 'flex',
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
boxSizing: 'border-box',
|
|
51
|
+
height: 40,
|
|
52
|
+
padding: token.paddingSM,
|
|
53
|
+
borderRadius: token.borderRadius,
|
|
54
|
+
backgroundColor: token.colorBgMask,
|
|
55
|
+
};
|
|
56
|
+
const textStyle = {
|
|
57
|
+
color: token.colorWhite,
|
|
58
|
+
fontSize: token.fontSize,
|
|
59
|
+
margin: token.marginXS,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div style={{ position: 'absolute', ...size }}>
|
|
64
|
+
<div
|
|
65
|
+
style={{
|
|
66
|
+
position: 'sticky',
|
|
67
|
+
width: '100%',
|
|
68
|
+
zIndex: 1,
|
|
69
|
+
top: `calc(50% - ${(buttonWrapStyle.height as number) / 2}px)`,
|
|
70
|
+
display: 'flex',
|
|
71
|
+
alignItems: 'center',
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
{unitCursor > 0 && (
|
|
75
|
+
<div style={{ left: '1rem', marginLeft: '1rem', ...buttonWrapStyle }}>
|
|
76
|
+
<UnitButton
|
|
77
|
+
type="doubleLeft"
|
|
78
|
+
onClick={() => setUnitCursor(0)}
|
|
79
|
+
disabled={unitCursor <= 0}
|
|
80
|
+
textStyle={textStyle}
|
|
81
|
+
/>
|
|
82
|
+
<UnitButton
|
|
83
|
+
type="left"
|
|
84
|
+
onClick={() => setUnitCursor(unitCursor - 1)}
|
|
85
|
+
disabled={unitCursor <= 0}
|
|
86
|
+
textStyle={textStyle}
|
|
87
|
+
/>
|
|
88
|
+
<Text strong style={textStyle}>
|
|
89
|
+
{unitCursor + 1}/{unitNum}
|
|
90
|
+
</Text>
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
{unitCursor + 1 < unitNum && (
|
|
94
|
+
<div
|
|
95
|
+
style={{ right: '1rem', marginLeft: 'auto', marginRight: '1rem', ...buttonWrapStyle }}
|
|
96
|
+
>
|
|
97
|
+
<Text strong style={textStyle}>
|
|
98
|
+
{unitCursor + 1}/{unitNum}
|
|
99
|
+
</Text>
|
|
100
|
+
<UnitButton
|
|
101
|
+
type="right"
|
|
102
|
+
onClick={() => setUnitCursor(unitCursor + 1)}
|
|
103
|
+
disabled={unitCursor + 1 >= unitNum}
|
|
104
|
+
textStyle={textStyle}
|
|
105
|
+
/>
|
|
106
|
+
<UnitButton
|
|
107
|
+
type="doubleRight"
|
|
108
|
+
onClick={() => setUnitCursor(unitNum - 1)}
|
|
109
|
+
disabled={unitCursor + 1 >= unitNum}
|
|
110
|
+
textStyle={textStyle}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export default UnitPager;
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const DEFAULT_LANG = 'en';
|
|
2
|
+
|
|
3
|
+
export const DESTROYED_ERR_MSG = '[@pdfme/ui] this instance is already destroyed';
|
|
4
|
+
|
|
5
|
+
export const SELECTABLE_CLASSNAME = 'selectable';
|
|
6
|
+
|
|
7
|
+
export const RULER_HEIGHT = 30;
|
|
8
|
+
|
|
9
|
+
export const PAGE_GAP = 10;
|
|
10
|
+
|
|
11
|
+
export const LEFT_SIDEBAR_WIDTH = 45;
|
|
12
|
+
|
|
13
|
+
export const RIGHT_SIDEBAR_WIDTH = 400;
|
|
14
|
+
|
|
15
|
+
export const BACKGROUND_COLOR = 'rgb(74, 74, 74)';
|
|
16
|
+
|
|
17
|
+
export const DEFAULT_MAX_ZOOM = 2;
|
|
18
|
+
|
|
19
|
+
export const DESIGNER_CLASSNAME = 'pdfme-designer-';
|
|
20
|
+
|
|
21
|
+
export const UI_CLASSNAME = 'pdfme-ui-';
|
package/src/contexts.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
import { i18n } from './i18n.js';
|
|
3
|
+
import { getDefaultFont, PluginRegistry, pluginRegistry, UIOptions } from '@pdfme/common';
|
|
4
|
+
import { builtInPlugins } from '@pdfme/schemas';
|
|
5
|
+
|
|
6
|
+
export const I18nContext = createContext(i18n);
|
|
7
|
+
|
|
8
|
+
export const FontContext = createContext(getDefaultFont());
|
|
9
|
+
|
|
10
|
+
export const PluginsRegistry = createContext<PluginRegistry>(pluginRegistry(builtInPlugins));
|
|
11
|
+
|
|
12
|
+
export const OptionsContext = createContext<UIOptions>({});
|
|
13
|
+
|
|
14
|
+
export const CacheContext = createContext<Map<string | number, unknown>>(new Map());
|