@pdfme/ui 1.0.0-beta.8 → 1.0.1

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.
Files changed (52) hide show
  1. package/README.md +0 -2
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.LICENSE.txt +2 -2
  4. package/dist/index.js.map +1 -1
  5. package/dist/types/class.d.ts +5 -4
  6. package/dist/types/components/Designer/Sidebar/DetailView/ExampleInputEditor.d.ts +4 -1
  7. package/dist/types/components/Designer/Sidebar/DetailView/PositionAndSizeEditor.d.ts +4 -1
  8. package/dist/types/components/Designer/Sidebar/DetailView/TextPropEditor.d.ts +4 -1
  9. package/dist/types/components/Designer/Sidebar/DetailView/TypeAndKeyEditor.d.ts +4 -1
  10. package/dist/types/components/Designer/Sidebar/DetailView/index.d.ts +4 -1
  11. package/dist/types/components/Designer/Sidebar/ListView/Item.d.ts +25 -0
  12. package/dist/types/components/Designer/Sidebar/ListView/SelectableSortableContainer.d.ts +3 -0
  13. package/dist/types/components/Designer/Sidebar/ListView/SelectableSortableItem.d.ts +14 -0
  14. package/dist/types/components/Designer/Sidebar/ListView/index.d.ts +3 -0
  15. package/dist/types/components/Designer/Sidebar/index.d.ts +2 -7
  16. package/dist/types/contexts.d.ts +1 -1
  17. package/dist/types/helper.d.ts +1 -1
  18. package/dist/types/hooks.d.ts +1 -0
  19. package/dist/types/i18n.d.ts +5 -2
  20. package/package.json +4 -3
  21. package/src/Designer.tsx +2 -1
  22. package/src/assets/icons/align-horizontal-center.svg +1 -0
  23. package/src/assets/icons/align-horizontal-left.svg +1 -0
  24. package/src/assets/icons/align-horizontal-right.svg +1 -0
  25. package/src/assets/icons/align-vertical-bottom.svg +1 -0
  26. package/src/assets/icons/align-vertical-middle.svg +1 -0
  27. package/src/assets/icons/align-vertical-top.svg +1 -0
  28. package/src/assets/icons/horizontal-distribute.svg +1 -0
  29. package/src/assets/icons/vertical-distribute.svg +1 -0
  30. package/src/class.ts +22 -2
  31. package/src/components/Designer/Main/index.tsx +16 -8
  32. package/src/components/Designer/Sidebar/DetailView/ExampleInputEditor.tsx +4 -1
  33. package/src/components/Designer/Sidebar/DetailView/PositionAndSizeEditor.tsx +107 -24
  34. package/src/components/Designer/Sidebar/DetailView/TextPropEditor.tsx +4 -1
  35. package/src/components/Designer/Sidebar/DetailView/TypeAndKeyEditor.tsx +2 -2
  36. package/src/components/Designer/Sidebar/DetailView/index.tsx +6 -3
  37. package/src/components/Designer/Sidebar/ListView/Item.tsx +113 -0
  38. package/src/components/Designer/Sidebar/ListView/SelectableSortableContainer.tsx +162 -0
  39. package/src/components/Designer/Sidebar/ListView/SelectableSortableItem.tsx +78 -0
  40. package/src/components/Designer/Sidebar/ListView/index.tsx +119 -0
  41. package/src/components/Designer/Sidebar/index.tsx +22 -8
  42. package/src/components/Designer/index.tsx +8 -22
  43. package/src/components/Error.tsx +2 -2
  44. package/src/components/Paper.tsx +10 -0
  45. package/src/components/Preview/index.tsx +1 -1
  46. package/src/components/Schemas/BarcodeSchema.tsx +13 -13
  47. package/src/components/Schemas/TextSchema.tsx +5 -2
  48. package/src/helper.ts +12 -10
  49. package/src/hooks.ts +11 -0
  50. package/src/i18n.ts +12 -7
  51. package/dist/types/components/Designer/Sidebar/ListView.d.ts +0 -3
  52. package/src/components/Designer/Sidebar/ListView.tsx +0 -202
@@ -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;
@@ -0,0 +1,78 @@
1
+ import React, { useContext } from 'react';
2
+ import { useSortable } from '@dnd-kit/sortable';
3
+ import { SchemaForUI } from '@pdfme/common';
4
+ import { I18nContext } from '../../../../contexts';
5
+ import Item from './Item';
6
+ import { useMountStatus } from '../../../../hooks';
7
+
8
+ interface Props {
9
+ isSelected: boolean;
10
+ style?: React.CSSProperties;
11
+ onSelect: (id: string, isShiftSelect: boolean) => void;
12
+ onEdit: (id: string) => void;
13
+ schema: SchemaForUI;
14
+ schemas: SchemaForUI[];
15
+ onMouseEnter: () => void;
16
+ onMouseLeave: () => void;
17
+ }
18
+ const SelectableSortableItem = ({
19
+ isSelected,
20
+ style,
21
+ onSelect,
22
+ onEdit,
23
+ schema,
24
+ schemas,
25
+ onMouseEnter,
26
+ onMouseLeave,
27
+ }: Props) => {
28
+ const i18n = useContext(I18nContext);
29
+ const { setNodeRef, listeners, isDragging, isSorting, transform, transition } = useSortable({
30
+ id: schema.id,
31
+ });
32
+ const mounted = useMountStatus();
33
+ const mountedWhileDragging = isDragging && !mounted;
34
+
35
+ const newListeners = {
36
+ ...listeners,
37
+ onClick: (event: any) => onSelect(schema.id, event.shiftKey),
38
+ };
39
+
40
+ let status: undefined | 'is-warning' | 'is-danger';
41
+ if (!schema.key) {
42
+ status = 'is-warning';
43
+ } else if (schemas.find((s) => schema.key && s.key === schema.key && s.id !== schema.id)) {
44
+ status = 'is-danger';
45
+ }
46
+
47
+ let title = i18n('edit');
48
+ if (status === 'is-warning') {
49
+ title = i18n('plsInputName');
50
+ } else if (status === 'is-danger') {
51
+ title = i18n('fieldMustUniq');
52
+ }
53
+
54
+ const selectedStyle = isSelected
55
+ ? { color: '#fff', background: '#18a0fb', opacity: isSorting || isDragging ? 0.5 : 1 }
56
+ : ({} as React.CSSProperties);
57
+
58
+ return (
59
+ <Item
60
+ ref={setNodeRef}
61
+ onMouseEnter={onMouseEnter}
62
+ onMouseLeave={onMouseLeave}
63
+ onClick={() => onEdit(schema.id)}
64
+ value={schema.key}
65
+ status={status}
66
+ title={title}
67
+ style={{ ...selectedStyle, ...style }}
68
+ dragging={isDragging}
69
+ sorting={isSorting}
70
+ transition={transition}
71
+ transform={transform}
72
+ fadeIn={mountedWhileDragging}
73
+ listeners={newListeners}
74
+ />
75
+ );
76
+ };
77
+
78
+ export default SelectableSortableItem;
@@ -0,0 +1,119 @@
1
+ import React, { useContext, useState } from 'react';
2
+ import { ZOOM, RULER_HEIGHT, SIDEBAR_WIDTH } from '../../../../constants';
3
+ import { I18nContext } from '../../../../contexts';
4
+ import Divider from '../../../Divider';
5
+ import SelectableSortableContainer from './SelectableSortableContainer';
6
+ import { SidebarProps } from '..';
7
+
8
+ const ListView = (
9
+ props: Pick<
10
+ SidebarProps,
11
+ | 'schemas'
12
+ | 'onSortEnd'
13
+ | 'onEdit'
14
+ | 'size'
15
+ | 'hoveringSchemaId'
16
+ | 'onChangeHoveringSchemaId'
17
+ | 'changeSchemas'
18
+ >
19
+ ) => {
20
+ const {
21
+ schemas,
22
+ onSortEnd,
23
+ onEdit,
24
+ size,
25
+ hoveringSchemaId,
26
+ onChangeHoveringSchemaId,
27
+ changeSchemas,
28
+ } = props;
29
+ const i18n = useContext(I18nContext);
30
+ const [isBulkUpdateFieldNamesMode, setIsBulkUpdateFieldNamesMode] = useState(false);
31
+ const [fieldNamesValue, setFieldNamesValue] = useState('');
32
+ const height = size.height - RULER_HEIGHT * ZOOM - 135;
33
+ return (
34
+ <div>
35
+ <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
36
+ <span style={{ textAlign: 'center', width: '100%', fontWeight: 'bold' }}>
37
+ {i18n('fieldsList')}
38
+ </span>
39
+ </div>
40
+ <Divider />
41
+ {isBulkUpdateFieldNamesMode ? (
42
+ <div>
43
+ <textarea
44
+ wrap="off"
45
+ value={fieldNamesValue}
46
+ onChange={(e) => setFieldNamesValue(e.target.value)}
47
+ style={{
48
+ height: height - 5,
49
+ width: SIDEBAR_WIDTH,
50
+ fontSize: '1rem',
51
+ lineHeight: '2.5rem',
52
+ background: 'transparent',
53
+ margin: 0,
54
+ padding: '1rem',
55
+ boxSizing: 'border-box',
56
+ fontFamily: 'inherit',
57
+ }}
58
+ ></textarea>
59
+ </div>
60
+ ) : (
61
+ <SelectableSortableContainer
62
+ height={height}
63
+ schemas={schemas}
64
+ hoveringSchemaId={hoveringSchemaId}
65
+ onChangeHoveringSchemaId={onChangeHoveringSchemaId}
66
+ onSortEnd={onSortEnd}
67
+ onEdit={onEdit}
68
+ />
69
+ )}
70
+
71
+ <div
72
+ style={{
73
+ display: 'flex',
74
+ justifyContent: 'flex-end',
75
+ cursor: 'pointer',
76
+ fontSize: '0.75rem',
77
+ }}
78
+ >
79
+ {isBulkUpdateFieldNamesMode ? (
80
+ <>
81
+ <u
82
+ onClick={() => {
83
+ const names = fieldNamesValue.split('\n');
84
+ if (names.length !== schemas.length) {
85
+ alert(i18n('errorBulkUpdateFieldName'));
86
+ } else {
87
+ changeSchemas(
88
+ names.map((value, index) => ({
89
+ key: 'key',
90
+ value,
91
+ schemaId: schemas[index].id,
92
+ }))
93
+ );
94
+ setIsBulkUpdateFieldNamesMode(false);
95
+ }
96
+ }}
97
+ >
98
+ {i18n('commitBulkUpdateFieldName')}
99
+ </u>
100
+ <span style={{ margin: '0 1rem' }}>/</span>
101
+ <u onClick={() => setIsBulkUpdateFieldNamesMode(false)}>{i18n('cancel')}</u>
102
+ </>
103
+ ) : (
104
+ <u
105
+ onClick={() => {
106
+ setFieldNamesValue(schemas.map((s) => s.key).join('\n'));
107
+ setIsBulkUpdateFieldNamesMode(true);
108
+ }}
109
+ >
110
+ {i18n('bulkUpdateFieldName')}
111
+ </u>
112
+ )}
113
+ </div>
114
+ <Divider />
115
+ </div>
116
+ );
117
+ };
118
+
119
+ export default ListView;
@@ -8,16 +8,14 @@ import ListView from './ListView';
8
8
  import DetailView from './DetailView';
9
9
 
10
10
  export type SidebarProps = {
11
- scale: number;
12
11
  height: number;
13
12
  hoveringSchemaId: string | null;
14
13
  onChangeHoveringSchemaId: (id: string | null) => void;
15
14
  size: Size;
16
15
  pageSize: Size;
17
- activeElement: HTMLElement | null;
18
- activeSchema: SchemaForUI;
16
+ activeElements: HTMLElement[];
19
17
  schemas: SchemaForUI[];
20
- onSortEnd: ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => void;
18
+ onSortEnd: (sortedSchemas: SchemaForUI[]) => void;
21
19
  onEdit: (id: string) => void;
22
20
  onEditEnd: () => void;
23
21
  changeSchemas: (objs: { key: string; value: string | number; schemaId: string }[]) => void;
@@ -25,12 +23,22 @@ export type SidebarProps = {
25
23
  };
26
24
 
27
25
  const Sidebar = (props: SidebarProps) => {
28
- const { height, size, addSchema } = props;
26
+ const { height, size, activeElements, schemas, addSchema } = props;
29
27
 
30
28
  const i18n = useContext(I18nContext);
31
29
  const [open, setOpen] = useState(true);
32
30
  const top = 0;
33
31
 
32
+ const getActiveSchemas = () => {
33
+ const ids = activeElements.map((ae) => ae.id);
34
+ return schemas.filter((s) => ids.includes(s.id));
35
+ };
36
+
37
+ const getLastActiveSchema = () => {
38
+ const activeSchemas = getActiveSchemas();
39
+ return activeSchemas[activeSchemas.length - 1];
40
+ };
41
+
34
42
  return (
35
43
  <div
36
44
  style={{ position: 'absolute', right: 0, zIndex: 1, height, width: open ? SIDEBAR_WIDTH : 0 }}
@@ -47,6 +55,7 @@ const Sidebar = (props: SidebarProps) => {
47
55
  padding: '0.5rem',
48
56
  cursor: 'pointer',
49
57
  background: '#eee',
58
+ width: 30,
50
59
  }}
51
60
  onClick={() => setOpen(!open)}
52
61
  >
@@ -67,9 +76,15 @@ const Sidebar = (props: SidebarProps) => {
67
76
  overflowY: 'auto',
68
77
  fontFamily: "'Open Sans', sans-serif",
69
78
  fontWeight: 400,
79
+ textAlign: 'left',
80
+ boxSizing: 'content-box',
70
81
  }}
71
82
  >
72
- {props.activeElement ? <DetailView {...props} /> : <ListView {...props} />}
83
+ {getActiveSchemas().length === 0 ? (
84
+ <ListView {...props} />
85
+ ) : (
86
+ <DetailView {...props} activeSchema={getLastActiveSchema()} />
87
+ )}
73
88
  <div
74
89
  style={{
75
90
  display: 'flex',
@@ -84,7 +99,6 @@ const Sidebar = (props: SidebarProps) => {
84
99
  <button
85
100
  style={{
86
101
  padding: '0.5rem',
87
- color: '#fff',
88
102
  background: '#18a0fb',
89
103
  border: 'none',
90
104
  borderRadius: 2,
@@ -92,7 +106,7 @@ const Sidebar = (props: SidebarProps) => {
92
106
  }}
93
107
  onClick={addSchema}
94
108
  >
95
- <strong>{i18n('addNewField')}</strong>
109
+ <strong style={{ color: '#fff' }}>{i18n('addNewField')}</strong>
96
110
  </button>
97
111
  </div>
98
112
  </div>
@@ -7,7 +7,6 @@ import { I18nContext } from '../../contexts';
7
7
  import {
8
8
  uuid,
9
9
  set,
10
- arrayMove,
11
10
  cloneDeep,
12
11
  initShortCuts,
13
12
  destroyShortCuts,
@@ -123,12 +122,12 @@ const TemplateEditor = ({
123
122
  };
124
123
  const timeTavel = (mode: 'undo' | 'redo') => {
125
124
  const isUndo = mode === 'undo';
126
- if ((isUndo ? past : future).current.length <= 0) return;
125
+ const stack = isUndo ? past : future;
126
+ if (stack.current.length <= 0) return;
127
127
  (isUndo ? future : past).current.push(cloneDeep(schemasList[pageCursor]));
128
128
  const s = cloneDeep(schemasList);
129
- s[pageCursor] = (isUndo ? past : future).current.pop()!;
129
+ s[pageCursor] = stack.current.pop()!;
130
130
  setSchemasList(s);
131
- onEditEnd();
132
131
  };
133
132
  initShortCuts({
134
133
  move: (command, isShift) => {
@@ -168,6 +167,7 @@ const TemplateEditor = ({
168
167
  save: () => onSaveTemplate && onSaveTemplate(modifiedTemplate),
169
168
  remove: () => removeSchemas(getActiveSchemas().map((s) => s.id)),
170
169
  esc: onEditEnd,
170
+ selectAll: () => onEdit(schemasList[pageCursor].map((s) => document.getElementById(s.id)!)),
171
171
  });
172
172
  }, [
173
173
  activeElements,
@@ -216,24 +216,14 @@ const TemplateEditor = ({
216
216
  setTimeout(() => onEdit([document.getElementById(s.id)!]));
217
217
  };
218
218
 
219
- const onSortEnd = (arg: { oldIndex: number; newIndex: number }) => {
220
- const movedSchema = arrayMove(cloneDeep(schemasList[pageCursor]), arg.oldIndex, arg.newIndex);
221
- commitSchemas(movedSchema);
219
+ const onSortEnd = (sortedSchemas: SchemaForUI[]) => {
220
+ commitSchemas(sortedSchemas);
222
221
  };
223
222
 
224
223
  const onChangeHoveringSchemaId = (id: string | null) => {
225
224
  setHoveringSchemaId(id);
226
225
  };
227
226
 
228
- const getLastActiveSchema = () => {
229
- if (activeElements.length === 0) return getInitialSchema();
230
- const last = activeElements[activeElements.length - 1];
231
-
232
- return schemasList[pageCursor].find((s) => s.id === last.id) || getInitialSchema();
233
- };
234
-
235
- const activeSchema = getLastActiveSchema();
236
-
237
227
  if (error) {
238
228
  return <Error size={size} error={error} />;
239
229
  }
@@ -241,22 +231,18 @@ const TemplateEditor = ({
241
231
  return (
242
232
  <Root ref={rootRef} size={size} scale={scale}>
243
233
  <Sidebar
244
- scale={scale}
245
234
  hoveringSchemaId={hoveringSchemaId}
246
235
  onChangeHoveringSchemaId={onChangeHoveringSchemaId}
247
236
  height={mainRef.current ? mainRef.current.scrollHeight : 0}
248
237
  size={size}
249
238
  pageSize={pageSizes[pageCursor]}
250
- activeElement={activeElements[activeElements.length - 1]}
239
+ activeElements={activeElements}
251
240
  schemas={schemasList[pageCursor]}
252
- activeSchema={activeSchema}
253
241
  changeSchemas={changeSchemas}
254
242
  onSortEnd={onSortEnd}
255
243
  onEdit={(id: string) => {
256
244
  const editingElem = document.getElementById(id);
257
- if (editingElem) {
258
- onEdit([editingElem]);
259
- }
245
+ editingElem && onEdit([editingElem]);
260
246
  }}
261
247
  onEditEnd={onEditEnd}
262
248
  addSchema={addSchema}
@@ -17,13 +17,13 @@ const Error = ({ size, error }: { size: Size; error: Error }) => {
17
17
  ...size,
18
18
  }}
19
19
  >
20
- <p style={{ color: '#fff', textAlign: 'center' }}>
20
+ <span style={{ color: '#fff', textAlign: 'center' }}>
21
21
  <span style={{ fontSize: 'large', fontWeight: 'bold', borderBottom: '1px solid #fff' }}>
22
22
  ERROR: {i18n('errorOccurred')}
23
23
  </span>
24
24
  <br />
25
25
  <span style={{ fontSize: 'small' }}>*{error.message}</span>
26
- </p>
26
+ </span>
27
27
  </div>
28
28
  );
29
29
  };
@@ -42,6 +42,16 @@ const Paper = (porps: {
42
42
  paperRefs.current[paperIndex] = e;
43
43
  }
44
44
  }}
45
+ onClick={(e) => {
46
+ if (
47
+ e.currentTarget === e.target &&
48
+ document &&
49
+ document.hasFocus() &&
50
+ document.activeElement instanceof HTMLElement
51
+ ) {
52
+ document.activeElement.blur();
53
+ }
54
+ }}
45
55
  style={{
46
56
  fontFamily: `'${getFallbackFontName(font)}'`,
47
57
  margin: `${RULER_HEIGHT * scale}px auto`,
@@ -80,7 +80,7 @@ const Preview = ({ template, inputs, size, onChangeInput }: PreviewReactProps) =
80
80
  backgrounds={backgrounds}
81
81
  renderSchema={({ schema, index }) => {
82
82
  const { key } = schema;
83
- const data = input[key] || '';
83
+ const data = (input && input[key]) || '';
84
84
  return (
85
85
  <SchemaUI
86
86
  key={schema.id}