@eeacms/volto-eea-map 4.1.0 → 5.0.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/package.json +5 -2
  3. package/src/Arcgis/Editor/Editor.jsx +130 -0
  4. package/src/Arcgis/Editor/EditorContext.jsx +2 -0
  5. package/src/Arcgis/Editor/Fold/Fold.jsx +56 -0
  6. package/src/Arcgis/Editor/Panels/Panel.jsx +8 -0
  7. package/src/Arcgis/Editor/Panels/SettingsGeneralPanel.jsx +217 -0
  8. package/src/Arcgis/Editor/Panels/SettingsLayersPanel.jsx +216 -0
  9. package/src/Arcgis/Editor/Panels/StructureBaseLayerPanel.jsx +60 -0
  10. package/src/Arcgis/Editor/Panels/StructureLayersPanel.jsx +394 -0
  11. package/src/Arcgis/Editor/Panels/StructureWidgetsPanel.jsx +181 -0
  12. package/src/Arcgis/Editor/Panels/index.js +6 -0
  13. package/src/Arcgis/Editor/SidebarGroup.jsx +62 -0
  14. package/src/Arcgis/Layer/Layer.jsx +247 -0
  15. package/src/Arcgis/Map/Map.jsx +287 -0
  16. package/src/Arcgis/Map/MapBuilder.jsx +111 -0
  17. package/src/Arcgis/Map/MapContext.jsx +3 -0
  18. package/src/Arcgis/Widget/Widget.jsx +170 -0
  19. package/src/Arcgis/helpers.js +140 -0
  20. package/src/Blocks/EmbedEEAMap/Edit.jsx +40 -0
  21. package/src/Blocks/EmbedEEAMap/View.jsx +122 -0
  22. package/src/Blocks/EmbedEEAMap/helpers.js +12 -0
  23. package/src/Blocks/EmbedEEAMap/schema.js +126 -0
  24. package/src/{components → Toolbar}/Share.jsx +1 -1
  25. package/src/{components/ExtraViews.jsx → Toolbar/Toolbar.jsx} +14 -16
  26. package/src/{components/visualization → Views}/VisualizationView.jsx +8 -9
  27. package/src/Widgets/ArcgisColorPickerWidget.jsx +95 -0
  28. package/src/Widgets/ArcgisRendererWidget/ArcgisRendererWidget.jsx +106 -0
  29. package/src/Widgets/ArcgisRendererWidget/RendererEditor/ClassBreaks.jsx +8 -0
  30. package/src/Widgets/ArcgisRendererWidget/RendererEditor/Dictionary.jsx +8 -0
  31. package/src/Widgets/ArcgisRendererWidget/RendererEditor/DotDensity.jsx +8 -0
  32. package/src/Widgets/ArcgisRendererWidget/RendererEditor/Heatmap.jsx +8 -0
  33. package/src/Widgets/ArcgisRendererWidget/RendererEditor/PieChart.jsx +8 -0
  34. package/src/Widgets/ArcgisRendererWidget/RendererEditor/Simple.jsx +109 -0
  35. package/src/Widgets/ArcgisRendererWidget/RendererEditor/UniqueValue.jsx +8 -0
  36. package/src/Widgets/ArcgisRendererWidget/RendererEditor/_Editor.jsx +29 -0
  37. package/src/Widgets/ArcgisRendererWidget/RendererEditor/_EditorModal.jsx +88 -0
  38. package/src/Widgets/ArcgisRendererWidget/RendererEditor/_defaults.js +30 -0
  39. package/src/Widgets/ArcgisSliderWidget.jsx +79 -0
  40. package/src/Widgets/ArcgisViewpointWidget.jsx +112 -0
  41. package/src/{components/visualization → Widgets}/VisualizationViewWidget.jsx +9 -10
  42. package/src/Widgets/VisualizationWidget.jsx +200 -0
  43. package/src/arcgis.js +48 -0
  44. package/src/constants.js +225 -7
  45. package/src/hocs/withArcgis.jsx +27 -0
  46. package/src/hooks/useChangedProps.jsx +24 -0
  47. package/src/hooks/useClass.jsx +17 -0
  48. package/src/hooks/useCopyToClipboard.jsx +25 -0
  49. package/src/index.js +16 -16
  50. package/src/jsoneditor.js +72 -0
  51. package/src/styles/editor.less +446 -0
  52. package/src/styles/map.less +3 -0
  53. package/src/components/Blocks/EmbedEEAMap/Edit.jsx +0 -161
  54. package/src/components/Blocks/EmbedEEAMap/Schema.js +0 -161
  55. package/src/components/Blocks/EmbedEEAMap/View.jsx +0 -79
  56. package/src/components/Blocks/EmbedEEAMap/helpers.js +0 -45
  57. package/src/components/LegendView.jsx +0 -150
  58. package/src/components/Webmap.jsx +0 -371
  59. package/src/components/index.js +0 -6
  60. package/src/components/visualization/VisualizationEditorWidget.jsx +0 -122
  61. package/src/components/visualization/panelsSchema.js +0 -229
  62. package/src/components/widgets/DataQueryWidget.jsx +0 -51
  63. package/src/components/widgets/LayerSelectWidget.jsx +0 -463
  64. package/src/components/widgets/LayersPanelWidget.jsx +0 -59
  65. package/src/components/widgets/SimpleColorPickerWidget.jsx +0 -121
  66. package/src/hocs/index.js +0 -3
  67. package/src/hocs/withDeviceSize.jsx +0 -45
  68. package/src/less/global.less +0 -253
  69. package/src/less/variables.less +0 -5
  70. package/src/utils.js +0 -151
  71. /package/src/{components → Toolbar}/FigureNote.jsx +0 -0
  72. /package/src/{components → Toolbar}/MoreInfoLink.jsx +0 -0
  73. /package/src/{components → Toolbar}/Sources.jsx +0 -0
@@ -0,0 +1,60 @@
1
+ import { useMemo } from 'react';
2
+ import { InlineForm } from '@plone/volto/components';
3
+ import { basemaps } from '@eeacms/volto-eea-map/constants';
4
+ import { getBasemap } from '@eeacms/volto-eea-map/Arcgis/helpers';
5
+ import Fold from '../Fold/Fold';
6
+
7
+ import Panel from './Panel';
8
+
9
+ const schema = {
10
+ title: 'Base layer',
11
+ fieldsets: [
12
+ {
13
+ id: 'default',
14
+ title: 'Default',
15
+ fields: ['name', 'url_template'],
16
+ },
17
+ ],
18
+ properties: {
19
+ name: {
20
+ title: 'Name',
21
+ choices: basemaps,
22
+ },
23
+ url_template: {
24
+ title: 'Custom basemap',
25
+ widget: 'textarea',
26
+ },
27
+ },
28
+ required: [],
29
+ };
30
+
31
+ export default function StructureBaseLayerPanel({ value, onChangeValue }) {
32
+ const basemap = useMemo(
33
+ () => getBasemap({ basemap: value.basemap, base: value.base }) || {},
34
+ [value.basemap, value.base],
35
+ );
36
+
37
+ return (
38
+ <Panel
39
+ content={
40
+ <Fold title="Base layer" foldable>
41
+ <InlineForm
42
+ schema={schema}
43
+ formData={basemap}
44
+ onChangeField={(id, fieldValue) => {
45
+ const $value = { ...value };
46
+ delete $value.base; // not needed (backward compatibility)
47
+ onChangeValue({
48
+ ...$value,
49
+ basemap: {
50
+ ...basemap,
51
+ [id]: fieldValue,
52
+ },
53
+ });
54
+ }}
55
+ />
56
+ </Fold>
57
+ }
58
+ />
59
+ );
60
+ }
@@ -0,0 +1,394 @@
1
+ import { memo, useState, useEffect, useContext, useMemo } from 'react';
2
+ import { compose } from 'redux';
3
+ import { isNil, toNumber } from 'lodash';
4
+ import { v4 as uuid } from 'uuid';
5
+ import { QueryBuilder, Rule as QBRule, useRule } from 'react-querybuilder';
6
+ import { Dimmer, Loader } from 'semantic-ui-react';
7
+ import { Icon, InlineForm } from '@plone/volto/components';
8
+ import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
9
+ import addSVG from '@plone/volto/icons/add.svg';
10
+ import {
11
+ debounce,
12
+ getLayers,
13
+ getLayerDefaults,
14
+ } from '@eeacms/volto-eea-map/Arcgis/helpers';
15
+ import EditorContext from '@eeacms/volto-eea-map/Arcgis/Editor/EditorContext';
16
+ import Panel from './Panel';
17
+ import Fold from '../Fold/Fold';
18
+
19
+ function getLayersChoices(layers = []) {
20
+ return layers.map((layer) => [
21
+ layer.id.toString(),
22
+ `${
23
+ (layer.parentLayerId > -1 ? `${layer.parentLayerId}.` : '') + layer.id
24
+ } - ${layer.name} (${layer.type})`,
25
+ ]);
26
+ }
27
+
28
+ function getSublayers(subLayerIds, data) {
29
+ return subLayerIds?.reduce((acc, subLayerId) => {
30
+ const subLayer = data?.layers.find((layer) => layer.id === subLayerId);
31
+ if (!subLayer) return acc;
32
+ acc.push({
33
+ ...(subLayer || {}),
34
+ id: subLayerId,
35
+ subLayers: getSublayers(subLayer?.subLayerIds, data),
36
+ });
37
+ return acc;
38
+ }, []);
39
+ }
40
+
41
+ const RuleComponent = memo((props) => {
42
+ const [inputValue, setInputValue] = useState('');
43
+ const r = useRule(props);
44
+
45
+ const dataQuery = props.rule.dataQuery;
46
+
47
+ function handleKeyDown(event) {
48
+ if (!inputValue) return;
49
+ switch (event.key) {
50
+ case 'Enter':
51
+ case 'Tab':
52
+ if (dataQuery?.includes(inputValue)) {
53
+ setInputValue('');
54
+ event.preventDefault();
55
+ break;
56
+ }
57
+ r.generateOnChangeHandler('dataQuery')([
58
+ ...(dataQuery || []),
59
+ event.target.value,
60
+ ]);
61
+ setInputValue('');
62
+ event.preventDefault();
63
+ break;
64
+ default:
65
+ break;
66
+ }
67
+ }
68
+
69
+ const Select = props.reactSelectCreateable.default;
70
+
71
+ return (
72
+ <div className="custom-rule">
73
+ <div>
74
+ <label htmlFor="dataQuery" style={{ fontWeight: '500' }}>
75
+ Query parameters
76
+ </label>
77
+ <p
78
+ style={{
79
+ fontSize: '90%',
80
+ marginBottom: '0.25rem',
81
+ fontStyle: 'italic',
82
+ }}
83
+ >
84
+ When using page level parameters to filter the map, please specify the
85
+ corresponding name
86
+ </p>
87
+ <Select
88
+ id="dataQuery"
89
+ title="Data Query"
90
+ className="react-select"
91
+ classNamePrefix="react-select"
92
+ placeholder=""
93
+ isClearable
94
+ isMulti
95
+ menuIsOpen={false}
96
+ options={[]}
97
+ inputValue={inputValue}
98
+ value={dataQuery?.map((value) => ({ label: value, value })) || null}
99
+ onChange={(newValue) => {
100
+ r.generateOnChangeHandler('dataQuery')(
101
+ newValue?.map((value) => value.value) || [],
102
+ );
103
+ }}
104
+ onInputChange={(newValue) => setInputValue(newValue)}
105
+ onKeyDown={handleKeyDown}
106
+ />
107
+ </div>
108
+ <QBRule {...props} />
109
+ </div>
110
+ );
111
+ });
112
+
113
+ const Rule = compose(injectLazyLibs(['reactSelectCreateable']))(RuleComponent);
114
+
115
+ function Layer({ layer, layers, index, value, onChangeValue }) {
116
+ const uid = useState(uuid());
117
+ const { servicesApi, layersApi } = useContext(EditorContext);
118
+
119
+ const layerPath = useMemo(
120
+ () =>
121
+ !isNil(layer.url) && !isNil(layer.id) ? `${layer.url}/${layer.id}` : null,
122
+ [layer.url, layer.id],
123
+ );
124
+
125
+ const $service = useMemo(
126
+ () => ({
127
+ data: servicesApi.data[layer.url],
128
+ error: servicesApi.error[layer.url],
129
+ loading: servicesApi.loading[layer.url],
130
+ loaded: servicesApi.loaded[layer.url],
131
+ load: servicesApi.load,
132
+ }),
133
+ [servicesApi, layer.url],
134
+ );
135
+
136
+ const $layer = useMemo(
137
+ () => ({
138
+ data: layersApi.data[layerPath],
139
+ error: layersApi.error[layerPath],
140
+ loading: layersApi.loading[layerPath],
141
+ loaded: layersApi.loaded[layerPath],
142
+ load: layersApi.load,
143
+ }),
144
+ [layersApi, layerPath],
145
+ );
146
+
147
+ useEffect(() => {
148
+ if (!$service.loaded && !$service.loading && layer.url) {
149
+ debounce(
150
+ () => {
151
+ $service.load(layer.url);
152
+ },
153
+ 300,
154
+ `fetch:${uid}:${layer.url}`,
155
+ );
156
+ }
157
+ }, [uid, $service, layer]);
158
+
159
+ useEffect(() => {
160
+ if (!$layer.loaded && !$layer.loading && !$layer.error && layerPath) {
161
+ debounce(
162
+ () => {
163
+ $layer.load(layerPath);
164
+ },
165
+ 300,
166
+ `fetch:${uid}:${layerPath}`,
167
+ );
168
+ }
169
+ }, [uid, $layer, layerPath]);
170
+
171
+ useEffect(() => {
172
+ if (!$layer.loaded || $layer.data.id !== layer.id) return;
173
+ const defaults = getLayerDefaults($layer.data);
174
+ if (JSON.stringify(defaults) !== JSON.stringify(layer.defaults)) {
175
+ onChangeValue({
176
+ ...value,
177
+ layers: layers.map((layer, i) => {
178
+ if (i !== index) return layer;
179
+ return {
180
+ ...layer,
181
+ defaults,
182
+ };
183
+ }),
184
+ });
185
+ }
186
+ }, [index, layer, $layer, layers, value, onChangeValue]);
187
+
188
+ return (
189
+ <>
190
+ <Dimmer active={$service.loading || $layer.loading}>
191
+ <Loader />
192
+ </Dimmer>
193
+
194
+ <InlineForm
195
+ schema={{
196
+ title: `Layer ${index + 1}`,
197
+ fieldsets: [
198
+ {
199
+ id: 'default',
200
+ title: 'Default',
201
+ fields: [
202
+ 'url',
203
+ ...($service.loaded ? ['layerId'] : []),
204
+ ...($service.loaded && $layer.loaded ? ['zoomToExtent'] : []),
205
+ ],
206
+ },
207
+ ],
208
+ properties: {
209
+ url: {
210
+ title: 'Service URL',
211
+ widget: 'textarea',
212
+ description: $service.error ? (
213
+ <p
214
+ className="error"
215
+ style={{
216
+ fontSize: '10px',
217
+ overflowWrap: 'break-word',
218
+ }}
219
+ >
220
+ {$service.error.message}
221
+ </p>
222
+ ) : null,
223
+ },
224
+ layerId: {
225
+ title: 'Layer',
226
+ choices: getLayersChoices($service.data?.layers),
227
+ widget: 'select',
228
+ description: $layer.error ? (
229
+ <p
230
+ className="error"
231
+ style={{
232
+ fontSize: '10px',
233
+ overflowWrap: 'break-word',
234
+ }}
235
+ >
236
+ {$layer.error.message}
237
+ </p>
238
+ ) : null,
239
+ },
240
+ zoomToExtent: {
241
+ title: 'Zoom to extent',
242
+ type: 'boolean',
243
+ },
244
+ },
245
+ required: [],
246
+ }}
247
+ formData={{
248
+ ...layer,
249
+ layerId: !isNil(layer.id)
250
+ ? {
251
+ label: `${layer.id}${layer.name ? ` - ${layer.name}` : ''}`,
252
+ value: layer.id,
253
+ }
254
+ : null,
255
+ }}
256
+ onChangeField={(id, fieldValue) => {
257
+ let $fieldValue = fieldValue;
258
+ let newLayer = {};
259
+
260
+ if (id === 'layerId') {
261
+ $fieldValue = toNumber(fieldValue) || 0;
262
+ newLayer =
263
+ $service.data?.layers.find((layer) => layer.id === $fieldValue) ||
264
+ {};
265
+ }
266
+
267
+ onChangeValue({
268
+ ...value,
269
+ layers: layers.map((layer, i) => {
270
+ if (i !== index)
271
+ return {
272
+ ...layer,
273
+ ...(id === 'zoomToExtent' ? { zoomToExtent: false } : {}),
274
+ };
275
+ if (id === 'url') {
276
+ return { url: $fieldValue };
277
+ }
278
+ return {
279
+ ...layer,
280
+ ...newLayer,
281
+ [id === 'layerId' ? 'id' : id]: $fieldValue,
282
+ subLayers: getSublayers(newLayer?.subLayerIds, $service.data),
283
+ };
284
+ }),
285
+ });
286
+ }}
287
+ />
288
+
289
+ {$service.loaded && $layer.loaded && $layer.data?.fields?.length && (
290
+ <>
291
+ <QueryBuilder
292
+ fields={
293
+ $layer.data.fields.map((field) => {
294
+ return { name: field.name, label: field.name };
295
+ }) || []
296
+ }
297
+ query={layer.definitionExpression}
298
+ onQueryChange={(query) => {
299
+ const newLayers = layers.map((layer, i) => {
300
+ if (i !== index) return layer;
301
+ return {
302
+ ...layer,
303
+ definitionExpression: query,
304
+ };
305
+ });
306
+ if (JSON.stringify(newLayers) !== JSON.stringify(layers)) {
307
+ onChangeValue({
308
+ ...value,
309
+ layers: newLayers,
310
+ });
311
+ }
312
+ }}
313
+ valueSource={'x'}
314
+ controlElements={{
315
+ rule: Rule,
316
+ }}
317
+ />
318
+ </>
319
+ )}
320
+ </>
321
+ );
322
+ }
323
+
324
+ export default function StructureLayersPanel({
325
+ value,
326
+ properties,
327
+ onChangeValue,
328
+ }) {
329
+ const data_query = properties?.data_query;
330
+
331
+ const layers = useMemo(
332
+ () =>
333
+ getLayers(
334
+ { layers: value.layers, styles: value.styles, data_query },
335
+ false,
336
+ ),
337
+ [value.layers, value.styles, data_query],
338
+ );
339
+
340
+ return (
341
+ <Panel
342
+ header={
343
+ <div style={{ width: '100%', textAlign: 'right' }}>
344
+ <button
345
+ className="btn-primary"
346
+ onClick={() => {
347
+ onChangeValue({
348
+ ...value,
349
+ layers: [
350
+ ...layers,
351
+ {
352
+ id: null,
353
+ url: null,
354
+ type: null,
355
+ },
356
+ ],
357
+ });
358
+ }}
359
+ >
360
+ <Icon name={addSVG} size="16px" /> Layer
361
+ </button>
362
+ </div>
363
+ }
364
+ content={
365
+ <>
366
+ {layers.map((layer, index) => (
367
+ <Fold
368
+ key={index}
369
+ title={`Layer ${index + 1}${
370
+ layer.name ? ` - ${layer.name}` : ''
371
+ }`}
372
+ onDelete={() => {
373
+ onChangeValue({
374
+ ...value,
375
+ layers: layers.filter((_, i) => i !== index),
376
+ });
377
+ }}
378
+ foldable
379
+ deletable
380
+ >
381
+ <Layer
382
+ layer={layer}
383
+ layers={layers}
384
+ index={index}
385
+ value={value}
386
+ onChangeValue={onChangeValue}
387
+ />
388
+ </Fold>
389
+ ))}
390
+ </>
391
+ }
392
+ />
393
+ );
394
+ }
@@ -0,0 +1,181 @@
1
+ import { useState } from 'react';
2
+ import { Icon, InlineForm } from '@plone/volto/components';
3
+ import { withVariationSchemaEnhancer } from '@plone/volto/helpers';
4
+ import addSVG from '@plone/volto/icons/add.svg';
5
+ import Panel from './Panel';
6
+ import Fold from '../Fold/Fold';
7
+ import { debounce, getWidgets } from '../../helpers';
8
+ import {
9
+ expandKeys,
10
+ positions,
11
+ widgets as widgetsOptions,
12
+ getDefaultWidgets,
13
+ widgetsSchema,
14
+ } from '@eeacms/volto-eea-map/constants';
15
+
16
+ const Form = withVariationSchemaEnhancer(InlineForm);
17
+
18
+ export default function StructureWidgetsPanel({ value, onChangeValue }) {
19
+ const [widgets, setWidgets] = useState(getWidgets(value));
20
+
21
+ return (
22
+ <Panel
23
+ header={
24
+ <div style={{ width: '100%', textAlign: 'right' }}>
25
+ <button
26
+ className="btn-primary"
27
+ onClick={() => {
28
+ const newWidgets = [
29
+ ...widgets,
30
+ { name: null, position: positions[0] },
31
+ ];
32
+ setWidgets(newWidgets);
33
+ debounce(
34
+ () => {
35
+ onChangeValue({
36
+ ...value,
37
+ widgets: newWidgets,
38
+ });
39
+ },
40
+ 600,
41
+ 'widgets:update',
42
+ );
43
+ }}
44
+ >
45
+ <Icon name={addSVG} size="16px" /> Widget
46
+ </button>
47
+ </div>
48
+ }
49
+ content={
50
+ <>
51
+ {widgets.map((widget, index) => {
52
+ const title = `Widget ${index + 1}${
53
+ widget.name ? ` (${widget.name})` : ''
54
+ }`;
55
+ const ExpandProperties = widget.ExpandProperties || {};
56
+ return (
57
+ <Fold
58
+ key={index}
59
+ title={title}
60
+ onDelete={() => {
61
+ const newWidgets = widgets.filter((_, i) => i !== index);
62
+ setWidgets(newWidgets);
63
+ debounce(
64
+ () => {
65
+ onChangeValue({
66
+ ...value,
67
+ widgets: newWidgets,
68
+ });
69
+ },
70
+ 600,
71
+ 'widgets:update',
72
+ );
73
+ }}
74
+ foldable
75
+ deletable
76
+ >
77
+ <Form
78
+ schema={{
79
+ title,
80
+ fieldsets: [
81
+ {
82
+ id: 'default',
83
+ title: 'Default',
84
+ fields: ['name', 'position', 'expand'],
85
+ },
86
+ ],
87
+ properties: {
88
+ name: {
89
+ title: 'Name',
90
+ choices: widgetsOptions,
91
+ },
92
+ position: {
93
+ title: 'Position',
94
+ choices: positions,
95
+ },
96
+ expand: {
97
+ title: 'Expand',
98
+ type: 'boolean',
99
+ },
100
+ expandTooltip: {
101
+ title: 'Expand tooltip',
102
+ },
103
+ },
104
+ required: [],
105
+ }}
106
+ blocksConfig={{
107
+ widget: {
108
+ schemaEnhancer: ({ schema, formData }) => {
109
+ return {
110
+ ...schema,
111
+ fieldsets: [
112
+ {
113
+ id: 'default',
114
+ title: 'Default',
115
+ fields: [
116
+ 'name',
117
+ 'position',
118
+ ...Object.keys(
119
+ widgetsSchema[formData.name] || {},
120
+ ),
121
+ 'expand',
122
+ ...(formData.expand ? ['expandTooltip'] : []),
123
+ ],
124
+ },
125
+ ],
126
+ properties: {
127
+ ...schema.properties,
128
+ ...(widgetsSchema[formData.name] || {}),
129
+ },
130
+ };
131
+ },
132
+ },
133
+ }}
134
+ formData={{
135
+ '@type': 'widget',
136
+ ...widget,
137
+ ...ExpandProperties,
138
+ }}
139
+ onChangeField={(id, fieldValue) => {
140
+ const newWidget =
141
+ id === 'name'
142
+ ? getDefaultWidgets(
143
+ value.settings?.map?.dimension,
144
+ ).find(($widget) => $widget.name === fieldValue) || {}
145
+ : {};
146
+ const newWidgets = widgets.map(($widget, i) => {
147
+ if (i !== index) return $widget;
148
+ return {
149
+ ...$widget,
150
+ ...newWidget,
151
+ ...(expandKeys.includes(id)
152
+ ? {
153
+ ExpandProperties: {
154
+ ...ExpandProperties,
155
+ [id]: fieldValue,
156
+ },
157
+ }
158
+ : { [id]: fieldValue }),
159
+ };
160
+ });
161
+ setWidgets(newWidgets);
162
+ debounce(
163
+ () => {
164
+ onChangeValue({
165
+ ...value,
166
+ widgets: newWidgets,
167
+ });
168
+ },
169
+ 600,
170
+ 'widgets:update',
171
+ );
172
+ }}
173
+ />
174
+ </Fold>
175
+ );
176
+ })}
177
+ </>
178
+ }
179
+ />
180
+ );
181
+ }
@@ -0,0 +1,6 @@
1
+ export { default as Panel } from './Panel';
2
+ export { default as StructureBaseLayerPanel } from './StructureBaseLayerPanel';
3
+ export { default as StructureLayersPanel } from './StructureLayersPanel';
4
+ export { default as StructureWidgetsPanel } from './StructureWidgetsPanel';
5
+ export { default as SettingsGeneralPanel } from './SettingsGeneralPanel';
6
+ export { default as SettingsLayersPanel } from './SettingsLayersPanel';
@@ -0,0 +1,62 @@
1
+ import { useEffect, useState, useMemo } from 'react';
2
+ import cx from 'classnames';
3
+
4
+ import { Icon } from '@plone/volto/components';
5
+
6
+ import upKeySVG from '@plone/volto/icons/up-key.svg';
7
+
8
+ export default function SidebarGroup({ title, items, active, setActive }) {
9
+ const [expanded, setExpanded] = useState(false);
10
+ const isActive = useMemo(
11
+ () =>
12
+ active.sidebar === title &&
13
+ items.filter((item) => active.panel.title === item.title).length === 1,
14
+ [active, title, items],
15
+ );
16
+
17
+ useEffect(() => {
18
+ if (isActive) {
19
+ setExpanded(true);
20
+ }
21
+ }, [isActive]);
22
+
23
+ return (
24
+ <div
25
+ className={cx('sidebar-group', {
26
+ 'sidebar-group--expanded': expanded,
27
+ 'sidebar-group--is-active': isActive,
28
+ })}
29
+ >
30
+ <div
31
+ role="button"
32
+ tabIndex={0}
33
+ className="sidebar-group--title"
34
+ onClick={() => setExpanded(!expanded)}
35
+ onKeyDown={() => {}}
36
+ >
37
+ <div className="sidebar-group--title__icon">
38
+ <Icon name={upKeySVG} size="20px" />
39
+ </div>
40
+ <div className="sidebar-group--title__label">{title}</div>
41
+ </div>
42
+ {expanded &&
43
+ items.map((item) => (
44
+ <div
45
+ role="button"
46
+ tabIndex={0}
47
+ key={item.title}
48
+ className={cx('sidebar-group--item', {
49
+ 'sidebar-group--item__is-active':
50
+ isActive && active.panel.title === item.title,
51
+ })}
52
+ onClick={() => {
53
+ setActive({ sidebar: title, panel: item });
54
+ }}
55
+ onKeyDown={() => {}}
56
+ >
57
+ {item.title}
58
+ </div>
59
+ ))}
60
+ </div>
61
+ );
62
+ }