homeflowjs 0.10.24 → 0.11.0
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/.eslintrc.js +1 -0
- package/__tests__/property-results.test.jsx +11 -0
- package/articles/load-more-button.component.jsx +1 -1
- package/branches/branches-map/branches-map.component.jsx +70 -28
- package/hooks/index.js +2 -0
- package/hooks/use-on-screen.js +22 -0
- package/package.json +1 -1
- package/properties/properties-display/properties-display.component.jsx +29 -6
- package/properties/property-utils/property-utils.js +22 -17
package/.eslintrc.js
CHANGED
@@ -9,6 +9,17 @@ import { Router } from 'react-router-dom';
|
|
9
9
|
import PropertyResults from 'properties/property-results/property-results.component';
|
10
10
|
import { properties } from './__mocks__/property-results.mock';
|
11
11
|
|
12
|
+
beforeEach(() => {
|
13
|
+
// IntersectionObserver isn't available in test environment
|
14
|
+
const mockIntersectionObserver = jest.fn();
|
15
|
+
mockIntersectionObserver.mockReturnValue({
|
16
|
+
observe: () => null,
|
17
|
+
unobserve: () => null,
|
18
|
+
disconnect: () => null,
|
19
|
+
});
|
20
|
+
window.IntersectionObserver = mockIntersectionObserver;
|
21
|
+
});
|
22
|
+
|
12
23
|
describe('Property Results', () => {
|
13
24
|
const initialState = {
|
14
25
|
properties: {
|
@@ -10,7 +10,7 @@ const LoadMoreButton = ({
|
|
10
10
|
const [loading, setLoading] = useState(false);
|
11
11
|
const [error, setError] = useState(false);
|
12
12
|
const hasNextPage = useSelector((state) => state.articles?.pagination?.has_next_page);
|
13
|
-
const storeArticles = useSelector((state) => state.articles.articles
|
13
|
+
const storeArticles = useSelector((state) => state.articles.articles || []);
|
14
14
|
const dispatch = useDispatch();
|
15
15
|
const loadNextArticlesPage = () => dispatch(loadNextPage());
|
16
16
|
|
@@ -13,9 +13,20 @@ import ReactLeafletGoogleLayer from 'react-leaflet-google-layer';
|
|
13
13
|
import 'leaflet/dist/leaflet.css';
|
14
14
|
|
15
15
|
const BranchesMap = ({
|
16
|
-
CustomPopup,
|
16
|
+
CustomPopup,
|
17
|
+
google,
|
18
|
+
gmapsKey,
|
19
|
+
iconConfig,
|
20
|
+
fallbackLatLng,
|
21
|
+
scrollWheelZoom,
|
22
|
+
markerClickHandler,
|
23
|
+
displayPopup,
|
24
|
+
selectedBranch,
|
25
|
+
branches: branchesProp,
|
17
26
|
}) => {
|
18
|
-
let branches =
|
27
|
+
let branches = !!branchesProp.length
|
28
|
+
? branchesProp
|
29
|
+
: Homeflow.get('branches');
|
19
30
|
let showMarkers = true;
|
20
31
|
|
21
32
|
// If no branches found use fallback lat, long and set showMarkers to false
|
@@ -30,17 +41,20 @@ const BranchesMap = ({
|
|
30
41
|
shadowUrl: '/assets/marker-shadow.png',
|
31
42
|
};
|
32
43
|
|
33
|
-
|
44
|
+
|
34
45
|
|
35
46
|
const bounds = latLngBounds([branches[0].lat, branches[0].lng]);
|
36
47
|
|
37
48
|
const markers = branches.map((branch) => {
|
38
49
|
bounds.extend([branch.lat, branch.lng]);
|
50
|
+
let icon = L.icon(iconConfig || defaultIconConfig);
|
39
51
|
// check if branch object have mapIcon payload, example:
|
40
52
|
// mapIcon: {
|
41
53
|
// iconUrl: 'specific-path.png' || 'data:image/svg+xml;utf8,<svg></svg>',
|
42
54
|
// iconSize: [149, 66],
|
43
55
|
// }
|
56
|
+
if (branch.branchID === selectedBranch.branchID && 'mapIcon' in selectedBranch) icon = L.icon(selectedBranch.mapIcon);
|
57
|
+
|
44
58
|
if ('mapIcon' in branch) icon = L.icon(branch.mapIcon);
|
45
59
|
// check if branch object have mapDivIcon payload, example:
|
46
60
|
// mapDivIcon: {
|
@@ -49,35 +63,55 @@ const BranchesMap = ({
|
|
49
63
|
// }
|
50
64
|
if ('mapDivIcon' in branch) icon = L.divIcon(branch.mapDivIcon);
|
51
65
|
|
66
|
+
if (displayPopup) {
|
67
|
+
return (
|
68
|
+
<Marker
|
69
|
+
position={[branch.lat, branch.lng]}
|
70
|
+
icon={icon}
|
71
|
+
key={branch?.branch_id || branch?.branchID}
|
72
|
+
eventHandlers={{
|
73
|
+
click: () => markerClickHandler(branch),
|
74
|
+
}}
|
75
|
+
>
|
76
|
+
{CustomPopup ? (
|
77
|
+
<Popup>
|
78
|
+
<CustomPopup branch={branch} />
|
79
|
+
</Popup>
|
80
|
+
) : (
|
81
|
+
<Popup>
|
82
|
+
<div className="hfjs-branch-marker-popup">
|
83
|
+
<h2>{branch.name}</h2>
|
84
|
+
<p>{branch.address}</p>
|
85
|
+
|
86
|
+
{branch.sales_enabled && (
|
87
|
+
<a href={`${branch.branch_url}/sales`}>
|
88
|
+
View all properties for sale >
|
89
|
+
</a>
|
90
|
+
)}
|
91
|
+
|
92
|
+
<br />
|
93
|
+
|
94
|
+
{branch.lettings_enabled && (
|
95
|
+
<a href={`${branch.branch_url}/lettings`}>
|
96
|
+
View all properties to let >
|
97
|
+
</a>
|
98
|
+
)}
|
99
|
+
</div>
|
100
|
+
</Popup>
|
101
|
+
)}
|
102
|
+
</Marker>
|
103
|
+
);
|
104
|
+
}
|
105
|
+
|
52
106
|
return (
|
53
107
|
<Marker
|
54
108
|
position={[branch.lat, branch.lng]}
|
55
109
|
icon={icon}
|
56
|
-
key={branch
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
</Popup>
|
62
|
-
) : (
|
63
|
-
<Popup>
|
64
|
-
<div className="hfjs-branch-marker-popup">
|
65
|
-
<h2>{branch.name}</h2>
|
66
|
-
<p>{branch.address}</p>
|
67
|
-
|
68
|
-
{branch.sales_enabled && (
|
69
|
-
<a href={`${branch.branch_url}/sales`}>View all properties for sale ></a>
|
70
|
-
)}
|
71
|
-
|
72
|
-
<br />
|
73
|
-
|
74
|
-
{branch.lettings_enabled && (
|
75
|
-
<a href={`${branch.branch_url}/lettings`}>View all properties to let ></a>
|
76
|
-
)}
|
77
|
-
</div>
|
78
|
-
</Popup>
|
79
|
-
)}
|
80
|
-
</Marker>
|
110
|
+
key={branch?.branch_id || branch?.branchID}
|
111
|
+
eventHandlers={{
|
112
|
+
click: () => markerClickHandler(branch),
|
113
|
+
}}
|
114
|
+
/>
|
81
115
|
);
|
82
116
|
});
|
83
117
|
|
@@ -112,6 +146,10 @@ BranchesMap.propTypes = {
|
|
112
146
|
iconConfig: PropTypes.object,
|
113
147
|
fallbackLatLng: PropTypes.object,
|
114
148
|
scrollWheelZoom: PropTypes.bool,
|
149
|
+
markerClickHandler: PropTypes.func,
|
150
|
+
displayPopup : PropTypes.bool,
|
151
|
+
selectedBranch: PropTypes.object,
|
152
|
+
branches: PropTypes.arrayOf(PropTypes.object)
|
115
153
|
};
|
116
154
|
|
117
155
|
BranchesMap.defaultProps = {
|
@@ -121,6 +159,10 @@ BranchesMap.defaultProps = {
|
|
121
159
|
iconConfig: null,
|
122
160
|
fallbackLatLng: { lat: 51.509865, lng: -0.118092 },
|
123
161
|
scrollWheelZoom: false,
|
162
|
+
markerClickHandler: () => {},
|
163
|
+
displayPopup: true,
|
164
|
+
selectedBranch: {},
|
165
|
+
branches: []
|
124
166
|
};
|
125
167
|
|
126
168
|
const mapStateToProps = (state) => ({
|
package/hooks/index.js
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
import useDefaultSort from './use-default-sort.hook';
|
2
2
|
import useGeolocate from './use-geolocate';
|
3
3
|
import useOutsideClick from './use-outside-click';
|
4
|
+
import { useOnScreen } from './use-on-screen';
|
4
5
|
|
5
6
|
export {
|
6
7
|
useDefaultSort,
|
7
8
|
useGeolocate,
|
8
9
|
useOutsideClick,
|
10
|
+
useOnScreen,
|
9
11
|
};
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { useEffect, useState, useRef } from 'react';
|
2
|
+
|
3
|
+
export function useOnScreen(ref) {
|
4
|
+
const [isOnScreen, setIsOnScreen] = useState(false);
|
5
|
+
const observerRef = useRef(null);
|
6
|
+
|
7
|
+
useEffect(() => {
|
8
|
+
observerRef.current = new IntersectionObserver(([entry]) => {
|
9
|
+
setIsOnScreen(entry.isIntersecting);
|
10
|
+
});
|
11
|
+
}, []);
|
12
|
+
|
13
|
+
useEffect(() => {
|
14
|
+
observerRef.current.observe(ref.current);
|
15
|
+
|
16
|
+
return () => {
|
17
|
+
observerRef.current.disconnect();
|
18
|
+
};
|
19
|
+
}, [ref]);
|
20
|
+
|
21
|
+
return isOnScreen;
|
22
|
+
}
|
package/package.json
CHANGED
@@ -2,8 +2,7 @@ import React, { useEffect } from 'react';
|
|
2
2
|
import { connect } from 'react-redux';
|
3
3
|
import PropTypes from 'prop-types';
|
4
4
|
|
5
|
-
import { handleScroll, propertiesByPage } from '../property-utils/property-utils';
|
6
|
-
import { uniqueKey } from '../../utils';
|
5
|
+
import { handleScroll, propertiesByPage, updatePageInURL } from '../property-utils/property-utils';
|
7
6
|
|
8
7
|
const ConditionalWrapper = ({ condition, wrapper, children }) => (
|
9
8
|
condition ? wrapper(children) : children
|
@@ -27,7 +26,27 @@ const PropertiesDisplay = ({
|
|
27
26
|
return function cleanUp() {
|
28
27
|
window.removeEventListener('scroll', onScroll);
|
29
28
|
};
|
30
|
-
});
|
29
|
+
}, []);
|
30
|
+
|
31
|
+
useEffect(() => {
|
32
|
+
const pageMarkers = document.querySelectorAll('[data-page-marker]');
|
33
|
+
|
34
|
+
const observer = new IntersectionObserver((entries) => {
|
35
|
+
entries.forEach((entry) => {
|
36
|
+
if (entry.isIntersecting && entry.target?.dataset?.pageMarker) {
|
37
|
+
updatePageInURL(parseInt(entry.target.dataset.pageMarker, 10));
|
38
|
+
}
|
39
|
+
});
|
40
|
+
});
|
41
|
+
|
42
|
+
pageMarkers.forEach((marker) => {
|
43
|
+
observer.observe(marker);
|
44
|
+
});
|
45
|
+
|
46
|
+
return function cleanUp() {
|
47
|
+
observer.disconnect();
|
48
|
+
};
|
49
|
+
}, [properties]);
|
31
50
|
|
32
51
|
if (!properties.length) {
|
33
52
|
return (
|
@@ -44,8 +63,7 @@ const PropertiesDisplay = ({
|
|
44
63
|
wrapper={(children) => (
|
45
64
|
<div
|
46
65
|
data-result-page={page[0].resultPage}
|
47
|
-
|
48
|
-
className={`clearfix results-page--${displayType}`}
|
66
|
+
className={`results-page results-page--${displayType}`}
|
49
67
|
>
|
50
68
|
{children}
|
51
69
|
</div>
|
@@ -65,7 +83,12 @@ const PropertiesDisplay = ({
|
|
65
83
|
);
|
66
84
|
}
|
67
85
|
return (
|
68
|
-
<Item
|
86
|
+
<Item
|
87
|
+
key={property.property_id}
|
88
|
+
property={property}
|
89
|
+
data-page-marker={!addWrapper && index === 0 ? page[0].resultPage : null}
|
90
|
+
{...other}
|
91
|
+
/>
|
69
92
|
);
|
70
93
|
})}
|
71
94
|
</ConditionalWrapper>
|
@@ -22,10 +22,29 @@ const infiniteScroll = () => {
|
|
22
22
|
return true;
|
23
23
|
};
|
24
24
|
|
25
|
+
export const updatePageInURL = (currentPage) => {
|
26
|
+
const { pathname, hash } = window.location;
|
27
|
+
|
28
|
+
let newPath;
|
29
|
+
|
30
|
+
if (currentPage === 1 && !pathname.includes('page-')) return null;
|
31
|
+
|
32
|
+
if (currentPage === 1) {
|
33
|
+
newPath = pathname.replace(/page-./, '');
|
34
|
+
} else if (pathname.includes('page-')) {
|
35
|
+
newPath = pathname.replace(/page-./, `page-${currentPage}`);
|
36
|
+
} else {
|
37
|
+
newPath = pathname[pathname.length - 1] === '/' ? pathname : `${pathname}/`;
|
38
|
+
newPath = `${newPath}page-${currentPage}`;
|
39
|
+
}
|
40
|
+
|
41
|
+
return history.replaceState(null, '', `${newPath}${hash}`);
|
42
|
+
};
|
43
|
+
|
25
44
|
// calculates which page we've scrolled to and updates the URL
|
26
45
|
export const handleScroll = (infinite) => {
|
27
|
-
// TODO: Only do this if the theme has infinite scroll enabled
|
28
46
|
if (infinite) infiniteScroll();
|
47
|
+
|
29
48
|
// get the current top of view scroll position
|
30
49
|
const currentPosition = document.documentElement.scrollTop;
|
31
50
|
document.querySelectorAll('[data-result-page]').forEach((resultPage) => {
|
@@ -38,22 +57,8 @@ export const handleScroll = (infinite) => {
|
|
38
57
|
// if top of current view is between the top and bottom of the page div, we're on that page
|
39
58
|
if (pageTop <= currentPosition && pageTop + (rect.bottom - rect.top) > currentPosition) {
|
40
59
|
const currentPage = parseInt(resultPage.dataset.resultPage, 10);
|
41
|
-
const { pathname, hash } = window.location;
|
42
|
-
|
43
|
-
let newPath;
|
44
60
|
|
45
|
-
|
46
|
-
|
47
|
-
if (currentPage === 1) {
|
48
|
-
newPath = pathname.replace(/page-./, '');
|
49
|
-
} else if (pathname.includes('page-')) {
|
50
|
-
newPath = pathname.replace(/page-./, `page-${currentPage}`);
|
51
|
-
} else {
|
52
|
-
newPath = pathname[pathname.length - 1] === '/' ? pathname : `${pathname}/`;
|
53
|
-
newPath = `${newPath}page-${currentPage}`;
|
54
|
-
}
|
55
|
-
|
56
|
-
history.replaceState(null, '', `${newPath}${hash}`);
|
61
|
+
updatePageInURL(currentPage);
|
57
62
|
}
|
58
63
|
});
|
59
64
|
};
|
@@ -75,4 +80,4 @@ export const propertiesByPage = (properties) => {
|
|
75
80
|
}
|
76
81
|
|
77
82
|
return properties2d;
|
78
|
-
}
|
83
|
+
};
|