@eeacms/volto-bise-policy 1.3.11 → 1.3.13

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 (39) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/package.json +2 -1
  3. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyCard.jsx +404 -0
  4. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyCard.test.jsx +366 -0
  5. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.jsx +192 -91
  6. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.test.jsx +205 -58
  7. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMap.jsx +329 -178
  8. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMap.test.jsx +243 -123
  9. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMapLazy.jsx +61 -0
  10. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMapLazy.test.jsx +89 -0
  11. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyResults.jsx +128 -0
  12. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyResults.test.jsx +160 -0
  13. package/src/components/manage/Blocks/CaseStudyExplorer/FacetPopover.jsx +180 -0
  14. package/src/components/manage/Blocks/CaseStudyExplorer/FacetPopover.test.jsx +125 -0
  15. package/src/components/manage/Blocks/CaseStudyExplorer/FilterPanel.jsx +171 -0
  16. package/src/components/manage/Blocks/CaseStudyExplorer/FilterPanel.test.jsx +181 -0
  17. package/src/components/manage/Blocks/CaseStudyExplorer/Pagination.jsx +81 -0
  18. package/src/components/manage/Blocks/CaseStudyExplorer/filters.js +350 -0
  19. package/src/components/manage/Blocks/CaseStudyExplorer/filters.test.js +399 -0
  20. package/src/components/manage/Blocks/CaseStudyExplorer/hooks.js +28 -4
  21. package/src/components/manage/Blocks/CaseStudyExplorer/hooks.test.js +73 -0
  22. package/src/components/manage/Blocks/CaseStudyExplorer/messages.js +138 -0
  23. package/src/components/manage/Blocks/CaseStudyExplorer/styles.less +1157 -317
  24. package/src/components/manage/Blocks/CaseStudyExplorer/testUtils.js +44 -46
  25. package/src/components/manage/Blocks/CaseStudyExplorer/utils.js +68 -184
  26. package/src/components/manage/Blocks/CaseStudyExplorer/utils.test.js +91 -158
  27. package/src/index.js +39 -0
  28. package/src/openlayers.js +51 -0
  29. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyFilters.jsx +0 -383
  30. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyFilters.test.jsx +0 -90
  31. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyListing.jsx +0 -342
  32. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyListing.test.jsx +0 -196
  33. package/src/components/manage/Blocks/CaseStudyExplorer/FeatureDisplay.jsx +0 -24
  34. package/src/components/manage/Blocks/CaseStudyExplorer/FeatureDisplay.test.jsx +0 -49
  35. package/src/components/manage/Blocks/CaseStudyExplorer/FeatureInteraction.jsx +0 -100
  36. package/src/components/manage/Blocks/CaseStudyExplorer/FeatureInteraction.test.jsx +0 -189
  37. package/src/components/manage/Blocks/CaseStudyExplorer/InfoOverlay.jsx +0 -111
  38. package/src/components/manage/Blocks/CaseStudyExplorer/InfoOverlay.test.jsx +0 -159
  39. package/src/components/manage/Blocks/CaseStudyExplorer/styles.less_old +0 -201
@@ -1,100 +0,0 @@
1
- import React from 'react';
2
- import { withOpenLayers } from '@eeacms/volto-openlayers-map';
3
- import { useMapContext } from '@eeacms/volto-openlayers-map/api';
4
- import { scrollToElement, zoomMapToFeatures } from './utils';
5
-
6
- export const useStyles = ({ ol }) => {
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
- [ol.style],
22
- );
23
-
24
- const selectStyle = React.useCallback(() => {
25
- selected.image_.getFill().setColor('#0A99FF');
26
- return selected;
27
- }, [selected]);
28
-
29
- return { selected, selectStyle };
30
- };
31
-
32
- function FeatureInteraction({ onFeatureSelect, hideFilters, ol }) {
33
- const { map } = useMapContext();
34
- const { selectStyle } = useStyles({ ol });
35
-
36
- // Keep callbacks current without recreating the OpenLayers interaction.
37
- const onFeatureSelectRef = React.useRef(onFeatureSelect);
38
- onFeatureSelectRef.current = onFeatureSelect;
39
- const hideFiltersRef = React.useRef(hideFilters);
40
- hideFiltersRef.current = hideFilters;
41
- const olRef = React.useRef(ol);
42
- olRef.current = ol;
43
-
44
- // Select stores the selected rendered cluster feature and applies its style
45
- // directly to that feature. Recreating it after selectedCase changes clears
46
- // that collection and restores the feature's original style.
47
- const selectRef = React.useRef(null);
48
- if (!selectRef.current) {
49
- selectRef.current = new ol.interaction.Select({
50
- condition: ol.condition.click,
51
- style: hideFilters ? null : selectStyle,
52
- });
53
- }
54
- const select = selectRef.current;
55
-
56
- React.useEffect(() => {
57
- if (!map) return;
58
-
59
- const onSelect = (e) => {
60
- const features = e.target.getFeatures().getArray();
61
-
62
- features.forEach((feature) => {
63
- const subfeatures = feature.values_.features;
64
- if (subfeatures.length === 1) {
65
- const selectedFeature = subfeatures[0].values_;
66
- if (hideFiltersRef.current) {
67
- const url = window.location.origin + selectedFeature.path;
68
- // window.open(url);
69
- window.location.href = url;
70
- }
71
- onFeatureSelectRef.current(selectedFeature);
72
- scrollToElement('ol-map-container');
73
- } else {
74
- onFeatureSelectRef.current(null);
75
- zoomMapToFeatures({ map, features: subfeatures, ol: olRef.current });
76
- }
77
- });
78
- };
79
-
80
- select.on('select', onSelect);
81
- map.addInteraction(select);
82
-
83
- const onPointerMove = (e) => {
84
- const pixel = map.getEventPixel(e.originalEvent);
85
- const hit = map.hasFeatureAtPixel(pixel);
86
- map.getViewport().style.cursor = hit ? 'pointer' : '';
87
- };
88
- map.on('pointermove', onPointerMove);
89
-
90
- return () => {
91
- select.un('select', onSelect);
92
- map.un('pointermove', onPointerMove);
93
- map.removeInteraction(select);
94
- };
95
- }, [map, select]);
96
-
97
- return null;
98
- }
99
-
100
- export default withOpenLayers(FeatureInteraction);
@@ -1,189 +0,0 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
- import FeatureInteraction from './FeatureInteraction';
4
- import { scrollToElement, zoomMapToFeatures } from './utils';
5
- import { createMockOl } from './testUtils';
6
-
7
- jest.mock('@eeacms/volto-openlayers-map', () => ({
8
- withOpenLayers: (Comp) => Comp,
9
- }));
10
-
11
- const addInteraction = jest.fn();
12
- const removeInteraction = jest.fn();
13
- const onMap = jest.fn();
14
- const getEventPixel = jest.fn(() => [10, 20]);
15
- const hasFeatureAtPixel = jest.fn(() => true);
16
- const getViewport = jest.fn(() => ({ style: {} }));
17
-
18
- jest.mock('@eeacms/volto-openlayers-map/api', () => ({
19
- useMapContext: () => ({
20
- map: mockMap,
21
- }),
22
- }));
23
-
24
- jest.mock('./utils', () => ({
25
- scrollToElement: jest.fn(),
26
- zoomMapToFeatures: jest.fn(),
27
- }));
28
-
29
- let mockSelectCb;
30
- const mockOl = createMockOl({
31
- interaction: {
32
- Select: jest.fn(() => ({
33
- on: jest.fn((evt, cb) => {
34
- if (evt === 'select') {
35
- mockSelectCb = cb;
36
- }
37
- }),
38
- un: jest.fn(),
39
- })),
40
- },
41
- });
42
-
43
- const mockMap = {
44
- addInteraction,
45
- removeInteraction,
46
- on: onMap,
47
- un: jest.fn(),
48
- getEventPixel,
49
- hasFeatureAtPixel,
50
- getViewport,
51
- };
52
-
53
- function triggerSelectEvent(subfeatures) {
54
- const feature = { values_: { features: subfeatures } };
55
- const mockE = {
56
- target: {
57
- getFeatures: () => ({
58
- getArray: () => [feature],
59
- }),
60
- },
61
- };
62
- mockSelectCb(mockE);
63
- }
64
-
65
- describe('FeatureInteraction', () => {
66
- beforeEach(() => {
67
- jest.clearAllMocks();
68
- mockSelectCb = null;
69
- });
70
-
71
- it('configures the selection style when filters are visible', () => {
72
- render(
73
- <FeatureInteraction
74
- onFeatureSelect={jest.fn()}
75
- hideFilters={false}
76
- selectedCase={null}
77
- ol={mockOl}
78
- />,
79
- );
80
-
81
- const options = mockOl.interaction.Select.mock.calls[0][0];
82
- expect(options.style).toEqual(expect.any(Function));
83
- });
84
-
85
- it('keeps one Select interaction across rerenders', () => {
86
- const { rerender } = render(
87
- <FeatureInteraction
88
- onFeatureSelect={jest.fn()}
89
- hideFilters={false}
90
- selectedCase={null}
91
- ol={mockOl}
92
- />,
93
- );
94
-
95
- rerender(
96
- <FeatureInteraction
97
- onFeatureSelect={jest.fn()}
98
- hideFilters={false}
99
- selectedCase={{ path: '/selected' }}
100
- ol={{ ...mockOl }}
101
- />,
102
- );
103
-
104
- expect(mockOl.interaction.Select).toHaveBeenCalledTimes(1);
105
- expect(addInteraction).toHaveBeenCalledTimes(1);
106
- });
107
-
108
- it('selects a single feature and calls onFeatureSelect + scrollToElement (hideFilters=false)', () => {
109
- const onFeatureSelect = jest.fn();
110
- const singleFeature = {
111
- values_: { path: '/path1', geometry: {}, foo: 'bar' },
112
- };
113
-
114
- render(
115
- <FeatureInteraction
116
- onFeatureSelect={onFeatureSelect}
117
- hideFilters={false}
118
- selectedCase={null}
119
- ol={mockOl}
120
- />,
121
- );
122
-
123
- triggerSelectEvent([singleFeature]);
124
-
125
- expect(onFeatureSelect).toHaveBeenCalledWith(singleFeature.values_);
126
- expect(scrollToElement).toHaveBeenCalledWith('ol-map-container');
127
- });
128
-
129
- it('selects a single feature and changes window.location when hideFilters=true', () => {
130
- const onFeatureSelect = jest.fn();
131
- const singleFeature = { values_: { path: '/go-here', geometry: {} } };
132
- delete window.location;
133
- window.location = { href: '' };
134
-
135
- render(
136
- <FeatureInteraction
137
- onFeatureSelect={onFeatureSelect}
138
- hideFilters={true}
139
- selectedCase={null}
140
- ol={mockOl}
141
- />,
142
- );
143
-
144
- triggerSelectEvent([singleFeature]);
145
-
146
- expect(window.location.href).toContain('/go-here');
147
- });
148
-
149
- it('selects multiple features and calls zoomMapToFeatures', () => {
150
- const onFeatureSelect = jest.fn();
151
- const subfeatures = [
152
- { values_: { path: '/f1' } },
153
- { values_: { path: '/f2' } },
154
- ];
155
-
156
- render(
157
- <FeatureInteraction
158
- onFeatureSelect={onFeatureSelect}
159
- hideFilters={false}
160
- selectedCase={null}
161
- ol={mockOl}
162
- />,
163
- );
164
-
165
- triggerSelectEvent(subfeatures);
166
-
167
- expect(onFeatureSelect).toHaveBeenCalledWith(null);
168
- expect(zoomMapToFeatures).toHaveBeenCalledWith({
169
- map: mockMap,
170
- features: subfeatures,
171
- ol: mockOl,
172
- });
173
- });
174
-
175
- it('adds and removes interaction on mount/unmount', () => {
176
- const { unmount } = render(
177
- <FeatureInteraction
178
- onFeatureSelect={jest.fn()}
179
- hideFilters={false}
180
- selectedCase={null}
181
- ol={mockOl}
182
- />,
183
- );
184
-
185
- expect(addInteraction).toHaveBeenCalled();
186
- unmount();
187
- expect(removeInteraction).toHaveBeenCalled();
188
- });
189
- });
@@ -1,111 +0,0 @@
1
- import React from 'react';
2
- import { useMapContext } from '@eeacms/volto-openlayers-map/api';
3
- import { withOpenLayers } from '@eeacms/volto-openlayers-map';
4
- import FeatureDisplay from './FeatureDisplay';
5
- import { usePrevious } from '@plone/volto/helpers/Utils/usePrevious';
6
-
7
- function InfoOverlay({
8
- selectedFeature,
9
- onFeatureSelect,
10
- layerId,
11
- hideFilters,
12
- ol,
13
- }) {
14
- const { map } = useMapContext();
15
- const [tooltip, setTooltipRef] = React.useState();
16
- const [showTooltip, setShowTooltip] = React.useState();
17
- const overlayRef = React.useRef(null);
18
-
19
- // `ol` is a fresh object literal on every render (see withOpenLayers) and
20
- // `onFeatureSelect`/`hideFilters` can change identity; keep them in refs so
21
- // the overlay and its map handler are created only once per mount.
22
- const olRef = React.useRef(ol);
23
- olRef.current = ol;
24
- const onFeatureSelectRef = React.useRef(onFeatureSelect);
25
- onFeatureSelectRef.current = onFeatureSelect;
26
- const hideFiltersRef = React.useRef(hideFilters);
27
- hideFiltersRef.current = hideFilters;
28
-
29
- const prevLayerId = usePrevious(layerId);
30
-
31
- React.useEffect(() => {
32
- if (prevLayerId && layerId !== prevLayerId) {
33
- setShowTooltip(false);
34
- }
35
- }, [layerId, prevLayerId]);
36
-
37
- React.useEffect(() => {
38
- if (!(map && tooltip)) return;
39
-
40
- const overlay = new olRef.current.Overlay({
41
- element: tooltip,
42
- positioning: 'bottom-center',
43
- offset: [0, -10],
44
- stopEvent: false,
45
- });
46
- overlayRef.current = overlay;
47
- map.addOverlay(overlay);
48
-
49
- function handler(evt) {
50
- const { pixel, target } = evt;
51
- const features = target.getFeaturesAtPixel(pixel);
52
- const popupOverlay = overlay.element; // document.getElementById('popup-overlay');
53
-
54
- if (
55
- features.length &&
56
- !hideFiltersRef.current // && !isCluster(features)
57
- ) {
58
- setShowTooltip(true);
59
- } else {
60
- // handle a click in an overlay popup
61
- if (evt.originalEvent.target.tagName === 'A') return;
62
- setShowTooltip(false);
63
- popupOverlay.style.display = 'none';
64
- onFeatureSelectRef.current(null);
65
- }
66
- }
67
-
68
- map.on('click', handler);
69
-
70
- return () => {
71
- map.un('click', handler);
72
- map.removeOverlay(overlay);
73
- overlayRef.current = null;
74
- };
75
- }, [map, tooltip]);
76
-
77
- // Anchor the popup to the selected case study and show it above the marker.
78
- // Selecting a cluster (null feature) or clicking the map clears the popup.
79
- React.useEffect(() => {
80
- const overlay = overlayRef.current;
81
- if (!overlay) return;
82
-
83
- const coordinates = selectedFeature?.geometry?.flatCoordinates;
84
- if (coordinates) {
85
- overlay.setPosition(coordinates);
86
- setShowTooltip(true);
87
- } else {
88
- overlay.setPosition(undefined);
89
- setShowTooltip(false);
90
- }
91
- }, [selectedFeature, tooltip]);
92
-
93
- const [isClient, setIsClient] = React.useState(false);
94
- React.useEffect(() => setIsClient(true), []);
95
-
96
- return isClient ? (
97
- <div
98
- id="popup-overlay"
99
- style={{
100
- position: 'absolute',
101
- zIndex: 1,
102
- visibility: showTooltip ? 'visible' : 'hidden',
103
- }}
104
- ref={setTooltipRef}
105
- >
106
- {selectedFeature ? <FeatureDisplay feature={selectedFeature} /> : null}
107
- </div>
108
- ) : null;
109
- }
110
-
111
- export default withOpenLayers(InfoOverlay);
@@ -1,159 +0,0 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
- import InfoOverlay from './InfoOverlay';
4
- import '@testing-library/jest-dom';
5
-
6
- jest.mock('@eeacms/volto-openlayers-map', () => ({
7
- withOpenLayers: (Comp) => Comp,
8
- }));
9
-
10
- const onMap = jest.fn();
11
- const unMap = jest.fn();
12
- const addOverlay = jest.fn();
13
- const removeOverlay = jest.fn();
14
- jest.mock('@eeacms/volto-openlayers-map/api', () => ({
15
- useMapContext: () => ({
16
- map: mockMap,
17
- }),
18
- }));
19
-
20
- jest.mock('./FeatureDisplay', () => () => (
21
- <div data-testid="feature-display" />
22
- ));
23
-
24
- const mockAddOverlayInstance = {};
25
- jest.mock('ol/Overlay', () => {
26
- return jest.fn().mockImplementation((config) => {
27
- return { ...mockAddOverlayInstance, element: config.element };
28
- });
29
- });
30
-
31
- const mockOl = {
32
- Overlay: jest.fn((config) => ({
33
- element: config.element,
34
- setPosition: jest.fn(),
35
- })),
36
- };
37
-
38
- const mockMap = {
39
- on: onMap,
40
- un: unMap,
41
- addOverlay,
42
- removeOverlay,
43
- };
44
-
45
- jest.mock('@plone/volto/helpers/Utils/usePrevious', () => ({
46
- usePrevious: (val) => undefined,
47
- }));
48
-
49
- const defaultProps = {
50
- layerId: 'layer1',
51
- hideFilters: false,
52
- ol: mockOl,
53
- onFeatureSelect: jest.fn(),
54
- };
55
-
56
- describe('InfoOverlay', () => {
57
- let overlayElement;
58
- beforeEach(() => {
59
- jest.clearAllMocks();
60
- overlayElement = document.createElement('div');
61
- overlayElement.id = 'popup-overlay';
62
- document.body.appendChild(overlayElement);
63
- });
64
-
65
- afterEach(() => {
66
- document.body.innerHTML = '';
67
- });
68
-
69
- it('renders FeatureDisplay when selectedFeature is provided', () => {
70
- const { getByTestId } = render(
71
- <InfoOverlay {...defaultProps} selectedFeature={{ id: 1 }} />,
72
- );
73
- expect(getByTestId('feature-display')).toBeInTheDocument();
74
- });
75
-
76
- it('does not render FeatureDisplay when selectedFeature is null', () => {
77
- const { queryByTestId } = render(
78
- <InfoOverlay {...defaultProps} selectedFeature={null} />,
79
- );
80
- expect(queryByTestId('feature-display')).toBeNull();
81
- });
82
-
83
- it('attaches and detaches click handler on map', () => {
84
- const { unmount } = render(
85
- <InfoOverlay {...defaultProps} selectedFeature={null} />,
86
- );
87
- expect(addOverlay).toHaveBeenCalled();
88
- expect(onMap).toHaveBeenCalledWith('click', expect.any(Function));
89
-
90
- unmount();
91
- expect(unMap).toHaveBeenCalledWith('click', expect.any(Function));
92
- expect(removeOverlay).toHaveBeenCalled();
93
- });
94
-
95
- it('click handler hides tooltip when no features found', () => {
96
- const onFeatureSelect = jest.fn();
97
- render(
98
- <InfoOverlay
99
- {...defaultProps}
100
- selectedFeature={{}}
101
- onFeatureSelect={onFeatureSelect}
102
- />,
103
- );
104
-
105
- const handler = onMap.mock.calls.find(([event]) => event === 'click')[1];
106
-
107
- const evt = {
108
- pixel: [0, 0],
109
- target: { getFeaturesAtPixel: () => [] },
110
- originalEvent: { target: { tagName: 'DIV' } },
111
- };
112
- handler(evt);
113
-
114
- expect(onFeatureSelect).toHaveBeenCalledWith(null);
115
- const overlay = mockOl.Overlay.mock.results[0].value;
116
- expect(overlay.element.style.display).toBe('none');
117
- });
118
-
119
- it('ignores click when target is a link (A tag)', () => {
120
- const onFeatureSelect = jest.fn();
121
- render(
122
- <InfoOverlay
123
- {...defaultProps}
124
- selectedFeature={{}}
125
- onFeatureSelect={onFeatureSelect}
126
- />,
127
- );
128
-
129
- const handler = onMap.mock.calls.find(([event]) => event === 'click')[1];
130
-
131
- const evt = {
132
- pixel: [0, 0],
133
- target: { getFeaturesAtPixel: () => [] },
134
- originalEvent: { target: { tagName: 'A' } },
135
- };
136
- handler(evt);
137
-
138
- expect(onFeatureSelect).not.toHaveBeenCalled();
139
- });
140
-
141
- it('positions the overlay over the selected feature', () => {
142
- render(
143
- <InfoOverlay
144
- {...defaultProps}
145
- selectedFeature={{ geometry: { flatCoordinates: [10, 20] } }}
146
- />,
147
- );
148
-
149
- const overlay = mockOl.Overlay.mock.results[0].value;
150
- expect(overlay.setPosition).toHaveBeenCalledWith([10, 20]);
151
- });
152
-
153
- it('clears the overlay position when no feature is selected', () => {
154
- render(<InfoOverlay {...defaultProps} selectedFeature={null} />);
155
-
156
- const overlay = mockOl.Overlay.mock.results[0].value;
157
- expect(overlay.setPosition).toHaveBeenCalledWith(undefined);
158
- });
159
- });