@plone/volto 18.10.0 → 18.11.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/CHANGELOG.md +59 -0
- package/README.md +3 -3
- package/locales/ca/LC_MESSAGES/volto.po +46 -0
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +46 -0
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +46 -0
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +46 -0
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +46 -0
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +46 -0
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +46 -0
- package/locales/fr.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +46 -0
- package/locales/hi.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +46 -0
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +46 -0
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +46 -0
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +46 -0
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +47 -1
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +46 -0
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +5243 -0
- package/locales/ru.json +1 -0
- package/locales/volto.pot +47 -1
- package/locales/zh_CN/LC_MESSAGES/volto.po +46 -0
- package/locales/zh_CN.json +1 -1
- package/package.json +4 -4
- package/razzle.config.js +1 -1
- package/src/components/index.js +2 -0
- package/src/components/manage/Blocks/Block/BlocksForm.jsx +5 -1
- package/src/components/manage/Blocks/Search/components/SearchInput.jsx +11 -1
- package/src/components/manage/DragDropList/DragDropList.jsx +78 -33
- package/src/components/manage/Form/Field.jsx +38 -29
- package/src/components/manage/Form/Form.jsx +141 -69
- package/src/components/manage/Form/ModalForm.jsx +29 -0
- package/src/components/manage/Sidebar/ObjectBrowserNav.jsx +1 -1
- package/src/components/manage/Sidebar/ObjectBrowserNav.test.jsx +34 -13
- package/src/components/manage/Widgets/CheckboxGroupWidget.jsx +214 -0
- package/src/components/manage/Widgets/CheckboxGroupWidget.stories.jsx +39 -0
- package/src/components/manage/Widgets/CheckboxGroupWidget.test.jsx +45 -0
- package/src/components/manage/Widgets/FileWidget.jsx +43 -2
- package/src/components/manage/Widgets/HiddenWidget.jsx +114 -0
- package/src/components/manage/Widgets/HiddenWidget.stories.jsx +20 -0
- package/src/components/manage/Widgets/HiddenWidget.test.jsx +32 -0
- package/src/components/manage/Widgets/ImageWidget.jsx +3 -0
- package/src/components/manage/Widgets/ObjectBrowserWidget.jsx +1 -1
- package/src/components/manage/Widgets/RadioGroupWidget.jsx +207 -0
- package/src/components/manage/Widgets/RadioGroupWidget.stories.jsx +39 -0
- package/src/components/manage/Widgets/RadioGroupWidget.test.jsx +46 -0
- package/src/components/manage/Widgets/SchemaWidget.jsx +806 -320
- package/src/components/manage/Widgets/SelectWidget.jsx +38 -2
- package/src/components/manage/Widgets/StaticTextWidget.jsx +34 -0
- package/src/components/manage/Widgets/StaticTextWidget.stories.jsx +20 -0
- package/src/components/manage/Widgets/StaticTextWidget.test.jsx +25 -0
- package/src/components/manage/Widgets/TimeWidget.jsx +96 -0
- package/src/components/manage/Widgets/TimeWidget.stories.jsx +22 -0
- package/src/components/manage/Widgets/TimeWidget.test.jsx +35 -0
- package/src/components/manage/Widgets/index.tsx +35 -0
- package/src/components/theme/ContactForm/ContactForm.jsx +7 -4
- package/src/components/theme/Widgets/HiddenWidget.jsx +2 -0
- package/src/components/theme/Widgets/HiddenWidget.stories.jsx +25 -0
- package/src/components/theme/Widgets/HiddenWidget.test.jsx +11 -0
- package/src/components/theme/Widgets/StaticTextWidget.jsx +16 -0
- package/src/components/theme/Widgets/StaticTextWidget.stories.jsx +29 -0
- package/src/components/theme/Widgets/StaticTextWidget.test.jsx +32 -0
- package/src/config/Blocks.jsx +2 -0
- package/src/config/Widgets.jsx +14 -0
- package/src/constants/Languages.cjs +1 -0
- package/src/helpers/FormValidation/FormValidation.jsx +4 -0
- package/src/helpers/FormValidation/FormValidation.test.js +147 -31
- package/src/helpers/FormValidation/validators.ts +4 -1
- package/src/helpers/Utils/Utils.jsx +14 -2
- package/theme/themes/pastanaga/collections/form.overrides +4 -0
- package/theme/themes/pastanaga/elements/input.overrides +7 -0
- package/theme/themes/pastanaga/extras/sidebar.less +2 -0
- package/types/components/index.d.ts +1 -1
- package/types/components/manage/Widgets/CheckboxGroupWidget.d.ts +6 -0
- package/types/components/manage/Widgets/CheckboxGroupWidget.stories.d.ts +15 -0
- package/types/components/manage/Widgets/CheckboxGroupWidget.test.d.ts +1 -0
- package/types/components/manage/Widgets/HiddenWidget.d.ts +54 -0
- package/types/components/manage/Widgets/HiddenWidget.stories.d.ts +9 -0
- package/types/components/manage/Widgets/HiddenWidget.test.d.ts +1 -0
- package/types/components/manage/Widgets/RadioGroupWidget.d.ts +6 -0
- package/types/components/manage/Widgets/RadioGroupWidget.stories.d.ts +15 -0
- package/types/components/manage/Widgets/RadioGroupWidget.test.d.ts +1 -0
- package/types/components/manage/Widgets/StaticTextWidget.d.ts +18 -0
- package/types/components/manage/Widgets/StaticTextWidget.stories.d.ts +9 -0
- package/types/components/manage/Widgets/StaticTextWidget.test.d.ts +1 -0
- package/types/components/manage/Widgets/TimeWidget.d.ts +2 -0
- package/types/components/manage/Widgets/TimeWidget.stories.d.ts +8 -0
- package/types/components/manage/Widgets/TimeWidget.test.d.ts +1 -0
- package/types/components/manage/Widgets/index.d.ts +5 -0
- package/types/components/theme/Widgets/HiddenWidget.d.ts +6 -0
- package/types/components/theme/Widgets/HiddenWidget.stories.d.ts +8 -0
- package/types/components/theme/Widgets/HiddenWidget.test.d.ts +1 -0
- package/types/components/theme/Widgets/StaticTextWidget.d.ts +5 -0
- package/types/components/theme/Widgets/StaticTextWidget.stories.d.ts +8 -0
- package/types/components/theme/Widgets/StaticTextWidget.test.d.ts +1 -0
- package/types/config/Widgets.d.ts +14 -0
- package/types/constants/Languages.d.cts +1 -0
- package/types/helpers/FormValidation/validators.d.ts +1 -1
- package/types/helpers/Utils/Utils.d.ts +1 -1
|
@@ -13,41 +13,46 @@ const MODE_HIDDEN = 'hidden'; //hidden mode. If mode is hidden, field is not ren
|
|
|
13
13
|
/**
|
|
14
14
|
* Get default widget
|
|
15
15
|
* @method getViewDefault
|
|
16
|
+
* @param {object} widgets Widgets config
|
|
16
17
|
* @returns {string} Widget component.
|
|
17
18
|
*/
|
|
18
|
-
const getWidgetDefault = () =>
|
|
19
|
+
const getWidgetDefault = (widgets) => widgets.default;
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Get widget by field's `id` attribute
|
|
22
23
|
* @method getWidgetById
|
|
24
|
+
* @param {object} widgets Widgets config
|
|
23
25
|
* @param {string} id Id
|
|
24
26
|
* @returns {string} Widget component.
|
|
25
27
|
*/
|
|
26
|
-
const getWidgetByFieldId = (id) =>
|
|
28
|
+
const getWidgetByFieldId = (widgets, id) => widgets.id[id] || null;
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Get widget by factory attribute
|
|
30
32
|
* @method getWidgetByFactory
|
|
33
|
+
* @param {object} widgets Widgets config
|
|
31
34
|
* @param {string} id Id
|
|
32
35
|
* @returns {string} Widget component.
|
|
33
36
|
*/
|
|
34
|
-
const getWidgetByFactory = (factory) =>
|
|
35
|
-
|
|
37
|
+
const getWidgetByFactory = (widgets, factory) =>
|
|
38
|
+
widgets.factory?.[factory] || null;
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* Get widget by field's `widget` attribute
|
|
39
42
|
* @method getWidgetByName
|
|
43
|
+
* @param {object} widgets Widgets config
|
|
40
44
|
* @param {string} widget Widget
|
|
41
45
|
* @returns {string} Widget component.
|
|
42
46
|
*/
|
|
43
|
-
const getWidgetByName = (widget) =>
|
|
47
|
+
const getWidgetByName = (widgets, widget) =>
|
|
44
48
|
typeof widget === 'string'
|
|
45
|
-
?
|
|
49
|
+
? widgets.widget[widget] || getWidgetDefault(widgets)
|
|
46
50
|
: null;
|
|
47
51
|
|
|
48
52
|
/**
|
|
49
53
|
* Get widget by tagged values
|
|
50
54
|
* @param {object} widgetOptions
|
|
55
|
+
* @param {object} widgets Widgets config
|
|
51
56
|
* @returns {string} Widget component.
|
|
52
57
|
*
|
|
53
58
|
|
|
@@ -59,13 +64,14 @@ directives.widget(
|
|
|
59
64
|
})
|
|
60
65
|
|
|
61
66
|
*/
|
|
62
|
-
const getWidgetFromTaggedValues = (widgetOptions) =>
|
|
67
|
+
const getWidgetFromTaggedValues = (widgets, widgetOptions) =>
|
|
63
68
|
typeof widgetOptions?.frontendOptions?.widget === 'string'
|
|
64
|
-
?
|
|
69
|
+
? widgets.widget[widgetOptions.frontendOptions.widget]
|
|
65
70
|
: null;
|
|
66
71
|
|
|
67
72
|
/**
|
|
68
73
|
* Get widget props from tagged values
|
|
74
|
+
* @param {object} widgets Widgets config
|
|
69
75
|
* @param {object} widgetOptions
|
|
70
76
|
* @returns {string} Widget component.
|
|
71
77
|
*
|
|
@@ -78,7 +84,7 @@ directives.widget(
|
|
|
78
84
|
})
|
|
79
85
|
|
|
80
86
|
*/
|
|
81
|
-
const getWidgetPropsFromTaggedValues = (widgetOptions) =>
|
|
87
|
+
const getWidgetPropsFromTaggedValues = (widgets, widgetOptions) =>
|
|
82
88
|
typeof widgetOptions?.frontendOptions?.widgetProps === 'object'
|
|
83
89
|
? widgetOptions.frontendOptions.widgetProps
|
|
84
90
|
: null;
|
|
@@ -86,25 +92,25 @@ const getWidgetPropsFromTaggedValues = (widgetOptions) =>
|
|
|
86
92
|
/**
|
|
87
93
|
* Get widget by field's `vocabulary` attribute
|
|
88
94
|
* @method getWidgetByVocabulary
|
|
95
|
+
* @param {object} widgets Widgets config
|
|
89
96
|
* @param {string} vocabulary Widget
|
|
90
97
|
* @returns {string} Widget component.
|
|
91
98
|
*/
|
|
92
|
-
const getWidgetByVocabulary = (vocabulary) =>
|
|
99
|
+
const getWidgetByVocabulary = (widgets, vocabulary) =>
|
|
93
100
|
vocabulary && vocabulary['@id']
|
|
94
|
-
?
|
|
95
|
-
vocabulary['@id'].replace(/^.*\/@vocabularies\//, '')
|
|
96
|
-
]
|
|
101
|
+
? widgets.vocabulary[vocabulary['@id'].replace(/^.*\/@vocabularies\//, '')]
|
|
97
102
|
: null;
|
|
98
103
|
|
|
99
104
|
/**
|
|
100
105
|
* Get widget by field's hints `vocabulary` attribute in widgetOptions
|
|
101
106
|
* @method getWidgetByVocabularyFromHint
|
|
107
|
+
* @param {object} widgets Widgets config
|
|
102
108
|
* @param {string} props Widget props
|
|
103
109
|
* @returns {string} Widget component.
|
|
104
110
|
*/
|
|
105
|
-
const getWidgetByVocabularyFromHint = (props) =>
|
|
111
|
+
const getWidgetByVocabularyFromHint = (widgets, props) =>
|
|
106
112
|
props.widgetOptions && props.widgetOptions.vocabulary
|
|
107
|
-
?
|
|
113
|
+
? widgets.vocabulary[
|
|
108
114
|
props.widgetOptions.vocabulary['@id'].replace(
|
|
109
115
|
/^.*\/@vocabularies\//,
|
|
110
116
|
'',
|
|
@@ -115,19 +121,20 @@ const getWidgetByVocabularyFromHint = (props) =>
|
|
|
115
121
|
/**
|
|
116
122
|
* Get widget by field's `choices` attribute
|
|
117
123
|
* @method getWidgetByChoices
|
|
124
|
+
* @param {object} widgets Widgets config
|
|
118
125
|
* @param {string} choices Widget
|
|
119
126
|
* @returns {string} Widget component.
|
|
120
127
|
*/
|
|
121
|
-
const getWidgetByChoices = (props) => {
|
|
128
|
+
const getWidgetByChoices = (widgets, props) => {
|
|
122
129
|
if (props.choices) {
|
|
123
|
-
return
|
|
130
|
+
return widgets.choices;
|
|
124
131
|
}
|
|
125
132
|
|
|
126
133
|
if (props.vocabulary) {
|
|
127
134
|
// If vocabulary exists, then it means it's a choice field in disguise with
|
|
128
135
|
// no widget specified that probably contains a string then we force it
|
|
129
136
|
// to be a select widget instead
|
|
130
|
-
return
|
|
137
|
+
return widgets.choices;
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
return null;
|
|
@@ -136,10 +143,11 @@ const getWidgetByChoices = (props) => {
|
|
|
136
143
|
/**
|
|
137
144
|
* Get widget by field's `type` attribute
|
|
138
145
|
* @method getWidgetByType
|
|
146
|
+
* @param {object} widgets Widgets config
|
|
139
147
|
* @param {string} type Type
|
|
140
148
|
* @returns {string} Widget component.
|
|
141
149
|
*/
|
|
142
|
-
const getWidgetByType = (type) =>
|
|
150
|
+
const getWidgetByType = (widgets, type) => widgets.type[type] || null;
|
|
143
151
|
|
|
144
152
|
/**
|
|
145
153
|
* Field component class.
|
|
@@ -148,16 +156,17 @@ const getWidgetByType = (type) => config.widgets.type[type] || null;
|
|
|
148
156
|
* @returns {string} Markup of the component.
|
|
149
157
|
*/
|
|
150
158
|
const UnconnectedField = (props, { intl }) => {
|
|
159
|
+
const widgets = props.widgets || config.widgets;
|
|
151
160
|
const Widget =
|
|
152
|
-
getWidgetByFieldId(props.id) ||
|
|
153
|
-
getWidgetFromTaggedValues(props.widgetOptions) ||
|
|
154
|
-
getWidgetByName(props.widget) ||
|
|
155
|
-
getWidgetByChoices(props) ||
|
|
156
|
-
getWidgetByVocabulary(props.vocabulary) ||
|
|
157
|
-
getWidgetByVocabularyFromHint(props) ||
|
|
158
|
-
getWidgetByFactory(props.factory) ||
|
|
159
|
-
getWidgetByType(props.type) ||
|
|
160
|
-
getWidgetDefault();
|
|
161
|
+
getWidgetByFieldId(widgets, props.id) ||
|
|
162
|
+
getWidgetFromTaggedValues(widgets, props.widgetOptions) ||
|
|
163
|
+
getWidgetByName(widgets, props.widget) ||
|
|
164
|
+
getWidgetByChoices(widgets, props) ||
|
|
165
|
+
getWidgetByVocabulary(widgets, props.vocabulary) ||
|
|
166
|
+
getWidgetByVocabularyFromHint(widgets, props) ||
|
|
167
|
+
getWidgetByFactory(widgets, props.factory) ||
|
|
168
|
+
getWidgetByType(widgets, props.type) ||
|
|
169
|
+
getWidgetDefault(widgets);
|
|
161
170
|
|
|
162
171
|
if (props.mode === MODE_HIDDEN) {
|
|
163
172
|
return null;
|
|
@@ -166,7 +175,7 @@ const UnconnectedField = (props, { intl }) => {
|
|
|
166
175
|
// Adding the widget props from tagged values (if any)
|
|
167
176
|
const widgetProps = {
|
|
168
177
|
...props,
|
|
169
|
-
...getWidgetPropsFromTaggedValues(props.widgetOptions),
|
|
178
|
+
...getWidgetPropsFromTaggedValues(widgets, props.widgetOptions),
|
|
170
179
|
};
|
|
171
180
|
|
|
172
181
|
if (props.onOrder) {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
getBlocksLayoutFieldname,
|
|
16
16
|
hasBlocksData,
|
|
17
17
|
} from '@plone/volto/helpers/Blocks/Blocks';
|
|
18
|
+
import { applySchemaEnhancer } from '@plone/volto/helpers/Extensions/withBlockSchemaEnhancer';
|
|
18
19
|
import { messages } from '@plone/volto/helpers/MessageLabels/MessageLabels';
|
|
19
20
|
import aheadSVG from '@plone/volto/icons/ahead.svg';
|
|
20
21
|
import clearSVG from '@plone/volto/icons/clear.svg';
|
|
@@ -81,6 +82,8 @@ class Form extends Component {
|
|
|
81
82
|
definitions: PropTypes.objectOf(PropTypes.any),
|
|
82
83
|
required: PropTypes.arrayOf(PropTypes.string),
|
|
83
84
|
}),
|
|
85
|
+
widgets: PropTypes.objectOf(PropTypes.any),
|
|
86
|
+
component: PropTypes.any,
|
|
84
87
|
formData: PropTypes.objectOf(PropTypes.any),
|
|
85
88
|
globalData: PropTypes.objectOf(PropTypes.any),
|
|
86
89
|
metadataFieldsets: PropTypes.arrayOf(PropTypes.string),
|
|
@@ -89,6 +92,9 @@ class Form extends Component {
|
|
|
89
92
|
onSubmit: PropTypes.func,
|
|
90
93
|
onCancel: PropTypes.func,
|
|
91
94
|
submitLabel: PropTypes.string,
|
|
95
|
+
cancelLabel: PropTypes.string,
|
|
96
|
+
textButtons: PropTypes.bool,
|
|
97
|
+
buttonComponent: PropTypes.any,
|
|
92
98
|
resetAfterSubmit: PropTypes.bool,
|
|
93
99
|
resetOnCancel: PropTypes.bool,
|
|
94
100
|
isEditForm: PropTypes.bool,
|
|
@@ -119,9 +125,14 @@ class Form extends Component {
|
|
|
119
125
|
*/
|
|
120
126
|
static defaultProps = {
|
|
121
127
|
formData: null,
|
|
128
|
+
widgets: null,
|
|
129
|
+
component: null,
|
|
122
130
|
onSubmit: null,
|
|
123
131
|
onCancel: null,
|
|
124
132
|
submitLabel: null,
|
|
133
|
+
cancelLabel: null,
|
|
134
|
+
textButtons: false,
|
|
135
|
+
buttonComponent: null,
|
|
125
136
|
resetAfterSubmit: false,
|
|
126
137
|
resetOnCancel: false,
|
|
127
138
|
isEditForm: false,
|
|
@@ -272,22 +283,19 @@ class Form extends Component {
|
|
|
272
283
|
selected: null,
|
|
273
284
|
});
|
|
274
285
|
}
|
|
275
|
-
|
|
286
|
+
|
|
287
|
+
if (requestError && prevProps.requestError !== requestError) {
|
|
276
288
|
errors =
|
|
277
289
|
FormValidation.giveServerErrorsToCorrespondingFields(requestError);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
errors,
|
|
288
|
-
activeIndex,
|
|
289
|
-
});
|
|
290
|
-
}
|
|
290
|
+
activeIndex = FormValidation.showFirstTabWithErrors({
|
|
291
|
+
errors,
|
|
292
|
+
schema: this.props.schema,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
this.setState({
|
|
296
|
+
errors,
|
|
297
|
+
activeIndex,
|
|
298
|
+
});
|
|
291
299
|
}
|
|
292
300
|
|
|
293
301
|
if (this.props.onChangeFormData) {
|
|
@@ -551,6 +559,18 @@ class Form extends Component {
|
|
|
551
559
|
formData: blocks[block],
|
|
552
560
|
});
|
|
553
561
|
}
|
|
562
|
+
|
|
563
|
+
if (config.blocks.blocksConfig[blocks[block]['@type']].blockSchema) {
|
|
564
|
+
blockSchema = applySchemaEnhancer({
|
|
565
|
+
schema: blockSchema,
|
|
566
|
+
formData: blocks[block],
|
|
567
|
+
intl: this.props.intl,
|
|
568
|
+
blocksConfig: config.blocks.blocksConfig,
|
|
569
|
+
navRoot: this.props.navRoot,
|
|
570
|
+
contentType: this.props.content['@type'],
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
554
574
|
const blockErrors = FormValidation.validateFieldsPerFieldset({
|
|
555
575
|
schema: blockSchema,
|
|
556
576
|
formData: blocks[block],
|
|
@@ -564,11 +584,13 @@ class Form extends Component {
|
|
|
564
584
|
}
|
|
565
585
|
});
|
|
566
586
|
}
|
|
587
|
+
|
|
567
588
|
if (keys(errors).length > 0 || keys(blocksErrors).length > 0) {
|
|
568
589
|
const activeIndex = FormValidation.showFirstTabWithErrors({
|
|
569
590
|
errors,
|
|
570
591
|
schema: this.props.schema,
|
|
571
592
|
});
|
|
593
|
+
|
|
572
594
|
this.setState({
|
|
573
595
|
errors: {
|
|
574
596
|
...errors,
|
|
@@ -579,23 +601,14 @@ class Form extends Component {
|
|
|
579
601
|
|
|
580
602
|
if (keys(errors).length > 0) {
|
|
581
603
|
// Changes the focus to the metadata tab in the sidebar if error
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
<strong>
|
|
591
|
-
{this.props.schema.properties[err].title || err}:
|
|
592
|
-
</strong>{' '}
|
|
593
|
-
{errors[err]}
|
|
594
|
-
</li>
|
|
595
|
-
))}
|
|
596
|
-
</ul>
|
|
597
|
-
}
|
|
598
|
-
/>,
|
|
604
|
+
Object.keys(errors).forEach((err) =>
|
|
605
|
+
toast.error(
|
|
606
|
+
<Toast
|
|
607
|
+
error
|
|
608
|
+
title={this.props.schema.properties[err].title || err}
|
|
609
|
+
content={errors[err].join(', ')}
|
|
610
|
+
/>,
|
|
611
|
+
),
|
|
599
612
|
);
|
|
600
613
|
this.props.setSidebarTab(0);
|
|
601
614
|
} else if (keys(blocksErrors).length > 0) {
|
|
@@ -605,12 +618,14 @@ class Form extends Component {
|
|
|
605
618
|
const errorMessage = Object.entries(
|
|
606
619
|
Object.entries(blocksErrors)[0][1],
|
|
607
620
|
)[0][1];
|
|
621
|
+
const errorFieldTitle = errorMessage.title || errorField;
|
|
622
|
+
|
|
608
623
|
toast.error(
|
|
609
624
|
<Toast
|
|
610
625
|
error
|
|
611
626
|
title={this.props.intl.formatMessage(
|
|
612
627
|
messages.blocksFieldsErrorTitle,
|
|
613
|
-
{ errorField },
|
|
628
|
+
{ errorField: errorFieldTitle },
|
|
614
629
|
)}
|
|
615
630
|
content={errorMessage}
|
|
616
631
|
/>,
|
|
@@ -717,11 +732,16 @@ class Form extends Component {
|
|
|
717
732
|
navRoot,
|
|
718
733
|
type,
|
|
719
734
|
metadataFieldsets,
|
|
735
|
+
component,
|
|
736
|
+
buttonComponent,
|
|
720
737
|
} = this.props;
|
|
721
738
|
const formData = this.state.formData;
|
|
722
739
|
const schema = this.removeBlocksLayoutFields(originalSchema);
|
|
723
740
|
const Container =
|
|
724
741
|
config.getComponent({ name: 'Container' }).component || SemanticContainer;
|
|
742
|
+
const FormComponent = component || UiForm;
|
|
743
|
+
const ButtonComponent = buttonComponent || Button;
|
|
744
|
+
|
|
725
745
|
return this.props.visual ? (
|
|
726
746
|
// Removing this from SSR is important, since react-beautiful-dnd supports SSR,
|
|
727
747
|
// but draftJS don't like it much and the hydration gets messed up
|
|
@@ -885,7 +905,7 @@ class Form extends Component {
|
|
|
885
905
|
)
|
|
886
906
|
) : (
|
|
887
907
|
<Container>
|
|
888
|
-
<
|
|
908
|
+
<FormComponent
|
|
889
909
|
method="post"
|
|
890
910
|
onSubmit={this.onSubmit}
|
|
891
911
|
error={keys(this.state.errors).length > 0}
|
|
@@ -927,6 +947,7 @@ class Form extends Component {
|
|
|
927
947
|
),
|
|
928
948
|
...map(item.fields, (field, index) => (
|
|
929
949
|
<Field
|
|
950
|
+
widgets={this.props.widgets}
|
|
930
951
|
{...schema.properties[field]}
|
|
931
952
|
id={field}
|
|
932
953
|
formData={formData}
|
|
@@ -982,6 +1003,7 @@ class Form extends Component {
|
|
|
982
1003
|
)}
|
|
983
1004
|
{map(schema.fieldsets[0].fields, (field) => (
|
|
984
1005
|
<Field
|
|
1006
|
+
widgets={this.props.widgets}
|
|
985
1007
|
{...schema.properties[field]}
|
|
986
1008
|
id={field}
|
|
987
1009
|
value={formData?.[field]}
|
|
@@ -997,47 +1019,97 @@ class Form extends Component {
|
|
|
997
1019
|
)}
|
|
998
1020
|
{!this.props.hideActions && (
|
|
999
1021
|
<Segment className="actions" clearing>
|
|
1000
|
-
{onSubmit &&
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1022
|
+
{onSubmit &&
|
|
1023
|
+
(this.props.textButtons ? (
|
|
1024
|
+
<ButtonComponent
|
|
1025
|
+
primary
|
|
1026
|
+
floated="right"
|
|
1027
|
+
type="submit"
|
|
1028
|
+
aria-label={
|
|
1029
|
+
this.props.submitLabel
|
|
1030
|
+
? this.props.submitLabel
|
|
1031
|
+
: this.props.intl.formatMessage(messages.save)
|
|
1032
|
+
}
|
|
1033
|
+
title={
|
|
1034
|
+
this.props.submitLabel
|
|
1035
|
+
? this.props.submitLabel
|
|
1036
|
+
: this.props.intl.formatMessage(messages.save)
|
|
1037
|
+
}
|
|
1038
|
+
loading={this.props.loading}
|
|
1039
|
+
>
|
|
1040
|
+
{this.props.submitLabel
|
|
1008
1041
|
? this.props.submitLabel
|
|
1009
|
-
: this.props.intl.formatMessage(messages.save)
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1042
|
+
: this.props.intl.formatMessage(messages.save)}
|
|
1043
|
+
</ButtonComponent>
|
|
1044
|
+
) : (
|
|
1045
|
+
<ButtonComponent
|
|
1046
|
+
basic
|
|
1047
|
+
primary
|
|
1048
|
+
floated="right"
|
|
1049
|
+
type="submit"
|
|
1050
|
+
aria-label={
|
|
1051
|
+
this.props.submitLabel
|
|
1052
|
+
? this.props.submitLabel
|
|
1053
|
+
: this.props.intl.formatMessage(messages.save)
|
|
1054
|
+
}
|
|
1055
|
+
title={
|
|
1056
|
+
this.props.submitLabel
|
|
1057
|
+
? this.props.submitLabel
|
|
1058
|
+
: this.props.intl.formatMessage(messages.save)
|
|
1059
|
+
}
|
|
1060
|
+
loading={this.props.loading}
|
|
1061
|
+
>
|
|
1062
|
+
<Icon className="circled" name={aheadSVG} size="30px" />
|
|
1063
|
+
</ButtonComponent>
|
|
1064
|
+
))}
|
|
1065
|
+
{onCancel &&
|
|
1066
|
+
(this.props.textButtons ? (
|
|
1067
|
+
<ButtonComponent
|
|
1068
|
+
secondary
|
|
1069
|
+
type="button"
|
|
1070
|
+
aria-label={
|
|
1071
|
+
this.props.cancelLabel
|
|
1072
|
+
? this.props.cancelLabel
|
|
1073
|
+
: this.props.intl.formatMessage(messages.cancel)
|
|
1074
|
+
}
|
|
1075
|
+
title={
|
|
1076
|
+
this.props.cancelLabel
|
|
1077
|
+
? this.props.cancelLabel
|
|
1078
|
+
: this.props.intl.formatMessage(messages.cancel)
|
|
1079
|
+
}
|
|
1080
|
+
floated="right"
|
|
1081
|
+
onClick={this.onCancel}
|
|
1082
|
+
>
|
|
1083
|
+
{this.props.cancelLabel
|
|
1084
|
+
? this.props.cancelLabel
|
|
1085
|
+
: this.props.intl.formatMessage(messages.cancel)}
|
|
1086
|
+
</ButtonComponent>
|
|
1087
|
+
) : (
|
|
1088
|
+
<ButtonComponent
|
|
1089
|
+
basic
|
|
1090
|
+
secondary
|
|
1091
|
+
type="button"
|
|
1092
|
+
aria-label={
|
|
1093
|
+
this.props.cancelLabel
|
|
1094
|
+
? this.props.cancelLabel
|
|
1095
|
+
: this.props.intl.formatMessage(messages.cancel)
|
|
1096
|
+
}
|
|
1097
|
+
title={
|
|
1098
|
+
this.props.cancelLabel
|
|
1099
|
+
? this.props.cancelLabel
|
|
1100
|
+
: this.props.intl.formatMessage(messages.cancel)
|
|
1101
|
+
}
|
|
1102
|
+
floated="right"
|
|
1103
|
+
onClick={this.onCancel}
|
|
1104
|
+
>
|
|
1105
|
+
<Icon className="circled" name={clearSVG} size="30px" />
|
|
1106
|
+
</ButtonComponent>
|
|
1107
|
+
))}
|
|
1036
1108
|
</Segment>
|
|
1037
1109
|
)}
|
|
1038
1110
|
</Segment.Group>
|
|
1039
1111
|
</fieldset>
|
|
1040
|
-
</
|
|
1112
|
+
</FormComponent>
|
|
1041
1113
|
</Container>
|
|
1042
1114
|
);
|
|
1043
1115
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import React, { Component } from 'react';
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
|
+
import isEqual from 'lodash/isEqual';
|
|
8
9
|
import keys from 'lodash/keys';
|
|
9
10
|
import map from 'lodash/map';
|
|
10
11
|
import {
|
|
@@ -76,6 +77,7 @@ class ModalForm extends Component {
|
|
|
76
77
|
submitError: PropTypes.string,
|
|
77
78
|
onSubmit: PropTypes.func.isRequired,
|
|
78
79
|
onCancel: PropTypes.func,
|
|
80
|
+
onChangeFormData: PropTypes.func,
|
|
79
81
|
open: PropTypes.bool,
|
|
80
82
|
submitLabel: PropTypes.string,
|
|
81
83
|
loading: PropTypes.bool,
|
|
@@ -207,6 +209,33 @@ class ModalForm extends Component {
|
|
|
207
209
|
});
|
|
208
210
|
}
|
|
209
211
|
|
|
212
|
+
/**
|
|
213
|
+
* Component did update lifecycle handler
|
|
214
|
+
* @param {Object} prevProps
|
|
215
|
+
* @param {Object} prevState
|
|
216
|
+
*/
|
|
217
|
+
async componentDidUpdate(prevProps, prevState) {
|
|
218
|
+
if (this.props.onChangeFormData) {
|
|
219
|
+
if (!isEqual(prevState?.formData, this.state.formData)) {
|
|
220
|
+
this.props.onChangeFormData(this.state.formData);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!isEqual(prevProps.formData, this.props.formData)) {
|
|
224
|
+
let newFormData = {};
|
|
225
|
+
map(keys(this.props.formData), (field) => {
|
|
226
|
+
if (!isEqual(prevProps.formData[field], this.props.formData[field])) {
|
|
227
|
+
newFormData[field] = this.props.formData[field];
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
this.setState({
|
|
231
|
+
formData: {
|
|
232
|
+
...this.state.formData,
|
|
233
|
+
...newFormData,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
210
239
|
/**
|
|
211
240
|
* Render method.
|
|
212
241
|
* @method render
|
|
@@ -99,23 +99,44 @@ const currentSearchResults = {
|
|
|
99
99
|
],
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
describe('ObjectBrowserNav', () => {
|
|
103
103
|
const store = mockStore({
|
|
104
104
|
intl: {
|
|
105
105
|
locale: 'en',
|
|
106
106
|
messages: {},
|
|
107
107
|
},
|
|
108
108
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
|
|
110
|
+
const baseProps = {
|
|
111
|
+
currentSearchResults,
|
|
112
|
+
isSelectable: () => true,
|
|
113
|
+
handleClickOnItem: jest.fn(),
|
|
114
|
+
handleDoubleClickOnItem: jest.fn(),
|
|
115
|
+
mode: 'link',
|
|
116
|
+
view: 'list',
|
|
117
|
+
navigateTo: jest.fn(),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
it('renders a view image component', () => {
|
|
121
|
+
const component = renderer.create(
|
|
122
|
+
<Provider store={store}>
|
|
123
|
+
<ObjectBrowserNav {...baseProps} />
|
|
124
|
+
</Provider>,
|
|
125
|
+
);
|
|
126
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('handles selected as empty object without errors', () => {
|
|
130
|
+
const component = renderer.create(
|
|
131
|
+
<Provider store={store}>
|
|
132
|
+
<ObjectBrowserNav
|
|
133
|
+
{...baseProps}
|
|
134
|
+
selected={{}} // Empty object being tested
|
|
135
|
+
/>
|
|
136
|
+
</Provider>,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const json = component.toJSON();
|
|
140
|
+
expect(json).toMatchSnapshot();
|
|
141
|
+
});
|
|
121
142
|
});
|