@eeacms/volto-cca-policy 0.1.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 (65) hide show
  1. package/.coverage.babel.config.js +9 -0
  2. package/.i18n.babel.config.js +1 -0
  3. package/.project.eslintrc.js +48 -0
  4. package/.release-it.json +17 -0
  5. package/CHANGELOG.md +30 -0
  6. package/DEVELOP.md +52 -0
  7. package/LICENSE.md +9 -0
  8. package/README.md +85 -0
  9. package/RELEASE.md +74 -0
  10. package/babel.config.js +17 -0
  11. package/bootstrap +41 -0
  12. package/cypress.config.js +26 -0
  13. package/jest-addon.config.js +36 -0
  14. package/locales/volto.pot +0 -0
  15. package/package.json +51 -0
  16. package/src/components/index.js +1 -0
  17. package/src/components/manage/Blocks/ContextNavigation/ContextNavigationEdit.jsx +31 -0
  18. package/src/components/manage/Blocks/ContextNavigation/ContextNavigationView.jsx +17 -0
  19. package/src/components/manage/Blocks/ContextNavigation/index.js +26 -0
  20. package/src/components/manage/Blocks/ContextNavigation/schema.js +81 -0
  21. package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsEdit.jsx +32 -0
  22. package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsView.jsx +15 -0
  23. package/src/components/manage/Blocks/LayoutSettings/edit.less +4 -0
  24. package/src/components/manage/Blocks/LayoutSettings/index.js +24 -0
  25. package/src/components/manage/Blocks/LayoutSettings/schema.js +32 -0
  26. package/src/components/manage/Blocks/Title/Edit.jsx +226 -0
  27. package/src/components/manage/Blocks/Title/View.jsx +35 -0
  28. package/src/components/manage/Blocks/Title/index.js +13 -0
  29. package/src/components/manage/Blocks/Title/schema.js +80 -0
  30. package/src/components/manage/Blocks/schema-utils.js +16 -0
  31. package/src/components/manage/Blocks/schema.js +52 -0
  32. package/src/components/theme/Banner/Banner.jsx +99 -0
  33. package/src/components/theme/Banner/View.jsx +241 -0
  34. package/src/components/theme/Banner/styles.less +20 -0
  35. package/src/components/theme/CustomCSS/CustomCSS.jsx +12 -0
  36. package/src/components/theme/DraftBackground/DraftBackground.jsx +16 -0
  37. package/src/components/theme/DraftBackground/draft.css +3 -0
  38. package/src/components/theme/DraftBackground/draft.png +0 -0
  39. package/src/components/theme/Homepage/HomePageInverseView.jsx +60 -0
  40. package/src/components/theme/Homepage/HomePageView.jsx +60 -0
  41. package/src/components/theme/Logo.jsx +34 -0
  42. package/src/components/theme/SubsiteClass.jsx +23 -0
  43. package/src/components/theme/Widgets/TokenWidget.jsx +16 -0
  44. package/src/config.js +307 -0
  45. package/src/customizations/@eeacms/volto-block-style/StyleWrapper/schema.js +44 -0
  46. package/src/customizations/@eeacms/volto-eea-design-system/ui/Header/HeaderSearchPopUp.js +80 -0
  47. package/src/customizations/@eeacms/volto-tabs-block/components/templates/default/schema.js +109 -0
  48. package/src/customizations/@eeacms/volto-tabs-block/components/templates/horizontal-responsive/schema.js +109 -0
  49. package/src/customizations/volto/components/manage/Form/Form.jsx +784 -0
  50. package/src/customizations/volto/components/manage/Form/ModalForm.jsx +326 -0
  51. package/src/customizations/volto/components/manage/Sharing/Sharing.jsx +495 -0
  52. package/src/customizations/volto/components/manage/Widgets/ObjectBrowserWidget.jsx +436 -0
  53. package/src/customizations/volto/components/theme/Breadcrumbs/Breadcrumbs.jsx +62 -0
  54. package/src/customizations/volto/components/theme/Comments/Comments.jsx +487 -0
  55. package/src/customizations/volto/components/theme/Footer/Footer.jsx +90 -0
  56. package/src/customizations/volto/components/theme/Header/Header.jsx +258 -0
  57. package/src/customizations/volto/components/theme/Tags/Tags.jsx +53 -0
  58. package/src/customizations/volto/components/theme/Unauthorized/Unauthorized.jsx +91 -0
  59. package/src/customizations/volto/components/theme/View/EventView.jsx +90 -0
  60. package/src/helpers/index.js +44 -0
  61. package/src/icons/content-box.svg +5 -0
  62. package/src/icons/image-narrow.svg +5 -0
  63. package/src/index.js +13 -0
  64. package/src/middleware/voltoCustom.js +37 -0
  65. package/src/policy.js +136 -0
@@ -0,0 +1,784 @@
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 {
17
+ findIndex,
18
+ isEmpty,
19
+ keys,
20
+ map,
21
+ mapValues,
22
+ pickBy,
23
+ without,
24
+ cloneDeep,
25
+ } from 'lodash';
26
+ import isBoolean from 'lodash/isBoolean';
27
+ import PropTypes from 'prop-types';
28
+ import React, { Component } from 'react';
29
+ import { injectIntl } from 'react-intl';
30
+ import { Portal } from 'react-portal';
31
+ import { connect } from 'react-redux';
32
+ import {
33
+ Button,
34
+ Container,
35
+ Form as UiForm,
36
+ Message,
37
+ Segment,
38
+ Tab,
39
+ } from 'semantic-ui-react';
40
+ import { v4 as uuid } from 'uuid';
41
+ import { toast } from 'react-toastify';
42
+ import { BlocksToolbar, UndoToolbar } from '@plone/volto/components';
43
+ import { setSidebarTab } from '@plone/volto/actions';
44
+ import { compose } from 'redux';
45
+ import config from '@plone/volto/registry';
46
+
47
+ /**
48
+ * Form container class.
49
+ * @class Form
50
+ * @extends Component
51
+ */
52
+ class Form extends Component {
53
+ /**
54
+ * Property types.
55
+ * @property {Object} propTypes Property types.
56
+ * @static
57
+ */
58
+ static propTypes = {
59
+ schema: PropTypes.shape({
60
+ fieldsets: PropTypes.arrayOf(
61
+ PropTypes.shape({
62
+ fields: PropTypes.arrayOf(PropTypes.string),
63
+ id: PropTypes.string,
64
+ title: PropTypes.string,
65
+ }),
66
+ ),
67
+ properties: PropTypes.objectOf(PropTypes.any),
68
+ definitions: PropTypes.objectOf(PropTypes.any),
69
+ required: PropTypes.arrayOf(PropTypes.string),
70
+ }),
71
+ formData: PropTypes.objectOf(PropTypes.any),
72
+ pathname: PropTypes.string,
73
+ onSubmit: PropTypes.func,
74
+ onCancel: PropTypes.func,
75
+ submitLabel: PropTypes.string,
76
+ resetAfterSubmit: PropTypes.bool,
77
+ resetOnCancel: PropTypes.bool,
78
+ isEditForm: PropTypes.bool,
79
+ isAdminForm: PropTypes.bool,
80
+ title: PropTypes.string,
81
+ error: PropTypes.shape({
82
+ message: PropTypes.string,
83
+ }),
84
+ loading: PropTypes.bool,
85
+ hideActions: PropTypes.bool,
86
+ description: PropTypes.string,
87
+ visual: PropTypes.bool,
88
+ blocks: PropTypes.arrayOf(PropTypes.object),
89
+ isFormSelected: PropTypes.bool,
90
+ onSelectForm: PropTypes.func,
91
+ editable: PropTypes.bool,
92
+ onChangeFormData: PropTypes.func,
93
+ requestError: PropTypes.string,
94
+ allowedBlocks: PropTypes.arrayOf(PropTypes.string),
95
+ showRestricted: PropTypes.bool,
96
+ };
97
+
98
+ /**
99
+ * Default properties.
100
+ * @property {Object} defaultProps Default properties.
101
+ * @static
102
+ */
103
+ static defaultProps = {
104
+ formData: null,
105
+ onSubmit: null,
106
+ onCancel: null,
107
+ submitLabel: null,
108
+ resetAfterSubmit: false,
109
+ resetOnCancel: false,
110
+ isEditForm: false,
111
+ isAdminForm: false,
112
+ title: null,
113
+ description: null,
114
+ error: null,
115
+ loading: null,
116
+ hideActions: false,
117
+ visual: false,
118
+ blocks: [],
119
+ pathname: '',
120
+ schema: {},
121
+ isFormSelected: true,
122
+ onSelectForm: null,
123
+ editable: true,
124
+ requestError: null,
125
+ allowedBlocks: null,
126
+ };
127
+
128
+ /**
129
+ * Constructor
130
+ * @method constructor
131
+ * @param {Object} props Component properties
132
+ * @constructs Form
133
+ */
134
+ constructor(props) {
135
+ super(props);
136
+ const ids = {
137
+ title: uuid(),
138
+ text: uuid(),
139
+ };
140
+ let { formData } = props;
141
+ const blocksFieldname = getBlocksFieldname(formData);
142
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);
143
+
144
+ if (!props.isEditForm) {
145
+ // It's a normal (add form), get defaults from schema
146
+ formData = {
147
+ ...mapValues(props.schema.properties, 'default'),
148
+ ...formData,
149
+ };
150
+ }
151
+ // defaults for block editor; should be moved to schema on server side
152
+ // Adding fallback in case the fields are empty, so we are sure that the edit form
153
+ // shows at least the default blocks
154
+ if (
155
+ formData.hasOwnProperty(blocksFieldname) &&
156
+ formData.hasOwnProperty(blocksLayoutFieldname)
157
+ ) {
158
+ if (
159
+ !formData[blocksLayoutFieldname] ||
160
+ isEmpty(formData[blocksLayoutFieldname].items)
161
+ ) {
162
+ formData[blocksLayoutFieldname] = {
163
+ items: [ids.title, ids.text],
164
+ };
165
+ }
166
+ if (!formData[blocksFieldname] || isEmpty(formData[blocksFieldname])) {
167
+ formData[blocksFieldname] = {
168
+ [ids.title]: {
169
+ '@type': 'title',
170
+ },
171
+ [ids.text]: {
172
+ '@type': config.settings.defaultBlockType,
173
+ },
174
+ };
175
+ }
176
+ }
177
+
178
+ let selectedBlock = null;
179
+ if (
180
+ formData.hasOwnProperty(blocksLayoutFieldname) &&
181
+ formData[blocksLayoutFieldname].items.length > 0
182
+ ) {
183
+ selectedBlock = formData[blocksLayoutFieldname].items[0];
184
+
185
+ if (config.blocks?.initialBlocksFocus?.[this.props.type]) {
186
+ //Default selected is not the first block, but the one from config.
187
+ Object.keys(formData[blocksFieldname]).forEach((b_key) => {
188
+ if (
189
+ formData[blocksFieldname][b_key]['@type'] ===
190
+ config.blocks?.initialBlocksFocus?.[this.props.type]
191
+ ) {
192
+ selectedBlock = b_key;
193
+ }
194
+ });
195
+ }
196
+ }
197
+ this.state = {
198
+ formData,
199
+ initialFormData: cloneDeep(formData),
200
+ errors: {},
201
+ selected: selectedBlock,
202
+ multiSelected: [],
203
+ isClient: false,
204
+ };
205
+ this.onChangeField = this.onChangeField.bind(this);
206
+ this.onSelectBlock = this.onSelectBlock.bind(this);
207
+ this.onSubmit = this.onSubmit.bind(this);
208
+ this.onCancel = this.onCancel.bind(this);
209
+ this.onTabChange = this.onTabChange.bind(this);
210
+ this.onBlurField = this.onBlurField.bind(this);
211
+ this.onClickInput = this.onClickInput.bind(this);
212
+ }
213
+
214
+ /**
215
+ * On updates caused by props change
216
+ * if errors from Backend come, these will be shown to their corresponding Fields
217
+ * also the first Tab to have any errors will be selected
218
+ * @param {Object} prevProps
219
+ */
220
+ async componentDidUpdate(prevProps, prevState) {
221
+ let { requestError } = this.props;
222
+ let errors = {};
223
+ let activeIndex = 0;
224
+
225
+ if (requestError && prevProps.requestError !== requestError) {
226
+ errors = FormValidation.giveServerErrorsToCorrespondingFields(
227
+ requestError,
228
+ );
229
+ activeIndex = FormValidation.showFirstTabWithErrors({
230
+ errors,
231
+ schema: this.props.schema,
232
+ });
233
+
234
+ this.setState({
235
+ errors,
236
+ activeIndex,
237
+ });
238
+ }
239
+
240
+ if (this.props.onChangeFormData) {
241
+ if (
242
+ // TODO: use fast-deep-equal
243
+ JSON.stringify(prevState?.formData) !==
244
+ JSON.stringify(this.state.formData)
245
+ ) {
246
+ this.props.onChangeFormData(this.state.formData);
247
+ }
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Tab selection is done only by setting activeIndex in state
253
+ */
254
+ onTabChange(e, { activeIndex }) {
255
+ this.setState({ activeIndex });
256
+ }
257
+
258
+ /**
259
+ * If user clicks on input, the form will be not considered pristine
260
+ * this will avoid onBlur effects without interraction with the form
261
+ * @param {Object} e event
262
+ */
263
+ onClickInput(e) {
264
+ this.setState({ isFormPristine: false });
265
+ }
266
+
267
+ /**
268
+ * Validate fields on blur
269
+ * @method onBlurField
270
+ * @param {string} id Id of the field
271
+ * @param {*} value Value of the field
272
+ * @returns {undefined}
273
+ */
274
+ onBlurField(id, value) {
275
+ if (!this.state.isFormPristine) {
276
+ const errors = FormValidation.validateFieldsPerFieldset({
277
+ schema: this.props.schema,
278
+ formData: this.state.formData,
279
+ formatMessage: this.props.intl.formatMessage,
280
+ touchedField: { [id]: value },
281
+ });
282
+
283
+ this.setState({
284
+ errors,
285
+ });
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Component did mount
291
+ * @method componentDidMount
292
+ * @returns {undefined}
293
+ */
294
+ componentDidMount() {
295
+ this.setState({ isClient: true });
296
+ }
297
+
298
+ static getDerivedStateFromProps(props, state) {
299
+ let newState = { ...state };
300
+ if (!props.isFormSelected) {
301
+ newState.selected = null;
302
+ }
303
+
304
+ return newState;
305
+ }
306
+
307
+ /**
308
+ * Change field handler
309
+ * Remove errors for changed field
310
+ * @method onChangeField
311
+ * @param {string} id Id of the field
312
+ * @param {*} value Value of the field
313
+ * @returns {undefined}
314
+ */
315
+ onChangeField(id, value) {
316
+ this.setState((prevState) => {
317
+ const { errors, formData } = prevState;
318
+ delete errors[id];
319
+ return {
320
+ errors,
321
+ formData: {
322
+ ...formData,
323
+ // We need to catch also when the value equals false this fixes #888
324
+ [id]:
325
+ value || (value !== undefined && isBoolean(value)) ? value : null,
326
+ },
327
+ };
328
+ });
329
+ }
330
+
331
+ /**
332
+ * Select block handler
333
+ * @method onSelectBlock
334
+ * @param {string} id Id of the field
335
+ * @param {string} isMultipleSelection true if multiple blocks are selected
336
+ * @returns {undefined}
337
+ */
338
+ onSelectBlock(id, isMultipleSelection, event) {
339
+ let multiSelected = [];
340
+ let selected = id;
341
+
342
+ if (isMultipleSelection) {
343
+ selected = null;
344
+ const blocksLayoutFieldname = getBlocksLayoutFieldname(
345
+ this.state.formData,
346
+ );
347
+
348
+ const blocks_layout = this.state.formData[blocksLayoutFieldname].items;
349
+
350
+ if (event.shiftKey) {
351
+ const anchor =
352
+ this.state.multiSelected.length > 0
353
+ ? blocks_layout.indexOf(this.state.multiSelected[0])
354
+ : blocks_layout.indexOf(this.state.selected);
355
+ const focus = blocks_layout.indexOf(id);
356
+
357
+ if (anchor === focus) {
358
+ multiSelected = [id];
359
+ } else if (focus > anchor) {
360
+ multiSelected = [...blocks_layout.slice(anchor, focus + 1)];
361
+ } else {
362
+ multiSelected = [...blocks_layout.slice(focus, anchor + 1)];
363
+ }
364
+ }
365
+
366
+ if ((event.ctrlKey || event.metaKey) && !event.shiftKey) {
367
+ multiSelected = this.state.multiSelected || [];
368
+ if (!this.state.multiSelected.includes(this.state.selected)) {
369
+ multiSelected = [...multiSelected, this.state.selected];
370
+ selected = null;
371
+ }
372
+ if (this.state.multiSelected.includes(id)) {
373
+ selected = null;
374
+ multiSelected = without(multiSelected, id);
375
+ } else {
376
+ multiSelected = [...multiSelected, id];
377
+ }
378
+ }
379
+ }
380
+
381
+ this.setState({
382
+ selected,
383
+ multiSelected,
384
+ });
385
+
386
+ if (this.props.onSelectForm) {
387
+ if (event) event.nativeEvent.stopImmediatePropagation();
388
+ this.props.onSelectForm();
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Cancel handler
394
+ * It prevents event from triggering submit, reset form if props.resetAfterSubmit
395
+ * and calls this.props.onCancel
396
+ * @method onCancel
397
+ * @param {Object} event Event object.
398
+ * @returns {undefined}
399
+ */
400
+ onCancel(event) {
401
+ if (event) {
402
+ event.preventDefault();
403
+ }
404
+ if (this.props.resetOnCancel || this.props.resetAfterSubmit) {
405
+ this.setState({
406
+ formData: this.props.formData,
407
+ });
408
+ }
409
+ this.props.onCancel(event);
410
+ }
411
+
412
+ /**
413
+ * Submit handler also validate form and collect errors
414
+ * @method onSubmit
415
+ * @param {Object} event Event object.
416
+ * @returns {undefined}
417
+ */
418
+ onSubmit(event) {
419
+ if (event) {
420
+ event.preventDefault();
421
+ }
422
+
423
+ const errors = this.props.schema
424
+ ? FormValidation.validateFieldsPerFieldset({
425
+ schema: this.props.schema,
426
+ formData: this.state.formData,
427
+ formatMessage: this.props.intl.formatMessage,
428
+ })
429
+ : {};
430
+
431
+ if (keys(errors).length > 0) {
432
+ const activeIndex = FormValidation.showFirstTabWithErrors({
433
+ errors,
434
+ schema: this.props.schema,
435
+ });
436
+ this.setState(
437
+ {
438
+ errors,
439
+ activeIndex,
440
+ },
441
+ () => {
442
+ Object.keys(errors).forEach((err) =>
443
+ toast.error(
444
+ <Toast error title={err} content={errors[err].join(', ')} />,
445
+ ),
446
+ );
447
+ },
448
+ );
449
+ // Changes the focus to the metadata tab in the sidebar if error
450
+ this.props.setSidebarTab(0);
451
+ } else {
452
+ // Get only the values that have been modified (Edit forms), send all in case that
453
+ // it's an add form
454
+ if (this.props.isEditForm) {
455
+ this.props.onSubmit(this.getOnlyFormModifiedValues());
456
+ } else {
457
+ this.props.onSubmit(this.state.formData);
458
+ }
459
+ if (this.props.resetAfterSubmit) {
460
+ this.setState({
461
+ formData: this.props.formData,
462
+ });
463
+ }
464
+ }
465
+ }
466
+
467
+ /**
468
+ * getOnlyFormModifiedValues handler
469
+ * It returns only the values of the fields that are have really changed since the
470
+ * form was loaded. Useful for edit forms and PATCH operations, when we only want to
471
+ * send the changed data.
472
+ * @method getOnlyFormModifiedValues
473
+ * @param {Object} event Event object.
474
+ * @returns {undefined}
475
+ */
476
+ getOnlyFormModifiedValues = () => {
477
+ const fieldsModified = Object.keys(
478
+ difference(this.state.formData, this.state.initialFormData),
479
+ );
480
+ return {
481
+ ...pickBy(this.state.formData, (value, key) =>
482
+ fieldsModified.includes(key),
483
+ ),
484
+ ...(this.state.formData['@static_behaviors'] && {
485
+ '@static_behaviors': this.state.formData['@static_behaviors'],
486
+ }),
487
+ };
488
+ };
489
+
490
+ /**
491
+ * Removed blocks and blocks_layout fields from the form.
492
+ * @method removeBlocksLayoutFields
493
+ * @param {object} schema The schema definition of the form.
494
+ * @returns A modified copy of the given schema.
495
+ */
496
+ removeBlocksLayoutFields = (schema) => {
497
+ const newSchema = { ...schema };
498
+ const layoutFieldsetIndex = findIndex(
499
+ newSchema.fieldsets,
500
+ (fieldset) => fieldset.id === 'layout',
501
+ );
502
+ if (layoutFieldsetIndex > -1) {
503
+ const layoutFields = newSchema.fieldsets[layoutFieldsetIndex].fields;
504
+ newSchema.fieldsets[layoutFieldsetIndex].fields = layoutFields.filter(
505
+ (field) => field !== 'blocks' && field !== 'blocks_layout',
506
+ );
507
+ if (newSchema.fieldsets[layoutFieldsetIndex].fields.length === 0) {
508
+ newSchema.fieldsets = [
509
+ ...newSchema.fieldsets.slice(0, layoutFieldsetIndex),
510
+ ...newSchema.fieldsets.slice(layoutFieldsetIndex + 1),
511
+ ];
512
+ }
513
+ }
514
+ return newSchema;
515
+ };
516
+
517
+ /**
518
+ * Render method.
519
+ * @method render
520
+ * @returns {string} Markup for the component.
521
+ */
522
+ render() {
523
+ const { settings } = config;
524
+ const { schema: originalSchema, onCancel, onSubmit } = this.props;
525
+ const { formData } = this.state;
526
+ const schema = this.removeBlocksLayoutFields(originalSchema);
527
+
528
+ return this.props.visual ? (
529
+ // Removing this from SSR is important, since react-beautiful-dnd supports SSR,
530
+ // but draftJS don't like it much and the hydration gets messed up
531
+ this.state.isClient && (
532
+ <div className="ui container">
533
+ <BlocksToolbar
534
+ formData={this.state.formData}
535
+ selectedBlock={this.state.selected}
536
+ selectedBlocks={this.state.multiSelected}
537
+ onChangeBlocks={(newBlockData) =>
538
+ this.setState({
539
+ formData: {
540
+ ...formData,
541
+ ...newBlockData,
542
+ },
543
+ })
544
+ }
545
+ onSetSelectedBlocks={(blockIds) =>
546
+ this.setState({ multiSelected: blockIds })
547
+ }
548
+ onSelectBlock={this.onSelectBlock}
549
+ />
550
+ <UndoToolbar
551
+ state={{
552
+ formData: this.state.formData,
553
+ selected: this.state.selected,
554
+ multiSelected: this.state.multiSelected,
555
+ }}
556
+ enableHotKeys
557
+ onUndoRedo={({ state }) => this.setState(state)}
558
+ />
559
+ <BlocksForm
560
+ onChangeFormData={(newFormData) =>
561
+ this.setState({
562
+ formData: {
563
+ ...formData,
564
+ ...newFormData,
565
+ },
566
+ })
567
+ }
568
+ onChangeField={this.onChangeField}
569
+ onSelectBlock={this.onSelectBlock}
570
+ properties={formData}
571
+ pathname={this.props.pathname}
572
+ selectedBlock={this.state.selected}
573
+ multiSelected={this.state.multiSelected}
574
+ manage={this.props.isAdminForm}
575
+ allowedBlocks={this.props.allowedBlocks}
576
+ showRestricted={this.props.showRestricted}
577
+ editable={this.props.editable}
578
+ isMainForm={this.props.editable}
579
+ />
580
+ {this.state.isClient && this.props.editable && (
581
+ <Portal
582
+ node={__CLIENT__ && document.getElementById('sidebar-metadata')}
583
+ >
584
+ <UiForm
585
+ method="post"
586
+ onSubmit={this.onSubmit}
587
+ error={keys(this.state.errors).length > 0}
588
+ >
589
+ {schema &&
590
+ map(schema.fieldsets, (item) => [
591
+ <Segment
592
+ secondary
593
+ attached
594
+ className={`fieldset-${item.id}`}
595
+ key={item.title}
596
+ >
597
+ {item.title}
598
+ </Segment>,
599
+ <Segment attached key={`fieldset-contents-${item.title}`}>
600
+ {map(item.fields, (field, index) => (
601
+ <Field
602
+ {...schema.properties[field]}
603
+ id={field}
604
+ fieldSet={item.title.toLowerCase()}
605
+ formData={this.state.formData}
606
+ focus={false}
607
+ value={this.state.formData?.[field]}
608
+ required={schema.required.indexOf(field) !== -1}
609
+ onChange={this.onChangeField}
610
+ onBlur={this.onBlurField}
611
+ onClick={this.onClickInput}
612
+ key={field}
613
+ error={this.state.errors[field]}
614
+ />
615
+ ))}
616
+ </Segment>,
617
+ ])}
618
+ </UiForm>
619
+ </Portal>
620
+ )}
621
+ </div>
622
+ )
623
+ ) : (
624
+ <Container>
625
+ <UiForm
626
+ method="post"
627
+ onSubmit={this.onSubmit}
628
+ error={keys(this.state.errors).length > 0}
629
+ className={settings.verticalFormTabs ? 'vertical-form' : ''}
630
+ >
631
+ <fieldset className="invisible" disabled={!this.props.editable}>
632
+ <Segment.Group raised>
633
+ {schema && schema.fieldsets.length > 1 && (
634
+ <>
635
+ {settings.verticalFormTabs && this.props.title && (
636
+ <Segment secondary attached key={this.props.title}>
637
+ {this.props.title}
638
+ </Segment>
639
+ )}
640
+ <Tab
641
+ menu={{
642
+ secondary: true,
643
+ pointing: true,
644
+ attached: true,
645
+ tabular: true,
646
+ className: 'formtabs',
647
+ vertical: settings.verticalFormTabs,
648
+ }}
649
+ grid={{ paneWidth: 9, tabWidth: 3, stackable: true }}
650
+ onTabChange={this.onTabChange}
651
+ activeIndex={this.state.activeIndex}
652
+ panes={map(schema.fieldsets, (item) => ({
653
+ menuItem: item.title,
654
+ render: () => [
655
+ !settings.verticalFormTabs && this.props.title && (
656
+ <Segment secondary attached key={this.props.title}>
657
+ {this.props.title}
658
+ </Segment>
659
+ ),
660
+ item.description && (
661
+ <Message attached="bottom">
662
+ {item.description}
663
+ </Message>
664
+ ),
665
+ ...map(item.fields, (field, index) => (
666
+ <Field
667
+ {...schema.properties[field]}
668
+ id={field}
669
+ formData={this.state.formData}
670
+ fieldSet={item.title.toLowerCase()}
671
+ focus={index === 0}
672
+ value={this.state.formData?.[field]}
673
+ required={schema.required.indexOf(field) !== -1}
674
+ onChange={this.onChangeField}
675
+ onBlur={this.onBlurField}
676
+ onClick={this.onClickInput}
677
+ key={field}
678
+ error={this.state.errors[field]}
679
+ />
680
+ )),
681
+ ],
682
+ }))}
683
+ />
684
+ </>
685
+ )}
686
+ {schema && schema.fieldsets.length === 1 && (
687
+ <Segment>
688
+ {this.props.title && (
689
+ <Segment className="primary">
690
+ <h1 style={{ fontSize: '16px' }}> {this.props.title}</h1>
691
+ </Segment>
692
+ )}
693
+ {this.props.description && (
694
+ <Segment secondary>{this.props.description}</Segment>
695
+ )}
696
+ {keys(this.state.errors).length > 0 && (
697
+ <Message
698
+ icon="warning"
699
+ negative
700
+ attached
701
+ header={this.props.intl.formatMessage(messages.error)}
702
+ content={this.props.intl.formatMessage(
703
+ messages.thereWereSomeErrors,
704
+ )}
705
+ />
706
+ )}
707
+ {this.props.error && (
708
+ <Message
709
+ icon="warning"
710
+ negative
711
+ attached
712
+ header={this.props.intl.formatMessage(messages.error)}
713
+ content={this.props.error.message}
714
+ />
715
+ )}
716
+ {map(schema.fieldsets[0].fields, (field) => (
717
+ <Field
718
+ {...schema.properties[field]}
719
+ id={field}
720
+ value={this.state.formData?.[field]}
721
+ required={schema.required.indexOf(field) !== -1}
722
+ onChange={this.onChangeField}
723
+ onBlur={this.onBlurField}
724
+ onClick={this.onClickInput}
725
+ key={field}
726
+ error={this.state.errors[field]}
727
+ />
728
+ ))}
729
+ </Segment>
730
+ )}
731
+ {!this.props.hideActions && (
732
+ <Segment className="actions" clearing>
733
+ {onSubmit && (
734
+ <Button
735
+ basic
736
+ icon
737
+ primary
738
+ floated="right"
739
+ type="submit"
740
+ aria-label={
741
+ this.props.submitLabel
742
+ ? this.props.submitLabel
743
+ : this.props.intl.formatMessage(messages.save)
744
+ }
745
+ title={
746
+ this.props.submitLabel
747
+ ? this.props.submitLabel
748
+ : this.props.intl.formatMessage(messages.save)
749
+ }
750
+ loading={this.props.loading}
751
+ >
752
+ <Icon className="circled" name={aheadSVG} size="30px" />
753
+ </Button>
754
+ )}
755
+ {onCancel && (
756
+ <Button
757
+ basic
758
+ icon
759
+ secondary
760
+ aria-label={this.props.intl.formatMessage(
761
+ messages.cancel,
762
+ )}
763
+ title={this.props.intl.formatMessage(messages.cancel)}
764
+ floated="right"
765
+ onClick={this.onCancel}
766
+ >
767
+ <Icon className="circled" name={clearSVG} size="30px" />
768
+ </Button>
769
+ )}
770
+ </Segment>
771
+ )}
772
+ </Segment.Group>
773
+ </fieldset>
774
+ </UiForm>
775
+ </Container>
776
+ );
777
+ }
778
+ }
779
+
780
+ const FormIntl = injectIntl(Form, { forwardRef: true });
781
+
782
+ export default compose(
783
+ connect(null, { setSidebarTab }, null, { forwardRef: true }),
784
+ )(FormIntl);