@eeacms/volto-eea-website-theme 2.1.4 → 2.1.5
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/CHANGELOG.md +8 -3
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/customizations/volto/components/manage/Blocks/Block/BlocksForm.diff +12 -0
- package/src/customizations/volto/components/manage/Blocks/Block/BlocksForm.jsx +289 -0
- package/src/customizations/volto/components/manage/Blocks/Block/BlocksForm.txt +2 -0
- package/src/customizations/volto/components/manage/Form/Form.diff +2 -0
- package/src/customizations/volto/components/manage/Form/Form.jsx +952 -0
- package/src/customizations/volto/components/manage/Form/Form.txt +2 -0
package/CHANGELOG.md
CHANGED
@@ -4,11 +4,16 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
4
4
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
6
6
|
|
7
|
-
### [2.1.
|
7
|
+
### [2.1.5](https://github.com/eea/volto-eea-website-theme/compare/2.1.4...2.1.5) - 5 August 2024
|
8
8
|
|
9
|
-
#### :
|
9
|
+
#### :rocket: New Features
|
10
|
+
|
11
|
+
- feat: Customization Pass errors blocksform - refs 269086 [dobri1408 - [`9e11656`](https://github.com/eea/volto-eea-website-theme/commit/9e116563111b4e741678db21e7edf72ca6f163c6)]
|
12
|
+
|
13
|
+
#### :hammer_and_wrench: Others
|
10
14
|
|
11
|
-
-
|
15
|
+
- Add more info to customized components [alin - [`1dc85ab`](https://github.com/eea/volto-eea-website-theme/commit/1dc85ab3d5b3c9de788cd9dbce90284a503adddf)]
|
16
|
+
### [2.1.4](https://github.com/eea/volto-eea-website-theme/compare/2.1.3...2.1.4) - 1 August 2024
|
12
17
|
|
13
18
|
### [2.1.3](https://github.com/eea/volto-eea-website-theme/compare/2.1.2...2.1.3) - 22 July 2024
|
14
19
|
|
package/README.md
CHANGED
@@ -34,6 +34,8 @@ See [Storybook](https://eea.github.io/eea-storybook/).
|
|
34
34
|
**!!IMPORTANT**: This change requires volto@^16.26.1
|
35
35
|
|
36
36
|
- `volto/components/manage/Sidebar/SidebarPopup` -> https://github.com/plone/volto/pull/5520
|
37
|
+
- `volto/components/manage/Form/Form.jsx` -> Pass errors of metadata validation to BlocksForm
|
38
|
+
- `volto/components/manage/Blocks/Block/BlocksForm.jsx` -> Pass errors of metadata validation to blocks.
|
37
39
|
|
38
40
|
## Getting started
|
39
41
|
|
package/package.json
CHANGED
@@ -0,0 +1,12 @@
|
|
1
|
+
3c3
|
2
|
+
< import EditBlock from './Edit';
|
3
|
+
---
|
4
|
+
> import EditBlock from '@plone/volto/components/manage/Blocks/Block/Edit.jsx';
|
5
|
+
20c20
|
6
|
+
< import EditBlockWrapper from './EditBlockWrapper';
|
7
|
+
---
|
8
|
+
> import EditBlockWrapper from '@plone/volto/components/manage/Blocks/Block/EditBlockWrapper.jsx';
|
9
|
+
41a42
|
10
|
+
> errors,
|
11
|
+
261a263
|
12
|
+
> errors,
|
@@ -0,0 +1,289 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { useIntl } from 'react-intl';
|
3
|
+
import EditBlock from '@plone/volto/components/manage/Blocks/Block/Edit.jsx';
|
4
|
+
import { DragDropList } from '@plone/volto/components';
|
5
|
+
import {
|
6
|
+
getBlocks,
|
7
|
+
getBlocksFieldname,
|
8
|
+
applyBlockDefaults,
|
9
|
+
} from '@plone/volto/helpers';
|
10
|
+
import {
|
11
|
+
addBlock,
|
12
|
+
insertBlock,
|
13
|
+
changeBlock,
|
14
|
+
deleteBlock,
|
15
|
+
moveBlock,
|
16
|
+
mutateBlock,
|
17
|
+
nextBlockId,
|
18
|
+
previousBlockId,
|
19
|
+
} from '@plone/volto/helpers';
|
20
|
+
import EditBlockWrapper from '@plone/volto/components/manage/Blocks/Block/EditBlockWrapper.jsx';
|
21
|
+
import { setSidebarTab } from '@plone/volto/actions';
|
22
|
+
import { useDispatch } from 'react-redux';
|
23
|
+
import { useDetectClickOutside, useEvent } from '@plone/volto/helpers';
|
24
|
+
import config from '@plone/volto/registry';
|
25
|
+
|
26
|
+
const BlocksForm = (props) => {
|
27
|
+
const {
|
28
|
+
pathname,
|
29
|
+
onChangeField,
|
30
|
+
properties,
|
31
|
+
type,
|
32
|
+
navRoot,
|
33
|
+
onChangeFormData,
|
34
|
+
selectedBlock,
|
35
|
+
multiSelected,
|
36
|
+
onSelectBlock,
|
37
|
+
allowedBlocks,
|
38
|
+
showRestricted,
|
39
|
+
title,
|
40
|
+
description,
|
41
|
+
metadata,
|
42
|
+
errors,
|
43
|
+
manage,
|
44
|
+
children,
|
45
|
+
isMainForm = true,
|
46
|
+
isContainer,
|
47
|
+
stopPropagation,
|
48
|
+
disableAddBlockOnEnterKey,
|
49
|
+
blocksConfig = config.blocks.blocksConfig,
|
50
|
+
editable = true,
|
51
|
+
direction = 'vertical',
|
52
|
+
} = props;
|
53
|
+
|
54
|
+
const blockList = getBlocks(properties);
|
55
|
+
|
56
|
+
const dispatch = useDispatch();
|
57
|
+
const intl = useIntl();
|
58
|
+
|
59
|
+
const ClickOutsideListener = () => {
|
60
|
+
onSelectBlock(null);
|
61
|
+
dispatch(setSidebarTab(0));
|
62
|
+
};
|
63
|
+
|
64
|
+
const ref = useDetectClickOutside({
|
65
|
+
onTriggered: ClickOutsideListener,
|
66
|
+
triggerKeys: ['Escape'],
|
67
|
+
// Disabled feature for now https://github.com/plone/volto/pull/2389#issuecomment-830027413
|
68
|
+
disableClick: true,
|
69
|
+
disableKeys: !isMainForm,
|
70
|
+
});
|
71
|
+
|
72
|
+
const handleKeyDown = (
|
73
|
+
e,
|
74
|
+
index,
|
75
|
+
block,
|
76
|
+
node,
|
77
|
+
{
|
78
|
+
disableEnter = false,
|
79
|
+
disableArrowUp = false,
|
80
|
+
disableArrowDown = false,
|
81
|
+
} = {},
|
82
|
+
) => {
|
83
|
+
const isMultipleSelection = e.shiftKey;
|
84
|
+
if (e.key === 'ArrowUp' && !disableArrowUp) {
|
85
|
+
onFocusPreviousBlock(block, node, isMultipleSelection);
|
86
|
+
e.preventDefault();
|
87
|
+
}
|
88
|
+
if (e.key === 'ArrowDown' && !disableArrowDown) {
|
89
|
+
onFocusNextBlock(block, node, isMultipleSelection);
|
90
|
+
e.preventDefault();
|
91
|
+
}
|
92
|
+
if (e.key === 'Enter' && !disableEnter) {
|
93
|
+
if (!disableAddBlockOnEnterKey) {
|
94
|
+
onSelectBlock(onAddBlock(config.settings.defaultBlockType, index + 1));
|
95
|
+
}
|
96
|
+
e.preventDefault();
|
97
|
+
}
|
98
|
+
};
|
99
|
+
|
100
|
+
const onFocusPreviousBlock = (
|
101
|
+
currentBlock,
|
102
|
+
blockNode,
|
103
|
+
isMultipleSelection,
|
104
|
+
) => {
|
105
|
+
const prev = previousBlockId(properties, currentBlock);
|
106
|
+
if (prev === null) return;
|
107
|
+
|
108
|
+
blockNode.blur();
|
109
|
+
|
110
|
+
onSelectBlock(prev, isMultipleSelection);
|
111
|
+
};
|
112
|
+
|
113
|
+
const onFocusNextBlock = (currentBlock, blockNode, isMultipleSelection) => {
|
114
|
+
const next = nextBlockId(properties, currentBlock);
|
115
|
+
if (next === null) return;
|
116
|
+
|
117
|
+
blockNode.blur();
|
118
|
+
|
119
|
+
onSelectBlock(next, isMultipleSelection);
|
120
|
+
};
|
121
|
+
|
122
|
+
const onMutateBlock = (id, value) => {
|
123
|
+
const newFormData = mutateBlock(properties, id, value);
|
124
|
+
onChangeFormData(newFormData);
|
125
|
+
};
|
126
|
+
|
127
|
+
const onInsertBlock = (id, value, current) => {
|
128
|
+
const [newId, newFormData] = insertBlock(
|
129
|
+
properties,
|
130
|
+
id,
|
131
|
+
value,
|
132
|
+
current,
|
133
|
+
config.experimental.addBlockButton.enabled ? 1 : 0,
|
134
|
+
);
|
135
|
+
|
136
|
+
const blocksFieldname = getBlocksFieldname(newFormData);
|
137
|
+
const blockData = newFormData[blocksFieldname][newId];
|
138
|
+
newFormData[blocksFieldname][newId] = applyBlockDefaults({
|
139
|
+
data: blockData,
|
140
|
+
intl,
|
141
|
+
metadata,
|
142
|
+
properties,
|
143
|
+
});
|
144
|
+
|
145
|
+
onChangeFormData(newFormData);
|
146
|
+
return newId;
|
147
|
+
};
|
148
|
+
|
149
|
+
const onAddBlock = (type, index) => {
|
150
|
+
if (editable) {
|
151
|
+
const [id, newFormData] = addBlock(properties, type, index);
|
152
|
+
const blocksFieldname = getBlocksFieldname(newFormData);
|
153
|
+
const blockData = newFormData[blocksFieldname][id];
|
154
|
+
newFormData[blocksFieldname][id] = applyBlockDefaults({
|
155
|
+
data: blockData,
|
156
|
+
intl,
|
157
|
+
metadata,
|
158
|
+
properties,
|
159
|
+
});
|
160
|
+
onChangeFormData(newFormData);
|
161
|
+
return id;
|
162
|
+
}
|
163
|
+
};
|
164
|
+
|
165
|
+
const onChangeBlock = (id, value) => {
|
166
|
+
const newFormData = changeBlock(properties, id, value);
|
167
|
+
onChangeFormData(newFormData);
|
168
|
+
};
|
169
|
+
|
170
|
+
const onDeleteBlock = (id, selectPrev) => {
|
171
|
+
const previous = previousBlockId(properties, id);
|
172
|
+
|
173
|
+
const newFormData = deleteBlock(properties, id);
|
174
|
+
onChangeFormData(newFormData);
|
175
|
+
|
176
|
+
onSelectBlock(selectPrev ? previous : null);
|
177
|
+
};
|
178
|
+
|
179
|
+
const onMoveBlock = (dragIndex, hoverIndex) => {
|
180
|
+
const newFormData = moveBlock(properties, dragIndex, hoverIndex);
|
181
|
+
onChangeFormData(newFormData);
|
182
|
+
};
|
183
|
+
|
184
|
+
const defaultBlockWrapper = ({ draginfo }, editBlock, blockProps) => (
|
185
|
+
<EditBlockWrapper draginfo={draginfo} blockProps={blockProps}>
|
186
|
+
{editBlock}
|
187
|
+
</EditBlockWrapper>
|
188
|
+
);
|
189
|
+
|
190
|
+
const editBlockWrapper = children || defaultBlockWrapper;
|
191
|
+
|
192
|
+
// Remove invalid blocks on saving
|
193
|
+
// Note they are alreaady filtered by DragDropList, but we also want them
|
194
|
+
// to be removed when the user saves the page next. Otherwise the invalid
|
195
|
+
// blocks would linger for ever.
|
196
|
+
for (const [n, v] of blockList) {
|
197
|
+
if (!v) {
|
198
|
+
const newFormData = deleteBlock(properties, n);
|
199
|
+
onChangeFormData(newFormData);
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
useEvent('voltoClickBelowContent', () => {
|
204
|
+
if (!config.experimental.addBlockButton.enabled || !isMainForm) return;
|
205
|
+
onSelectBlock(
|
206
|
+
onAddBlock(config.settings.defaultBlockType, blockList.length),
|
207
|
+
);
|
208
|
+
});
|
209
|
+
|
210
|
+
return (
|
211
|
+
<div
|
212
|
+
className="blocks-form"
|
213
|
+
role="presentation"
|
214
|
+
ref={ref}
|
215
|
+
onKeyDown={(e) => {
|
216
|
+
if (stopPropagation) {
|
217
|
+
e.stopPropagation();
|
218
|
+
}
|
219
|
+
}}
|
220
|
+
>
|
221
|
+
<fieldset className="invisible" disabled={!editable}>
|
222
|
+
<DragDropList
|
223
|
+
childList={blockList}
|
224
|
+
onMoveItem={(result) => {
|
225
|
+
const { source, destination } = result;
|
226
|
+
if (!destination) {
|
227
|
+
return;
|
228
|
+
}
|
229
|
+
const newFormData = moveBlock(
|
230
|
+
properties,
|
231
|
+
source.index,
|
232
|
+
destination.index,
|
233
|
+
);
|
234
|
+
onChangeFormData(newFormData);
|
235
|
+
return true;
|
236
|
+
}}
|
237
|
+
direction={direction}
|
238
|
+
>
|
239
|
+
{(dragProps) => {
|
240
|
+
const { child, childId, index } = dragProps;
|
241
|
+
const blockProps = {
|
242
|
+
allowedBlocks,
|
243
|
+
showRestricted,
|
244
|
+
block: childId,
|
245
|
+
data: child,
|
246
|
+
handleKeyDown,
|
247
|
+
id: childId,
|
248
|
+
formTitle: title,
|
249
|
+
formDescription: description,
|
250
|
+
index,
|
251
|
+
manage,
|
252
|
+
onAddBlock,
|
253
|
+
onInsertBlock,
|
254
|
+
onChangeBlock,
|
255
|
+
onChangeField,
|
256
|
+
onChangeFormData,
|
257
|
+
onDeleteBlock,
|
258
|
+
onFocusNextBlock,
|
259
|
+
onFocusPreviousBlock,
|
260
|
+
onMoveBlock,
|
261
|
+
onMutateBlock,
|
262
|
+
onSelectBlock,
|
263
|
+
errors,
|
264
|
+
pathname,
|
265
|
+
metadata,
|
266
|
+
properties,
|
267
|
+
contentType: type,
|
268
|
+
navRoot,
|
269
|
+
blocksConfig,
|
270
|
+
selected: selectedBlock === childId,
|
271
|
+
multiSelected: multiSelected?.includes(childId),
|
272
|
+
type: child['@type'],
|
273
|
+
editable,
|
274
|
+
showBlockChooser: selectedBlock === childId,
|
275
|
+
detached: isContainer,
|
276
|
+
};
|
277
|
+
return editBlockWrapper(
|
278
|
+
dragProps,
|
279
|
+
<EditBlock key={childId} {...blockProps} />,
|
280
|
+
blockProps,
|
281
|
+
);
|
282
|
+
}}
|
283
|
+
</DragDropList>
|
284
|
+
</fieldset>
|
285
|
+
</div>
|
286
|
+
);
|
287
|
+
};
|
288
|
+
|
289
|
+
export default BlocksForm;
|
@@ -0,0 +1,952 @@
|
|
1
|
+
/**
|
2
|
+
* Form component.
|
3
|
+
* @module components/manage/Form/Form
|
4
|
+
*/
|
5
|
+
|
6
|
+
import { BlocksForm, Field, Icon, Toast } from '@plone/volto/components';
|
7
|
+
import {
|
8
|
+
difference,
|
9
|
+
FormValidation,
|
10
|
+
getBlocksFieldname,
|
11
|
+
getBlocksLayoutFieldname,
|
12
|
+
messages,
|
13
|
+
} from '@plone/volto/helpers';
|
14
|
+
import aheadSVG from '@plone/volto/icons/ahead.svg';
|
15
|
+
import clearSVG from '@plone/volto/icons/clear.svg';
|
16
|
+
import upSVG from '@plone/volto/icons/up-key.svg';
|
17
|
+
import downSVG from '@plone/volto/icons/down-key.svg';
|
18
|
+
import {
|
19
|
+
findIndex,
|
20
|
+
isEmpty,
|
21
|
+
isEqual,
|
22
|
+
keys,
|
23
|
+
map,
|
24
|
+
mapValues,
|
25
|
+
pickBy,
|
26
|
+
without,
|
27
|
+
cloneDeep,
|
28
|
+
xor,
|
29
|
+
} from 'lodash';
|
30
|
+
import isBoolean from 'lodash/isBoolean';
|
31
|
+
import PropTypes from 'prop-types';
|
32
|
+
import React, { Component } from 'react';
|
33
|
+
import { injectIntl } from 'react-intl';
|
34
|
+
import { Portal } from 'react-portal';
|
35
|
+
import { connect } from 'react-redux';
|
36
|
+
import {
|
37
|
+
Accordion,
|
38
|
+
Button,
|
39
|
+
Container as SemanticContainer,
|
40
|
+
Form as UiForm,
|
41
|
+
Message,
|
42
|
+
Segment,
|
43
|
+
Tab,
|
44
|
+
} from 'semantic-ui-react';
|
45
|
+
import { v4 as uuid } from 'uuid';
|
46
|
+
import { toast } from 'react-toastify';
|
47
|
+
import { BlocksToolbar, UndoToolbar } from '@plone/volto/components';
|
48
|
+
import {
|
49
|
+
setMetadataFieldsets,
|
50
|
+
resetMetadataFocus,
|
51
|
+
setSidebarTab,
|
52
|
+
setFormData,
|
53
|
+
} from '@plone/volto/actions';
|
54
|
+
import { compose } from 'redux';
|
55
|
+
import config from '@plone/volto/registry';
|
56
|
+
|
57
|
+
/**
|
58
|
+
* Form container class.
|
59
|
+
* @class Form
|
60
|
+
* @extends Component
|
61
|
+
*/
|
62
|
+
class Form extends Component {
|
63
|
+
/**
|
64
|
+
* Property types.
|
65
|
+
* @property {Object} propTypes Property types.
|
66
|
+
* @static
|
67
|
+
*/
|
68
|
+
static propTypes = {
|
69
|
+
schema: PropTypes.shape({
|
70
|
+
fieldsets: PropTypes.arrayOf(
|
71
|
+
PropTypes.shape({
|
72
|
+
fields: PropTypes.arrayOf(PropTypes.string),
|
73
|
+
id: PropTypes.string,
|
74
|
+
title: PropTypes.string,
|
75
|
+
}),
|
76
|
+
),
|
77
|
+
properties: PropTypes.objectOf(PropTypes.any),
|
78
|
+
definitions: PropTypes.objectOf(PropTypes.any),
|
79
|
+
required: PropTypes.arrayOf(PropTypes.string),
|
80
|
+
}),
|
81
|
+
formData: PropTypes.objectOf(PropTypes.any),
|
82
|
+
globalData: PropTypes.objectOf(PropTypes.any),
|
83
|
+
metadataFieldsets: PropTypes.arrayOf(PropTypes.string),
|
84
|
+
metadataFieldFocus: PropTypes.string,
|
85
|
+
pathname: PropTypes.string,
|
86
|
+
onSubmit: PropTypes.func,
|
87
|
+
onCancel: PropTypes.func,
|
88
|
+
submitLabel: PropTypes.string,
|
89
|
+
resetAfterSubmit: PropTypes.bool,
|
90
|
+
resetOnCancel: PropTypes.bool,
|
91
|
+
isEditForm: PropTypes.bool,
|
92
|
+
isAdminForm: PropTypes.bool,
|
93
|
+
title: PropTypes.string,
|
94
|
+
error: PropTypes.shape({
|
95
|
+
message: PropTypes.string,
|
96
|
+
}),
|
97
|
+
loading: PropTypes.bool,
|
98
|
+
hideActions: PropTypes.bool,
|
99
|
+
description: PropTypes.string,
|
100
|
+
visual: PropTypes.bool,
|
101
|
+
blocks: PropTypes.arrayOf(PropTypes.object),
|
102
|
+
isFormSelected: PropTypes.bool,
|
103
|
+
onSelectForm: PropTypes.func,
|
104
|
+
editable: PropTypes.bool,
|
105
|
+
onChangeFormData: PropTypes.func,
|
106
|
+
requestError: PropTypes.string,
|
107
|
+
allowedBlocks: PropTypes.arrayOf(PropTypes.string),
|
108
|
+
showRestricted: PropTypes.bool,
|
109
|
+
global: PropTypes.bool,
|
110
|
+
};
|
111
|
+
|
112
|
+
/**
|
113
|
+
* Default properties.
|
114
|
+
* @property {Object} defaultProps Default properties.
|
115
|
+
* @static
|
116
|
+
*/
|
117
|
+
static defaultProps = {
|
118
|
+
formData: null,
|
119
|
+
onSubmit: null,
|
120
|
+
onCancel: null,
|
121
|
+
submitLabel: null,
|
122
|
+
resetAfterSubmit: false,
|
123
|
+
resetOnCancel: false,
|
124
|
+
isEditForm: false,
|
125
|
+
isAdminForm: false,
|
126
|
+
title: null,
|
127
|
+
description: null,
|
128
|
+
error: null,
|
129
|
+
loading: null,
|
130
|
+
hideActions: false,
|
131
|
+
visual: false,
|
132
|
+
blocks: [],
|
133
|
+
pathname: '',
|
134
|
+
schema: {},
|
135
|
+
isFormSelected: true,
|
136
|
+
onSelectForm: null,
|
137
|
+
editable: true,
|
138
|
+
requestError: null,
|
139
|
+
allowedBlocks: null,
|
140
|
+
global: false,
|
141
|
+
};
|
142
|
+
|
143
|
+
/**
|
144
|
+
* Constructor
|
145
|
+
* @method constructor
|
146
|
+
* @param {Object} props Component properties
|
147
|
+
* @constructs Form
|
148
|
+
*/
|
149
|
+
constructor(props) {
|
150
|
+
super(props);
|
151
|
+
const ids = {
|
152
|
+
title: uuid(),
|
153
|
+
text: uuid(),
|
154
|
+
};
|
155
|
+
let { formData, schema: originalSchema } = props;
|
156
|
+
const blocksFieldname = getBlocksFieldname(formData);
|
157
|
+
const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
|
158
|
+
|
159
|
+
const schema = this.removeBlocksLayoutFields(originalSchema);
|
160
|
+
|
161
|
+
this.props.setMetadataFieldsets(
|
162
|
+
schema?.fieldsets ? schema.fieldsets.map((fieldset) => fieldset.id) : [],
|
163
|
+
);
|
164
|
+
|
165
|
+
if (!props.isEditForm) {
|
166
|
+
// It's a normal (add form), get defaults from schema
|
167
|
+
formData = {
|
168
|
+
...mapValues(props.schema.properties, 'default'),
|
169
|
+
...formData,
|
170
|
+
};
|
171
|
+
}
|
172
|
+
|
173
|
+
// We initialize the formData snapshot in here, before the initial data checks
|
174
|
+
const initialFormData = cloneDeep(formData);
|
175
|
+
|
176
|
+
// Adding fallback in case the fields are empty, so we are sure that the edit form
|
177
|
+
// shows at least the default blocks
|
178
|
+
if (
|
179
|
+
formData.hasOwnProperty(blocksFieldname) &&
|
180
|
+
formData.hasOwnProperty(blocksLayoutFieldname)
|
181
|
+
) {
|
182
|
+
if (
|
183
|
+
!formData[blocksLayoutFieldname] ||
|
184
|
+
isEmpty(formData[blocksLayoutFieldname].items)
|
185
|
+
) {
|
186
|
+
formData[blocksLayoutFieldname] = {
|
187
|
+
items: [ids.title, ids.text],
|
188
|
+
};
|
189
|
+
}
|
190
|
+
if (!formData[blocksFieldname] || isEmpty(formData[blocksFieldname])) {
|
191
|
+
formData[blocksFieldname] = {
|
192
|
+
[ids.title]: {
|
193
|
+
'@type': 'title',
|
194
|
+
},
|
195
|
+
[ids.text]: {
|
196
|
+
'@type': config.settings.defaultBlockType,
|
197
|
+
},
|
198
|
+
};
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
let selectedBlock = null;
|
203
|
+
if (
|
204
|
+
formData.hasOwnProperty(blocksLayoutFieldname) &&
|
205
|
+
formData[blocksLayoutFieldname].items.length > 0
|
206
|
+
) {
|
207
|
+
if (config.blocks?.initialBlocksFocus === null) {
|
208
|
+
selectedBlock = null;
|
209
|
+
} else if (this.props.type in config.blocks?.initialBlocksFocus) {
|
210
|
+
// Default selected is not the first block, but the one from config.
|
211
|
+
// TODO Select first block and not an arbitrary one.
|
212
|
+
Object.keys(formData[blocksFieldname]).forEach((b_key) => {
|
213
|
+
if (
|
214
|
+
formData[blocksFieldname][b_key]['@type'] ===
|
215
|
+
config.blocks?.initialBlocksFocus?.[this.props.type]
|
216
|
+
) {
|
217
|
+
selectedBlock = b_key;
|
218
|
+
}
|
219
|
+
});
|
220
|
+
} else {
|
221
|
+
selectedBlock = formData[blocksLayoutFieldname].items[0];
|
222
|
+
}
|
223
|
+
}
|
224
|
+
|
225
|
+
// Sync state to global state
|
226
|
+
if (this.props.global) {
|
227
|
+
this.props.setFormData(formData);
|
228
|
+
}
|
229
|
+
|
230
|
+
// Set initial state
|
231
|
+
this.state = {
|
232
|
+
formData,
|
233
|
+
initialFormData,
|
234
|
+
errors: {},
|
235
|
+
selected: selectedBlock,
|
236
|
+
multiSelected: [],
|
237
|
+
isClient: false,
|
238
|
+
// Ensure focus remain in field after change
|
239
|
+
inFocus: {},
|
240
|
+
};
|
241
|
+
this.onChangeField = this.onChangeField.bind(this);
|
242
|
+
this.onSelectBlock = this.onSelectBlock.bind(this);
|
243
|
+
this.onSubmit = this.onSubmit.bind(this);
|
244
|
+
this.onCancel = this.onCancel.bind(this);
|
245
|
+
this.onTabChange = this.onTabChange.bind(this);
|
246
|
+
this.onBlurField = this.onBlurField.bind(this);
|
247
|
+
this.onClickInput = this.onClickInput.bind(this);
|
248
|
+
this.onToggleMetadataFieldset = this.onToggleMetadataFieldset.bind(this);
|
249
|
+
}
|
250
|
+
|
251
|
+
/**
|
252
|
+
* On updates caused by props change
|
253
|
+
* if errors from Backend come, these will be shown to their corresponding Fields
|
254
|
+
* also the first Tab to have any errors will be selected
|
255
|
+
* @param {Object} prevProps
|
256
|
+
*/
|
257
|
+
async componentDidUpdate(prevProps, prevState) {
|
258
|
+
let { requestError } = this.props;
|
259
|
+
let errors = {};
|
260
|
+
let activeIndex = 0;
|
261
|
+
|
262
|
+
if (requestError && prevProps.requestError !== requestError) {
|
263
|
+
errors =
|
264
|
+
FormValidation.giveServerErrorsToCorrespondingFields(requestError);
|
265
|
+
activeIndex = FormValidation.showFirstTabWithErrors({
|
266
|
+
errors,
|
267
|
+
schema: this.props.schema,
|
268
|
+
});
|
269
|
+
|
270
|
+
this.setState({
|
271
|
+
errors,
|
272
|
+
activeIndex,
|
273
|
+
});
|
274
|
+
}
|
275
|
+
|
276
|
+
if (this.props.onChangeFormData) {
|
277
|
+
if (!isEqual(prevState?.formData, this.state.formData)) {
|
278
|
+
this.props.onChangeFormData(this.state.formData);
|
279
|
+
}
|
280
|
+
}
|
281
|
+
if (
|
282
|
+
this.props.global &&
|
283
|
+
!isEqual(this.props.globalData, prevProps.globalData)
|
284
|
+
) {
|
285
|
+
this.setState({
|
286
|
+
formData: this.props.globalData,
|
287
|
+
});
|
288
|
+
}
|
289
|
+
|
290
|
+
if (!isEqual(prevProps.schema, this.props.schema)) {
|
291
|
+
this.props.setMetadataFieldsets(
|
292
|
+
this.removeBlocksLayoutFields(this.props.schema).fieldsets.map(
|
293
|
+
(fieldset) => fieldset.id,
|
294
|
+
),
|
295
|
+
);
|
296
|
+
}
|
297
|
+
|
298
|
+
if (
|
299
|
+
this.props.metadataFieldFocus !== '' &&
|
300
|
+
!isEqual(prevProps.metadataFieldFocus, this.props.metadataFieldFocus)
|
301
|
+
) {
|
302
|
+
// Scroll into view
|
303
|
+
document
|
304
|
+
.querySelector(`.field-wrapper-${this.props.metadataFieldFocus}`)
|
305
|
+
.scrollIntoView();
|
306
|
+
|
307
|
+
// Set focus to first input if available
|
308
|
+
document
|
309
|
+
.querySelector(`.field-wrapper-${this.props.metadataFieldFocus} input`)
|
310
|
+
.focus();
|
311
|
+
|
312
|
+
// Reset focus field
|
313
|
+
this.props.resetMetadataFocus();
|
314
|
+
}
|
315
|
+
}
|
316
|
+
|
317
|
+
/**
|
318
|
+
* Tab selection is done only by setting activeIndex in state
|
319
|
+
*/
|
320
|
+
onTabChange(e, { activeIndex }) {
|
321
|
+
const defaultFocus = this.props.schema.fieldsets[activeIndex].fields[0];
|
322
|
+
this.setState({
|
323
|
+
activeIndex,
|
324
|
+
...(defaultFocus ? { inFocus: { [defaultFocus]: true } } : {}),
|
325
|
+
});
|
326
|
+
}
|
327
|
+
|
328
|
+
/**
|
329
|
+
* If user clicks on input, the form will be not considered pristine
|
330
|
+
* this will avoid onBlur effects without interraction with the form
|
331
|
+
* @param {Object} e event
|
332
|
+
*/
|
333
|
+
onClickInput(e) {
|
334
|
+
this.setState({ isFormPristine: false });
|
335
|
+
}
|
336
|
+
|
337
|
+
/**
|
338
|
+
* Validate fields on blur
|
339
|
+
* @method onBlurField
|
340
|
+
* @param {string} id Id of the field
|
341
|
+
* @param {*} value Value of the field
|
342
|
+
* @returns {undefined}
|
343
|
+
*/
|
344
|
+
onBlurField(id, value) {
|
345
|
+
if (!this.state.isFormPristine) {
|
346
|
+
const errors = FormValidation.validateFieldsPerFieldset({
|
347
|
+
schema: this.props.schema,
|
348
|
+
formData: this.state.formData,
|
349
|
+
formatMessage: this.props.intl.formatMessage,
|
350
|
+
touchedField: { [id]: value },
|
351
|
+
});
|
352
|
+
|
353
|
+
this.setState({
|
354
|
+
errors,
|
355
|
+
});
|
356
|
+
}
|
357
|
+
}
|
358
|
+
|
359
|
+
/**
|
360
|
+
* Component did mount
|
361
|
+
* @method componentDidMount
|
362
|
+
* @returns {undefined}
|
363
|
+
*/
|
364
|
+
componentDidMount() {
|
365
|
+
this.setState({ isClient: true });
|
366
|
+
}
|
367
|
+
|
368
|
+
static getDerivedStateFromProps(props, state) {
|
369
|
+
let newState = { ...state };
|
370
|
+
if (!props.isFormSelected) {
|
371
|
+
newState.selected = null;
|
372
|
+
}
|
373
|
+
|
374
|
+
return newState;
|
375
|
+
}
|
376
|
+
|
377
|
+
/**
|
378
|
+
* Change field handler
|
379
|
+
* Remove errors for changed field
|
380
|
+
* @method onChangeField
|
381
|
+
* @param {string} id Id of the field
|
382
|
+
* @param {*} value Value of the field
|
383
|
+
* @returns {undefined}
|
384
|
+
*/
|
385
|
+
onChangeField(id, value) {
|
386
|
+
this.setState((prevState) => {
|
387
|
+
const { errors, formData } = prevState;
|
388
|
+
const newFormData = {
|
389
|
+
...formData,
|
390
|
+
// We need to catch also when the value equals false this fixes #888
|
391
|
+
[id]: value || (value !== undefined && isBoolean(value)) ? value : null,
|
392
|
+
};
|
393
|
+
delete errors[id];
|
394
|
+
if (this.props.global) {
|
395
|
+
this.props.setFormData(newFormData);
|
396
|
+
}
|
397
|
+
return {
|
398
|
+
errors,
|
399
|
+
formData: newFormData,
|
400
|
+
// Changing the form data re-renders the select widget which causes the
|
401
|
+
// focus to get lost. To circumvent this, we set the focus back to
|
402
|
+
// the input.
|
403
|
+
// This could fix other widgets too but currently targeted
|
404
|
+
// against the select widget only.
|
405
|
+
// Ensure field to be in focus after the change
|
406
|
+
inFocus: { [id]: true },
|
407
|
+
};
|
408
|
+
});
|
409
|
+
}
|
410
|
+
|
411
|
+
/**
|
412
|
+
* Select block handler
|
413
|
+
* @method onSelectBlock
|
414
|
+
* @param {string} id Id of the field
|
415
|
+
* @param {string} isMultipleSelection true if multiple blocks are selected
|
416
|
+
* @returns {undefined}
|
417
|
+
*/
|
418
|
+
onSelectBlock(id, isMultipleSelection, event) {
|
419
|
+
let multiSelected = [];
|
420
|
+
let selected = id;
|
421
|
+
const formData = this.state.formData;
|
422
|
+
|
423
|
+
if (isMultipleSelection) {
|
424
|
+
selected = null;
|
425
|
+
const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
|
426
|
+
|
427
|
+
const blocks_layout = formData[blocksLayoutFieldname].items;
|
428
|
+
|
429
|
+
if (event.shiftKey) {
|
430
|
+
const anchor =
|
431
|
+
this.state.multiSelected.length > 0
|
432
|
+
? blocks_layout.indexOf(this.state.multiSelected[0])
|
433
|
+
: blocks_layout.indexOf(this.state.selected);
|
434
|
+
const focus = blocks_layout.indexOf(id);
|
435
|
+
|
436
|
+
if (anchor === focus) {
|
437
|
+
multiSelected = [id];
|
438
|
+
} else if (focus > anchor) {
|
439
|
+
multiSelected = [...blocks_layout.slice(anchor, focus + 1)];
|
440
|
+
} else {
|
441
|
+
multiSelected = [...blocks_layout.slice(focus, anchor + 1)];
|
442
|
+
}
|
443
|
+
}
|
444
|
+
|
445
|
+
if ((event.ctrlKey || event.metaKey) && !event.shiftKey) {
|
446
|
+
multiSelected = this.state.multiSelected || [];
|
447
|
+
if (!this.state.multiSelected.includes(this.state.selected)) {
|
448
|
+
multiSelected = [...multiSelected, this.state.selected];
|
449
|
+
selected = null;
|
450
|
+
}
|
451
|
+
if (this.state.multiSelected.includes(id)) {
|
452
|
+
selected = null;
|
453
|
+
multiSelected = without(multiSelected, id);
|
454
|
+
} else {
|
455
|
+
multiSelected = [...multiSelected, id];
|
456
|
+
}
|
457
|
+
}
|
458
|
+
}
|
459
|
+
|
460
|
+
this.setState({
|
461
|
+
selected,
|
462
|
+
multiSelected,
|
463
|
+
});
|
464
|
+
|
465
|
+
if (this.props.onSelectForm) {
|
466
|
+
if (event) event.nativeEvent.stopImmediatePropagation();
|
467
|
+
this.props.onSelectForm();
|
468
|
+
}
|
469
|
+
}
|
470
|
+
|
471
|
+
/**
|
472
|
+
* Cancel handler
|
473
|
+
* It prevents event from triggering submit, reset form if props.resetAfterSubmit
|
474
|
+
* and calls this.props.onCancel
|
475
|
+
* @method onCancel
|
476
|
+
* @param {Object} event Event object.
|
477
|
+
* @returns {undefined}
|
478
|
+
*/
|
479
|
+
onCancel(event) {
|
480
|
+
if (event) {
|
481
|
+
event.preventDefault();
|
482
|
+
}
|
483
|
+
if (this.props.resetOnCancel || this.props.resetAfterSubmit) {
|
484
|
+
this.setState({
|
485
|
+
formData: this.props.formData,
|
486
|
+
});
|
487
|
+
if (this.props.global) {
|
488
|
+
this.props.setFormData(this.props.formData);
|
489
|
+
}
|
490
|
+
}
|
491
|
+
this.props.onCancel(event);
|
492
|
+
}
|
493
|
+
|
494
|
+
/**
|
495
|
+
* Submit handler also validate form and collect errors
|
496
|
+
* @method onSubmit
|
497
|
+
* @param {Object} event Event object.
|
498
|
+
* @returns {undefined}
|
499
|
+
*/
|
500
|
+
onSubmit(event) {
|
501
|
+
const formData = this.state.formData;
|
502
|
+
|
503
|
+
if (event) {
|
504
|
+
event.preventDefault();
|
505
|
+
}
|
506
|
+
|
507
|
+
const errors = this.props.schema
|
508
|
+
? FormValidation.validateFieldsPerFieldset({
|
509
|
+
schema: this.props.schema,
|
510
|
+
formData,
|
511
|
+
formatMessage: this.props.intl.formatMessage,
|
512
|
+
})
|
513
|
+
: {};
|
514
|
+
|
515
|
+
if (keys(errors).length > 0) {
|
516
|
+
const activeIndex = FormValidation.showFirstTabWithErrors({
|
517
|
+
errors,
|
518
|
+
schema: this.props.schema,
|
519
|
+
});
|
520
|
+
this.setState(
|
521
|
+
{
|
522
|
+
errors,
|
523
|
+
activeIndex,
|
524
|
+
},
|
525
|
+
() => {
|
526
|
+
Object.keys(errors).forEach((err) =>
|
527
|
+
toast.error(
|
528
|
+
<Toast
|
529
|
+
error
|
530
|
+
title={this.props.schema.properties[err].title || err}
|
531
|
+
content={errors[err].join(', ')}
|
532
|
+
/>,
|
533
|
+
),
|
534
|
+
);
|
535
|
+
},
|
536
|
+
);
|
537
|
+
// Changes the focus to the metadata tab in the sidebar if error
|
538
|
+
this.props.setSidebarTab(0);
|
539
|
+
} else {
|
540
|
+
// Get only the values that have been modified (Edit forms), send all in case that
|
541
|
+
// it's an add form
|
542
|
+
if (this.props.isEditForm) {
|
543
|
+
this.props.onSubmit(this.getOnlyFormModifiedValues());
|
544
|
+
} else {
|
545
|
+
this.props.onSubmit(formData);
|
546
|
+
}
|
547
|
+
if (this.props.resetAfterSubmit) {
|
548
|
+
this.setState({
|
549
|
+
formData: this.props.formData,
|
550
|
+
});
|
551
|
+
if (this.props.global) {
|
552
|
+
this.props.setFormData(this.props.formData);
|
553
|
+
}
|
554
|
+
}
|
555
|
+
}
|
556
|
+
}
|
557
|
+
|
558
|
+
/**
|
559
|
+
* getOnlyFormModifiedValues handler
|
560
|
+
* It returns only the values of the fields that are have really changed since the
|
561
|
+
* form was loaded. Useful for edit forms and PATCH operations, when we only want to
|
562
|
+
* send the changed data.
|
563
|
+
* @method getOnlyFormModifiedValues
|
564
|
+
* @param {Object} event Event object.
|
565
|
+
* @returns {undefined}
|
566
|
+
*/
|
567
|
+
getOnlyFormModifiedValues = () => {
|
568
|
+
const formData = this.state.formData;
|
569
|
+
|
570
|
+
const fieldsModified = Object.keys(
|
571
|
+
difference(formData, this.state.initialFormData),
|
572
|
+
);
|
573
|
+
return {
|
574
|
+
...pickBy(formData, (value, key) => fieldsModified.includes(key)),
|
575
|
+
...(formData['@static_behaviors'] && {
|
576
|
+
'@static_behaviors': formData['@static_behaviors'],
|
577
|
+
}),
|
578
|
+
};
|
579
|
+
};
|
580
|
+
|
581
|
+
/**
|
582
|
+
* Removed blocks and blocks_layout fields from the form.
|
583
|
+
* @method removeBlocksLayoutFields
|
584
|
+
* @param {object} schema The schema definition of the form.
|
585
|
+
* @returns A modified copy of the given schema.
|
586
|
+
*/
|
587
|
+
removeBlocksLayoutFields = (schema) => {
|
588
|
+
const newSchema = { ...schema };
|
589
|
+
const layoutFieldsetIndex = findIndex(
|
590
|
+
newSchema.fieldsets,
|
591
|
+
(fieldset) => fieldset.id === 'layout',
|
592
|
+
);
|
593
|
+
if (layoutFieldsetIndex > -1) {
|
594
|
+
const layoutFields = newSchema.fieldsets[layoutFieldsetIndex].fields;
|
595
|
+
newSchema.fieldsets[layoutFieldsetIndex].fields = layoutFields.filter(
|
596
|
+
(field) => field !== 'blocks' && field !== 'blocks_layout',
|
597
|
+
);
|
598
|
+
if (newSchema.fieldsets[layoutFieldsetIndex].fields.length === 0) {
|
599
|
+
newSchema.fieldsets = [
|
600
|
+
...newSchema.fieldsets.slice(0, layoutFieldsetIndex),
|
601
|
+
...newSchema.fieldsets.slice(layoutFieldsetIndex + 1),
|
602
|
+
];
|
603
|
+
}
|
604
|
+
}
|
605
|
+
return newSchema;
|
606
|
+
};
|
607
|
+
|
608
|
+
/**
|
609
|
+
* Toggle metadata fieldset handler
|
610
|
+
* @method onToggleMetadataFieldset
|
611
|
+
* @param {Object} event Event object.
|
612
|
+
* @param {Object} blockProps Block properties.
|
613
|
+
* @returns {undefined}
|
614
|
+
*/
|
615
|
+
onToggleMetadataFieldset(event, blockProps) {
|
616
|
+
const { index } = blockProps;
|
617
|
+
this.props.setMetadataFieldsets(xor(this.props.metadataFieldsets, [index]));
|
618
|
+
}
|
619
|
+
|
620
|
+
/**
|
621
|
+
* Render method.
|
622
|
+
* @method render
|
623
|
+
* @returns {string} Markup for the component.
|
624
|
+
*/
|
625
|
+
render() {
|
626
|
+
const { settings } = config;
|
627
|
+
const {
|
628
|
+
schema: originalSchema,
|
629
|
+
onCancel,
|
630
|
+
onSubmit,
|
631
|
+
navRoot,
|
632
|
+
type,
|
633
|
+
metadataFieldsets,
|
634
|
+
} = this.props;
|
635
|
+
const formData = this.state.formData;
|
636
|
+
const schema = this.removeBlocksLayoutFields(originalSchema);
|
637
|
+
const Container =
|
638
|
+
config.getComponent({ name: 'Container' }).component || SemanticContainer;
|
639
|
+
|
640
|
+
return this.props.visual ? (
|
641
|
+
// Removing this from SSR is important, since react-beautiful-dnd supports SSR,
|
642
|
+
// but draftJS don't like it much and the hydration gets messed up
|
643
|
+
this.state.isClient && (
|
644
|
+
<Container>
|
645
|
+
<BlocksToolbar
|
646
|
+
formData={formData}
|
647
|
+
selectedBlock={this.state.selected}
|
648
|
+
selectedBlocks={this.state.multiSelected}
|
649
|
+
onChangeBlocks={(newBlockData) => {
|
650
|
+
const newFormData = {
|
651
|
+
...formData,
|
652
|
+
...newBlockData,
|
653
|
+
};
|
654
|
+
this.setState({
|
655
|
+
formData: newFormData,
|
656
|
+
});
|
657
|
+
if (this.props.global) {
|
658
|
+
this.props.setFormData(newFormData);
|
659
|
+
}
|
660
|
+
}}
|
661
|
+
onSetSelectedBlocks={(blockIds) =>
|
662
|
+
this.setState({ multiSelected: blockIds })
|
663
|
+
}
|
664
|
+
onSelectBlock={this.onSelectBlock}
|
665
|
+
/>
|
666
|
+
<UndoToolbar
|
667
|
+
state={{
|
668
|
+
formData,
|
669
|
+
selected: this.state.selected,
|
670
|
+
multiSelected: this.state.multiSelected,
|
671
|
+
}}
|
672
|
+
enableHotKeys
|
673
|
+
onUndoRedo={({ state }) => {
|
674
|
+
if (this.props.global) {
|
675
|
+
this.props.setFormData(state.formData);
|
676
|
+
}
|
677
|
+
return this.setState(state);
|
678
|
+
}}
|
679
|
+
/>
|
680
|
+
<BlocksForm
|
681
|
+
onChangeFormData={(newData) => {
|
682
|
+
const newFormData = {
|
683
|
+
...formData,
|
684
|
+
...newData,
|
685
|
+
};
|
686
|
+
this.setState({
|
687
|
+
formData: newFormData,
|
688
|
+
});
|
689
|
+
if (this.props.global) {
|
690
|
+
this.props.setFormData(newFormData);
|
691
|
+
}
|
692
|
+
}}
|
693
|
+
onChangeField={this.onChangeField}
|
694
|
+
onSelectBlock={this.onSelectBlock}
|
695
|
+
properties={formData}
|
696
|
+
navRoot={navRoot}
|
697
|
+
type={type}
|
698
|
+
errors={this.state.errors}
|
699
|
+
pathname={this.props.pathname}
|
700
|
+
selectedBlock={this.state.selected}
|
701
|
+
multiSelected={this.state.multiSelected}
|
702
|
+
manage={this.props.isAdminForm}
|
703
|
+
allowedBlocks={this.props.allowedBlocks}
|
704
|
+
showRestricted={this.props.showRestricted}
|
705
|
+
editable={this.props.editable}
|
706
|
+
isMainForm={this.props.editable}
|
707
|
+
/>
|
708
|
+
{this.state.isClient && this.props.editable && (
|
709
|
+
<Portal
|
710
|
+
node={__CLIENT__ && document.getElementById('sidebar-metadata')}
|
711
|
+
>
|
712
|
+
<UiForm
|
713
|
+
method="post"
|
714
|
+
onSubmit={this.onSubmit}
|
715
|
+
error={keys(this.state.errors).length > 0}
|
716
|
+
>
|
717
|
+
{schema &&
|
718
|
+
map(schema.fieldsets, (fieldset) => (
|
719
|
+
<Accordion
|
720
|
+
fluid
|
721
|
+
styled
|
722
|
+
className="form"
|
723
|
+
key={fieldset.title}
|
724
|
+
>
|
725
|
+
<div
|
726
|
+
key={fieldset.id}
|
727
|
+
id={`metadataform-fieldset-${fieldset.id}`}
|
728
|
+
>
|
729
|
+
<Accordion.Title
|
730
|
+
active={metadataFieldsets.includes(fieldset.id)}
|
731
|
+
index={fieldset.id}
|
732
|
+
onClick={this.onToggleMetadataFieldset}
|
733
|
+
>
|
734
|
+
{fieldset.title}
|
735
|
+
{metadataFieldsets.includes(fieldset.id) ? (
|
736
|
+
<Icon name={upSVG} size="20px" />
|
737
|
+
) : (
|
738
|
+
<Icon name={downSVG} size="20px" />
|
739
|
+
)}
|
740
|
+
</Accordion.Title>
|
741
|
+
<Accordion.Content
|
742
|
+
active={metadataFieldsets.includes(fieldset.id)}
|
743
|
+
>
|
744
|
+
<Segment className="attached">
|
745
|
+
{map(fieldset.fields, (field, index) => (
|
746
|
+
<Field
|
747
|
+
{...schema.properties[field]}
|
748
|
+
id={field}
|
749
|
+
fieldSet={fieldset.title.toLowerCase()}
|
750
|
+
formData={formData}
|
751
|
+
focus={
|
752
|
+
__CLIENT__ &&
|
753
|
+
document
|
754
|
+
.getElementById('sidebar-metadata')
|
755
|
+
?.contains(document.activeElement)
|
756
|
+
? this.state.inFocus[field]
|
757
|
+
: false
|
758
|
+
}
|
759
|
+
value={formData?.[field]}
|
760
|
+
required={schema.required.indexOf(field) !== -1}
|
761
|
+
onChange={this.onChangeField}
|
762
|
+
onBlur={this.onBlurField}
|
763
|
+
onClick={this.onClickInput}
|
764
|
+
key={field}
|
765
|
+
error={this.state.errors[field]}
|
766
|
+
/>
|
767
|
+
))}
|
768
|
+
</Segment>
|
769
|
+
</Accordion.Content>
|
770
|
+
</div>
|
771
|
+
</Accordion>
|
772
|
+
))}
|
773
|
+
</UiForm>
|
774
|
+
</Portal>
|
775
|
+
)}
|
776
|
+
</Container>
|
777
|
+
)
|
778
|
+
) : (
|
779
|
+
<Container>
|
780
|
+
<UiForm
|
781
|
+
method="post"
|
782
|
+
onSubmit={this.onSubmit}
|
783
|
+
error={keys(this.state.errors).length > 0}
|
784
|
+
className={settings.verticalFormTabs ? 'vertical-form' : ''}
|
785
|
+
>
|
786
|
+
<fieldset className="invisible">
|
787
|
+
<Segment.Group raised>
|
788
|
+
{schema && schema.fieldsets.length > 1 && (
|
789
|
+
<>
|
790
|
+
{settings.verticalFormTabs && this.props.title && (
|
791
|
+
<Segment secondary attached key={this.props.title}>
|
792
|
+
{this.props.title}
|
793
|
+
</Segment>
|
794
|
+
)}
|
795
|
+
<Tab
|
796
|
+
menu={{
|
797
|
+
secondary: true,
|
798
|
+
pointing: true,
|
799
|
+
attached: true,
|
800
|
+
tabular: true,
|
801
|
+
className: 'formtabs',
|
802
|
+
vertical: settings.verticalFormTabs,
|
803
|
+
}}
|
804
|
+
grid={{ paneWidth: 9, tabWidth: 3, stackable: true }}
|
805
|
+
onTabChange={this.onTabChange}
|
806
|
+
activeIndex={this.state.activeIndex}
|
807
|
+
panes={map(schema.fieldsets, (item) => ({
|
808
|
+
menuItem: item.title,
|
809
|
+
render: () => [
|
810
|
+
!settings.verticalFormTabs && this.props.title && (
|
811
|
+
<Segment secondary attached key={this.props.title}>
|
812
|
+
{this.props.title}
|
813
|
+
</Segment>
|
814
|
+
),
|
815
|
+
item.description && (
|
816
|
+
<Message attached="bottom">
|
817
|
+
{item.description}
|
818
|
+
</Message>
|
819
|
+
),
|
820
|
+
...map(item.fields, (field, index) => (
|
821
|
+
<Field
|
822
|
+
{...schema.properties[field]}
|
823
|
+
isDisabled={!this.props.editable}
|
824
|
+
id={field}
|
825
|
+
formData={formData}
|
826
|
+
fieldSet={item.title.toLowerCase()}
|
827
|
+
focus={this.state.inFocus[field]}
|
828
|
+
value={formData?.[field]}
|
829
|
+
required={schema.required.indexOf(field) !== -1}
|
830
|
+
onChange={this.onChangeField}
|
831
|
+
onBlur={this.onBlurField}
|
832
|
+
onClick={this.onClickInput}
|
833
|
+
key={field}
|
834
|
+
error={this.state.errors[field]}
|
835
|
+
/>
|
836
|
+
)),
|
837
|
+
],
|
838
|
+
}))}
|
839
|
+
/>
|
840
|
+
</>
|
841
|
+
)}
|
842
|
+
{schema && schema.fieldsets.length === 1 && (
|
843
|
+
<Segment>
|
844
|
+
{this.props.title && (
|
845
|
+
<Segment className="primary">
|
846
|
+
<h1 style={{ fontSize: '16px' }}> {this.props.title}</h1>
|
847
|
+
</Segment>
|
848
|
+
)}
|
849
|
+
{this.props.description && (
|
850
|
+
<Segment secondary>{this.props.description}</Segment>
|
851
|
+
)}
|
852
|
+
{keys(this.state.errors).length > 0 && (
|
853
|
+
<Message
|
854
|
+
icon="warning"
|
855
|
+
negative
|
856
|
+
attached
|
857
|
+
header={this.props.intl.formatMessage(messages.error)}
|
858
|
+
content={this.props.intl.formatMessage(
|
859
|
+
messages.thereWereSomeErrors,
|
860
|
+
)}
|
861
|
+
/>
|
862
|
+
)}
|
863
|
+
{this.props.error && (
|
864
|
+
<Message
|
865
|
+
icon="warning"
|
866
|
+
negative
|
867
|
+
attached
|
868
|
+
header={this.props.intl.formatMessage(messages.error)}
|
869
|
+
content={this.props.error.message}
|
870
|
+
/>
|
871
|
+
)}
|
872
|
+
{map(schema.fieldsets[0].fields, (field) => (
|
873
|
+
<Field
|
874
|
+
{...schema.properties[field]}
|
875
|
+
id={field}
|
876
|
+
value={formData?.[field]}
|
877
|
+
required={schema.required.indexOf(field) !== -1}
|
878
|
+
onChange={this.onChangeField}
|
879
|
+
onBlur={this.onBlurField}
|
880
|
+
onClick={this.onClickInput}
|
881
|
+
key={field}
|
882
|
+
error={this.state.errors[field]}
|
883
|
+
/>
|
884
|
+
))}
|
885
|
+
</Segment>
|
886
|
+
)}
|
887
|
+
{!this.props.hideActions && (
|
888
|
+
<Segment className="actions" clearing>
|
889
|
+
{onSubmit && (
|
890
|
+
<Button
|
891
|
+
basic
|
892
|
+
primary
|
893
|
+
floated="right"
|
894
|
+
type="submit"
|
895
|
+
aria-label={
|
896
|
+
this.props.submitLabel
|
897
|
+
? this.props.submitLabel
|
898
|
+
: this.props.intl.formatMessage(messages.save)
|
899
|
+
}
|
900
|
+
title={
|
901
|
+
this.props.submitLabel
|
902
|
+
? this.props.submitLabel
|
903
|
+
: this.props.intl.formatMessage(messages.save)
|
904
|
+
}
|
905
|
+
loading={this.props.loading}
|
906
|
+
>
|
907
|
+
<Icon className="circled" name={aheadSVG} size="30px" />
|
908
|
+
</Button>
|
909
|
+
)}
|
910
|
+
{onCancel && (
|
911
|
+
<Button
|
912
|
+
basic
|
913
|
+
secondary
|
914
|
+
aria-label={this.props.intl.formatMessage(
|
915
|
+
messages.cancel,
|
916
|
+
)}
|
917
|
+
title={this.props.intl.formatMessage(messages.cancel)}
|
918
|
+
floated="right"
|
919
|
+
onClick={this.onCancel}
|
920
|
+
>
|
921
|
+
<Icon className="circled" name={clearSVG} size="30px" />
|
922
|
+
</Button>
|
923
|
+
)}
|
924
|
+
</Segment>
|
925
|
+
)}
|
926
|
+
</Segment.Group>
|
927
|
+
</fieldset>
|
928
|
+
</UiForm>
|
929
|
+
</Container>
|
930
|
+
);
|
931
|
+
}
|
932
|
+
}
|
933
|
+
|
934
|
+
const FormIntl = injectIntl(Form, { forwardRef: true });
|
935
|
+
|
936
|
+
export default compose(
|
937
|
+
connect(
|
938
|
+
(state, props) => ({
|
939
|
+
globalData: state.form?.global,
|
940
|
+
metadataFieldsets: state.sidebar?.metadataFieldsets,
|
941
|
+
metadataFieldFocus: state.sidebar?.metadataFieldFocus,
|
942
|
+
}),
|
943
|
+
{
|
944
|
+
setMetadataFieldsets,
|
945
|
+
setSidebarTab,
|
946
|
+
setFormData,
|
947
|
+
resetMetadataFocus,
|
948
|
+
},
|
949
|
+
null,
|
950
|
+
{ forwardRef: true },
|
951
|
+
),
|
952
|
+
)(FormIntl);
|