@pdfme/ui 1.0.0-beta.7 → 1.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/coverage/clover.xml +6 -0
- package/coverage/coverage-final.json +1 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +101 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov.info +0 -0
- package/dist/index.js +1 -1
- package/dist/index.js.LICENSE.txt +2 -2
- package/dist/index.js.map +1 -1
- package/dist/types/class.d.ts +5 -4
- package/dist/types/components/Designer/Main/Moveable.d.ts +1 -1
- package/dist/types/components/Designer/Sidebar/DetailView/ExampleInputEditor.d.ts +4 -1
- package/dist/types/components/Designer/Sidebar/DetailView/PositionAndSizeEditor.d.ts +4 -1
- package/dist/types/components/Designer/Sidebar/DetailView/TextPropEditor.d.ts +4 -1
- package/dist/types/components/Designer/Sidebar/DetailView/TypeAndKeyEditor.d.ts +4 -1
- package/dist/types/components/Designer/Sidebar/DetailView/index.d.ts +4 -1
- package/dist/types/components/Designer/Sidebar/ListView/Item.d.ts +25 -0
- package/dist/types/components/Designer/Sidebar/ListView/SelectableSortableContainer.d.ts +3 -0
- package/dist/types/components/Designer/Sidebar/ListView/SelectableSortableItem.d.ts +14 -0
- package/dist/types/components/Designer/Sidebar/{ListView.d.ts → ListView/index.d.ts} +2 -2
- package/dist/types/components/Designer/Sidebar/index.d.ts +2 -6
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/contexts.d.ts +1 -1
- package/dist/types/helper.d.ts +1 -1
- package/dist/types/hooks.d.ts +1 -0
- package/dist/types/i18n.d.ts +4 -2
- package/package.json +10 -7
- package/src/Designer.tsx +4 -2
- package/src/Form.tsx +1 -0
- package/src/Viewer.tsx +1 -0
- package/src/assets/icons/align-horizontal-center.svg +1 -0
- package/src/assets/icons/align-horizontal-left.svg +1 -0
- package/src/assets/icons/align-horizontal-right.svg +1 -0
- package/src/assets/icons/align-vertical-bottom.svg +1 -0
- package/src/assets/icons/align-vertical-middle.svg +1 -0
- package/src/assets/icons/align-vertical-top.svg +1 -0
- package/src/assets/icons/close.svg +4 -0
- package/src/assets/icons/horizontal-distribute.svg +1 -0
- package/src/assets/icons/vertical-distribute.svg +1 -0
- package/src/assets/imageExample.png +0 -0
- package/src/class.ts +27 -8
- package/src/components/Designer/Main/Moveable.tsx +1 -1
- package/src/components/Designer/Main/index.tsx +22 -16
- package/src/components/Designer/Sidebar/DetailView/ExampleInputEditor.tsx +28 -6
- package/src/components/Designer/Sidebar/DetailView/PositionAndSizeEditor.tsx +115 -24
- package/src/components/Designer/Sidebar/DetailView/TextPropEditor.tsx +36 -6
- package/src/components/Designer/Sidebar/DetailView/TypeAndKeyEditor.tsx +14 -6
- package/src/components/Designer/Sidebar/DetailView/index.tsx +21 -16
- package/src/components/Designer/Sidebar/ListView/Item.tsx +113 -0
- package/src/components/Designer/Sidebar/ListView/SelectableSortableContainer.tsx +162 -0
- package/src/components/Designer/Sidebar/ListView/SelectableSortableItem.tsx +78 -0
- package/src/components/Designer/Sidebar/ListView/index.tsx +119 -0
- package/src/components/Designer/Sidebar/index.tsx +30 -14
- package/src/components/Designer/index.tsx +12 -24
- package/src/components/Error.tsx +2 -2
- package/src/components/Paper.tsx +11 -3
- package/src/components/Preview/Pager/Page.tsx +1 -1
- package/src/components/Preview/Pager/Unit.tsx +1 -1
- package/src/components/Preview/index.tsx +3 -4
- package/src/components/Root.tsx +2 -7
- package/src/components/Schemas/BarcodeSchema.tsx +43 -28
- package/src/components/Schemas/ImageSchema.tsx +71 -66
- package/src/components/Schemas/TextSchema.tsx +6 -3
- package/src/constants.ts +2 -0
- package/src/helper.ts +43 -37
- package/src/hooks.ts +11 -0
- package/src/i18n.ts +10 -7
- package/tsconfig.json +1 -1
- package/webpack.config.js +0 -18
- package/src/components/Designer/Sidebar/ListView.tsx +0 -180
@@ -1,30 +1,128 @@
|
|
1
|
-
import React from 'react';
|
1
|
+
import React, { CSSProperties } from 'react';
|
2
|
+
import { SchemaForUI } from '@pdfme/common';
|
3
|
+
import { round } from '../../../../helper';
|
2
4
|
import { SidebarProps } from '..';
|
5
|
+
import alignVerticalTop from '../../../../assets/icons/align-vertical-top.svg';
|
6
|
+
import alignVerticalMiddle from '../../../../assets/icons/align-vertical-middle.svg';
|
7
|
+
import alignVerticalBottom from '../../../../assets/icons/align-vertical-bottom.svg';
|
8
|
+
import alignHorizontalRight from '../../../../assets/icons/align-horizontal-right.svg';
|
9
|
+
import alignHorizontalLeft from '../../../../assets/icons/align-horizontal-left.svg';
|
10
|
+
import alignHorizontalCenter from '../../../../assets/icons/align-horizontal-center.svg';
|
11
|
+
import verticalDistribute from '../../../../assets/icons/vertical-distribute.svg';
|
12
|
+
import horizontalDistribute from '../../../../assets/icons/horizontal-distribute.svg';
|
3
13
|
|
4
|
-
const inputSetStyle:
|
5
|
-
|
14
|
+
const inputSetStyle: CSSProperties = { marginRight: '1rem', display: 'flex', alignItems: 'center' };
|
15
|
+
|
16
|
+
const inputStyle: CSSProperties = {
|
17
|
+
width: 70,
|
18
|
+
border: '1px solid #767676',
|
19
|
+
borderRadius: 2,
|
20
|
+
color: '#333',
|
21
|
+
background: 'none',
|
22
|
+
};
|
23
|
+
|
24
|
+
const buttonStyle: CSSProperties = {
|
6
25
|
display: 'flex',
|
26
|
+
background: 'none',
|
7
27
|
alignItems: 'center',
|
28
|
+
borderRadius: 2,
|
29
|
+
border: '1px solid rgb(118, 118, 118)',
|
30
|
+
cursor: 'pointer',
|
8
31
|
};
|
9
32
|
|
10
33
|
const PositionAndSizeEditor = (
|
11
|
-
props: Pick<SidebarProps, 'pageSize' | 'changeSchemas' | '
|
34
|
+
props: Pick<SidebarProps, 'pageSize' | 'schemas' | 'changeSchemas' | 'activeElements'> & {
|
35
|
+
activeSchema: SchemaForUI;
|
36
|
+
}
|
12
37
|
) => {
|
13
|
-
const { changeSchemas, activeSchema, pageSize } = props;
|
38
|
+
const { changeSchemas, schemas, activeSchema, activeElements, pageSize } = props;
|
39
|
+
|
40
|
+
const align = (type: 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom') => {
|
41
|
+
const ids = activeElements.map((ae) => ae.id);
|
42
|
+
const ass = schemas.filter((s) => ids.includes(s.id));
|
43
|
+
|
44
|
+
const isVertical = ['left', 'center', 'right'].includes(type);
|
45
|
+
const tgtPos = isVertical ? 'x' : 'y';
|
46
|
+
const tgtSize = isVertical ? 'width' : 'height';
|
47
|
+
const isSingle = ass.length === 1;
|
48
|
+
const root = pageSize[tgtSize];
|
49
|
+
|
50
|
+
const min = isSingle ? 0 : Math.min(...ass.map((as) => as.position[tgtPos]));
|
51
|
+
const max = isSingle ? root : Math.max(...ass.map((as) => as.position[tgtPos] + as[tgtSize]));
|
52
|
+
|
53
|
+
let basePos = min;
|
54
|
+
let adjust = (_: number) => 0;
|
55
|
+
|
56
|
+
if (['center', 'middle'].includes(type)) {
|
57
|
+
basePos = (min + max) / 2;
|
58
|
+
adjust = (num: number) => num / 2;
|
59
|
+
} else if (['right', 'bottom'].includes(type)) {
|
60
|
+
basePos = max;
|
61
|
+
adjust = (num: number) => num;
|
62
|
+
}
|
63
|
+
|
64
|
+
changeSchemas(
|
65
|
+
ass.map((as) => ({
|
66
|
+
key: `position.${tgtPos}`,
|
67
|
+
value: round(basePos - adjust(as[tgtSize]), 2),
|
68
|
+
schemaId: as.id,
|
69
|
+
}))
|
70
|
+
);
|
71
|
+
};
|
72
|
+
|
73
|
+
const distribute = (type: 'vertical' | 'horizontal') => {
|
74
|
+
const ids = activeElements.map((ae) => ae.id);
|
75
|
+
const ass = schemas.filter((s) => ids.includes(s.id));
|
76
|
+
|
77
|
+
const isVertical = type === 'vertical';
|
78
|
+
const tgtPos = isVertical ? 'y' : 'x';
|
79
|
+
const tgtSize = isVertical ? 'height' : 'width';
|
80
|
+
const min = Math.min(...ass.map((as) => as.position[tgtPos]));
|
81
|
+
const max = Math.max(...ass.map((as) => as.position[tgtPos] + as[tgtSize]));
|
82
|
+
|
83
|
+
if (ass.length < 3) return;
|
84
|
+
|
85
|
+
const boxPos = min;
|
86
|
+
const boxSize = max - min;
|
87
|
+
const sum = ass.reduce((acc, cur) => acc + cur[tgtSize], 0);
|
88
|
+
const remain = boxSize - sum;
|
89
|
+
const unit = remain / (ass.length - 1);
|
90
|
+
|
91
|
+
let prev = 0;
|
92
|
+
changeSchemas(
|
93
|
+
ass.map((as, index) => {
|
94
|
+
prev += index === 0 ? 0 : ass[index - 1][tgtSize] + unit;
|
95
|
+
const value = round(boxPos + prev, 2);
|
96
|
+
return { key: `position.${tgtPos}`, value, schemaId: as.id };
|
97
|
+
})
|
98
|
+
);
|
99
|
+
};
|
100
|
+
|
101
|
+
const layoutBtns: { id: string; icon: any; action: () => void }[] = [
|
102
|
+
{ id: 'left', icon: alignHorizontalLeft, action: () => align('left') },
|
103
|
+
{ id: 'center', icon: alignHorizontalCenter, action: () => align('center') },
|
104
|
+
{ id: 'right', icon: alignHorizontalRight, action: () => align('right') },
|
105
|
+
{ id: 'top', icon: alignVerticalTop, action: () => align('top') },
|
106
|
+
{ id: 'middle', icon: alignVerticalMiddle, action: () => align('middle') },
|
107
|
+
{ id: 'bottom', icon: alignVerticalBottom, action: () => align('bottom') },
|
108
|
+
{ id: 'vertical', icon: verticalDistribute, action: () => distribute('vertical') },
|
109
|
+
{ id: 'horizontal', icon: horizontalDistribute, action: () => distribute('horizontal') },
|
110
|
+
];
|
14
111
|
|
15
112
|
return (
|
16
113
|
<div>
|
17
|
-
<div
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
}
|
23
|
-
>
|
114
|
+
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '0.5rem' }}>
|
115
|
+
{layoutBtns.map((b) => (
|
116
|
+
<button key={b.id} title={b.id} onClick={b.action} style={buttonStyle}>
|
117
|
+
<img width={15} src={b.icon} />
|
118
|
+
</button>
|
119
|
+
))}
|
120
|
+
</div>
|
121
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
24
122
|
<div style={inputSetStyle}>
|
25
123
|
<label style={{ width: 17 }}>X</label>
|
26
124
|
<input
|
27
|
-
style={
|
125
|
+
style={inputStyle}
|
28
126
|
type="number"
|
29
127
|
onChange={(e) => {
|
30
128
|
const value = Number(e.target.value);
|
@@ -39,7 +137,7 @@ const PositionAndSizeEditor = (
|
|
39
137
|
<div style={inputSetStyle}>
|
40
138
|
<label style={{ width: 17 }}>Y</label>
|
41
139
|
<input
|
42
|
-
style={
|
140
|
+
style={inputStyle}
|
43
141
|
type="number"
|
44
142
|
onChange={(e) => {
|
45
143
|
const value = Number(e.target.value);
|
@@ -52,18 +150,11 @@ const PositionAndSizeEditor = (
|
|
52
150
|
<span style={{ fontSize: '0.6rem' }}>mm</span>
|
53
151
|
</div>
|
54
152
|
</div>
|
55
|
-
<div
|
56
|
-
style={{
|
57
|
-
display: 'flex',
|
58
|
-
alignItems: 'center',
|
59
|
-
justifyContent: 'space-between',
|
60
|
-
marginTop: '0.25rem',
|
61
|
-
}}
|
62
|
-
>
|
153
|
+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
63
154
|
<div style={inputSetStyle}>
|
64
155
|
<label style={{ width: 17 }}>W</label>
|
65
156
|
<input
|
66
|
-
style={
|
157
|
+
style={inputStyle}
|
67
158
|
type="number"
|
68
159
|
onChange={(e) => {
|
69
160
|
const value = Number(e.target.value);
|
@@ -78,7 +169,7 @@ const PositionAndSizeEditor = (
|
|
78
169
|
<div style={inputSetStyle}>
|
79
170
|
<label style={{ width: 17 }}>H</label>
|
80
171
|
<input
|
81
|
-
style={
|
172
|
+
style={inputStyle}
|
82
173
|
type="number"
|
83
174
|
onChange={(e) => {
|
84
175
|
const value = Number(e.target.value);
|
@@ -1,12 +1,24 @@
|
|
1
1
|
import React, { useContext } from 'react';
|
2
2
|
import {
|
3
|
+
SchemaForUI,
|
3
4
|
getFallbackFontName,
|
4
5
|
DEFAULT_FONT_SIZE,
|
5
6
|
DEFAULT_LINE_HEIGHT,
|
6
7
|
DEFAULT_CHARACTER_SPACING,
|
8
|
+
DEFAULT_FONT_COLOR,
|
7
9
|
} from '@pdfme/common';
|
8
10
|
import { FontContext } from '../../../../contexts';
|
9
11
|
import { SidebarProps } from '..';
|
12
|
+
import closeIcon from '../../../../assets/icons/close.svg';
|
13
|
+
|
14
|
+
const inputStyle = {
|
15
|
+
width: '90%',
|
16
|
+
color: '#333',
|
17
|
+
background: 'none',
|
18
|
+
borderRadius: 2,
|
19
|
+
border: '1px solid #767676',
|
20
|
+
};
|
21
|
+
const selectStyle = inputStyle;
|
10
22
|
|
11
23
|
const NumberInputSet = (props: {
|
12
24
|
width: string;
|
@@ -19,7 +31,7 @@ const NumberInputSet = (props: {
|
|
19
31
|
return (
|
20
32
|
<div style={{ width }}>
|
21
33
|
<label>{label}</label>
|
22
|
-
<input style={
|
34
|
+
<input style={inputStyle} onChange={onChange} value={value} type="number" />
|
23
35
|
</div>
|
24
36
|
);
|
25
37
|
};
|
@@ -36,8 +48,20 @@ const ColorInputSet = (props: {
|
|
36
48
|
<div style={{ width: '45%' }}>
|
37
49
|
<label>{label}</label>
|
38
50
|
<div style={{ display: 'flex' }}>
|
39
|
-
<input onChange={onChange} value={value} type="color" style={
|
40
|
-
<button
|
51
|
+
<input onChange={onChange} value={value || '#ffffff'} type="color" style={inputStyle} />
|
52
|
+
<button
|
53
|
+
onClick={onClear}
|
54
|
+
style={{
|
55
|
+
display: 'flex',
|
56
|
+
background: 'none',
|
57
|
+
alignItems: 'center',
|
58
|
+
borderRadius: 2,
|
59
|
+
border: '1px solid #767676',
|
60
|
+
cursor: 'pointer',
|
61
|
+
}}
|
62
|
+
>
|
63
|
+
<img src={closeIcon} width={10} alt="Close icon" />
|
64
|
+
</button>
|
41
65
|
</div>
|
42
66
|
</div>
|
43
67
|
);
|
@@ -54,7 +78,7 @@ const SelectSet = (props: {
|
|
54
78
|
return (
|
55
79
|
<div style={{ width: '45%' }}>
|
56
80
|
<label>{label}:</label>
|
57
|
-
<select style={
|
81
|
+
<select style={selectStyle} onChange={onChange} value={value}>
|
58
82
|
{options.map((o) => (
|
59
83
|
<option key={o} value={o}>
|
60
84
|
{o}
|
@@ -65,7 +89,9 @@ const SelectSet = (props: {
|
|
65
89
|
);
|
66
90
|
};
|
67
91
|
|
68
|
-
const TextPropEditor = (
|
92
|
+
const TextPropEditor = (
|
93
|
+
props: Pick<SidebarProps, 'changeSchemas'> & { activeSchema: SchemaForUI }
|
94
|
+
) => {
|
69
95
|
const { changeSchemas, activeSchema } = props;
|
70
96
|
const alignments = ['left', 'center', 'right'];
|
71
97
|
const font = useContext(FontContext);
|
@@ -80,6 +106,7 @@ const TextPropEditor = (props: Pick<SidebarProps, 'changeSchemas' | 'activeSchem
|
|
80
106
|
display: 'flex',
|
81
107
|
alignItems: 'center',
|
82
108
|
justifyContent: 'space-between',
|
109
|
+
marginBottom: '0.25rem',
|
83
110
|
}}
|
84
111
|
>
|
85
112
|
<SelectSet
|
@@ -105,6 +132,7 @@ const TextPropEditor = (props: Pick<SidebarProps, 'changeSchemas' | 'activeSchem
|
|
105
132
|
display: 'flex',
|
106
133
|
alignItems: 'center',
|
107
134
|
justifyContent: 'space-between',
|
135
|
+
marginBottom: '0.25rem',
|
108
136
|
}}
|
109
137
|
>
|
110
138
|
<NumberInputSet
|
@@ -154,7 +182,9 @@ const TextPropEditor = (props: Pick<SidebarProps, 'changeSchemas' | 'activeSchem
|
|
154
182
|
changeSchemas([{ key: 'fontColor', value: e.target.value, schemaId: activeSchema.id }])
|
155
183
|
}
|
156
184
|
onClear={() =>
|
157
|
-
changeSchemas([
|
185
|
+
changeSchemas([
|
186
|
+
{ key: 'fontColor', value: DEFAULT_FONT_COLOR, schemaId: activeSchema.id },
|
187
|
+
])
|
158
188
|
}
|
159
189
|
/>
|
160
190
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import React, { useContext, useRef, useCallback } from 'react';
|
2
|
-
import { schemaTypes } from '@pdfme/common';
|
2
|
+
import { schemaTypes, SchemaForUI } from '@pdfme/common';
|
3
3
|
import { SidebarProps } from '..';
|
4
4
|
import { I18nContext } from '../../../../contexts';
|
5
5
|
|
@@ -12,7 +12,7 @@ const ErrorLabel = ({ isError, msg }: { isError: boolean; msg: string }) => (
|
|
12
12
|
);
|
13
13
|
|
14
14
|
const TypeAndKeyEditor = (
|
15
|
-
props: Pick<SidebarProps, 'schemas' | 'changeSchemas'
|
15
|
+
props: Pick<SidebarProps, 'schemas' | 'changeSchemas'> & { activeSchema: SchemaForUI }
|
16
16
|
) => {
|
17
17
|
const { changeSchemas, activeSchema, schemas } = props;
|
18
18
|
const i18n = useContext(I18nContext);
|
@@ -37,7 +37,13 @@ const TypeAndKeyEditor = (
|
|
37
37
|
<div>
|
38
38
|
<label style={{ marginBottom: 0 }}>{i18n('type')}</label>
|
39
39
|
<select
|
40
|
-
style={{
|
40
|
+
style={{
|
41
|
+
width: '100%',
|
42
|
+
border: '1px solid #767676',
|
43
|
+
borderRadius: 2,
|
44
|
+
color: '#333',
|
45
|
+
background: 'none',
|
46
|
+
}}
|
41
47
|
onChange={(e) =>
|
42
48
|
changeSchemas([{ key: 'type', value: e.target.value, schemaId: activeSchema.id }])
|
43
49
|
}
|
@@ -50,7 +56,7 @@ const TypeAndKeyEditor = (
|
|
50
56
|
))}
|
51
57
|
</select>
|
52
58
|
</div>
|
53
|
-
<div
|
59
|
+
<div>
|
54
60
|
<label style={{ marginBottom: 0 }}>
|
55
61
|
{i18n('fieldName')}
|
56
62
|
<u style={{ fontSize: '0.7rem' }}>
|
@@ -65,9 +71,11 @@ const TypeAndKeyEditor = (
|
|
65
71
|
changeSchemas([{ key: 'key', value: e.target.value, schemaId: activeSchema.id }])
|
66
72
|
}
|
67
73
|
style={{
|
68
|
-
|
74
|
+
width: '100%',
|
69
75
|
border: '1px solid #767676',
|
70
|
-
|
76
|
+
borderRadius: 2,
|
77
|
+
color: '#333',
|
78
|
+
background: hasSameKey || blankKey ? '#ffa19b' : 'none',
|
71
79
|
}}
|
72
80
|
value={activeSchema.key}
|
73
81
|
/>
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import React, { useContext } from 'react';
|
2
|
+
import { SchemaForUI } from '@pdfme/common';
|
2
3
|
import { I18nContext } from '../../../../contexts';
|
3
4
|
import Divider from '../../../Divider';
|
4
5
|
import { SidebarProps } from '..';
|
@@ -8,31 +9,35 @@ import PositionAndSizeEditor from './PositionAndSizeEditor';
|
|
8
9
|
import TypeAndKeyEditor from './TypeAndKeyEditor';
|
9
10
|
|
10
11
|
const DetailView = (
|
11
|
-
props: Pick<SidebarProps, 'schemas' | 'pageSize' | 'changeSchemas' | '
|
12
|
+
props: Pick<SidebarProps, 'schemas' | 'pageSize' | 'changeSchemas' | 'activeElements'> & {
|
13
|
+
activeSchema: SchemaForUI;
|
14
|
+
}
|
12
15
|
) => {
|
13
16
|
const { activeSchema } = props;
|
14
17
|
const i18n = useContext(I18nContext);
|
15
18
|
|
16
19
|
return (
|
17
|
-
<
|
20
|
+
<div>
|
18
21
|
<div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
|
19
|
-
<
|
22
|
+
<span style={{ textAlign: 'center', width: '100%', fontWeight: 'bold' }}>
|
20
23
|
{i18n('editField')}
|
21
|
-
</
|
24
|
+
</span>
|
22
25
|
</div>
|
23
26
|
<Divider />
|
24
|
-
<
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
27
|
+
<div style={{ fontSize: '0.9rem' }}>
|
28
|
+
<TypeAndKeyEditor {...props} />
|
29
|
+
<Divider />
|
30
|
+
<PositionAndSizeEditor {...props} />
|
31
|
+
<Divider />
|
32
|
+
{activeSchema.type === 'text' && (
|
33
|
+
<>
|
34
|
+
<TextPropEditor {...props} />
|
35
|
+
<Divider />
|
36
|
+
</>
|
37
|
+
)}
|
38
|
+
<ExampleInputEditor {...props} />
|
39
|
+
</div>
|
40
|
+
</div>
|
36
41
|
);
|
37
42
|
};
|
38
43
|
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import React, { useEffect, useContext } from 'react';
|
2
|
+
import { DraggableSyntheticListeners } from '@dnd-kit/core';
|
3
|
+
import { I18nContext } from '../../../../contexts';
|
4
|
+
import dragIcon from '../../../../assets/icons/drag.svg';
|
5
|
+
import warningIcon from '../../../../assets/icons/warning.svg';
|
6
|
+
|
7
|
+
interface Props {
|
8
|
+
value: React.ReactNode;
|
9
|
+
style?: React.CSSProperties;
|
10
|
+
status?: 'is-warning' | 'is-danger';
|
11
|
+
title?: string;
|
12
|
+
dragOverlay?: boolean;
|
13
|
+
onClick?: () => void;
|
14
|
+
onMouseEnter?: () => void;
|
15
|
+
onMouseLeave?: () => void;
|
16
|
+
dragging?: boolean;
|
17
|
+
sorting?: boolean;
|
18
|
+
transition?: string;
|
19
|
+
transform?: { x: number; y: number; scaleX: number; scaleY: number } | null;
|
20
|
+
fadeIn?: boolean;
|
21
|
+
listeners?: DraggableSyntheticListeners;
|
22
|
+
}
|
23
|
+
const Item = React.memo(
|
24
|
+
React.forwardRef<HTMLLIElement, Props>(
|
25
|
+
(
|
26
|
+
{
|
27
|
+
value,
|
28
|
+
status,
|
29
|
+
title,
|
30
|
+
style,
|
31
|
+
dragOverlay,
|
32
|
+
onClick,
|
33
|
+
onMouseEnter,
|
34
|
+
onMouseLeave,
|
35
|
+
dragging,
|
36
|
+
fadeIn,
|
37
|
+
listeners,
|
38
|
+
sorting,
|
39
|
+
transition,
|
40
|
+
transform,
|
41
|
+
...props
|
42
|
+
},
|
43
|
+
ref
|
44
|
+
) => {
|
45
|
+
const i18n = useContext(I18nContext);
|
46
|
+
|
47
|
+
useEffect(() => {
|
48
|
+
if (!dragOverlay) {
|
49
|
+
return;
|
50
|
+
}
|
51
|
+
|
52
|
+
document.body.style.cursor = 'grabbing';
|
53
|
+
|
54
|
+
return () => {
|
55
|
+
document.body.style.cursor = '';
|
56
|
+
};
|
57
|
+
}, [dragOverlay]);
|
58
|
+
|
59
|
+
const { x, y, scaleX, scaleY } = transform || { x: 0, y: 0, scaleX: 1, scaleY: 1 };
|
60
|
+
|
61
|
+
return (
|
62
|
+
<li
|
63
|
+
style={{
|
64
|
+
transition,
|
65
|
+
transform: `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`,
|
66
|
+
}}
|
67
|
+
onMouseEnter={onMouseEnter}
|
68
|
+
onMouseLeave={onMouseLeave}
|
69
|
+
ref={ref}
|
70
|
+
>
|
71
|
+
<div style={{ display: 'flex', alignItems: 'center', ...style }} {...props}>
|
72
|
+
<button
|
73
|
+
{...listeners}
|
74
|
+
style={{ padding: '0.5rem', background: 'none', border: 'none', display: 'flex' }}
|
75
|
+
>
|
76
|
+
<img style={{ cursor: 'grab' }} src={dragIcon} width={15} alt="Drag icon" />
|
77
|
+
</button>
|
78
|
+
<div
|
79
|
+
style={{
|
80
|
+
width: '100%',
|
81
|
+
padding: '0.5rem',
|
82
|
+
paddingLeft: 0,
|
83
|
+
cursor: 'pointer',
|
84
|
+
overflow: 'hidden',
|
85
|
+
whiteSpace: 'nowrap',
|
86
|
+
textOverflow: 'ellipsis',
|
87
|
+
}}
|
88
|
+
title={title || ''}
|
89
|
+
onClick={() => onClick && onClick()}
|
90
|
+
>
|
91
|
+
{status === undefined ? (
|
92
|
+
value
|
93
|
+
) : (
|
94
|
+
<span style={{ display: 'flex', alignItems: 'center' }}>
|
95
|
+
<img
|
96
|
+
alt="Warning icon"
|
97
|
+
src={warningIcon}
|
98
|
+
width={15}
|
99
|
+
style={{ marginRight: '0.5rem' }}
|
100
|
+
/>
|
101
|
+
{status === 'is-warning' ? i18n('noKeyName') : value}
|
102
|
+
{status === 'is-danger' ? i18n('notUniq') : ''}
|
103
|
+
</span>
|
104
|
+
)}
|
105
|
+
</div>
|
106
|
+
</div>
|
107
|
+
</li>
|
108
|
+
);
|
109
|
+
}
|
110
|
+
)
|
111
|
+
);
|
112
|
+
|
113
|
+
export default Item;
|
@@ -0,0 +1,162 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import { createPortal } from 'react-dom';
|
3
|
+
import {
|
4
|
+
closestCorners,
|
5
|
+
DndContext,
|
6
|
+
DragOverlay,
|
7
|
+
KeyboardSensor,
|
8
|
+
PointerSensor,
|
9
|
+
useSensors,
|
10
|
+
useSensor,
|
11
|
+
} from '@dnd-kit/core';
|
12
|
+
import {
|
13
|
+
SortableContext,
|
14
|
+
arrayMove,
|
15
|
+
sortableKeyboardCoordinates,
|
16
|
+
verticalListSortingStrategy,
|
17
|
+
} from '@dnd-kit/sortable';
|
18
|
+
import { SchemaForUI } from '@pdfme/common';
|
19
|
+
import Item from './Item';
|
20
|
+
import SelectableSortableItem from './SelectableSortableItem';
|
21
|
+
import { SidebarProps } from '../';
|
22
|
+
|
23
|
+
const SelectableSortableContainer = (
|
24
|
+
props: Pick<
|
25
|
+
SidebarProps,
|
26
|
+
'schemas' | 'onEdit' | 'onSortEnd' | 'height' | 'hoveringSchemaId' | 'onChangeHoveringSchemaId'
|
27
|
+
>
|
28
|
+
) => {
|
29
|
+
const { schemas, onEdit, onSortEnd, height, hoveringSchemaId, onChangeHoveringSchemaId } = props;
|
30
|
+
const [selectedSchemas, setSelectedSchemas] = useState<SchemaForUI[]>([]);
|
31
|
+
const [dragOverlaydItems, setClonedItems] = useState<SchemaForUI[] | null>(null);
|
32
|
+
const [activeId, setActiveId] = useState<string | null>(null);
|
33
|
+
const sensors = useSensors(
|
34
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 15 } }),
|
35
|
+
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
36
|
+
);
|
37
|
+
|
38
|
+
const isItemSelected = (itemId: string): boolean =>
|
39
|
+
selectedSchemas.map((i) => i.id).includes(itemId);
|
40
|
+
|
41
|
+
const onSelectionChanged = (id: string, isShiftSelect: boolean) => {
|
42
|
+
if (isShiftSelect) {
|
43
|
+
if (isItemSelected(id)) {
|
44
|
+
const newSelectedSchemas = selectedSchemas.filter((item) => item.id !== id);
|
45
|
+
setSelectedSchemas(newSelectedSchemas);
|
46
|
+
} else {
|
47
|
+
const newSelectedItem = schemas.find((schema) => schema.id === id)!;
|
48
|
+
const newSelectedSchemas = selectedSchemas.concat(newSelectedItem);
|
49
|
+
setSelectedSchemas(newSelectedSchemas);
|
50
|
+
}
|
51
|
+
} else {
|
52
|
+
setSelectedSchemas([]);
|
53
|
+
}
|
54
|
+
};
|
55
|
+
|
56
|
+
return (
|
57
|
+
<DndContext
|
58
|
+
sensors={sensors}
|
59
|
+
collisionDetection={closestCorners}
|
60
|
+
onDragStart={({ active }) => {
|
61
|
+
setActiveId(active.id);
|
62
|
+
setClonedItems(schemas);
|
63
|
+
|
64
|
+
if (!isItemSelected(active.id)) {
|
65
|
+
const newSelectedSchemas: SchemaForUI[] = [];
|
66
|
+
setSelectedSchemas(newSelectedSchemas);
|
67
|
+
} else if (selectedSchemas.length > 0) {
|
68
|
+
onSortEnd(
|
69
|
+
selectedSchemas.reduce((ret, selectedItem) => {
|
70
|
+
if (selectedItem.id === active.id) {
|
71
|
+
return ret;
|
72
|
+
}
|
73
|
+
return ret.filter((schema) => schema !== selectedItem);
|
74
|
+
}, schemas)
|
75
|
+
);
|
76
|
+
}
|
77
|
+
}}
|
78
|
+
onDragEnd={({ active, over }) => {
|
79
|
+
const overId = over?.id || '';
|
80
|
+
|
81
|
+
const activeIndex = schemas.map((i) => i.id).indexOf(active.id);
|
82
|
+
const overIndex = schemas.map((i) => i.id).indexOf(overId);
|
83
|
+
|
84
|
+
if (selectedSchemas.length) {
|
85
|
+
let newSchemas = [...schemas];
|
86
|
+
newSchemas = arrayMove(newSchemas, activeIndex, overIndex);
|
87
|
+
newSchemas.splice(
|
88
|
+
overIndex + 1,
|
89
|
+
0,
|
90
|
+
...selectedSchemas.filter((item) => item.id !== activeId)
|
91
|
+
);
|
92
|
+
onSortEnd(newSchemas);
|
93
|
+
setSelectedSchemas([]);
|
94
|
+
} else if (activeIndex !== overIndex) {
|
95
|
+
onSortEnd(arrayMove(schemas, activeIndex, overIndex));
|
96
|
+
}
|
97
|
+
|
98
|
+
setActiveId(null);
|
99
|
+
}}
|
100
|
+
onDragCancel={() => {
|
101
|
+
if (dragOverlaydItems) {
|
102
|
+
onSortEnd(dragOverlaydItems);
|
103
|
+
}
|
104
|
+
|
105
|
+
setActiveId(null);
|
106
|
+
setClonedItems(null);
|
107
|
+
}}
|
108
|
+
>
|
109
|
+
<div style={{ height, overflowY: 'auto' }}>
|
110
|
+
<SortableContext items={schemas} strategy={verticalListSortingStrategy}>
|
111
|
+
<ul style={{ margin: 0, padding: 0, listStyle: 'none', borderRadius: 5 }}>
|
112
|
+
{schemas.map((schema) => (
|
113
|
+
<SelectableSortableItem
|
114
|
+
key={schema.id}
|
115
|
+
style={{
|
116
|
+
border: `1px solid ${schema.id === hoveringSchemaId ? '#18a0fb' : 'transparent'}`,
|
117
|
+
}}
|
118
|
+
schema={schema}
|
119
|
+
schemas={schemas}
|
120
|
+
isSelected={isItemSelected(schema.id) || activeId === schema.id}
|
121
|
+
onEdit={onEdit}
|
122
|
+
onSelect={onSelectionChanged}
|
123
|
+
onMouseEnter={() => onChangeHoveringSchemaId(schema.id)}
|
124
|
+
onMouseLeave={() => onChangeHoveringSchemaId(null)}
|
125
|
+
/>
|
126
|
+
))}
|
127
|
+
</ul>
|
128
|
+
</SortableContext>
|
129
|
+
</div>
|
130
|
+
{createPortal(
|
131
|
+
<DragOverlay adjustScale>
|
132
|
+
{activeId ? (
|
133
|
+
<>
|
134
|
+
<ul style={{ margin: 0, padding: 0, listStyle: 'none' }}>
|
135
|
+
<Item
|
136
|
+
value={schemas.find((schema) => schema.id === activeId)!.key}
|
137
|
+
style={{ color: '#fff', background: '#18a0fb' }}
|
138
|
+
dragOverlay
|
139
|
+
/>
|
140
|
+
</ul>
|
141
|
+
<ul style={{ margin: 0, padding: 0, listStyle: 'none' }}>
|
142
|
+
{selectedSchemas
|
143
|
+
.filter((item) => item.id !== activeId)
|
144
|
+
.map((item) => (
|
145
|
+
<Item
|
146
|
+
key={item.id}
|
147
|
+
value={item.key}
|
148
|
+
style={{ color: '#fff', background: '#18a0fb' }}
|
149
|
+
dragOverlay
|
150
|
+
/>
|
151
|
+
))}
|
152
|
+
</ul>
|
153
|
+
</>
|
154
|
+
) : null}
|
155
|
+
</DragOverlay>,
|
156
|
+
document.body
|
157
|
+
)}
|
158
|
+
</DndContext>
|
159
|
+
);
|
160
|
+
};
|
161
|
+
|
162
|
+
export default SelectableSortableContainer;
|