@eeacms/volto-marine-policy 2.0.2 → 2.0.4
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 +65 -1
- package/jest-addon.config.js +4 -4
- package/package.json +9 -8
- package/src/components/Blocks/DemoSitesExplorer/DemoSitesExplorerEdit.js +5 -0
- package/src/components/Blocks/DemoSitesExplorer/DemoSitesExplorerView.js +115 -0
- package/src/components/Blocks/DemoSitesExplorer/DemoSitesFilters.jsx +406 -0
- package/src/components/Blocks/DemoSitesExplorer/DemoSitesFilters.test.jsxZ +91 -0
- package/src/components/Blocks/DemoSitesExplorer/DemoSitesListing.jsx +383 -0
- package/src/components/Blocks/DemoSitesExplorer/DemoSitesMap.jsx +230 -0
- package/src/components/Blocks/DemoSitesExplorer/FeatureDisplay.jsx +97 -0
- package/src/components/Blocks/DemoSitesExplorer/FeatureDisplay.test.jsxZ +48 -0
- package/src/components/Blocks/DemoSitesExplorer/FeatureInteraction.jsx +95 -0
- package/src/components/Blocks/DemoSitesExplorer/InfoOverlay.jsx +79 -0
- package/src/components/Blocks/DemoSitesExplorer/hooks.js +20 -0
- package/src/components/Blocks/DemoSitesExplorer/images/icon-depth.png +0 -0
- package/src/components/Blocks/DemoSitesExplorer/images/icon-light.png +0 -0
- package/src/components/Blocks/DemoSitesExplorer/images/search.svg +3 -0
- package/src/components/Blocks/DemoSitesExplorer/index.js +16 -0
- package/src/components/Blocks/DemoSitesExplorer/mockJsdom.js +8 -0
- package/src/components/Blocks/DemoSitesExplorer/styles.less +376 -0
- package/src/components/Blocks/DemoSitesExplorer/utils.js +193 -0
- package/src/components/Blocks/DemoSitesExplorer/utils.test.jsZ +63 -0
- package/src/components/index.js +1 -0
- package/src/components/theme/DatabaseItemView/DatabaseItemView.jsx +0 -1
- package/src/express-middleware.js +37 -0
- package/src/index.js +13 -12
- package/theme/extras/print.less +64 -0
- package/theme/globals/site.overrides +11 -6
- package/theme/globals/site.variables +1 -0
- package/src/components/Blocks/ContextNavigation/Accordion.jsx +0 -85
- package/src/components/Blocks/ContextNavigation/Accordion.test.jsx +0 -106
- package/src/components/Blocks/ContextNavigation/AccordionContent.jsx +0 -66
- package/src/components/Blocks/ContextNavigation/ContextNavigation.jsx +0 -41
- package/src/components/Blocks/ContextNavigation/Edit.jsx +0 -41
- package/src/components/Blocks/ContextNavigation/Edit.test.jsx +0 -71
- package/src/components/Blocks/ContextNavigation/View.jsx +0 -42
- package/src/components/Blocks/ContextNavigation/View.test.jsx +0 -71
- package/src/components/Blocks/ContextNavigation/index.js +0 -25
- package/src/components/Blocks/ContextNavigation/schema.js +0 -43
- package/src/components/Blocks/ContextNavigation/styles.less +0 -65
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import './mockJsdom';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
4
|
+
import { render } from '@testing-library/react';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
DemoSitesFilters,
|
|
8
|
+
ActiveFilters,
|
|
9
|
+
SearchBox,
|
|
10
|
+
DemoSitesFilter,
|
|
11
|
+
} from './DemoSitesFilters';
|
|
12
|
+
|
|
13
|
+
describe('DemoSitesFilters', () => {
|
|
14
|
+
const mockSetActiveFilters = jest.fn();
|
|
15
|
+
window.URL.createObjectURL = function () {};
|
|
16
|
+
global.URL.createObjectURL = jest.fn();
|
|
17
|
+
|
|
18
|
+
const mockFilters = {
|
|
19
|
+
sectors: { sector1: 'Sector 1', sector2: 'Sector 2' },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
it('renders without crashing', () => {
|
|
23
|
+
const { container } = render(
|
|
24
|
+
<DemoSitesFilters
|
|
25
|
+
filters={mockFilters}
|
|
26
|
+
activeFilters={{ sectors: [] }}
|
|
27
|
+
setActiveFilters={mockSetActiveFilters}
|
|
28
|
+
/>,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(container.querySelector('.filter-wrapper')).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('ActiveFilters', () => {
|
|
36
|
+
const mockSetActiveFilters = jest.fn();
|
|
37
|
+
const mockFilters = {
|
|
38
|
+
sectors: { sector1: 'Sector 1', sector2: 'Sector 2' },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
it('renders without crashing', () => {
|
|
42
|
+
render(
|
|
43
|
+
<ActiveFilters
|
|
44
|
+
filters={mockFilters}
|
|
45
|
+
activeFilters={{ sectors: [] }}
|
|
46
|
+
setActiveFilters={mockSetActiveFilters}
|
|
47
|
+
/>,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('SearchBox', () => {
|
|
53
|
+
const mockSetActiveFilters = jest.fn();
|
|
54
|
+
const mockFilters = {
|
|
55
|
+
sectors: { sector1: 'Sector 1', sector2: 'Sector 2' },
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const mockSetSearchInput = jest.fn();
|
|
59
|
+
const mockSearchInput = 'freshwater';
|
|
60
|
+
|
|
61
|
+
it('renders without crashing', () => {
|
|
62
|
+
render(
|
|
63
|
+
<SearchBox
|
|
64
|
+
filters={mockFilters}
|
|
65
|
+
activeFilters={{ sectors: [] }}
|
|
66
|
+
setActiveFilters={mockSetActiveFilters}
|
|
67
|
+
searchInput={mockSearchInput}
|
|
68
|
+
setSearchInput={mockSetSearchInput}
|
|
69
|
+
/>,
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('DemoSitesFilter', () => {
|
|
75
|
+
const mockSetActiveFilters = jest.fn();
|
|
76
|
+
const mockFilters = {
|
|
77
|
+
sectors: { sector1: 'Sector 1', sector2: 'Sector 2' },
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
it('renders without crashing', () => {
|
|
81
|
+
render(
|
|
82
|
+
<DemoSitesFilter
|
|
83
|
+
filterTitle={'Case study filter'}
|
|
84
|
+
filters={mockFilters}
|
|
85
|
+
activeFilters={{ sectors: [] }}
|
|
86
|
+
setActiveFilters={mockSetActiveFilters}
|
|
87
|
+
filterName={'Filter name'}
|
|
88
|
+
/>,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
centerAndResetMapZoom,
|
|
4
|
+
scrollToElement,
|
|
5
|
+
zoomMapToFeatures,
|
|
6
|
+
isValidURL,
|
|
7
|
+
} from './utils';
|
|
8
|
+
|
|
9
|
+
const showPageNr = (pageNr, currentPage, numberOfPages) => {
|
|
10
|
+
// show first 5 pages
|
|
11
|
+
if (currentPage < 4 && pageNr <= 5) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// show last 5 pages
|
|
16
|
+
if (numberOfPages - currentPage < 4 && numberOfPages - pageNr < 5) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (
|
|
21
|
+
currentPage >= 4 &&
|
|
22
|
+
numberOfPages - currentPage >= 4 &&
|
|
23
|
+
pageNr >= currentPage - 2 &&
|
|
24
|
+
pageNr <= currentPage + 2
|
|
25
|
+
) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default function DemoSitesList(props) {
|
|
33
|
+
const { selectedCase, onSelectedCase, pointsSource, map } = props;
|
|
34
|
+
// const reSearch = new RegExp(`\\b(${searchInput})\\b`, 'gi');
|
|
35
|
+
const [currentPage, setCurrentPage] = React.useState(1);
|
|
36
|
+
|
|
37
|
+
const features = pointsSource
|
|
38
|
+
.getFeatures(selectedCase)
|
|
39
|
+
.sort((item1, item2) =>
|
|
40
|
+
item1.values_.title.localeCompare(item2.values_.title),
|
|
41
|
+
);
|
|
42
|
+
const numberOfPages = Math.ceil(features.length / 10);
|
|
43
|
+
|
|
44
|
+
const displayFatures = features.slice(
|
|
45
|
+
10 * (currentPage - 1),
|
|
46
|
+
10 * currentPage,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return displayFatures.length === 0 ? (
|
|
50
|
+
<>
|
|
51
|
+
<h3 style={{ margin: 'calc(2rem - 0.1em) 0 1rem' }}>
|
|
52
|
+
We could not find any results for your search criteria
|
|
53
|
+
</h3>
|
|
54
|
+
<ul>
|
|
55
|
+
<li>check the selected filters</li>
|
|
56
|
+
</ul>
|
|
57
|
+
</>
|
|
58
|
+
) : (
|
|
59
|
+
<>
|
|
60
|
+
<div className="listing">
|
|
61
|
+
{selectedCase ? (
|
|
62
|
+
<div
|
|
63
|
+
className="content-box u-item listing-item result-item"
|
|
64
|
+
style={{
|
|
65
|
+
marginTop: '2em',
|
|
66
|
+
padding: 'em',
|
|
67
|
+
// border: '3px solid #f2f2f2',
|
|
68
|
+
// borderTop: '1em solid #f2f2f2',
|
|
69
|
+
paddingTop: 0,
|
|
70
|
+
backgroundColor: '#f2f2f2',
|
|
71
|
+
border: 'none',
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
<div className="slot-top">
|
|
75
|
+
<div className="listing-body">
|
|
76
|
+
<h3 className="listing-header">
|
|
77
|
+
<a
|
|
78
|
+
target="_blank"
|
|
79
|
+
rel="noopener noreferrer"
|
|
80
|
+
href={selectedCase.path}
|
|
81
|
+
title={selectedCase.title}
|
|
82
|
+
>
|
|
83
|
+
{selectedCase.title}
|
|
84
|
+
</a>
|
|
85
|
+
</h3>
|
|
86
|
+
<p className="listing-description">
|
|
87
|
+
{selectedCase.description}
|
|
88
|
+
</p>
|
|
89
|
+
<div className="slot-bottom">
|
|
90
|
+
<div className="result-bottom">
|
|
91
|
+
{selectedCase.info ? (
|
|
92
|
+
<div className="result-info">
|
|
93
|
+
<span className="result-info-title">Info: </span>
|
|
94
|
+
<span>
|
|
95
|
+
{isValidURL(selectedCase.info) ? (
|
|
96
|
+
<a
|
|
97
|
+
href={selectedCase.info}
|
|
98
|
+
target="_blank"
|
|
99
|
+
rel="noopener noreferrer"
|
|
100
|
+
>
|
|
101
|
+
{selectedCase.info}
|
|
102
|
+
</a>
|
|
103
|
+
) : (
|
|
104
|
+
<span>{selectedCase.info}</span>
|
|
105
|
+
)}
|
|
106
|
+
</span>
|
|
107
|
+
</div>
|
|
108
|
+
) : (
|
|
109
|
+
''
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{selectedCase.project ? (
|
|
113
|
+
<div className="result-info">
|
|
114
|
+
<span className="result-info-title">Project: </span>
|
|
115
|
+
<span>{selectedCase.project}</span>
|
|
116
|
+
</div>
|
|
117
|
+
) : (
|
|
118
|
+
''
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
{selectedCase.country ? (
|
|
122
|
+
<div className="result-info">
|
|
123
|
+
<span className="result-info-title">Country: </span>
|
|
124
|
+
<span>{selectedCase.country}</span>
|
|
125
|
+
</div>
|
|
126
|
+
) : (
|
|
127
|
+
''
|
|
128
|
+
)}
|
|
129
|
+
|
|
130
|
+
{selectedCase.project_link ? (
|
|
131
|
+
<div className="result-info">
|
|
132
|
+
<span className="result-info-title">
|
|
133
|
+
Project link:{' '}
|
|
134
|
+
</span>
|
|
135
|
+
<span>
|
|
136
|
+
{isValidURL(selectedCase.project_link) ? (
|
|
137
|
+
<a
|
|
138
|
+
href={selectedCase.project_link}
|
|
139
|
+
target="_blank"
|
|
140
|
+
rel="noopener noreferrer"
|
|
141
|
+
>
|
|
142
|
+
{selectedCase.project_link}
|
|
143
|
+
</a>
|
|
144
|
+
) : (
|
|
145
|
+
<span>{selectedCase.project_link}</span>
|
|
146
|
+
)}
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
) : (
|
|
150
|
+
''
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
<div
|
|
154
|
+
className="result-info show-on-map"
|
|
155
|
+
tabIndex="0"
|
|
156
|
+
role="button"
|
|
157
|
+
onKeyDown={() => {}}
|
|
158
|
+
onClick={() => {
|
|
159
|
+
// scroll to the map
|
|
160
|
+
scrollToElement('search-input');
|
|
161
|
+
// reset map zoom
|
|
162
|
+
onSelectedCase(null);
|
|
163
|
+
centerAndResetMapZoom(map);
|
|
164
|
+
map.getInteractions().array_[9].getFeatures().clear();
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
<span className="result-info-title">Reset map</span>
|
|
168
|
+
<i className="icon ri-map-2-line"></i>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
) : (
|
|
176
|
+
displayFatures.map((item, index) => {
|
|
177
|
+
return (
|
|
178
|
+
<div className="u-item listing-item result-item" key={index}>
|
|
179
|
+
<div className="slot-top">
|
|
180
|
+
<div className="listing-body">
|
|
181
|
+
<h3 className="listing-header">
|
|
182
|
+
<a
|
|
183
|
+
target="_blank"
|
|
184
|
+
rel="noopener noreferrer"
|
|
185
|
+
href={item.values_.path}
|
|
186
|
+
title={item.values_.title}
|
|
187
|
+
>
|
|
188
|
+
{item.values_.title}
|
|
189
|
+
</a>
|
|
190
|
+
</h3>
|
|
191
|
+
{/* <p
|
|
192
|
+
className="listing-description"
|
|
193
|
+
dangerouslySetInnerHTML={{
|
|
194
|
+
__html: searchInput
|
|
195
|
+
? item.values_.description.replaceAll(
|
|
196
|
+
reSearch,
|
|
197
|
+
'<b>$1</b>',
|
|
198
|
+
)
|
|
199
|
+
: item.values_.description,
|
|
200
|
+
}}
|
|
201
|
+
></p> */}
|
|
202
|
+
<div className="slot-bottom">
|
|
203
|
+
<div className="result-bottom">
|
|
204
|
+
{item.values_.info ? (
|
|
205
|
+
<div className="result-info">
|
|
206
|
+
<span className="result-info-title">Info: </span>
|
|
207
|
+
<span>
|
|
208
|
+
{isValidURL(item.values_.info) ? (
|
|
209
|
+
<a
|
|
210
|
+
href={item.values_.info}
|
|
211
|
+
target="_blank"
|
|
212
|
+
rel="noopener noreferrer"
|
|
213
|
+
>
|
|
214
|
+
{item.values_.info}
|
|
215
|
+
</a>
|
|
216
|
+
) : (
|
|
217
|
+
<span>{item.values_.info}</span>
|
|
218
|
+
)}
|
|
219
|
+
</span>
|
|
220
|
+
</div>
|
|
221
|
+
) : (
|
|
222
|
+
''
|
|
223
|
+
)}
|
|
224
|
+
|
|
225
|
+
{item.values_.project ? (
|
|
226
|
+
<div className="result-info">
|
|
227
|
+
<span className="result-info-title">Project: </span>
|
|
228
|
+
<span>{item.values_.project}</span>
|
|
229
|
+
</div>
|
|
230
|
+
) : (
|
|
231
|
+
''
|
|
232
|
+
)}
|
|
233
|
+
|
|
234
|
+
{item.values_.country ? (
|
|
235
|
+
<div className="result-info">
|
|
236
|
+
<span className="result-info-title">Country: </span>
|
|
237
|
+
<span>{item.values_.country}</span>
|
|
238
|
+
</div>
|
|
239
|
+
) : (
|
|
240
|
+
''
|
|
241
|
+
)}
|
|
242
|
+
|
|
243
|
+
{item.values_.project_link ? (
|
|
244
|
+
<div className="result-info">
|
|
245
|
+
<span className="result-info-title">
|
|
246
|
+
Project link:{' '}
|
|
247
|
+
</span>
|
|
248
|
+
<span>
|
|
249
|
+
{isValidURL(item.values_.project_link) ? (
|
|
250
|
+
<a
|
|
251
|
+
href={item.values_.project_link}
|
|
252
|
+
target="_blank"
|
|
253
|
+
rel="noopener noreferrer"
|
|
254
|
+
>
|
|
255
|
+
{item.values_.project_link}
|
|
256
|
+
</a>
|
|
257
|
+
) : (
|
|
258
|
+
<span>{item.values_.project_link}</span>
|
|
259
|
+
)}
|
|
260
|
+
</span>
|
|
261
|
+
</div>
|
|
262
|
+
) : (
|
|
263
|
+
''
|
|
264
|
+
)}
|
|
265
|
+
|
|
266
|
+
<div
|
|
267
|
+
className="result-info show-on-map"
|
|
268
|
+
tabIndex="0"
|
|
269
|
+
role="button"
|
|
270
|
+
onKeyDown={() => {}}
|
|
271
|
+
onClick={() => {
|
|
272
|
+
map
|
|
273
|
+
.getInteractions()
|
|
274
|
+
.array_[9].getFeatures()
|
|
275
|
+
.clear();
|
|
276
|
+
// scroll to the map
|
|
277
|
+
scrollToElement('ol-map-container');
|
|
278
|
+
|
|
279
|
+
zoomMapToFeatures(map, [item], 5000);
|
|
280
|
+
onSelectedCase(item.values_);
|
|
281
|
+
|
|
282
|
+
const popupOverlay =
|
|
283
|
+
document.getElementById('popup-overlay');
|
|
284
|
+
popupOverlay.style.visibility = 'visible';
|
|
285
|
+
|
|
286
|
+
setTimeout(() => {
|
|
287
|
+
const coords =
|
|
288
|
+
item.values_.geometry.flatCoordinates;
|
|
289
|
+
const pixel = map.getPixelFromCoordinate(coords);
|
|
290
|
+
map
|
|
291
|
+
.getInteractions()
|
|
292
|
+
.array_[9].getFeatures()
|
|
293
|
+
.push(map.getFeaturesAtPixel(pixel)[0]);
|
|
294
|
+
}, 1100);
|
|
295
|
+
}}
|
|
296
|
+
>
|
|
297
|
+
<span className="result-info-title">Show on map</span>
|
|
298
|
+
<i className="icon ri-road-map-line"></i>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
);
|
|
306
|
+
})
|
|
307
|
+
)}
|
|
308
|
+
</div>
|
|
309
|
+
{!selectedCase ? (
|
|
310
|
+
<div className="search-body-footer">
|
|
311
|
+
<div className="ui centered grid">
|
|
312
|
+
<div className="center aligned column">
|
|
313
|
+
<div className="prev-next-paging">
|
|
314
|
+
<div className="paging-wrapper">
|
|
315
|
+
{currentPage !== 1 ? (
|
|
316
|
+
<button
|
|
317
|
+
className="ui button prev double-angle"
|
|
318
|
+
onClick={() => {
|
|
319
|
+
setCurrentPage(1);
|
|
320
|
+
}}
|
|
321
|
+
></button>
|
|
322
|
+
) : (
|
|
323
|
+
''
|
|
324
|
+
)}
|
|
325
|
+
{currentPage !== 1 ? (
|
|
326
|
+
<button
|
|
327
|
+
className="ui button prev single-angle"
|
|
328
|
+
onClick={() => {
|
|
329
|
+
setCurrentPage(currentPage - 1);
|
|
330
|
+
}}
|
|
331
|
+
></button>
|
|
332
|
+
) : (
|
|
333
|
+
''
|
|
334
|
+
)}
|
|
335
|
+
{Array.from(Array(numberOfPages).keys()).map((index) => {
|
|
336
|
+
const pageNr = index + 1;
|
|
337
|
+
return showPageNr(pageNr, currentPage, numberOfPages) ? (
|
|
338
|
+
<button
|
|
339
|
+
className={
|
|
340
|
+
'ui button pagination-item' +
|
|
341
|
+
(currentPage === pageNr ? ' active' : '')
|
|
342
|
+
}
|
|
343
|
+
onClick={() => {
|
|
344
|
+
setCurrentPage(pageNr);
|
|
345
|
+
}}
|
|
346
|
+
>
|
|
347
|
+
{pageNr}
|
|
348
|
+
</button>
|
|
349
|
+
) : (
|
|
350
|
+
''
|
|
351
|
+
);
|
|
352
|
+
})}
|
|
353
|
+
{currentPage !== numberOfPages ? (
|
|
354
|
+
<button
|
|
355
|
+
className="ui button next single-angle"
|
|
356
|
+
onClick={() => {
|
|
357
|
+
setCurrentPage(currentPage + 1);
|
|
358
|
+
}}
|
|
359
|
+
></button>
|
|
360
|
+
) : (
|
|
361
|
+
''
|
|
362
|
+
)}
|
|
363
|
+
{currentPage !== numberOfPages ? (
|
|
364
|
+
<button
|
|
365
|
+
className="ui button next double-angle"
|
|
366
|
+
onClick={() => {
|
|
367
|
+
setCurrentPage(numberOfPages);
|
|
368
|
+
}}
|
|
369
|
+
></button>
|
|
370
|
+
) : (
|
|
371
|
+
''
|
|
372
|
+
)}{' '}
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
) : (
|
|
379
|
+
''
|
|
380
|
+
)}
|
|
381
|
+
</>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import cx from 'classnames';
|
|
4
|
+
|
|
5
|
+
import { Map, Layer, Layers, Controls } from '@eeacms/volto-openlayers-map/api';
|
|
6
|
+
import { openlayers as ol } from '@eeacms/volto-openlayers-map';
|
|
7
|
+
|
|
8
|
+
import InfoOverlay from './InfoOverlay';
|
|
9
|
+
import FeatureInteraction from './FeatureInteraction';
|
|
10
|
+
import DemoSitesList from './DemoSitesListing';
|
|
11
|
+
import { useMapContext } from '@eeacms/volto-openlayers-map/api';
|
|
12
|
+
|
|
13
|
+
import { centerAndResetMapZoom, getFeatures, scrollToElement } from './utils';
|
|
14
|
+
|
|
15
|
+
const styleCache = {};
|
|
16
|
+
const MapContextGateway = ({ setMap }) => {
|
|
17
|
+
const { map } = useMapContext();
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
setMap(map);
|
|
20
|
+
}, [map, setMap]);
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default function DemoSitesMap(props) {
|
|
25
|
+
const {
|
|
26
|
+
items,
|
|
27
|
+
activeItems,
|
|
28
|
+
hideFilters,
|
|
29
|
+
selectedCase,
|
|
30
|
+
onSelectedCase,
|
|
31
|
+
searchInput,
|
|
32
|
+
map,
|
|
33
|
+
setMap,
|
|
34
|
+
} = props;
|
|
35
|
+
const features = getFeatures(items);
|
|
36
|
+
const [resetMapButtonClass, setResetMapButtonClass] =
|
|
37
|
+
React.useState('inactive');
|
|
38
|
+
|
|
39
|
+
const [tileWMSSources] = React.useState([
|
|
40
|
+
new ol.source.TileWMS({
|
|
41
|
+
url: 'https://gisco-services.ec.europa.eu/maps/service',
|
|
42
|
+
params: {
|
|
43
|
+
// LAYERS: 'OSMBlossomComposite', OSMCartoComposite, OSMPositronComposite
|
|
44
|
+
LAYERS: 'OSMPositronComposite',
|
|
45
|
+
TILED: true,
|
|
46
|
+
},
|
|
47
|
+
serverType: 'geoserver',
|
|
48
|
+
transition: 0,
|
|
49
|
+
}),
|
|
50
|
+
]);
|
|
51
|
+
const [pointsSource] = React.useState(
|
|
52
|
+
new ol.source.Vector({
|
|
53
|
+
features,
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const [clusterSource] = React.useState(
|
|
58
|
+
new ol.source.Cluster({
|
|
59
|
+
distance: 19,
|
|
60
|
+
source: pointsSource,
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
if (activeItems) {
|
|
66
|
+
pointsSource.clear();
|
|
67
|
+
pointsSource.addFeatures(getFeatures(activeItems));
|
|
68
|
+
}
|
|
69
|
+
}, [activeItems, pointsSource]);
|
|
70
|
+
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
if (!map) return null;
|
|
73
|
+
|
|
74
|
+
const moveendListener = (e) => {
|
|
75
|
+
// console.log('map.getView()', map.getView());
|
|
76
|
+
// console.log('selectedCase', selectedCase);
|
|
77
|
+
const mapZoom = Math.round(map.getView().getZoom() * 10) / 10;
|
|
78
|
+
const mapCenter = map.getView().getCenter();
|
|
79
|
+
|
|
80
|
+
if (selectedCase) {
|
|
81
|
+
const coords = selectedCase.geometry.flatCoordinates;
|
|
82
|
+
const pixel = map.getPixelFromCoordinate(coords);
|
|
83
|
+
map.getInteractions().array_[9].getFeatures().clear();
|
|
84
|
+
map
|
|
85
|
+
.getInteractions()
|
|
86
|
+
.array_[9].getFeatures()
|
|
87
|
+
.push(map.getFeaturesAtPixel(pixel)[0]);
|
|
88
|
+
} else {
|
|
89
|
+
map.getInteractions().array_[9].getFeatures().clear();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (
|
|
93
|
+
mapZoom === 4 &&
|
|
94
|
+
JSON.stringify(mapCenter) ===
|
|
95
|
+
JSON.stringify(ol.proj.transform([10, 49], 'EPSG:4326', 'EPSG:3857'))
|
|
96
|
+
) {
|
|
97
|
+
setResetMapButtonClass('inactive');
|
|
98
|
+
} else {
|
|
99
|
+
setResetMapButtonClass('active');
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
map.on('moveend', moveendListener);
|
|
104
|
+
|
|
105
|
+
return () => {
|
|
106
|
+
map.un('moveend', moveendListener);
|
|
107
|
+
};
|
|
108
|
+
}, [map, selectedCase, resetMapButtonClass, setResetMapButtonClass]);
|
|
109
|
+
|
|
110
|
+
const clusterStyle = React.useMemo(
|
|
111
|
+
() => selectedClusterStyle(selectedCase),
|
|
112
|
+
[selectedCase],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const MapWithSelection = React.useMemo(() => Map, []);
|
|
116
|
+
// console.log('render');
|
|
117
|
+
|
|
118
|
+
return features.length > 0 ? (
|
|
119
|
+
<div id="ol-map-container">
|
|
120
|
+
<MapWithSelection
|
|
121
|
+
view={{
|
|
122
|
+
center: ol.proj.fromLonLat([10, 54]),
|
|
123
|
+
showFullExtent: true,
|
|
124
|
+
zoom: 2.5,
|
|
125
|
+
}}
|
|
126
|
+
pixelRatio={1}
|
|
127
|
+
// controls={ol.control.defaults({ attribution: false })}
|
|
128
|
+
>
|
|
129
|
+
<Controls attribution={false} />
|
|
130
|
+
<Layers>
|
|
131
|
+
{hideFilters ? null : (
|
|
132
|
+
<button
|
|
133
|
+
className={cx(
|
|
134
|
+
'reset-map-button ui button secondary',
|
|
135
|
+
String(resetMapButtonClass),
|
|
136
|
+
)}
|
|
137
|
+
onClick={() => {
|
|
138
|
+
scrollToElement('search-input');
|
|
139
|
+
onSelectedCase(null);
|
|
140
|
+
centerAndResetMapZoom(map);
|
|
141
|
+
map.getInteractions().array_[9].getFeatures().clear();
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
<span className="result-info-title">Reset map</span>
|
|
145
|
+
<i className="icon ri-map-2-line"></i>
|
|
146
|
+
</button>
|
|
147
|
+
)}
|
|
148
|
+
<InfoOverlay
|
|
149
|
+
selectedFeature={selectedCase}
|
|
150
|
+
onFeatureSelect={onSelectedCase}
|
|
151
|
+
layerId={tileWMSSources[0]}
|
|
152
|
+
hideFilters={hideFilters}
|
|
153
|
+
/>
|
|
154
|
+
<FeatureInteraction
|
|
155
|
+
onFeatureSelect={onSelectedCase}
|
|
156
|
+
hideFilters={hideFilters}
|
|
157
|
+
selectedCase={selectedCase}
|
|
158
|
+
/>
|
|
159
|
+
<Layer.Tile source={tileWMSSources[0]} zIndex={0} />
|
|
160
|
+
<Layer.Vector
|
|
161
|
+
style={clusterStyle}
|
|
162
|
+
source={clusterSource}
|
|
163
|
+
zIndex={1}
|
|
164
|
+
/>
|
|
165
|
+
<MapContextGateway setMap={setMap} />
|
|
166
|
+
</Layers>
|
|
167
|
+
</MapWithSelection>
|
|
168
|
+
{hideFilters ? null : (
|
|
169
|
+
<DemoSitesList
|
|
170
|
+
map={map}
|
|
171
|
+
activeItems={activeItems}
|
|
172
|
+
selectedCase={selectedCase}
|
|
173
|
+
onSelectedCase={onSelectedCase}
|
|
174
|
+
pointsSource={pointsSource}
|
|
175
|
+
searchInput={searchInput}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
) : null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const selectedClusterStyle = (selectedFeature) => {
|
|
183
|
+
function _clusterStyle(feature) {
|
|
184
|
+
const size = feature.get('features').length;
|
|
185
|
+
let style = styleCache[size];
|
|
186
|
+
|
|
187
|
+
if (!style) {
|
|
188
|
+
style = new ol.style.Style({
|
|
189
|
+
image: new ol.style.Circle({
|
|
190
|
+
radius: 12 + Math.min(Math.floor(size / 3), 10),
|
|
191
|
+
stroke: new ol.style.Stroke({
|
|
192
|
+
color: '#fff',
|
|
193
|
+
}),
|
|
194
|
+
fill: new ol.style.Fill({
|
|
195
|
+
// 309ebc blue 3 + green 3 mix
|
|
196
|
+
color: '#309ebc', // #006BB8 #309ebc
|
|
197
|
+
}),
|
|
198
|
+
}),
|
|
199
|
+
text: new ol.style.Text({
|
|
200
|
+
text: size.toString(),
|
|
201
|
+
fill: new ol.style.Fill({
|
|
202
|
+
color: '#fff',
|
|
203
|
+
}),
|
|
204
|
+
}),
|
|
205
|
+
});
|
|
206
|
+
styleCache[size] = style;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (size === 1) {
|
|
210
|
+
// let color = feature.values_.features[0].values_['color'];
|
|
211
|
+
let color = '#50B0A4'; // #0083E0 #50B0A4
|
|
212
|
+
|
|
213
|
+
return new ol.style.Style({
|
|
214
|
+
image: new ol.style.Circle({
|
|
215
|
+
radius: 6,
|
|
216
|
+
fill: new ol.style.Fill({
|
|
217
|
+
color: '#fff',
|
|
218
|
+
}),
|
|
219
|
+
stroke: new ol.style.Stroke({
|
|
220
|
+
color: color,
|
|
221
|
+
width: 6,
|
|
222
|
+
}),
|
|
223
|
+
}),
|
|
224
|
+
});
|
|
225
|
+
} else {
|
|
226
|
+
return style;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return _clusterStyle;
|
|
230
|
+
};
|