@eeacms/volto-marine-policy 2.0.8 → 2.0.10

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,51 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [2.0.10](https://github.com/eea/volto-marine-policy/compare/2.0.9...2.0.10) - 9 May 2025
8
+
9
+ #### :rocket: Dependency updates
10
+
11
+ - Release @eeacms/volto-globalsearch@2.1.0 [EEA Jenkins - [`3b20bc2`](https://github.com/eea/volto-marine-policy/commit/3b20bc292d04ab81385732840ffe2c469f83fbd6)]
12
+
13
+ #### :house: Internal changes
14
+
15
+ - style: Automated code fix [eea-jenkins - [`69cc9c8`](https://github.com/eea/volto-marine-policy/commit/69cc9c86e32d35cce4f979b36bc7d20cc4b01e8f)]
16
+ - style: Automated code fix [eea-jenkins - [`d1a8da0`](https://github.com/eea/volto-marine-policy/commit/d1a8da01dfc0ddcc24058ba5aafb5d76be0d2ffc)]
17
+
18
+ #### :hammer_and_wrench: Others
19
+
20
+ - eslint [laszlocseh - [`8c27f84`](https://github.com/eea/volto-marine-policy/commit/8c27f84fcdc8adf9b12746b585c66f34b9e785a4)]
21
+ - further improvements to demo sites map [laszlocseh - [`b15e205`](https://github.com/eea/volto-marine-policy/commit/b15e2053256552c30f26fdab581330cba420b412)]
22
+ - eslint [laszlocseh - [`4514ee0`](https://github.com/eea/volto-marine-policy/commit/4514ee0a409e8dfe0d6975da989228f39033b343)]
23
+ - no results message [laszlocseh - [`06b7ae6`](https://github.com/eea/volto-marine-policy/commit/06b7ae62eb7b27a2ec9e2d3f9ec62aed42a85045)]
24
+ - fix eslint [laszlocseh - [`2a4605a`](https://github.com/eea/volto-marine-policy/commit/2a4605ac45b141d5e3f2b737a24c6afca0b5d884)]
25
+ - optimize demo sites map rendering [laszlocseh - [`2345b60`](https://github.com/eea/volto-marine-policy/commit/2345b609a95a00a53c46ce9b92686eeb598a14a6)]
26
+ ### [2.0.9](https://github.com/eea/volto-marine-policy/compare/2.0.8...2.0.9) - 7 May 2025
27
+
28
+ #### :rocket: Dependency updates
29
+
30
+ - Release @eeacms/volto-searchlib@2.0.16 [EEA Jenkins - [`9d425b4`](https://github.com/eea/volto-marine-policy/commit/9d425b44fa813cf7d08d95d36af6c0f01d0e52ce)]
31
+
32
+ #### :house: Internal changes
33
+
34
+ - style: Automated code fix [eea-jenkins - [`1c3cbdc`](https://github.com/eea/volto-marine-policy/commit/1c3cbdcc74127d45e46db9ae4fd1ac3b237ad32c)]
35
+ - style: Automated code fix [eea-jenkins - [`b00b251`](https://github.com/eea/volto-marine-policy/commit/b00b2515e54b8ffdbc51bf301f0b0bb232b83efe)]
36
+ - style: Automated code fix [eea-jenkins - [`e02c515`](https://github.com/eea/volto-marine-policy/commit/e02c5156ac34e4cf2fef61a27dfe64d60aad97af)]
37
+ - style: Automated code fix [eea-jenkins - [`678b9bf`](https://github.com/eea/volto-marine-policy/commit/678b9bf2b8f83f9b3a6facaa42f5851a013462ad)]
38
+ - style: Automated code fix [eea-jenkins - [`b1cccfb`](https://github.com/eea/volto-marine-policy/commit/b1cccfbd674890392948ad5227bfe334f55a2721)]
39
+
40
+ #### :hammer_and_wrench: Others
41
+
42
+ - small fixes demmo sites map [laszlocseh - [`f83d070`](https://github.com/eea/volto-marine-policy/commit/f83d070d0b1c7a5bdf1136eb542782477c9266b0)]
43
+ - update objectives [laszlocseh - [`0c09eb1`](https://github.com/eea/volto-marine-policy/commit/0c09eb1b093ffba34deafa079127175629f3f2b9)]
44
+ - fix eslint [laszlocseh - [`128fbb0`](https://github.com/eea/volto-marine-policy/commit/128fbb04277cc11bc6406df8cc815ea4a41291d0)]
45
+ - fix eslint [laszlocseh - [`68e7168`](https://github.com/eea/volto-marine-policy/commit/68e7168d6322cb07fe7df7c2376a26e297cb015f)]
46
+ - fix eslint [laszlocseh - [`1be214a`](https://github.com/eea/volto-marine-policy/commit/1be214accdac3657f156691f09c149f9f678d344)]
47
+ - fix eslint [laszlocseh - [`fbed39c`](https://github.com/eea/volto-marine-policy/commit/fbed39c59d7841234cd8ffe699a9e84766a501bc)]
48
+ - fix eslint [laszlocseh - [`ed3a2d2`](https://github.com/eea/volto-marine-policy/commit/ed3a2d26194d18aed50b1781e2d84a3d1482b0ab)]
49
+ - fix eslint [laszlocseh - [`15805ce`](https://github.com/eea/volto-marine-policy/commit/15805ce9adb0ca66a99368a432d54d74fcbc8077)]
50
+ - WiP demo sites map [laszlocseh - [`ca84585`](https://github.com/eea/volto-marine-policy/commit/ca84585f692b03c7011d46393fa5b937fa985e4d)]
51
+ - WiP demo sites map [laszlocseh - [`b51ca84`](https://github.com/eea/volto-marine-policy/commit/b51ca842dba4fe0700100b5a6328d1bd4b962acb)]
7
52
  ### [2.0.8](https://github.com/eea/volto-marine-policy/compare/2.0.7...2.0.8) - 16 April 2025
8
53
 
9
54
  #### :house: Internal changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-marine-policy",
3
- "version": "2.0.8",
3
+ "version": "2.0.10",
4
4
  "description": "@eeacms/volto-marine-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -34,15 +34,16 @@
34
34
  "@eeacms/volto-eea-design-system": "*",
35
35
  "@eeacms/volto-eea-website-theme": "*",
36
36
  "@eeacms/volto-embed": "*",
37
- "@eeacms/volto-globalsearch": "2.0.11",
37
+ "@eeacms/volto-globalsearch": "2.1.0",
38
38
  "@eeacms/volto-openlayers-map": "0.3.2",
39
- "@eeacms/volto-searchlib": "2.0.15",
39
+ "@eeacms/volto-searchlib": "2.0.16",
40
40
  "@eeacms/volto-tabs-block": "*",
41
41
  "axios": "0.30.0",
42
42
  "d3-array": "^2.12.1",
43
43
  "jquery": "3.6.0",
44
44
  "razzle-plugin-scss": "^4.2.18",
45
45
  "react-lazy-load-image-component": "^1.4.0",
46
+ "react-plotly.js": "2.6.0",
46
47
  "react-slick": "^0.24.0",
47
48
  "slick-carousel": "^1.8.1"
48
49
  },
@@ -2,9 +2,11 @@ import React from 'react';
2
2
  import { Grid } from 'semantic-ui-react'; // Dropdown,
3
3
  import { addAppURL } from '@plone/volto/helpers';
4
4
  import config from '@plone/volto/registry';
5
+ import { VisibilitySensor } from '@eeacms/volto-datablocks/components';
6
+
5
7
  import DemoSitesMap from './DemoSitesMap';
6
8
  import { ActiveFilters, DemoSitesFilters } from './DemoSitesFilters';
7
-
9
+ import ObjectivesChart from './ObjectivesChart';
8
10
  import { filterCases, getFilters } from './utils';
9
11
  import { useCases } from './hooks';
10
12
 
@@ -23,6 +25,7 @@ export default function DemoSitesExplorerView(props) {
23
25
 
24
26
  const [activeFilters, setActiveFilters] = React.useState({
25
27
  objective_filter: [],
28
+ target_filter: [],
26
29
  indicator_filter: [],
27
30
  project_filter: [],
28
31
  country_filter: [],
@@ -31,6 +34,7 @@ export default function DemoSitesExplorerView(props) {
31
34
  const [activeItems, setActiveItems] = React.useState(cases);
32
35
  const [filters, setFilters] = React.useState([]);
33
36
  const [map, setMap] = React.useState();
37
+ const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
34
38
 
35
39
  React.useEffect(() => {
36
40
  const _filters = getFilters(cases, indicatorOnly);
@@ -38,6 +42,7 @@ export default function DemoSitesExplorerView(props) {
38
42
  }, [
39
43
  cases,
40
44
  activeFilters.objective_filter,
45
+ activeFilters.target_filter,
41
46
  activeFilters.indicator_filter,
42
47
  activeFilters.project_filter,
43
48
  activeFilters.country_filter,
@@ -74,33 +79,62 @@ export default function DemoSitesExplorerView(props) {
74
79
  <Grid.Row>
75
80
  {cases.length ? (
76
81
  <Grid columns={12}>
77
- <Grid.Column mobile={10} tablet={10} computer={10}>
82
+ <Grid.Column mobile={8} tablet={8} computer={8}>
78
83
  <DemoSitesMap
79
84
  items={cases}
80
85
  activeItems={activeItems}
86
+ setActiveFilters={setActiveFilters}
81
87
  hideFilters={hideFilters}
82
88
  selectedCase={selectedCase}
83
89
  onSelectedCase={onSelectedCase}
84
90
  map={map}
85
91
  setMap={setMap}
92
+ highlightedIndex={highlightedIndex}
93
+ setHighlightedIndex={setHighlightedIndex}
86
94
  />
87
95
  </Grid.Column>
88
- <Grid.Column mobile={2} tablet={2} computer={2}>
89
- <div className="legend">
90
- <div className="legend-row legend-subtitle">Legend</div>
91
- <div className="legend-row">
92
- <div className="circle">
93
- <div className="dot-demosite"></div>
96
+ <Grid.Column
97
+ mobile={4}
98
+ tablet={4}
99
+ computer={4}
100
+ className="right-side-filters"
101
+ >
102
+ <Grid.Row>
103
+ {!hideFilters ? (
104
+ <VisibilitySensor>
105
+ <ObjectivesChart
106
+ items={cases}
107
+ // activeItems={activeItems}
108
+ // filters={filters}
109
+ activeFilters={activeFilters}
110
+ setActiveFilters={setActiveFilters}
111
+ // hideFilters={hideFilters}
112
+ // map={map}
113
+ highlightedIndex={highlightedIndex}
114
+ setHighlightedIndex={setHighlightedIndex}
115
+ />
116
+ </VisibilitySensor>
117
+ ) : (
118
+ ''
119
+ )}
120
+ </Grid.Row>
121
+ <Grid.Row>
122
+ <div className="legend">
123
+ {/* <div className="legend-row legend-subtitle">Legend</div> */}
124
+ <div className="legend-row">
125
+ <div className="circle">
126
+ <div className="dot-demosite"></div>
127
+ </div>
128
+ <div>Demo site</div>
94
129
  </div>
95
- <div>Demo site</div>
96
- </div>
97
- <div className="legend-row">
98
- <div className="circle">
99
- <div className="dot-region"></div>
130
+ <div className="legend-row">
131
+ <div className="circle">
132
+ <div className="dot-region"></div>
133
+ </div>
134
+ <div>Associated region</div>
100
135
  </div>
101
- <div>Associated region</div>
102
136
  </div>
103
- </div>
137
+ </Grid.Row>
104
138
  </Grid.Column>
105
139
  </Grid>
106
140
  ) : null}
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
 
3
- import { centerAndResetMapZoom } from './utils';
3
+ import { clearFilters } from './utils';
4
4
 
5
5
  const normalizeSearchInput = (searchInput) => {
6
6
  let normInput = searchInput
@@ -20,7 +20,7 @@ export function DemoSitesFilter(props) {
20
20
  activeFilters,
21
21
  setActiveFilters,
22
22
  filterName,
23
- map,
23
+ // map,
24
24
  } = props;
25
25
 
26
26
  const customOrder = props?.customOrder || [];
@@ -103,7 +103,7 @@ export function DemoSitesFilter(props) {
103
103
  }
104
104
  setActiveFilters(temp);
105
105
  // scrollToElement('search-input');
106
- centerAndResetMapZoom(map);
106
+ // centerAndResetMapZoom(map);
107
107
  }}
108
108
  />
109
109
  <span>{label}</span>
@@ -132,7 +132,7 @@ export function DemoSitesFilters(props) {
132
132
 
133
133
  return (
134
134
  <>
135
- {!hideFilters ? (
135
+ {/* {!hideFilters ? (
136
136
  <DemoSitesFilter
137
137
  filterTitle="Objective/Enabler"
138
138
  filterName="objective_filter"
@@ -140,13 +140,19 @@ export function DemoSitesFilters(props) {
140
140
  activeFilters={activeFilters}
141
141
  setActiveFilters={setActiveFilters}
142
142
  map={map}
143
- customOrder={[
144
- 'Protect and restore marine and freshwater ecosystems',
145
- 'Prevent and eliminate pollution of waters',
146
- 'Carbon-neutral and circular blue economy',
147
- 'Digital twin of the ocean',
148
- 'Public mobilisation and engagement',
149
- ]}
143
+ customOrder={objectivesCustomOrder}
144
+ />
145
+ ) : (
146
+ ''
147
+ )} */}
148
+ {!hideFilters ? (
149
+ <DemoSitesFilter
150
+ filterTitle="Target"
151
+ filterName="target_filter"
152
+ filters={filters}
153
+ activeFilters={activeFilters}
154
+ setActiveFilters={setActiveFilters}
155
+ map={map}
150
156
  />
151
157
  ) : (
152
158
  ''
@@ -184,7 +190,7 @@ export function DemoSitesFilters(props) {
184
190
  }
185
191
 
186
192
  export function SearchBox(props) {
187
- const { setSearchInput, map, onSelectedCase } = props;
193
+ const { setSearchInput, onSelectedCase } = props;
188
194
  const [showClearButton, setShowClearButton] = React.useState(false);
189
195
 
190
196
  return (
@@ -209,7 +215,7 @@ export function SearchBox(props) {
209
215
  // popupOverlay.style.visibility = 'hidden';
210
216
  setSearchInput(searchInput);
211
217
  // scrollToElement('search-input');
212
- centerAndResetMapZoom(map);
218
+ // centerAndResetMapZoom(map);
213
219
  }}
214
220
  ></input>
215
221
  <div className="terms-box-left">
@@ -227,7 +233,7 @@ export function SearchBox(props) {
227
233
  setSearchInput('');
228
234
  setShowClearButton(false);
229
235
  // scrollToElement('search-input');
230
- centerAndResetMapZoom(map);
236
+ // centerAndResetMapZoom(map);
231
237
  }}
232
238
  ></i>
233
239
  </div>
@@ -247,7 +253,7 @@ export function SearchBox(props) {
247
253
 
248
254
  setSearchInput(searchInputVal);
249
255
  // scrollToElement('search-input');
250
- centerAndResetMapZoom(map);
256
+ // centerAndResetMapZoom(map);
251
257
  }}
252
258
  onKeyDown={() => {}}
253
259
  tabIndex="0"
@@ -266,30 +272,16 @@ export function SearchBox(props) {
266
272
 
267
273
  export function ActiveFilters(props) {
268
274
  const { filters, activeFilters, setActiveFilters } = props;
269
- const hasActiveFilters = Object.entries(activeFilters).some(
270
- ([filterName, filterList]) => {
271
- if (filterList.length > 0) {
272
- return true;
273
- }
274
- return false;
275
- },
276
- );
277
-
278
- const clearFilters = () => {
279
- const filterInputs = document.querySelectorAll(
280
- '#cse-filter .filter-input input',
281
- );
282
- for (let i = 0; i < filterInputs.length; i++) {
283
- filterInputs[i].checked = false;
284
- }
285
- setActiveFilters({
286
- objective_filter: [],
287
- indicator_filter: [],
288
- project_filter: [],
289
- country_filter: [],
290
- });
291
- // scrollToElement('search-input');
292
- };
275
+ const hasActiveFilters = false;
276
+ // const hasActiveFilters = Object.entries(activeFilters).some(
277
+ // ([filterName, filterList]) => {
278
+ // return false;
279
+ // if (filterList.length > 0) {
280
+ // return true;
281
+ // }
282
+ // return false;
283
+ // },
284
+ // );
293
285
 
294
286
  const removeFilter = (filterName, filterCode) => {
295
287
  const temp = JSON.parse(JSON.stringify(activeFilters));
@@ -316,7 +308,7 @@ export function ActiveFilters(props) {
316
308
  <div className="filter-list-header">
317
309
  <h4 className="filter-list-title">Active filters</h4>
318
310
  <button
319
- onClick={clearFilters}
311
+ onClick={() => clearFilters(setActiveFilters)}
320
312
  className="ui mini basic compact button clear-btn"
321
313
  >
322
314
  clear all
@@ -349,6 +341,31 @@ export function ActiveFilters(props) {
349
341
  ) : (
350
342
  ''
351
343
  )}
344
+ {activeFilters.target_filter.length > 0 ? (
345
+ <div className="filter-wrapper">
346
+ <div className="filter-label">Target:</div>
347
+ {activeFilters.target_filter.map((filterCode) => {
348
+ const filterLabel = filters.target_filter[filterCode];
349
+ return (
350
+ <div className="ui basic label filter-value">
351
+ <span>{filterLabel}</span>
352
+ <i
353
+ tabIndex="0"
354
+ onKeyPress={() => {}}
355
+ onClick={() => {
356
+ removeFilter('target_filter', filterCode);
357
+ // scrollToElement('search-input');
358
+ }}
359
+ role="button"
360
+ className="close icon"
361
+ ></i>
362
+ </div>
363
+ );
364
+ })}
365
+ </div>
366
+ ) : (
367
+ ''
368
+ )}
352
369
  {activeFilters.indicator_filter.length > 0 ? (
353
370
  <div className="filter-wrapper">
354
371
  <div className="filter-label">Indicator:</div>
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { centerAndResetMapZoom, zoomMapToFeatures, isValidURL } from './utils';
2
+ import { centerAndResetMapZoom, isValidURL } from './utils';
3
3
 
4
4
  const showPageNr = (pageNr, currentPage, numberOfPages) => {
5
5
  // show first 5 pages
@@ -271,7 +271,7 @@ export default function DemoSitesList(props) {
271
271
  // scroll to the map
272
272
  // scrollToElement('ol-map-container');
273
273
 
274
- zoomMapToFeatures(map, [item], 5000);
274
+ // zoomMapToFeatures(map, [item], 5000);
275
275
  onSelectedCase(item.values_);
276
276
 
277
277
  const popupOverlay =
@@ -9,7 +9,12 @@ import InfoOverlay from './InfoOverlay';
9
9
  import FeatureInteraction from './FeatureInteraction';
10
10
  import { useMapContext } from '@eeacms/volto-openlayers-map/api';
11
11
 
12
- import { centerAndResetMapZoom, getFeatures, zoomMapToFeatures } from './utils';
12
+ import {
13
+ centerAndResetMapZoom,
14
+ clearFilters,
15
+ getFeatures,
16
+ // zoomMapToFeatures,
17
+ } from './utils';
13
18
 
14
19
  const styleCache = {};
15
20
  const MapContextGateway = ({ setMap }) => {
@@ -24,11 +29,14 @@ export default function DemoSitesMap(props) {
24
29
  const {
25
30
  items,
26
31
  activeItems,
32
+ setActiveFilters,
27
33
  hideFilters,
28
34
  selectedCase,
29
35
  onSelectedCase,
30
36
  map,
31
37
  setMap,
38
+ highlightedIndex,
39
+ setHighlightedIndex,
32
40
  } = props;
33
41
  const features = getFeatures(items);
34
42
  const [resetMapButtonClass, setResetMapButtonClass] =
@@ -65,7 +73,8 @@ export default function DemoSitesMap(props) {
65
73
  if (activeItems) {
66
74
  pointsSource.clear();
67
75
  pointsSource.addFeatures(getFeatures(activeItems));
68
- hideFilters && zoomMapToFeatures(map, getFeatures(activeItems));
76
+ // hideFilters && zoomMapToFeatures(map, getFeatures(activeItems));
77
+ // zoomMapToFeatures(map, getFeatures(activeItems));
69
78
  }
70
79
  }, [map, activeItems, pointsSource, hideFilters]);
71
80
 
@@ -122,13 +131,18 @@ export default function DemoSitesMap(props) {
122
131
  view={{
123
132
  center: ol.proj.fromLonLat([10, 54]),
124
133
  showFullExtent: true,
125
- zoom: 2.5,
134
+ zoom: 3.4,
126
135
  }}
127
136
  pixelRatio={1}
128
137
  // controls={ol.control.defaults({ attribution: false })}
129
138
  >
130
139
  <Controls attribution={false} />
131
140
  <Layers>
141
+ {highlightedIndex > 0 && !activeItems.length && (
142
+ <div className="no-results-message">
143
+ No results found. Please refine your filters.
144
+ </div>
145
+ )}
132
146
  <button
133
147
  className={cx(
134
148
  'reset-map-button ui button secondary',
@@ -137,15 +151,19 @@ export default function DemoSitesMap(props) {
137
151
  onClick={() => {
138
152
  // scrollToElement('search-input');
139
153
  onSelectedCase(null);
154
+ clearFilters(setActiveFilters);
155
+ setHighlightedIndex(5);
140
156
  if (hideFilters) {
141
- zoomMapToFeatures(map, getFeatures(activeItems));
157
+ // zoomMapToFeatures(map, getFeatures(activeItems));
158
+ centerAndResetMapZoom(map);
142
159
  } else {
143
160
  centerAndResetMapZoom(map);
161
+ // zoomMapToFeatures(map, getFeatures(activeItems));
144
162
  }
145
163
  map.getInteractions().array_[9].getFeatures().clear();
146
164
  }}
147
165
  >
148
- <span className="result-info-title">Reset map</span>
166
+ <span className="result-info-title">Reset filters</span>
149
167
  <i className="icon ri-map-2-line"></i>
150
168
  </button>
151
169
  <InfoOverlay
@@ -200,7 +218,8 @@ const selectedClusterStyle = (selectedFeature) => {
200
218
  }
201
219
  // set size === 1 to enable clusterization
202
220
  if (size) {
203
- let color = feature.values_.features[0].values_['color'];
221
+ // let color = feature.values_.features[0].values_['color'];
222
+ let color = '#0179cf';
204
223
  let width = feature.values_.features[0].values_['width'];
205
224
  let radius = feature.values_.features[0].values_['radius'];
206
225
  // console.log(color)
@@ -6,9 +6,13 @@ export default function FeatureDisplay({ feature }) {
6
6
  <div id="csepopup">
7
7
  <h3>
8
8
  <strong>
9
- <a target="_blank" rel="noopener noreferrer" href={feature.path}>
9
+ <span
10
+ // target="_blank"
11
+ // rel="noopener noreferrer"
12
+ // href={feature.path}
13
+ >
10
14
  {truncateText(feature.title)}
11
- </a>
15
+ </span>
12
16
  </strong>
13
17
  </h3>
14
18
  {feature.info ? (
@@ -57,6 +61,19 @@ export default function FeatureDisplay({ feature }) {
57
61
  ''
58
62
  )}
59
63
 
64
+ {feature.objective.length > 0 ? (
65
+ <div>
66
+ <span className="popup-title blue">Objective/Enabler</span>
67
+ <ul>
68
+ {feature.objective.map((item, index) => {
69
+ return <li key={index}>{item}</li>;
70
+ })}
71
+ </ul>
72
+ </div>
73
+ ) : (
74
+ ''
75
+ )}
76
+
60
77
  {/* {feature.project_link ? (
61
78
  <div>
62
79
  <span className="popup-title blue">Project link: </span>
@@ -85,13 +102,13 @@ export default function FeatureDisplay({ feature }) {
85
102
  {feature.indicators.map((item, index) => {
86
103
  return (
87
104
  <li key={index}>
88
- <a
89
- target="_blank"
90
- rel="noopener noreferrer"
91
- href={item['path']}
105
+ <span
106
+ // target="_blank"
107
+ // rel="noopener noreferrer"
108
+ // href={item['path']}
92
109
  >
93
110
  {item['title']}
94
- </a>
111
+ </span>
95
112
  </li>
96
113
  );
97
114
  })}
@@ -100,14 +117,6 @@ export default function FeatureDisplay({ feature }) {
100
117
  ) : (
101
118
  ''
102
119
  )}
103
- {/* <div>
104
- <h4>Indicators</h4>
105
- <ul>
106
- {feature.sectors.map((item, index) => {
107
- return <li key={index}>{item}</li>;
108
- })}
109
- </ul>
110
- </div> */}
111
120
  </div>
112
121
  ) : null;
113
122
  }
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { openlayers as ol } from '@eeacms/volto-openlayers-map';
3
3
  import { useMapContext } from '@eeacms/volto-openlayers-map/api';
4
- import { zoomMapToFeatures } from './utils';
4
+ // import { zoomMapToFeatures } from './utils';
5
5
 
6
6
  export const useStyles = () => {
7
7
  const selected = React.useMemo(
@@ -67,7 +67,7 @@ export default function FeatureInteraction({ onFeatureSelect }) {
67
67
  // });
68
68
  } else {
69
69
  onFeatureSelect(null);
70
- zoomMapToFeatures(map, subfeatures);
70
+ // zoomMapToFeatures(map, subfeatures);
71
71
  }
72
72
  });
73
73
 
@@ -0,0 +1,255 @@
1
+ import React, { useRef } from 'react';
2
+ import cx from 'classnames';
3
+ import loadable from '@loadable/component';
4
+ import { Grid } from 'semantic-ui-react'; // Dropdown,
5
+
6
+ import { objectivesCustomOrder } from './utils';
7
+
8
+ const Plot = loadable(() => import('react-plotly.js'));
9
+
10
+ const ObjectivesChart = ({
11
+ items,
12
+ // activeItems,
13
+ // filters,
14
+ activeFilters,
15
+ setActiveFilters,
16
+ // hideFilters,
17
+ // map,
18
+ highlightedIndex,
19
+ setHighlightedIndex,
20
+ }) => {
21
+ const initialized = useRef(false);
22
+ const chartRef = useRef(null);
23
+ const objectiveCounts = {};
24
+
25
+ for (const item of items) {
26
+ item.properties.objective.map((obj) => {
27
+ objectiveCounts[obj] = (objectiveCounts[obj] || 0) + 1;
28
+ return [];
29
+ });
30
+ }
31
+ const _sorted = Object.entries(objectiveCounts).sort(
32
+ (a, b) =>
33
+ objectivesCustomOrder.indexOf(a[0]) - objectivesCustomOrder.indexOf(b[0]),
34
+ );
35
+ const objectives = Object.fromEntries(_sorted);
36
+ const objectivesLength = Object.keys(objectives).length;
37
+
38
+ React.useEffect(() => {
39
+ // set the objectives and the count
40
+ if (!items) return;
41
+ if (initialized.current) return;
42
+
43
+ const interval = setInterval(() => {
44
+ setHighlightedIndex((prevIndex) => {
45
+ if (prevIndex + 1 >= objectivesLength + 1) {
46
+ clearInterval(interval); // Stop after last item
47
+ initialized.current = true;
48
+ return prevIndex;
49
+ }
50
+ return prevIndex + 1;
51
+ });
52
+ }, 2000);
53
+ // console.log('init');
54
+ return () => clearInterval(interval); // Cleanup on unmount
55
+ }, [items, objectivesLength, highlightedIndex, setHighlightedIndex]);
56
+
57
+ React.useEffect(() => {
58
+ // if (!objectives) return;
59
+ const filterKey = 'objective_filter';
60
+
61
+ if (highlightedIndex === -1) {
62
+ const newValue = [undefined];
63
+
64
+ setActiveFilters((prev) => {
65
+ const currentValues = prev[filterKey];
66
+ // Don't update if no change
67
+ const isSame =
68
+ currentValues.length === newValue.length &&
69
+ currentValues.every((v, i) => v === newValue[i]);
70
+
71
+ if (isSame) return prev;
72
+
73
+ return {
74
+ ...prev,
75
+ [filterKey]: newValue,
76
+ };
77
+ });
78
+ return;
79
+ }
80
+
81
+ const currentObjective = Object.keys(objectives)[highlightedIndex];
82
+ const newValue = currentObjective ? [currentObjective] : [];
83
+
84
+ setActiveFilters((prev) => {
85
+ const currentValues = prev[filterKey];
86
+ // Don't update if no change
87
+ const isSame =
88
+ currentValues.length === newValue.length &&
89
+ currentValues.every((v, i) => v === newValue[i]);
90
+
91
+ if (isSame) return prev;
92
+
93
+ return {
94
+ ...prev,
95
+ [filterKey]: newValue,
96
+ };
97
+ });
98
+ // console.log(highlightedIndex, activeFilters);
99
+ }, [objectives, activeFilters, setActiveFilters, highlightedIndex]);
100
+
101
+ const handleClick = (event) => {
102
+ let label = '';
103
+
104
+ if (event.points) {
105
+ const point = event.points[0];
106
+ label = point.label;
107
+ // const value = point.value;
108
+ } else {
109
+ label = event.target.textContent;
110
+ }
111
+
112
+ const tempFilters = JSON.parse(JSON.stringify(activeFilters));
113
+ if (tempFilters['objective_filter'].includes(label)) {
114
+ tempFilters['objective_filter'] = tempFilters['objective_filter'].filter(
115
+ (i) => i !== label,
116
+ );
117
+ setHighlightedIndex(5);
118
+ } else {
119
+ tempFilters['objective_filter'] = [];
120
+ tempFilters['objective_filter'].push(label);
121
+ setHighlightedIndex(Object.keys(objectives).indexOf(label));
122
+ }
123
+ setActiveFilters(tempFilters);
124
+ };
125
+
126
+ const handleHover = () => {
127
+ if (chartRef.current) {
128
+ chartRef.current.style.cursor = 'pointer'; // Change cursor on hover
129
+ }
130
+ };
131
+
132
+ const handleUnhover = () => {
133
+ if (chartRef.current) {
134
+ chartRef.current.style.cursor = 'default'; // Reset cursor
135
+ }
136
+ };
137
+
138
+ const labels = Object.keys(objectives);
139
+ const values = Object.values(objectives);
140
+ const totalCount = values.reduce((acc, curr) => acc + curr, 0);
141
+ const customColors = ['#007b6c', '#fdaf20', '#004b7f', '#f9eb8a', '#9e83b6']; // Adjust colors as needed
142
+ const grayColor = '#d3d3d3';
143
+ // const pull = labels.map((_, i) => (i === highlightedIndex ? 0.1 : 0));
144
+ const inactiveColors = labels.map((_, i) =>
145
+ i === highlightedIndex ? customColors[i % customColors.length] : grayColor,
146
+ );
147
+
148
+ // console.log(highlightedIndex);
149
+
150
+ return highlightedIndex >= -1 ? (
151
+ <div className="objectives-chart fade-in">
152
+ <Grid.Row className="chart-title">
153
+ Demo sites and Associated regions
154
+ </Grid.Row>
155
+ <Grid.Row className="chart-title">Objective/Enabler</Grid.Row>
156
+ <Grid.Row className="chart-container">
157
+ <div ref={chartRef}>
158
+ <Plot
159
+ useResizeHandler
160
+ data={[
161
+ {
162
+ type: 'pie',
163
+ labels: labels,
164
+ values: values,
165
+ textinfo: 'none',
166
+ // textinfo: highlightedIndex === 5 ? 'value' : 'none',
167
+ hole: 0.4,
168
+ insidetextorientation: 'radial',
169
+ hoverinfo: 'label+value',
170
+ marker: {
171
+ colors:
172
+ highlightedIndex === 5 ? customColors : inactiveColors, // Apply custom colors here
173
+ line: {
174
+ color: '#e0e0e0', // light gray
175
+ width: 1, // thin border
176
+ },
177
+ },
178
+ direction: 'clockwise',
179
+ // pull,
180
+ },
181
+ ]}
182
+ layout={{
183
+ width: 300,
184
+ height: 300,
185
+ // title: 'Objectives Distribution',
186
+ showlegend: false,
187
+ margin: {
188
+ t: 20, // Top margin
189
+ b: 20, // Bottom margin
190
+ l: 0, // Left margin
191
+ r: 0, // Right margin
192
+ },
193
+ annotations: [
194
+ {
195
+ text:
196
+ highlightedIndex === 5
197
+ ? `${totalCount}`
198
+ : values[highlightedIndex] || '', // Display total count in the center
199
+ font: {
200
+ family:
201
+ "'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif",
202
+ size: 24, // Adjust font size as needed
203
+ weight: 'bold',
204
+ },
205
+ showarrow: false, // No arrow pointing to the center
206
+ x: 0.5, // Position in the center (horizontal)
207
+ y: 0.5, // Position in the center (vertical)
208
+ align: 'center',
209
+ valign: 'middle',
210
+ bgcolor: 'rgba(255, 255, 255, 0.6)', // Optional background color
211
+ borderpad: 10, // Padding around the text
212
+ },
213
+ ],
214
+ }}
215
+ config={{
216
+ responsive: true,
217
+ displayModeBar: true,
218
+ modeBarButtonsToRemove: ['toImage'], // Removes download button
219
+ displaylogo: false, // Removes "Produced with Plotly.js"
220
+ }}
221
+ onClick={handleClick}
222
+ onHover={handleHover}
223
+ onUnhover={handleUnhover}
224
+ />
225
+ </div>
226
+ </Grid.Row>
227
+ <Grid.Row>
228
+ <ul>
229
+ {Object.entries(objectives).map(([item, count], index) => (
230
+ <li
231
+ className={cx(
232
+ index === highlightedIndex ? 'active' : '',
233
+ customColors[index].replace('#', 'C'),
234
+ )}
235
+ >
236
+ <div
237
+ onClick={handleClick}
238
+ onKeyDown={() => {}}
239
+ tabIndex={index}
240
+ role="button"
241
+ key={item}
242
+ >
243
+ {item}
244
+ </div>
245
+ </li>
246
+ ))}
247
+ </ul>
248
+ </Grid.Row>
249
+ </div>
250
+ ) : (
251
+ ''
252
+ );
253
+ };
254
+
255
+ export default ObjectivesChart;
@@ -12,6 +12,23 @@
12
12
  .ol-map {
13
13
  height: 600px;
14
14
  min-height: 600px;
15
+ font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
16
+
17
+ .no-results-message {
18
+ position: absolute;
19
+ z-index: 9;
20
+ top: 46%;
21
+ right: 28%;
22
+ bottom: 47%;
23
+ left: 27%;
24
+ padding: 0.5em 1em;
25
+ border: 4px solid #dfdfdf;
26
+ border-radius: 2px;
27
+ background-color: #0088e9;
28
+ color: #ffffff;
29
+ opacity: 0.6;
30
+ text-shadow: none;
31
+ }
15
32
  }
16
33
 
17
34
  #csepopup {
@@ -29,6 +46,7 @@
29
46
  }
30
47
 
31
48
  h3 {
49
+ color: #069;
32
50
  font-size: 1.4rem;
33
51
  }
34
52
 
@@ -388,12 +406,12 @@
388
406
  }
389
407
 
390
408
  .legend {
391
- position: absolute;
409
+ // position: absolute;
392
410
  bottom: 2em;
393
411
  display: flex;
394
- flex-direction: column;
412
+ flex-direction: row;
395
413
  align-items: flex-start;
396
- gap: 0.5em;
414
+ gap: 1em;
397
415
 
398
416
  .legend-row {
399
417
  display: flex;
@@ -411,9 +429,9 @@
411
429
  width: 22px;
412
430
  min-width: 22px;
413
431
  height: 22px;
414
- /* dark green shade */
415
432
  border-radius: 50%;
416
- background-color: #007265;
433
+ // background-color: #007265;
434
+ background-color: #0179cf;
417
435
  gap: 0.5em;
418
436
  }
419
437
 
@@ -439,3 +457,111 @@
439
457
  transform: translate(-50%, -50%);
440
458
  }
441
459
  }
460
+
461
+ .right-side-filters {
462
+ display: flex !important;
463
+ flex-direction: column;
464
+ align-items: center;
465
+ justify-content: space-around;
466
+ }
467
+
468
+ .objectives-chart {
469
+ font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
470
+
471
+ .js-plotly-plot .plotly,
472
+ .js-plotly-plot .plotly div {
473
+ font-family: 'Roboto', 'Helvetica Neue', Arial, Helvetica, sans-serif !important;
474
+ }
475
+
476
+ &.fade-in {
477
+ animation: fadeIn 1s ease-in forwards;
478
+ opacity: 0;
479
+ /* Start invisible */
480
+ }
481
+
482
+ @keyframes fadeIn {
483
+ to {
484
+ opacity: 1;
485
+ }
486
+ }
487
+
488
+ .main-svg {
489
+ overflow: visible !important;
490
+ }
491
+
492
+ .hoverlayer {
493
+ overflow: visible !important;
494
+ }
495
+
496
+ .chart-title {
497
+ font-size: 1em;
498
+ font-weight: 500;
499
+ text-align: center;
500
+ }
501
+
502
+ .chart-container {
503
+ text-align: center;
504
+ }
505
+
506
+ ul {
507
+ display: flex;
508
+ flex-direction: column;
509
+ padding-left: 1.3em;
510
+ gap: 0.5em;
511
+ list-style: none;
512
+ }
513
+
514
+ li {
515
+ display: flex;
516
+ flex-direction: row;
517
+ align-items: center;
518
+ font-size: 0.8em;
519
+ line-height: 1.5em;
520
+
521
+ &:hover {
522
+ cursor: pointer;
523
+ font-weight: 500;
524
+ }
525
+
526
+ &.C007b6c::before {
527
+ background-color: #007b6c;
528
+ }
529
+
530
+ &.Cfdaf20::before {
531
+ background-color: #fdaf20;
532
+ }
533
+
534
+ &.C004b7f::before {
535
+ background-color: #004b7f;
536
+ }
537
+
538
+ &.Cf9eb8a::before {
539
+ background-color: #f9eb8a;
540
+ }
541
+
542
+ &.C9e83b6::before {
543
+ background-color: #9e83b6;
544
+ }
545
+
546
+ &::before {
547
+ width: 0.7em;
548
+ height: 0.7em;
549
+ flex-shrink: 0;
550
+ border-radius: 2px;
551
+ /* change as needed */
552
+ margin-right: 8px;
553
+ content: '';
554
+ }
555
+
556
+ &.active {
557
+ font-size: 1em;
558
+ // color: red;
559
+ font-weight: 500;
560
+
561
+ &::before {
562
+ width: 1em;
563
+ height: 1em;
564
+ }
565
+ }
566
+ }
567
+ }
@@ -1,5 +1,30 @@
1
1
  import { openlayers as ol } from '@eeacms/volto-openlayers-map';
2
2
 
3
+ export const objectivesCustomOrder = [
4
+ 'Objective 1: Protect and restore marine and freshwater ecosystems and biodiversity',
5
+ 'Objective 2: Prevent and eliminate pollution of our oceans, seas and waters',
6
+ 'Objective 3: Make the sustainable blue economy carbon-neutral and circular',
7
+ 'Enabler 1: Digital twin of the ocean',
8
+ 'Enabler 2: Public mobilisation and engagement',
9
+ ];
10
+
11
+ export const clearFilters = (setActiveFilters) => {
12
+ const filterInputs = document.querySelectorAll(
13
+ '#cse-filter .filter-input input',
14
+ );
15
+ for (let i = 0; i < filterInputs.length; i++) {
16
+ filterInputs[i].checked = false;
17
+ }
18
+ setActiveFilters({
19
+ objective_filter: [],
20
+ target_filter: [],
21
+ indicator_filter: [],
22
+ project_filter: [],
23
+ country_filter: [],
24
+ });
25
+ // scrollToElement('search-input');
26
+ };
27
+
3
28
  export const truncateText = (str, max = 50) => {
4
29
  if (str.length <= max) {
5
30
  return str;
@@ -18,7 +43,7 @@ export function isValidURL(string) {
18
43
 
19
44
  export function centerAndResetMapZoom(map) {
20
45
  map.getView().animate({
21
- zoom: 2.5,
46
+ zoom: 3.4,
22
47
  duration: 1000,
23
48
  center: ol.proj.transform([10, 54], 'EPSG:4326', 'EPSG:3857'),
24
49
  });
@@ -39,20 +64,42 @@ export function getExtentOfFeatures(features) {
39
64
 
40
65
  export function zoomMapToFeatures(map, features, threshold = 500) {
41
66
  const extent = getExtentOfFeatures(features);
42
- let extentBuffer = (extent[3] - extent[1] + extent[2] - extent[0]) / 4;
67
+
68
+ // let extentBuffer = (extent[3] - extent[1] + extent[2] - extent[0]) / 4;
69
+ // extentBuffer = extentBuffer < threshold ? threshold : extentBuffer;
70
+ // const paddedExtent = ol.extent.buffer(extent, extentBuffer);
71
+
72
+ const width = extent[2] - extent[0];
73
+ const height = extent[3] - extent[1];
74
+ const bufferFactor = 0.05; // 5% buffer
75
+
76
+ let extentBuffer = Math.max(width, height) * bufferFactor;
43
77
  extentBuffer = extentBuffer < threshold ? threshold : extentBuffer;
44
78
  const paddedExtent = ol.extent.buffer(extent, extentBuffer);
45
- map.getView().fit(paddedExtent, { ...map.getSize(), duration: 1000 });
79
+
80
+ try {
81
+ // map.getView().fit(paddedExtent, { ...map.getSize(), duration: 1000 });
82
+ map.getView().fit(paddedExtent, {
83
+ size: map.getSize(), // pass size explicitly
84
+ // padding: [0, 0, 0, 0], // top, right, bottom, left
85
+ duration: 1000,
86
+ });
87
+ } catch (error) {
88
+ // no features available, zooming fails
89
+ }
46
90
  }
47
91
 
48
92
  export function getFeatures(cases) {
49
93
  const Feature = ol.ol.Feature;
50
94
  const colors = {
51
- 'Carbon-neutral and circular blue economy': '#f9eb8a',
52
- 'Digital twin of the ocean': '#004b7f',
53
- 'Prevent and eliminate pollution of waters': '#fdaf20',
54
- 'Protect and restore marine and freshwater ecosystems': '#007b6c',
55
- 'Public mobilisation and engagement': '#9e83b6',
95
+ 'Objective 1: Protect and restore marine and freshwater ecosystems and biodiversity':
96
+ '#007b6c',
97
+ 'Objective 2: Prevent and eliminate pollution of our oceans, seas and waters':
98
+ '#fdaf20',
99
+ 'Objective 3: Make the sustainable blue economy carbon-neutral and circular':
100
+ '#004b7f',
101
+ 'Enabler 1: Digital twin of the ocean': '#f9eb8a',
102
+ 'Enabler 2: Public mobilisation and engagement': '#9e83b6',
56
103
  };
57
104
  const width = {
58
105
  'Demo site': 6,
@@ -85,10 +132,11 @@ export function getFeatures(cases) {
85
132
  info: c.properties.info,
86
133
  website: c.properties.website,
87
134
  objective: c.properties.objective,
135
+ target: c.properties.target,
88
136
  description: c.properties.description,
89
137
  index: index,
90
138
  path: c.properties.path,
91
- color: colors[c.properties.objective] || '#B83230',
139
+ color: colors[c.properties.objective[0]] || '#B83230',
92
140
  width: width[c.properties.type_is_region],
93
141
  radius: radius[c.properties.type_is_region],
94
142
  },
@@ -101,6 +149,7 @@ export function getFeatures(cases) {
101
149
  export function filterCases(cases, activeFilters, indicatorOnly) {
102
150
  const data = cases.filter((_case) => {
103
151
  let flag_objective = false;
152
+ let flag_target = false;
104
153
  let flag_indicator = false;
105
154
  let flag_project = false;
106
155
  let flag_country = false;
@@ -122,7 +171,17 @@ export function filterCases(cases, activeFilters, indicatorOnly) {
122
171
  let objective = _case.properties.objective;
123
172
 
124
173
  activeFilters.objective_filter.forEach((filter) => {
125
- if (objective === filter) flag_objective = true;
174
+ if (objective?.includes(filter)) flag_objective = true;
175
+ });
176
+ }
177
+
178
+ if (!activeFilters.target_filter.length) {
179
+ flag_target = true;
180
+ } else {
181
+ let target = _case.properties.target;
182
+
183
+ activeFilters.target_filter.forEach((filter) => {
184
+ if (target?.includes(filter)) flag_target = true;
126
185
  });
127
186
  }
128
187
 
@@ -162,6 +221,7 @@ export function filterCases(cases, activeFilters, indicatorOnly) {
162
221
 
163
222
  return flag_indicatorOnly &&
164
223
  flag_objective &&
224
+ flag_target &&
165
225
  flag_indicator &&
166
226
  flag_country &&
167
227
  flag_project
@@ -175,6 +235,7 @@ export function filterCases(cases, activeFilters, indicatorOnly) {
175
235
  export function getFilters(cases, indicatorOnly) {
176
236
  let _filters = {
177
237
  objective_filter: {},
238
+ target_filter: {},
178
239
  indicator_filter: {},
179
240
  project_filter: {},
180
241
  country_filter: {},
@@ -197,9 +258,20 @@ export function getFilters(cases, indicatorOnly) {
197
258
  });
198
259
 
199
260
  let objective = _case.properties.objective;
200
- if (objective && !_filters.objective_filter.hasOwnProperty(objective)) {
201
- _filters.objective_filter[objective] = objective;
202
- }
261
+ objective.map((item) => {
262
+ if (item && !_filters.objective_filter.hasOwnProperty(item)) {
263
+ _filters.objective_filter[item] = item;
264
+ }
265
+ return [];
266
+ });
267
+
268
+ let target = _case.properties.target;
269
+ target.map((item) => {
270
+ if (item && !_filters.target_filter.hasOwnProperty(item)) {
271
+ _filters.target_filter[item] = item;
272
+ }
273
+ return [];
274
+ });
203
275
 
204
276
  let project = _case.properties.project;
205
277
  if (project && !_filters.project_filter.hasOwnProperty(project)) {