@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.
- package/CHANGELOG.md +43 -0
- package/jest-addon.config.js +4 -4
- package/package.json +6 -4
- package/src/components/Blocks/DemoSitesExplorer/DemoSitesExplorerEdit.js +5 -0
- package/src/components/Blocks/DemoSitesExplorer/DemoSitesExplorerView.js +94 -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 +378 -0
- package/src/components/Blocks/DemoSitesExplorer/DemoSitesMap.jsx +221 -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 +211 -0
- package/src/components/Blocks/DemoSitesExplorer/utils.test.jsZ +63 -0
- package/src/components/index.js +1 -0
- package/src/express-middleware.js +37 -0
- package/src/index.js +11 -5
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { isValidURL, truncateText } from './utils';
|
|
3
|
+
|
|
4
|
+
export default function FeatureDisplay({ feature }) {
|
|
5
|
+
return feature ? (
|
|
6
|
+
<div id="csepopup">
|
|
7
|
+
<h3>
|
|
8
|
+
<strong>
|
|
9
|
+
<a target="_blank" rel="noopener noreferrer" href={feature.path}>
|
|
10
|
+
{truncateText(feature.title)}
|
|
11
|
+
</a>
|
|
12
|
+
</strong>
|
|
13
|
+
</h3>
|
|
14
|
+
{feature.info ? (
|
|
15
|
+
<div>
|
|
16
|
+
<span className="popup-title blue">Info: </span>
|
|
17
|
+
<span>
|
|
18
|
+
{isValidURL(feature.info) ? (
|
|
19
|
+
<a href={feature.info} target="_blank" rel="noopener noreferrer">
|
|
20
|
+
{feature.info}
|
|
21
|
+
</a>
|
|
22
|
+
) : (
|
|
23
|
+
<span>{feature.info}</span>
|
|
24
|
+
)}
|
|
25
|
+
</span>
|
|
26
|
+
</div>
|
|
27
|
+
) : (
|
|
28
|
+
''
|
|
29
|
+
)}
|
|
30
|
+
|
|
31
|
+
{feature.country ? (
|
|
32
|
+
<div>
|
|
33
|
+
<span className="popup-title blue">Country: </span>
|
|
34
|
+
<span>{feature.country}</span>
|
|
35
|
+
</div>
|
|
36
|
+
) : (
|
|
37
|
+
''
|
|
38
|
+
)}
|
|
39
|
+
{feature.project ? (
|
|
40
|
+
<div>
|
|
41
|
+
<span className="popup-title blue">Project: </span>
|
|
42
|
+
<span>{feature.project}</span>
|
|
43
|
+
</div>
|
|
44
|
+
) : (
|
|
45
|
+
''
|
|
46
|
+
)}
|
|
47
|
+
|
|
48
|
+
{feature.project_link ? (
|
|
49
|
+
<div>
|
|
50
|
+
<span className="popup-title blue">Project link: </span>
|
|
51
|
+
<span>
|
|
52
|
+
{isValidURL(feature.project_link) ? (
|
|
53
|
+
<a
|
|
54
|
+
href={feature.project_link}
|
|
55
|
+
target="_blank"
|
|
56
|
+
rel="noopener noreferrer"
|
|
57
|
+
>
|
|
58
|
+
{feature.project_link}
|
|
59
|
+
</a>
|
|
60
|
+
) : (
|
|
61
|
+
<span>{feature.project_link}</span>
|
|
62
|
+
)}
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
|
+
) : (
|
|
66
|
+
''
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
<div>
|
|
70
|
+
<h4>Indicators</h4>
|
|
71
|
+
<ul>
|
|
72
|
+
{feature.indicators.map((item, index) => {
|
|
73
|
+
return (
|
|
74
|
+
<li key={index}>
|
|
75
|
+
<a
|
|
76
|
+
target="_blank"
|
|
77
|
+
rel="noopener noreferrer"
|
|
78
|
+
href={item['path']}
|
|
79
|
+
>
|
|
80
|
+
{item['title']}
|
|
81
|
+
</a>
|
|
82
|
+
</li>
|
|
83
|
+
);
|
|
84
|
+
})}
|
|
85
|
+
</ul>
|
|
86
|
+
</div>
|
|
87
|
+
{/* <div>
|
|
88
|
+
<h4>Indicators</h4>
|
|
89
|
+
<ul>
|
|
90
|
+
{feature.sectors.map((item, index) => {
|
|
91
|
+
return <li key={index}>{item}</li>;
|
|
92
|
+
})}
|
|
93
|
+
</ul>
|
|
94
|
+
</div> */}
|
|
95
|
+
</div>
|
|
96
|
+
) : null;
|
|
97
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
4
|
+
|
|
5
|
+
import FeatureDisplay from './FeatureDisplay';
|
|
6
|
+
|
|
7
|
+
describe('FeatureDisplay Component', () => {
|
|
8
|
+
it('renders correctly with feature data', () => {
|
|
9
|
+
const feature = {
|
|
10
|
+
title: 'Sample Feature',
|
|
11
|
+
path: 'https://example.com/sample-feature',
|
|
12
|
+
nwrms_implemented: [
|
|
13
|
+
{ path: 'https://example.com/nwrm-1', title: 'NWRM 1' },
|
|
14
|
+
{ path: 'https://example.com/nwrm-2', title: 'NWRM 2' },
|
|
15
|
+
],
|
|
16
|
+
sectors: ['Sector 1', 'Sector 2'],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const { getByText, getByRole } = render(
|
|
20
|
+
<FeatureDisplay feature={feature} />,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const titleLink = getByRole('link', { name: 'Sample Feature' });
|
|
24
|
+
expect(titleLink).toHaveAttribute(
|
|
25
|
+
'href',
|
|
26
|
+
'https://example.com/sample-feature',
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const nwrmLinks =
|
|
30
|
+
getByText('NWRMs implemented').parentElement.querySelectorAll('a');
|
|
31
|
+
expect(nwrmLinks.length).toBe(2);
|
|
32
|
+
expect(nwrmLinks[0]).toHaveAttribute('href', 'https://example.com/nwrm-1');
|
|
33
|
+
expect(nwrmLinks[1]).toHaveAttribute('href', 'https://example.com/nwrm-2');
|
|
34
|
+
expect(nwrmLinks[0]).toHaveTextContent('NWRM 1');
|
|
35
|
+
expect(nwrmLinks[1]).toHaveTextContent('NWRM 2');
|
|
36
|
+
|
|
37
|
+
const sectorItems =
|
|
38
|
+
getByText('Sectors').parentElement.querySelectorAll('li');
|
|
39
|
+
expect(sectorItems.length).toBe(2);
|
|
40
|
+
expect(sectorItems[0]).toHaveTextContent('Sector 1');
|
|
41
|
+
expect(sectorItems[1]).toHaveTextContent('Sector 2');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders nothing when feature is null', () => {
|
|
45
|
+
const { container } = render(<FeatureDisplay feature={null} />);
|
|
46
|
+
expect(container.firstChild).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { openlayers as ol } from '@eeacms/volto-openlayers-map';
|
|
3
|
+
import { useMapContext } from '@eeacms/volto-openlayers-map/api';
|
|
4
|
+
import { zoomMapToFeatures } from './utils';
|
|
5
|
+
|
|
6
|
+
export const useStyles = () => {
|
|
7
|
+
const selected = React.useMemo(
|
|
8
|
+
() =>
|
|
9
|
+
new ol.style.Style({
|
|
10
|
+
image: new ol.style.Circle({
|
|
11
|
+
radius: 12,
|
|
12
|
+
fill: new ol.style.Fill({
|
|
13
|
+
color: '#ccc',
|
|
14
|
+
}),
|
|
15
|
+
stroke: new ol.style.Stroke({
|
|
16
|
+
color: '#fff',
|
|
17
|
+
width: 0,
|
|
18
|
+
}),
|
|
19
|
+
}),
|
|
20
|
+
}),
|
|
21
|
+
[],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const selectStyle = React.useCallback(
|
|
25
|
+
(feature) => {
|
|
26
|
+
// const color = feature.values_.features[0].values_['color'] || '#ccc';
|
|
27
|
+
const color = '#0A99FF'; // #004B7F #309ebc #0A99FF
|
|
28
|
+
// console.log(color);
|
|
29
|
+
selected.image_.getFill().setColor(color);
|
|
30
|
+
return selected;
|
|
31
|
+
},
|
|
32
|
+
[selected],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return { selected, selectStyle };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default function FeatureInteraction({
|
|
39
|
+
onFeatureSelect,
|
|
40
|
+
hideFilters,
|
|
41
|
+
selectedCase,
|
|
42
|
+
}) {
|
|
43
|
+
// console.log('featureinteraction', selectedCase);
|
|
44
|
+
const { map } = useMapContext();
|
|
45
|
+
const { selectStyle } = useStyles();
|
|
46
|
+
|
|
47
|
+
const select = new ol.interaction.Select({
|
|
48
|
+
condition: ol.condition.click,
|
|
49
|
+
style: hideFilters ? null : selectStyle,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
React.useEffect(() => {
|
|
53
|
+
if (!map) return;
|
|
54
|
+
|
|
55
|
+
select.on('select', function (e) {
|
|
56
|
+
const features = e.target.getFeatures().getArray();
|
|
57
|
+
|
|
58
|
+
features.forEach((feature) => {
|
|
59
|
+
const subfeatures = feature.values_.features;
|
|
60
|
+
if (subfeatures.length === 1) {
|
|
61
|
+
const selectedFeature = subfeatures[0].values_;
|
|
62
|
+
if (hideFilters) {
|
|
63
|
+
const url = window.location.origin + selectedFeature.path;
|
|
64
|
+
// window.open(url);
|
|
65
|
+
window.location.href = url;
|
|
66
|
+
}
|
|
67
|
+
onFeatureSelect(selectedFeature);
|
|
68
|
+
// scrollToElement('ol-map-container');
|
|
69
|
+
// map.getView().animate({
|
|
70
|
+
// duration: 10,
|
|
71
|
+
// center: selectedFeature.geometry.flatCoordinates,
|
|
72
|
+
// });
|
|
73
|
+
} else {
|
|
74
|
+
onFeatureSelect(null);
|
|
75
|
+
zoomMapToFeatures(map, subfeatures);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
map.addInteraction(select);
|
|
83
|
+
|
|
84
|
+
map.on('pointermove', (e) => {
|
|
85
|
+
const pixel = map.getEventPixel(e.originalEvent);
|
|
86
|
+
const hit = map.hasFeatureAtPixel(pixel);
|
|
87
|
+
map.getViewport().style.cursor = hit ? 'pointer' : '';
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return () => map.removeInteraction(select);
|
|
91
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
92
|
+
}, [map, selectStyle, onFeatureSelect, hideFilters]);
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useMapContext } from '@eeacms/volto-openlayers-map/api';
|
|
3
|
+
import { openlayers as ol } from '@eeacms/volto-openlayers-map';
|
|
4
|
+
import FeatureDisplay from './FeatureDisplay';
|
|
5
|
+
import { usePrevious } from '@plone/volto/helpers/Utils/usePrevious';
|
|
6
|
+
|
|
7
|
+
export default function InfoOverlay({
|
|
8
|
+
selectedFeature,
|
|
9
|
+
onFeatureSelect,
|
|
10
|
+
layerId,
|
|
11
|
+
hideFilters,
|
|
12
|
+
}) {
|
|
13
|
+
const { map } = useMapContext();
|
|
14
|
+
const [tooltip, setTooltipRef] = React.useState();
|
|
15
|
+
const [showTooltip, setShowTooltip] = React.useState();
|
|
16
|
+
|
|
17
|
+
const prevLayerId = usePrevious(layerId);
|
|
18
|
+
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
if (prevLayerId && layerId !== prevLayerId) {
|
|
21
|
+
setShowTooltip(false);
|
|
22
|
+
}
|
|
23
|
+
}, [layerId, prevLayerId]);
|
|
24
|
+
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
if (!(map && tooltip)) return;
|
|
27
|
+
|
|
28
|
+
const overlay = new ol.Overlay({
|
|
29
|
+
element: document.getElementById('popup-overlay'),
|
|
30
|
+
positioning: 'bottom-center',
|
|
31
|
+
offset: [0, -10],
|
|
32
|
+
stopEvent: false,
|
|
33
|
+
});
|
|
34
|
+
map.addOverlay(overlay);
|
|
35
|
+
|
|
36
|
+
function handler(evt) {
|
|
37
|
+
const { pixel, target } = evt;
|
|
38
|
+
const features = target.getFeaturesAtPixel(pixel);
|
|
39
|
+
// const popupOverlay = overlay.element; // document.getElementById('popup-overlay');
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
features.length &&
|
|
43
|
+
!hideFilters // && !isCluster(features)
|
|
44
|
+
) {
|
|
45
|
+
setShowTooltip(true);
|
|
46
|
+
} else {
|
|
47
|
+
// handle a click in an overlay popup
|
|
48
|
+
if (evt.originalEvent.target.tagName === 'A') return;
|
|
49
|
+
setShowTooltip(false);
|
|
50
|
+
// popupOverlay.style.display = 'none';
|
|
51
|
+
onFeatureSelect(null);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
map.on('click', handler);
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
map.un('click', handler);
|
|
59
|
+
map.removeOverlay(overlay);
|
|
60
|
+
};
|
|
61
|
+
}, [map, tooltip, onFeatureSelect, hideFilters]); //
|
|
62
|
+
|
|
63
|
+
const [isClient, setIsClient] = React.useState(false);
|
|
64
|
+
React.useEffect(() => setIsClient(true), []);
|
|
65
|
+
|
|
66
|
+
return isClient ? (
|
|
67
|
+
<div
|
|
68
|
+
id="popup-overlay"
|
|
69
|
+
style={{
|
|
70
|
+
position: 'absolute',
|
|
71
|
+
zIndex: 1,
|
|
72
|
+
visibility: showTooltip ? 'visible' : 'hidden',
|
|
73
|
+
}}
|
|
74
|
+
ref={setTooltipRef}
|
|
75
|
+
>
|
|
76
|
+
{selectedFeature ? <FeatureDisplay feature={selectedFeature} /> : null}
|
|
77
|
+
</div>
|
|
78
|
+
) : null;
|
|
79
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import superagent from 'superagent';
|
|
3
|
+
|
|
4
|
+
// const cases_url = '@@demo-sites-map.arcgis.json';
|
|
5
|
+
|
|
6
|
+
export function useCases(url) {
|
|
7
|
+
const [cases, setCases] = React.useState([]);
|
|
8
|
+
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
superagent
|
|
11
|
+
.get(url)
|
|
12
|
+
.set('accept', 'json')
|
|
13
|
+
.then((resp) => {
|
|
14
|
+
const res = JSON.parse(resp.text);
|
|
15
|
+
setCases(res.features);
|
|
16
|
+
});
|
|
17
|
+
}, [url]);
|
|
18
|
+
|
|
19
|
+
return cases;
|
|
20
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M37.3757 34.3789L30.2003 27.2246C32.5154 24.2751 33.7716 20.633 33.7669 16.8835C33.7669 13.5442 32.7767 10.28 30.9215 7.50351C29.0663 4.72705 26.4295 2.56305 23.3445 1.28518C20.2594 0.00731515 16.8647 -0.327033 13.5897 0.324418C10.3146 0.97587 7.30625 2.58386 4.94506 4.94506C2.58386 7.30625 0.97587 10.3146 0.324418 13.5897C-0.327033 16.8647 0.00731528 20.2594 1.28518 23.3445C2.56305 26.4295 4.72705 29.0664 7.50351 30.9215C10.28 32.7767 13.5442 33.7669 16.8835 33.7669C20.633 33.7716 24.2751 32.5154 27.2246 30.2003L34.3789 37.3757C34.5751 37.5735 34.8085 37.7306 35.0657 37.8377C35.3229 37.9448 35.5987 38 35.8773 38C36.1559 38 36.4318 37.9448 36.689 37.8377C36.9461 37.7306 37.1795 37.5735 37.3757 37.3757C37.5735 37.1795 37.7305 36.9461 37.8377 36.689C37.9448 36.4318 38 36.1559 38 35.8773C38 35.5987 37.9448 35.3229 37.8377 35.0657C37.7305 34.8085 37.5735 34.5751 37.3757 34.3789ZM4.22087 16.8835C4.22087 14.379 4.96352 11.9309 6.3549 9.8485C7.74628 7.76615 9.72391 6.14315 12.0377 5.18475C14.3515 4.22635 16.8975 3.97559 19.3538 4.46418C21.8101 4.95277 24.0664 6.15876 25.8373 7.92966C27.6081 9.70055 28.8141 11.9568 29.3027 14.4131C29.7913 16.8694 29.5406 19.4154 28.5822 21.7292C27.6238 24.043 26.0008 26.0206 23.9184 27.412C21.8361 28.8034 19.3879 29.546 16.8835 29.546C13.5251 29.546 10.3043 28.2119 7.92966 25.8373C5.55496 23.4626 4.22087 20.2418 4.22087 16.8835Z"/>
|
|
3
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import worldSVG from '@plone/volto/icons/world.svg';
|
|
2
|
+
import DemoSitesExplorerEdit from './DemoSitesExplorerEdit';
|
|
3
|
+
import DemoSitesExplorerView from './DemoSitesExplorerView';
|
|
4
|
+
|
|
5
|
+
export default function installDemoSitesExplorerBlock(config) {
|
|
6
|
+
config.blocks.blocksConfig.demoSitesExplorer = {
|
|
7
|
+
id: 'demoSitesExplorer',
|
|
8
|
+
title: 'Demo Sites Explorer',
|
|
9
|
+
icon: worldSVG,
|
|
10
|
+
group: 'marine_addons',
|
|
11
|
+
edit: DemoSitesExplorerEdit,
|
|
12
|
+
view: DemoSitesExplorerView,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return config;
|
|
16
|
+
}
|