@eeacms/volto-bise-policy 1.2.28 → 1.2.30

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 CHANGED
@@ -4,6 +4,30 @@ 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
+ ### [1.2.30](https://github.com/eea/volto-bise-policy/compare/1.2.29...1.2.30) - 7 November 2025
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat: update EUNIS marine habitat widgets [laszlocseh - [`702fe69`](https://github.com/eea/volto-bise-policy/commit/702fe69ff8d69c6bd7c130bb12fd57f346b534fd)]
12
+
13
+ #### :house: Internal changes
14
+
15
+ - style: Automated code fix [eea-jenkins - [`e880e27`](https://github.com/eea/volto-bise-policy/commit/e880e27804d16e394b3d5fc9ced17168a68157bf)]
16
+
17
+ ### [1.2.29](https://github.com/eea/volto-bise-policy/compare/1.2.28...1.2.29) - 5 November 2025
18
+
19
+ #### :rocket: New Features
20
+
21
+ - feat: refs #292988 added widgets for the EUNIS Marine Habitats ct fields [laszlocseh - [`c35a36c`](https://github.com/eea/volto-bise-policy/commit/c35a36c57eed53eef0914db0346a0e2a5a51d9a2)]
22
+
23
+ #### :house: Internal changes
24
+
25
+ - style: Automated code fix [eea-jenkins - [`dbd6b93`](https://github.com/eea/volto-bise-policy/commit/dbd6b93f71c0ef52470cdbcf8810796adc0b0f4d)]
26
+
27
+ #### :hammer_and_wrench: Others
28
+
29
+ - temporarily disable sonarqube test [laszlocseh - [`3696d49`](https://github.com/eea/volto-bise-policy/commit/3696d498192b0fb27299de0063d781914616d61e)]
30
+ - fix eslint [laszlocseh - [`01431f6`](https://github.com/eea/volto-bise-policy/commit/01431f6de4fabc32861e53677d3fa43990ef8f8f)]
7
31
  ### [1.2.28](https://github.com/eea/volto-bise-policy/compare/1.2.27...1.2.28) - 3 November 2025
8
32
 
9
33
  #### :hammer_and_wrench: Others
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-bise-policy",
3
- "version": "1.2.28",
3
+ "version": "1.2.30",
4
4
  "description": "@eeacms/volto-bise-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -0,0 +1,683 @@
1
+ import React from 'react';
2
+ import { defineMessages, useIntl } from 'react-intl';
3
+ import { Accordion, Button, Segment } from 'semantic-ui-react';
4
+ import { DragDropList, FormFieldWrapper, Icon } from '@plone/volto/components';
5
+ import { applySchemaDefaults, reorderArray } from '@plone/volto/helpers';
6
+ import ObjectWidget from '@plone/volto/components/manage/Widgets/ObjectWidget';
7
+
8
+ import upSVG from '@plone/volto/icons/up-key.svg';
9
+ import downSVG from '@plone/volto/icons/down-key.svg';
10
+ import deleteSVG from '@plone/volto/icons/delete.svg';
11
+ import addSVG from '@plone/volto/icons/add.svg';
12
+ import dragSVG from '@plone/volto/icons/drag.svg';
13
+ import { v4 as uuid } from 'uuid';
14
+
15
+ import { EuropeanRedListVocab, countryCodesVocab } from './vocabularies';
16
+ import './styles.less';
17
+ // import ObjectListWidget from '@plone/volto/components/manage/Widgets/ObjectListWidget';
18
+
19
+ const messages = defineMessages({
20
+ labelRemoveItem: {
21
+ id: 'Remove item',
22
+ defaultMessage: 'Remove item',
23
+ },
24
+ labelCollapseItem: {
25
+ id: 'Collapse item',
26
+ defaultMessage: 'Collapse item',
27
+ },
28
+ labelShowItem: {
29
+ id: 'Show item',
30
+ defaultMessage: 'Show item',
31
+ },
32
+ emptyObjectList: {
33
+ id: 'Empty object list',
34
+ defaultMessage: 'Empty object list',
35
+ },
36
+ add: {
37
+ id: 'Add (object list)',
38
+ defaultMessage: 'Add',
39
+ },
40
+ });
41
+
42
+ const BaseObjectListWidget = (props) => {
43
+ const { block, fieldSet, id, schema, onChange, schemaExtender } = props;
44
+ const value = props.value?.value || props.value || [];
45
+ const [localActiveObject, setLocalActiveObject] = React.useState(
46
+ props.activeObject ?? value.length - 1,
47
+ );
48
+
49
+ let activeObject, setActiveObject;
50
+ if (
51
+ (props.activeObject || props.activeObject === 0) &&
52
+ props.setActiveObject
53
+ ) {
54
+ activeObject = props.activeObject;
55
+ setActiveObject = props.setActiveObject;
56
+ } else {
57
+ activeObject = localActiveObject;
58
+ setActiveObject = setLocalActiveObject;
59
+ }
60
+
61
+ const intl = useIntl();
62
+
63
+ function handleChangeActiveObject(e, blockProps) {
64
+ const { index } = blockProps;
65
+ const newIndex = activeObject === index ? -1 : index;
66
+
67
+ setActiveObject(newIndex);
68
+ }
69
+ const objectSchema = typeof schema === 'function' ? schema(props) : schema;
70
+
71
+ const topLayerShadow = '0 1px 1px rgba(0,0,0,0.15)';
72
+ const secondLayer = ', 0 10px 0 -5px #eee, 0 10px 1px -4px rgba(0,0,0,0.15)';
73
+ const thirdLayer = ', 0 20px 0 -10px #eee, 0 20px 1px -9px rgba(0,0,0,0.15)';
74
+
75
+ return (
76
+ <div className="bise-objectlist-widget">
77
+ <FormFieldWrapper {...props} noForInFieldLabel className="objectlist">
78
+ <div className="add-item-button-wrapper">
79
+ <Button
80
+ compact
81
+ icon
82
+ aria-label={
83
+ objectSchema.addMessage ||
84
+ `${intl.formatMessage(messages.add)} ${objectSchema.title}`
85
+ }
86
+ onClick={(e) => {
87
+ e.preventDefault();
88
+ const data = {
89
+ '@id': uuid(),
90
+ };
91
+ const objSchema = schemaExtender
92
+ ? schemaExtender(schema, data, intl)
93
+ : objectSchema;
94
+ const dataWithDefaults = applySchemaDefaults({
95
+ data,
96
+ schema: objSchema,
97
+ intl,
98
+ });
99
+
100
+ onChange(id, { value: [...value, dataWithDefaults] });
101
+ setActiveObject(value.length);
102
+ }}
103
+ >
104
+ <Icon name={addSVG} size="18px" />
105
+ &nbsp;
106
+ {/* Custom addMessage in schema, else default to English */}
107
+ {objectSchema.addMessage ||
108
+ `${intl.formatMessage(messages.add)} ${objectSchema.title}`}
109
+ </Button>
110
+ </div>
111
+ {value.length === 0 && (
112
+ <input
113
+ aria-labelledby={`fieldset-${
114
+ fieldSet || 'default'
115
+ }-field-label-${id}`}
116
+ type="hidden"
117
+ value={intl.formatMessage(messages.emptyObjectList)}
118
+ />
119
+ )}
120
+ </FormFieldWrapper>
121
+ <DragDropList
122
+ style={{
123
+ boxShadow: `${topLayerShadow}${value.length > 1 ? secondLayer : ''}${
124
+ value.length > 2 ? thirdLayer : ''
125
+ }`,
126
+ }}
127
+ forwardedAriaLabelledBy={`fieldset-${
128
+ fieldSet || 'default'
129
+ }-field-label-${id}`}
130
+ childList={value.map((o) => [o['@id'] || uuid(), o])}
131
+ onMoveItem={(result) => {
132
+ const { source, destination } = result;
133
+ if (!destination) {
134
+ return;
135
+ }
136
+ const newValue = reorderArray(value, source.index, destination.index);
137
+ onChange(id, { value: newValue });
138
+ return true;
139
+ }}
140
+ >
141
+ {({ child, childId, index, draginfo }) => {
142
+ return (
143
+ <div
144
+ ref={draginfo.innerRef}
145
+ {...draginfo.draggableProps}
146
+ key={childId}
147
+ >
148
+ <Accordion key={index} fluid styled>
149
+ <Accordion.Title
150
+ active={activeObject === index}
151
+ index={index}
152
+ onClick={handleChangeActiveObject}
153
+ aria-label={`${
154
+ activeObject === index
155
+ ? intl.formatMessage(messages.labelCollapseItem)
156
+ : intl.formatMessage(messages.labelShowItem)
157
+ } #${index + 1}`}
158
+ >
159
+ <button
160
+ style={{
161
+ visibility: 'visible',
162
+ display: 'inline-block',
163
+ }}
164
+ {...draginfo.dragHandleProps}
165
+ className="drag handle"
166
+ >
167
+ <Icon name={dragSVG} size="18px" />
168
+ </button>
169
+
170
+ <div className="accordion-title-wrapper">
171
+ {`${objectSchema.title} #${index + 1}`}
172
+ </div>
173
+ <div className="accordion-tools">
174
+ <button
175
+ aria-label={`${intl.formatMessage(
176
+ messages.labelRemoveItem,
177
+ )} #${index + 1}`}
178
+ onClick={() => {
179
+ onChange(id, {
180
+ value: value.filter((v, i) => i !== index),
181
+ });
182
+ }}
183
+ >
184
+ <Icon name={deleteSVG} size="20px" color="#e40166" />
185
+ </button>
186
+ {activeObject === index ? (
187
+ <Icon name={upSVG} size="20px" />
188
+ ) : (
189
+ <Icon name={downSVG} size="20px" />
190
+ )}
191
+ </div>
192
+ </Accordion.Title>
193
+ <Accordion.Content active={activeObject === index}>
194
+ <Segment>
195
+ <ObjectWidget
196
+ id={`${id}-${index}`}
197
+ key={`ow-${id}-${index}`}
198
+ block={block}
199
+ schema={
200
+ schemaExtender
201
+ ? schemaExtender(schema, child, intl)
202
+ : objectSchema
203
+ }
204
+ value={child}
205
+ onChange={(fi, fv) => {
206
+ const newvalue = value.map((v, i) =>
207
+ i !== index ? v : fv,
208
+ );
209
+ onChange(id, { value: newvalue });
210
+ }}
211
+ />
212
+ </Segment>
213
+ </Accordion.Content>
214
+ </Accordion>
215
+ </div>
216
+ );
217
+ }}
218
+ </DragDropList>
219
+ </div>
220
+ );
221
+ };
222
+
223
+ export const EUNISMSFDView = ({ value }) => {
224
+ let parsedValue = value;
225
+
226
+ if (typeof value === 'string') {
227
+ try {
228
+ parsedValue = JSON.parse(value);
229
+ } catch (e) {
230
+ return null;
231
+ }
232
+ }
233
+
234
+ const items = Array.isArray(parsedValue)
235
+ ? parsedValue
236
+ : parsedValue?.value || [];
237
+
238
+ if (!items || items.length === 0) return null;
239
+
240
+ return (
241
+ <div className="eunis-widget-view">
242
+ {items.map((item) => (
243
+ <div key={item['@id']} className="msfd-item">
244
+ <div>
245
+ {item.relation}
246
+ {item.value}
247
+ </div>
248
+ </div>
249
+ ))}
250
+ </div>
251
+ );
252
+ };
253
+
254
+ export const EUNISCodeView = ({ value }) => {
255
+ if (!value) return value;
256
+
257
+ return (
258
+ <span>
259
+ <a
260
+ href={`/habitats_eunis_revised/EUNISrev_${value}`}
261
+ target="_blank"
262
+ rel="noopener"
263
+ >
264
+ {value}
265
+ </a>
266
+ </span>
267
+ );
268
+ };
269
+
270
+ export const EUNISHDView = ({ value }) => {
271
+ let parsedValue = value;
272
+
273
+ if (typeof value === 'string') {
274
+ try {
275
+ parsedValue = JSON.parse(value);
276
+ } catch (e) {
277
+ return null;
278
+ }
279
+ }
280
+
281
+ const items = Array.isArray(parsedValue)
282
+ ? parsedValue
283
+ : parsedValue?.value || [];
284
+
285
+ if (!items || items.length === 0) return null;
286
+
287
+ return (
288
+ <div className="eunis-widget-view">
289
+ {items.map((item) => (
290
+ <div key={item['@id']} className="msfd-item">
291
+ <div>
292
+ {item.link ? (
293
+ <>
294
+ <span>{item.relation}</span>
295
+ <a href={item.link}>{item.value}</a>
296
+ </>
297
+ ) : (
298
+ <>
299
+ <span>{item.relation}</span>
300
+ <a
301
+ href={`/habitats/ANNEX1_${item.value}`}
302
+ target="_blank"
303
+ rel="noopener"
304
+ >
305
+ {item.value}
306
+ </a>
307
+ </>
308
+ )}
309
+ </div>
310
+ </div>
311
+ ))}
312
+ </div>
313
+ );
314
+ };
315
+
316
+ export const EUNISEuropeanRedListView = ({ value }) => {
317
+ let parsedValue = value;
318
+
319
+ if (typeof value === 'string') {
320
+ try {
321
+ parsedValue = JSON.parse(value);
322
+ } catch (e) {
323
+ return null;
324
+ }
325
+ }
326
+
327
+ const items = Array.isArray(parsedValue)
328
+ ? parsedValue
329
+ : parsedValue?.value || [];
330
+
331
+ if (!items || items.length === 0) return null;
332
+
333
+ return (
334
+ <div className="eunis-widget-view">
335
+ {items.map((item) => (
336
+ <div key={item['@id']} className="msfd-item">
337
+ <div>
338
+ {item.link ? (
339
+ <>
340
+ <span>{item.relation}</span>
341
+ <a href={item.link}>{item.value}</a>
342
+ </>
343
+ ) : (
344
+ <>
345
+ <span>{item.relation}</span>
346
+ <a
347
+ href={`/habitats_rl/REDLIST_${item.value.split(' - ')[1]}`}
348
+ target="_blank"
349
+ rel="noopener"
350
+ >
351
+ {item.value}
352
+ </a>
353
+ </>
354
+ )}
355
+ </div>
356
+ </div>
357
+ ))}
358
+ </div>
359
+ );
360
+ };
361
+
362
+ export const EUNISLinksToFinerEUNISHabitatsView = ({ value }) => {
363
+ let parsedValue = value;
364
+
365
+ if (typeof value === 'string') {
366
+ try {
367
+ parsedValue = JSON.parse(value);
368
+ } catch (e) {
369
+ return null;
370
+ }
371
+ }
372
+
373
+ const items = Array.isArray(parsedValue)
374
+ ? parsedValue
375
+ : parsedValue?.value || [];
376
+
377
+ if (!items || items.length === 0) return null;
378
+
379
+ return (
380
+ <div className="eunis-widget-view">
381
+ {items.map((item) => (
382
+ <div key={item['@id']} className="msfd-item">
383
+ <div>
384
+ {item.link ? (
385
+ <>
386
+ <a href={item.link}>{item.value}</a>
387
+ </>
388
+ ) : (
389
+ <>
390
+ <a
391
+ href={`/habitats_eunis_revised/EUNISrev_${item.value}`}
392
+ target="_blank"
393
+ rel="noopener"
394
+ >
395
+ {item.value}
396
+ </a>
397
+ </>
398
+ )}
399
+ </div>
400
+ </div>
401
+ ))}
402
+ </div>
403
+ );
404
+ };
405
+
406
+ export const EUNISCountryCodeView = ({ value }) => {
407
+ let parsedValue = value;
408
+
409
+ if (typeof value === 'string') {
410
+ try {
411
+ parsedValue = JSON.parse(value);
412
+ } catch (e) {
413
+ return null;
414
+ }
415
+ }
416
+
417
+ const items = Array.isArray(parsedValue)
418
+ ? parsedValue
419
+ : parsedValue?.value || [];
420
+
421
+ if (!items || items.length === 0) return null;
422
+
423
+ return items.map((country) => (
424
+ <div>
425
+ <div key={country['@id']} className="country-item">
426
+ {country.countryCode?.join(', ')}
427
+ </div>
428
+ <div className="eunis-widget-view">
429
+ {country.national.map((item) => (
430
+ <div key={item['@id']} className="msfd-item">
431
+ <div>
432
+ {item.relation}
433
+ {item.value}
434
+ </div>
435
+ </div>
436
+ ))}
437
+ </div>
438
+ </div>
439
+ ));
440
+ };
441
+
442
+ export const EUNISRegionalSeaConventionValueView = EUNISMSFDView;
443
+
444
+ const msfdSchema = {
445
+ title: 'MSFD',
446
+ fieldsets: [
447
+ {
448
+ id: 'default',
449
+ title: 'default',
450
+ fields: ['relation', 'value'],
451
+ },
452
+ ],
453
+ properties: {
454
+ relation: {
455
+ title: 'Relation',
456
+ choices: [
457
+ ['=', '= equal to'],
458
+ ['#', '# overlaps'],
459
+ ['<', '< included in'],
460
+ ['>', '> including'],
461
+ ],
462
+ required: true,
463
+ },
464
+ value: {
465
+ title: 'Value',
466
+ required: true,
467
+ },
468
+ },
469
+ required: [],
470
+ };
471
+
472
+ const hdSchema = {
473
+ title: 'HD (Annex I)',
474
+ fieldsets: [
475
+ {
476
+ id: 'default',
477
+ title: 'default',
478
+ fields: ['relation', 'value'],
479
+ },
480
+ ],
481
+ properties: {
482
+ relation: {
483
+ title: 'Relation',
484
+ choices: [
485
+ ['=', '= equal to'],
486
+ ['#', '# overlaps'],
487
+ ['<', '< included in'],
488
+ ['>', '> including'],
489
+ ],
490
+ required: true,
491
+ },
492
+ value: {
493
+ title: 'Value',
494
+ required: true,
495
+ },
496
+ link: {
497
+ title: 'Link',
498
+ required: true,
499
+ },
500
+ },
501
+ required: [],
502
+ };
503
+
504
+ const europeanRedlistSchema = {
505
+ title: 'European Red List',
506
+ fieldsets: [
507
+ {
508
+ id: 'default',
509
+ title: 'default',
510
+ fields: ['relation', 'value'],
511
+ },
512
+ ],
513
+ properties: {
514
+ relation: {
515
+ title: 'Relation',
516
+ choices: [
517
+ ['=', '= equal to'],
518
+ ['#', '# overlaps'],
519
+ ['<', '< included in'],
520
+ ['>', '> including'],
521
+ ],
522
+ required: true,
523
+ },
524
+ value: {
525
+ title: 'Value',
526
+ choices: EuropeanRedListVocab,
527
+ required: true,
528
+ },
529
+ link: {
530
+ title: 'Link',
531
+ required: true,
532
+ },
533
+ },
534
+ required: [],
535
+ };
536
+
537
+ const linksToFinerEUNISHabitatsSchema = {
538
+ title: 'Links to finer EUNIS habitats',
539
+ fieldsets: [
540
+ {
541
+ id: 'default',
542
+ title: 'default',
543
+ fields: ['value'],
544
+ },
545
+ ],
546
+ properties: {
547
+ value: {
548
+ title: 'Value',
549
+ required: true,
550
+ },
551
+ link: {
552
+ title: 'Link',
553
+ required: true,
554
+ },
555
+ },
556
+ required: [],
557
+ };
558
+
559
+ const regionalSeaConventionValueSchema = {
560
+ title: 'Regional Sea Convention Value',
561
+ fieldsets: [
562
+ {
563
+ id: 'default',
564
+ title: 'default',
565
+ fields: ['relation', 'value'],
566
+ },
567
+ ],
568
+ properties: {
569
+ relation: {
570
+ title: 'Relation',
571
+ choices: [
572
+ ['=', '= equal to'],
573
+ ['#', '# overlaps'],
574
+ ['<', '< included in'],
575
+ ['>', '> including'],
576
+ ],
577
+
578
+ required: true,
579
+ },
580
+ value: {
581
+ title: 'Value',
582
+ required: true,
583
+ },
584
+ },
585
+ required: [],
586
+ };
587
+
588
+ const nationalSchema = {
589
+ title: 'National',
590
+ fieldsets: [
591
+ {
592
+ id: 'default',
593
+ title: 'default',
594
+ fields: ['relation', 'value'],
595
+ },
596
+ ],
597
+ properties: {
598
+ relation: {
599
+ title: 'Relation',
600
+ choices: [
601
+ ['=', '= equal to'],
602
+ ['#', '# overlaps'],
603
+ ['<', '< included in'],
604
+ ['>', '> including'],
605
+ ],
606
+ required: true,
607
+ },
608
+ value: {
609
+ title: 'Value',
610
+ required: true,
611
+ },
612
+ },
613
+ required: [],
614
+ };
615
+
616
+ const countryCodeSchema = {
617
+ title: 'Country Code',
618
+ fieldsets: [
619
+ {
620
+ id: 'default',
621
+ title: 'default',
622
+ fields: ['countryCode', 'national'],
623
+ },
624
+ ],
625
+ properties: {
626
+ countryCode: {
627
+ title: 'Country Code',
628
+ type: 'array',
629
+ choices: countryCodesVocab,
630
+ },
631
+ national: {
632
+ title: 'National',
633
+ schema: nationalSchema,
634
+ widget: 'object_list',
635
+ },
636
+ },
637
+ required: [],
638
+ };
639
+
640
+ // const EUNIScountryCodeSchema = {
641
+ // title: 'Country Codes',
642
+ // fieldsets: [
643
+ // {
644
+ // id: 'default',
645
+ // title: 'default',
646
+ // fields: ['countries'],
647
+ // },
648
+ // ],
649
+ // properties: {
650
+ // countries: {
651
+ // title: 'Countries',
652
+ // schema: countryCodeSchema,
653
+ // widget: 'object_list',
654
+ // },
655
+ // },
656
+ // required: [],
657
+ // };
658
+
659
+ export const EUNISEuropeanRedListWidget = (props) => (
660
+ <BaseObjectListWidget {...props} schema={europeanRedlistSchema} />
661
+ );
662
+
663
+ export const EUNISMSFDWidget = (props) => (
664
+ <BaseObjectListWidget {...props} schema={msfdSchema} />
665
+ );
666
+
667
+ export const EUNISHDWidget = (props) => (
668
+ <BaseObjectListWidget {...props} schema={hdSchema} />
669
+ );
670
+
671
+ export const EUNISLinksToFinerEUNISHabitatsWidget = (props) => (
672
+ <BaseObjectListWidget {...props} schema={linksToFinerEUNISHabitatsSchema} />
673
+ );
674
+
675
+ export const EUNISRegionalSeaConventionValueWidget = (props) => (
676
+ <BaseObjectListWidget {...props} schema={regionalSeaConventionValueSchema} />
677
+ );
678
+
679
+ export const EUNISCountryCodeWidget = (props) => (
680
+ <BaseObjectListWidget {...props} schema={countryCodeSchema} />
681
+ );
682
+
683
+ export default BaseObjectListWidget;