@eeacms/volto-marine-policy 2.0.3 → 2.0.5

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 (25) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/jest-addon.config.js +4 -4
  3. package/package.json +6 -4
  4. package/src/components/Blocks/DemoSitesExplorer/DemoSitesExplorerEdit.js +5 -0
  5. package/src/components/Blocks/DemoSitesExplorer/DemoSitesExplorerView.js +94 -0
  6. package/src/components/Blocks/DemoSitesExplorer/DemoSitesFilters.jsx +406 -0
  7. package/src/components/Blocks/DemoSitesExplorer/DemoSitesFilters.test.jsxZ +91 -0
  8. package/src/components/Blocks/DemoSitesExplorer/DemoSitesListing.jsx +378 -0
  9. package/src/components/Blocks/DemoSitesExplorer/DemoSitesMap.jsx +221 -0
  10. package/src/components/Blocks/DemoSitesExplorer/FeatureDisplay.jsx +97 -0
  11. package/src/components/Blocks/DemoSitesExplorer/FeatureDisplay.test.jsxZ +48 -0
  12. package/src/components/Blocks/DemoSitesExplorer/FeatureInteraction.jsx +95 -0
  13. package/src/components/Blocks/DemoSitesExplorer/InfoOverlay.jsx +79 -0
  14. package/src/components/Blocks/DemoSitesExplorer/hooks.js +20 -0
  15. package/src/components/Blocks/DemoSitesExplorer/images/icon-depth.png +0 -0
  16. package/src/components/Blocks/DemoSitesExplorer/images/icon-light.png +0 -0
  17. package/src/components/Blocks/DemoSitesExplorer/images/search.svg +3 -0
  18. package/src/components/Blocks/DemoSitesExplorer/index.js +16 -0
  19. package/src/components/Blocks/DemoSitesExplorer/mockJsdom.js +8 -0
  20. package/src/components/Blocks/DemoSitesExplorer/styles.less +376 -0
  21. package/src/components/Blocks/DemoSitesExplorer/utils.js +211 -0
  22. package/src/components/Blocks/DemoSitesExplorer/utils.test.jsZ +63 -0
  23. package/src/components/index.js +1 -0
  24. package/src/express-middleware.js +37 -0
  25. package/src/index.js +11 -5
@@ -0,0 +1,376 @@
1
+ .section-nwrm-case-studies .listing .slot-top {
2
+ padding-top: 2em;
3
+ }
4
+
5
+ #csepopup > span {
6
+ display: 'block';
7
+ margin-bottom: '10px';
8
+ background-color: '#ddd';
9
+ }
10
+
11
+ .ol-map-wrapper,
12
+ .ol-map {
13
+ height: 600px;
14
+ min-height: 600px;
15
+ }
16
+
17
+ #csepopup {
18
+ // width: 300px;
19
+ width: 100%;
20
+ // height: 100%;
21
+ padding: 1em;
22
+ // border: 1px solid black;
23
+ // font-size: 14px;
24
+ // line-height: initial;
25
+ font-size: 1rem;
26
+
27
+ a:hover {
28
+ color: #0083e0;
29
+ }
30
+
31
+ h3 {
32
+ font-size: 1.4rem;
33
+ }
34
+
35
+ h4 {
36
+ margin: 0px;
37
+ }
38
+
39
+ div {
40
+ margin-bottom: 16px;
41
+ }
42
+
43
+ ul {
44
+ margin: 0px;
45
+
46
+ li {
47
+ line-height: 22px;
48
+ }
49
+ }
50
+
51
+ p {
52
+ font-size: 13px;
53
+
54
+ p {
55
+ margin-bottom: 10px;
56
+ }
57
+ }
58
+
59
+ span.blue {
60
+ color: #069;
61
+ }
62
+
63
+ span.popup-title {
64
+ font-weight: bold;
65
+ }
66
+
67
+ span.img {
68
+ display: block;
69
+ margin-bottom: 10px;
70
+ background-color: #ddd;
71
+
72
+ img {
73
+ max-height: 133px;
74
+ }
75
+ }
76
+ }
77
+
78
+ .ol-overlaycontainer {
79
+ // width: 370px;
80
+ // height: 100%;
81
+
82
+ .ol-overlay-container {
83
+ // position: relative !important;
84
+ position: absolute !important;
85
+ right: 30px;
86
+ bottom: 30px;
87
+ display: block !important;
88
+ // width: 100%;
89
+ // height: 100%;
90
+ background-color: #fefefe;
91
+
92
+ #popup-overlay {
93
+ position: relative !important;
94
+ display: flex;
95
+ width: 100%;
96
+ height: 100%;
97
+ align-items: center;
98
+ }
99
+ }
100
+ }
101
+
102
+ #cse-filter {
103
+ display: flex;
104
+ flex-direction: row;
105
+ margin-top: 1em;
106
+ font-family: sans-serif;
107
+ // gap: 3em;
108
+
109
+ .filter-wrapper {
110
+ // flex-grow: 1;
111
+
112
+ &.active {
113
+ button span {
114
+ // box-shadow: 0 5px 0 #ffffff, 0 0 4px rgba(0, 0, 0, 0.25);
115
+ color: #0083e0;
116
+ }
117
+
118
+ button {
119
+ box-shadow:
120
+ 0 5px 0 #ffffff,
121
+ 0 0 4px rgba(0, 0, 0, 0.25) !important;
122
+ }
123
+
124
+ .filter-inputs-wrapper {
125
+ display: block;
126
+ }
127
+ }
128
+
129
+ .ri-arrow-down-s-line:before {
130
+ content: '\ea4e';
131
+ }
132
+
133
+ .ui.basic.button.facet-btn {
134
+ position: relative !important;
135
+ z-index: 99999;
136
+ display: inline-block;
137
+ overflow: hidden;
138
+ padding: 0.5em 0em;
139
+ margin: 0 0.25em 0.25em 0;
140
+ background-color: white !important;
141
+ color: #3d5265 !important;
142
+
143
+ &:active {
144
+ background: white !important;
145
+ }
146
+
147
+ // &:hover,
148
+ // &:focus {
149
+ // color: rgba(0, 0, 0, 0.6) !important;
150
+ // }
151
+ &:hover {
152
+ box-shadow: none;
153
+ }
154
+
155
+ span {
156
+ position: relative !important;
157
+ z-index: 99999;
158
+ display: inline-block;
159
+ overflow: hidden;
160
+ padding: 0.5em 1em;
161
+ background-color: white;
162
+ }
163
+ }
164
+
165
+ .filter-inputs-wrapper {
166
+ position: absolute;
167
+ z-index: 1;
168
+ display: none;
169
+ padding: 1em;
170
+ margin-top: -3px;
171
+ background-color: #fff;
172
+ box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);
173
+ color: #4f4f4f;
174
+
175
+ .filterInputText {
176
+ padding: 6px;
177
+ border: 0.5px solid #c4c4c4;
178
+ border-radius: 0;
179
+ margin-bottom: 0.7em;
180
+ font-size: 14px;
181
+ }
182
+
183
+ .filter-inputs {
184
+ overflow: auto;
185
+ min-width: 200px;
186
+ max-width: 400px;
187
+ max-height: 200px;
188
+
189
+ &::-webkit-scrollbar {
190
+ width: 8px;
191
+ height: 0.3rem !important;
192
+ }
193
+
194
+ &::-webkit-scrollbar-track {
195
+ border-radius: 10px;
196
+ background-color: #d9d9d9;
197
+ }
198
+
199
+ &::-webkit-scrollbar-thumb {
200
+ border-radius: 10px;
201
+ background: #004b7f;
202
+ }
203
+
204
+ .filter-input {
205
+ display: flex;
206
+ align-items: center;
207
+ justify-content: flex-start;
208
+ cursor: pointer;
209
+ font-size: 14px;
210
+
211
+ span {
212
+ padding-right: 5px;
213
+ }
214
+
215
+ input {
216
+ margin-right: 8px;
217
+ cursor: pointer;
218
+ }
219
+ }
220
+ }
221
+ }
222
+ }
223
+
224
+ h4 {
225
+ position: relative;
226
+ padding: 10px;
227
+ border-bottom: 1px solid #d8d8d8;
228
+ margin-bottom: 0 !important;
229
+ background-color: #005c96;
230
+ color: #fff;
231
+ cursor: pointer;
232
+ font-size: 14px;
233
+ }
234
+ }
235
+
236
+ .active-filter-list {
237
+ border: 1px solid #e6e7e8;
238
+ background-color: #f9f9f9 !important;
239
+
240
+ .filter-list-header {
241
+ display: flex;
242
+ align-items: center;
243
+ gap: 1em;
244
+
245
+ h4 {
246
+ margin: 0;
247
+ color: #3d5265;
248
+ }
249
+ }
250
+
251
+ .filter-list-content {
252
+ padding: 1em 0 0;
253
+ background-color: transparent;
254
+
255
+ .filter {
256
+ display: flex;
257
+ flex-wrap: wrap;
258
+ font-size: 15px;
259
+
260
+ .filter-wrapper {
261
+ display: flex;
262
+ flex-direction: row;
263
+ flex-wrap: wrap;
264
+ align-items: baseline;
265
+ padding: 8px 15px;
266
+ margin-top: 10px;
267
+ margin-right: 11px;
268
+ background-color: #ebebeb;
269
+ gap: 1em;
270
+
271
+ .filter-label {
272
+ font-weight: 500;
273
+ }
274
+
275
+ .filter-value {
276
+ position: relative;
277
+ display: flex;
278
+ align-items: center;
279
+ padding: 0;
280
+ border: none;
281
+ margin: 0;
282
+ background-color: transparent;
283
+ font-size: 15px;
284
+
285
+ .close.icon {
286
+ position: relative;
287
+ width: 16px;
288
+ height: 16px;
289
+ padding: 3px;
290
+ border: 1px solid #004b7f !important;
291
+ border-radius: 2px;
292
+ color: #2e3e4c;
293
+ opacity: 1;
294
+
295
+ &:before,
296
+ &:after {
297
+ position: absolute;
298
+ top: 2px;
299
+ left: 7px;
300
+ width: 1px;
301
+ height: 9px;
302
+ background-color: #004b7f !important;
303
+ content: ' ';
304
+ }
305
+
306
+ &:before {
307
+ transform: rotate(45deg);
308
+ }
309
+
310
+ &:after {
311
+ transform: rotate(-45deg);
312
+ }
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+ }
319
+
320
+ .sui-search-box .search-input .terms-box .terms-box-left .search-icon img {
321
+ fill: #004b7f !important;
322
+ }
323
+
324
+ .searchlib-block .ui.basic.button.clear-btn {
325
+ color: #0083e0 !important;
326
+ }
327
+
328
+ .searchlib-block .result-item .result-info {
329
+ align-items: center;
330
+
331
+ &.show-on-map {
332
+ color: #0083e0 !important;
333
+ cursor: pointer;
334
+
335
+ .result-info-title {
336
+ color: #0083e0 !important;
337
+ }
338
+ }
339
+ }
340
+
341
+ .u-item.listing-item .result-info a {
342
+ color: #0077bc;
343
+ }
344
+
345
+ .searchlib-block .search-body-footer {
346
+ margin-top: 2em;
347
+ }
348
+
349
+ .reset-map-button.ui.button.secondary {
350
+ position: absolute;
351
+ z-index: 2;
352
+ top: 1.3em;
353
+ right: 0.7em;
354
+ display: flex;
355
+ padding: 8px 6px 8px 12px;
356
+ border: 4px solid #efefef;
357
+ border-radius: 2px;
358
+ background-color: #0083e0;
359
+ gap: 0.5em;
360
+ opacity: 0.6;
361
+
362
+ &.active {
363
+ display: flex;
364
+ }
365
+
366
+ &.inactive {
367
+ display: none;
368
+ }
369
+ }
370
+
371
+ .ol-zoom.ol-control {
372
+ .ol-zoom-in,
373
+ .ol-zoom-out {
374
+ background-color: rgba(0, 131, 224, 0.5);
375
+ }
376
+ }
@@ -0,0 +1,211 @@
1
+ import { openlayers as ol } from '@eeacms/volto-openlayers-map';
2
+
3
+ export const truncateText = (str, max = 50) => {
4
+ if (str.length <= max) {
5
+ return str;
6
+ }
7
+ return str.substring(0, max) + '...';
8
+ };
9
+
10
+ export function isValidURL(string) {
11
+ try {
12
+ new URL(string);
13
+ return true;
14
+ } catch (e) {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ export function centerAndResetMapZoom(map) {
20
+ map.getView().animate({
21
+ zoom: 2.5,
22
+ duration: 1000,
23
+ center: ol.proj.transform([10, 54], 'EPSG:4326', 'EPSG:3857'),
24
+ });
25
+ }
26
+
27
+ export function scrollToElement(elementId) {
28
+ const element = document.getElementById(elementId);
29
+ element.scrollIntoView({
30
+ behavior: 'smooth',
31
+ });
32
+ }
33
+
34
+ export function getExtentOfFeatures(features) {
35
+ const points = features.map((f) => f.getGeometry().flatCoordinates);
36
+ const point = new ol.geom.MultiPoint(points);
37
+ return point.getExtent();
38
+ }
39
+
40
+ export function zoomMapToFeatures(map, features, threshold = 500) {
41
+ const extent = getExtentOfFeatures(features);
42
+ let extentBuffer = (extent[3] - extent[1] + extent[2] - extent[0]) / 4;
43
+ extentBuffer = extentBuffer < threshold ? threshold : extentBuffer;
44
+ const paddedExtent = ol.extent.buffer(extent, extentBuffer);
45
+ map.getView().fit(paddedExtent, { ...map.getSize(), duration: 1000 });
46
+ }
47
+
48
+ export function getFeatures(cases) {
49
+ const Feature = ol.ol.Feature;
50
+ const colors = {
51
+ 'Carbon-neutral and circular blue economy': '#004b7f',
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': '#004b7f',
56
+ };
57
+ const width = {
58
+ 'Demo site': 6,
59
+ 'Associated region': 8,
60
+ };
61
+
62
+ const radius = {
63
+ 'Demo site': 6,
64
+ 'Associated region': 5,
65
+ };
66
+
67
+ return cases.map((c, index) => {
68
+ const {
69
+ geometry: { coordinates },
70
+ } = c;
71
+ const point = new Feature(
72
+ new ol.geom.Point(ol.proj.fromLonLat(coordinates)),
73
+ );
74
+ point.setId(index);
75
+ point.setProperties(
76
+ {
77
+ title: c.properties.title,
78
+ image: c.properties.image,
79
+ project: c.properties.project,
80
+ project_link: c.properties.project_link,
81
+ country: c.properties.country,
82
+ type_is_region: c.properties.type_is_region,
83
+ type: c.properties.type,
84
+ indicators: c.properties.indicators,
85
+ info: c.properties.info,
86
+ website: c.properties.website,
87
+ objective: c.properties.objective,
88
+ description: c.properties.description,
89
+ index: index,
90
+ path: c.properties.path,
91
+ color: colors[c.properties.objective] || '#B83230',
92
+ width: width[c.properties.type_is_region],
93
+ radius: radius[c.properties.type_is_region],
94
+ },
95
+ false,
96
+ );
97
+ return point;
98
+ });
99
+ }
100
+
101
+ export function filterCases(cases, activeFilters, demoSitesIds) {
102
+ const data = cases.filter((_case) => {
103
+ let flag_objective = false;
104
+ let flag_indicator = false;
105
+ let flag_project = false;
106
+ let flag_country = false;
107
+ let flag_case = demoSitesIds
108
+ ? demoSitesIds.includes(_case.properties.url.split('/').pop())
109
+ : true;
110
+
111
+ // debugger;
112
+ if (!activeFilters.objective_filter.length) {
113
+ flag_objective = true;
114
+ } else {
115
+ let objective = _case.properties.objective;
116
+
117
+ activeFilters.objective_filter.forEach((filter) => {
118
+ if (objective === filter) flag_objective = true;
119
+ });
120
+ }
121
+
122
+ if (!activeFilters.indicator_filter.length) {
123
+ flag_indicator = true;
124
+ } else {
125
+ let indicators = _case.properties.indicators?.map((item) => {
126
+ return item['id'].toString();
127
+ });
128
+
129
+ activeFilters.indicator_filter.forEach((filter) => {
130
+ if (indicators?.includes(filter)) flag_indicator = true;
131
+ });
132
+ }
133
+
134
+ if (!activeFilters.project_filter.length) {
135
+ flag_project = true;
136
+ } else {
137
+ let project = _case.properties.project;
138
+
139
+ activeFilters.project_filter.forEach((filter) => {
140
+ if (project === filter) flag_project = true;
141
+ });
142
+ }
143
+
144
+ if (!activeFilters.country_filter.length) {
145
+ flag_country = true;
146
+ } else {
147
+ let countries = _case.properties.country?.map((item) => {
148
+ return item.toString();
149
+ });
150
+
151
+ activeFilters.country_filter.forEach((filter) => {
152
+ if (countries?.includes(filter)) flag_country = true;
153
+ });
154
+ }
155
+
156
+ return flag_case &&
157
+ flag_objective &&
158
+ flag_indicator &&
159
+ flag_country &&
160
+ flag_project
161
+ ? _case
162
+ : false;
163
+ });
164
+
165
+ return data;
166
+ }
167
+
168
+ export function getFilters(cases) {
169
+ let _filters = {
170
+ objective_filter: {},
171
+ indicator_filter: {},
172
+ project_filter: {},
173
+ country_filter: {},
174
+ };
175
+
176
+ for (let key of Object.keys(cases)) {
177
+ const _case = cases[key];
178
+ // debugger;
179
+
180
+ let indicators = _case.properties.indicators;
181
+ indicators.map((item) => {
182
+ if (
183
+ item['title'] &&
184
+ !_filters.indicator_filter.hasOwnProperty(item['id'])
185
+ ) {
186
+ _filters.indicator_filter[item['id']] = item['title'];
187
+ }
188
+ return [];
189
+ });
190
+
191
+ let objective = _case.properties.objective;
192
+ if (objective && !_filters.objective_filter.hasOwnProperty(objective)) {
193
+ _filters.objective_filter[objective] = objective;
194
+ }
195
+
196
+ let project = _case.properties.project;
197
+ if (project && !_filters.project_filter.hasOwnProperty(project)) {
198
+ _filters.project_filter[project] = project;
199
+ }
200
+
201
+ let countries = _case.properties.country || [];
202
+ countries.map((item) => {
203
+ if (item && !_filters.country_filter.hasOwnProperty(item)) {
204
+ _filters.country_filter[item] = item;
205
+ }
206
+ return [];
207
+ });
208
+ }
209
+
210
+ return _filters;
211
+ }
@@ -0,0 +1,63 @@
1
+ import './mockJsdom';
2
+ import '@testing-library/jest-dom/extend-expect';
3
+ import { getFeatures, filterCases, getFilters } from './utils';
4
+
5
+ describe('utils.js', () => {
6
+ const mockCases = [
7
+ {
8
+ geometry: { coordinates: [0, 0] },
9
+ properties: {
10
+ title: 'test case study',
11
+ image: '',
12
+ nwrm_type: 'light',
13
+ measures: [{ id: 'test-measure1', title: 'test measure 1' }],
14
+ description: 'test',
15
+ sectors: ['testsector'],
16
+ path: '/test-case-study',
17
+ url: 'localhost.com/test-case-study',
18
+ },
19
+ },
20
+ {
21
+ geometry: { coordinates: [0, 0] },
22
+ properties: {
23
+ title: 'case study 2',
24
+ image: '',
25
+ nwrm_type: 'light',
26
+ measures: [{ id: 'test-measure1', title: 'test measure 1' }],
27
+ description: 'test',
28
+ sectors: ['testsector'],
29
+ path: '/test-case-study',
30
+ url: 'localhost.com/test-case-study',
31
+ },
32
+ },
33
+ ];
34
+
35
+ test('getFeatures', () => {
36
+ expect(() => {
37
+ getFeatures(mockCases);
38
+ }).not.toThrowError();
39
+ });
40
+
41
+ test('filterCases', () => {
42
+ const mockActiveFilters = {
43
+ nwrms_implemented: ['test measure 1'],
44
+ sectors: ['testsector'],
45
+ };
46
+ const mockDemoSitesIds = ['test-case-study'];
47
+ const mockCasesFiltered = filterCases(
48
+ mockCases,
49
+ mockActiveFilters,
50
+ mockDemoSitesIds,
51
+ 'test',
52
+ );
53
+ expect(mockCasesFiltered).toStrictEqual([]);
54
+ });
55
+
56
+ test('getFilters', () => {
57
+ const mockFilters = getFilters(mockCases);
58
+ expect(mockFilters).toStrictEqual({
59
+ nwrms_implemented: { 'test-measure1': 'test measure 1' },
60
+ sectors: { testsector: 'testsector' },
61
+ });
62
+ });
63
+ });
@@ -13,3 +13,4 @@ export TableauShare from './theme/Tableau/TableauShare';
13
13
  export TableauFullscreen from './theme/Tableau/TableauFullscreen';
14
14
  export ItemMetadataSnippet from './theme/ItemMetadata/ItemMetadataSnippet';
15
15
  export MapPreview from './theme/MetadataListingView/MapPreview';
16
+ export DemoSitesExplorer from './Blocks/DemoSitesExplorer/DemoSitesExplorerView';
@@ -0,0 +1,37 @@
1
+ import express from 'express';
2
+ import { getAPIResourceWithAuth } from '@plone/volto/helpers';
3
+
4
+ const HEADERS = [
5
+ 'Accept-Ranges',
6
+ 'Cache-Control',
7
+ 'Content-Disposition',
8
+ 'Content-Range',
9
+ 'Content-Type',
10
+ ];
11
+
12
+ function viewMiddleware(req, res, next) {
13
+ getAPIResourceWithAuth(req)
14
+ .then((resource) => {
15
+ // Just forward the headers that we need
16
+ HEADERS.forEach((header) => {
17
+ if (resource.get(header)) {
18
+ res.set(header, resource.get(header));
19
+ }
20
+ });
21
+ res.status(resource.statusCode);
22
+ res.send(resource.body);
23
+ })
24
+ .catch(next);
25
+ }
26
+
27
+ export default function middleware(config) {
28
+ const middleware = express.Router();
29
+
30
+ // TODO: do we want catch all?
31
+ // middleware.all(['**/@@*'], viewMiddleware);
32
+
33
+ middleware.all(['**/@@demo-sites-map.arcgis.json'], viewMiddleware);
34
+ middleware.id = 'viewsMiddleware';
35
+ config.settings.expressMiddleware.push(middleware);
36
+ return config;
37
+ }