homeflowjs 0.7.29 → 0.8.2

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.
@@ -188,3 +188,8 @@ export const fetchSavedProperties = () => (dispatch) => {
188
188
  })
189
189
  .catch(() => dispatch(setSavedProperties([])));
190
190
  };
191
+
192
+ export const setSelectedMarker = (payload) => ({
193
+ type: PropertiesActionTypes.SET_SELECTED_MARKER,
194
+ payload,
195
+ });
@@ -6,6 +6,7 @@ const PropertiesActionTypes = {
6
6
  LOAD_NEXT: 'LOAD_NEXT',
7
7
  SET_PAGINATION: 'SET_PAGINATION',
8
8
  SET_PROPERTY_LINKS: 'SET_PROPERTY_LINKS',
9
+ SET_SELECTED_MARKER: 'SET_SELECTED_MARKER',
9
10
  };
10
11
 
11
12
  export default PropertiesActionTypes;
@@ -32,6 +32,8 @@ class BranchMap extends React.Component {
32
32
  zoom,
33
33
  disableStreetview,
34
34
  custom,
35
+ iconConfig,
36
+ fullscreenControl,
35
37
  } = this.props;
36
38
 
37
39
  // Should this be added to Redux?
@@ -45,6 +47,8 @@ class BranchMap extends React.Component {
45
47
  scrollWheel,
46
48
  zoom,
47
49
  streetViewControl: !disableStreetview,
50
+ zoomControl: false,
51
+ fullscreenControl: false,
48
52
  };
49
53
 
50
54
  if (custom === 'simple_bw') {
@@ -52,9 +56,11 @@ class BranchMap extends React.Component {
52
56
  }
53
57
 
54
58
  const map = new google.maps.Map(document.getElementById('hfjs-branch-map'), options);
55
- const marker = new google.maps.Marker({
59
+
60
+ new google.maps.Marker({
56
61
  position: { lat: branch.lat, lng: branch.lng },
57
62
  map,
63
+ icon: iconConfig.iconUrl,
58
64
  });
59
65
  }
60
66
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homeflowjs",
3
- "version": "0.7.29",
3
+ "version": "0.8.2",
4
4
  "description": "JavaScript toolkit for Homeflow themes",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -0,0 +1,95 @@
1
+ import React, { useEffect } from 'react';
2
+ import { connect } from 'react-redux';
3
+ import PropTypes from 'prop-types';
4
+
5
+ import { handleScroll, propertiesByPage } from '../property-utils/property-utils';
6
+ import { uniqueKey } from '../../utils';
7
+
8
+ const ConditionalWrapper = ({ condition, wrapper, children }) => (
9
+ condition ? wrapper(children) : children
10
+ );
11
+
12
+ const PropertiesDisplay = ({
13
+ properties,
14
+ Item,
15
+ displayType,
16
+ infiniteScroll,
17
+ inserts,
18
+ ...other
19
+ }) => {
20
+ const onScroll = () => {
21
+ handleScroll(infiniteScroll);
22
+ };
23
+
24
+ useEffect(() => {
25
+ window.addEventListener('scroll', onScroll);
26
+
27
+ return function cleanUp() {
28
+ window.removeEventListener('scroll', onScroll);
29
+ };
30
+ });
31
+
32
+ if (!properties.length) {
33
+ return (
34
+ <p>There were no properties matching your search.</p>
35
+ );
36
+ }
37
+ const addWrapper = displayType === 'list';
38
+
39
+ const items = propertiesByPage(properties).map((page) => (
40
+ <ConditionalWrapper
41
+ condition={addWrapper}
42
+ wrapper={(children) => (
43
+ <div
44
+ data-result-page={page[0].resultPage}
45
+ key={uniqueKey()}
46
+ className={`clearfix results-page--${displayType}`}
47
+ >
48
+ {children}
49
+ </div>
50
+ )}
51
+ >
52
+ {page.map((property, index) => {
53
+ /**
54
+ * TODO: Allow for multiple inserts
55
+ * This code only allows one insert from the inserts array to be show at a time
56
+ */
57
+ if (inserts && (index % inserts[0].frequency) === 0 && index !== 0) {
58
+ return (
59
+ <>
60
+ {inserts[0].component}
61
+ <Item key={property.property_id} property={property} {...other} />
62
+ </>
63
+ );
64
+ }
65
+ return (
66
+ <Item key={property.property_id} property={property} {...other} />
67
+ );
68
+ })}
69
+ </ConditionalWrapper>
70
+ ));
71
+
72
+ return (
73
+ <div className={`hf-property-results hf-property-results__${displayType}`}>
74
+ {items}
75
+ </div>
76
+ );
77
+ };
78
+
79
+ PropertiesDisplay.propTypes = {
80
+ properties: PropTypes.array.isRequired,
81
+ Item: PropTypes.elementType.isRequired,
82
+ displayType: PropTypes.string.isRequired,
83
+ infiniteScroll: PropTypes.bool.isRequired,
84
+ inserts: PropTypes.array,
85
+ };
86
+
87
+ PropertiesDisplay.defaultProps = {
88
+ inserts: null,
89
+ };
90
+
91
+ const mapStateToProps = (state) => ({
92
+ properties: state.properties.properties || [],
93
+ });
94
+
95
+ export default connect(mapStateToProps)(PropertiesDisplay);
@@ -1,52 +1,18 @@
1
- import React, { useEffect } from 'react';
2
- import { connect } from 'react-redux';
1
+ import React from 'react';
3
2
  import PropTypes from 'prop-types';
4
3
 
5
- import { uniqueKey } from '../../utils';
6
- import { handleScroll, propertiesByPage } from '../property-utils/property-utils';
4
+ import PropertiesDisplay from '../properties-display/properties-display.component';
7
5
 
8
- const PropertiesGrid = ({ properties, GridItem, infiniteScroll }) => {
9
- const onScroll = () => {
10
- handleScroll(infiniteScroll);
11
- };
12
-
13
- useEffect(() => {
14
- window.addEventListener('scroll', onScroll);
15
-
16
- return function cleanUp() {
17
- window.removeEventListener('scroll', onScroll);
18
- };
19
- });
20
-
21
- if (!properties.length) {
22
- return (
23
- <p>There were no properties matching your search.</p>
24
- );
25
- }
26
-
27
- const items = propertiesByPage(properties).map((page) =>
28
- page.map((property) => (
29
- <GridItem key={property.property_id} property={property} />
30
- )
31
- )).flat();
32
-
33
- return (
34
- <div className="hf-property-results hf-property-results__list">
35
- <div className="clearfix results-page--grid">
36
- {items}
37
- </div>
38
- </div>
39
- );
40
- };
6
+ const PropertiesGrid = ({ GridItem, ...other }) => (
7
+ <PropertiesDisplay
8
+ Item={GridItem}
9
+ displayType="grid"
10
+ {...other}
11
+ />
12
+ );
41
13
 
42
14
  PropertiesGrid.propTypes = {
43
- properties: PropTypes.array.isRequired,
44
15
  GridItem: PropTypes.elementType.isRequired,
45
- infiniteScroll: PropTypes.bool.isRequired,
46
16
  };
47
17
 
48
- const mapStateToProps = (state) => ({
49
- properties: state.properties.properties,
50
- });
51
-
52
- export default connect(mapStateToProps)(PropertiesGrid);
18
+ export default PropertiesGrid;
@@ -1,56 +1,18 @@
1
- import React, { useEffect } from 'react';
2
- import { connect } from 'react-redux';
1
+ import React from 'react';
3
2
  import PropTypes from 'prop-types';
4
3
 
5
- import { handleScroll, propertiesByPage } from '../property-utils/property-utils';
6
- import { uniqueKey } from '../../utils';
4
+ import PropertiesDisplay from '../properties-display/properties-display.component'
7
5
 
8
- const PropertiesList = ({ properties, ListItem, infiniteScroll }) => {
9
- const onScroll = () => {
10
- handleScroll(infiniteScroll);
11
- };
12
-
13
- useEffect(() => {
14
- window.addEventListener('scroll', onScroll);
15
-
16
- return function cleanUp() {
17
- window.removeEventListener('scroll', onScroll);
18
- };
19
- });
20
-
21
- const propertiesToRender = properties || [];
22
-
23
- const items = propertiesByPage(propertiesToRender).map((page) => (
24
- <div
25
- data-result-page={page[0].resultPage}
26
- key={uniqueKey()}
27
- className="clearfix results-page--list"
28
- >
29
- {page.map((property) => (
30
- <ListItem key={property.property_id} property={property} />
31
- ))}
32
- </div>
33
- ));
34
-
35
- return (
36
- <div className="hf-property-results hf-property-results__list">
37
- {items}
38
- </div>
39
- );
40
- };
6
+ const PropertiesList = ({ ListItem, ...other}) => (
7
+ <PropertiesDisplay
8
+ Item={ListItem}
9
+ displayType="list"
10
+ {...other}
11
+ />
12
+ );
41
13
 
42
14
  PropertiesList.propTypes = {
43
- properties: PropTypes.array,
44
15
  ListItem: PropTypes.elementType.isRequired,
45
- infiniteScroll: PropTypes.bool.isRequired,
46
- };
47
-
48
- PropertiesList.defaultProps = {
49
- properties: [],
50
16
  };
51
17
 
52
- const mapStateToProps = state => ({
53
- properties: state.properties.properties,
54
- });
55
-
56
- export default connect(mapStateToProps)(PropertiesList);
18
+ export default PropertiesList;
@@ -5,7 +5,7 @@ import base64 from '../../utils/base64';
5
5
  import store from '../../store';
6
6
  import { buildQueryString } from '../../search/property-search/property-search';
7
7
  import { setPlace, setSearchField } from '../../actions/search.actions';
8
- import { setProperties } from '../../actions/properties.actions';
8
+ import { setProperties, setSelectedMarker } from '../../actions/properties.actions';
9
9
  import { setLoading } from '../../actions/app.actions';
10
10
 
11
11
  const element = function (X, Y) {
@@ -23,6 +23,18 @@ export default class DraggableMap {
23
23
  this.render = this.render.bind(this);
24
24
  this.generateMap = this.generateMap.bind(this);
25
25
  this.properties = this.getProperties();
26
+ if (Homeflow.get('select_marker_on_click')) {
27
+ this.selectedMarker = null;
28
+ store.subscribe(() => {
29
+ const newSelectedMarker = store.getState().properties.selectedMarker;
30
+ const newSelectedPropertyID = newSelectedMarker ? newSelectedMarker.property.property_id : null;
31
+ const stateSelectedPropertyID = this.selectedMarker ? this.selectedMarker.property.property_id : null;
32
+
33
+ if (newSelectedPropertyID !== stateSelectedPropertyID) {
34
+ this.activateMarker(newSelectedMarker);
35
+ }
36
+ })
37
+ }
26
38
  }
27
39
 
28
40
  getSearch() {
@@ -42,7 +54,9 @@ export default class DraggableMap {
42
54
  this.render();
43
55
  this.buildPolygon();
44
56
  const properties = this.properties;
45
- if (properties != null) { this.addMarkersFor(properties); }
57
+ if (properties != null) {
58
+ this.setMarkersFor(properties);
59
+ }
46
60
  if (this.noLocationfound) { this.setToMarkeredBounds(); }
47
61
  if (Homeflow.get('custom_map_zoom')) {
48
62
  this.map.setZoom(Homeflow.get('custom_map_zoom'));
@@ -58,10 +72,12 @@ export default class DraggableMap {
58
72
  // if (lastSearch = Ctesius.getUserHistoryCollection().last()) {
59
73
  // Ctesius.storedPlace = lastSearch.get('place');
60
74
  // }
75
+
61
76
  this.generateMap();
62
77
  if (!Homeflow.get('free_text_search')) { this.map.on('zoomend', () => (this.onMapDrag())); }
63
78
  this.map.on('dragend', () => (this.onMapDrag()));
64
79
  const _this = this;
80
+
65
81
  return this.map.on('popupopen', function (e) {
66
82
  Homeflow.kickEvent('map_marker_opened', e);
67
83
  const px = _this.map.project(e.popup._latlng);
@@ -72,28 +88,39 @@ export default class DraggableMap {
72
88
  });
73
89
  }
74
90
 
75
- generateMarker(property) {
76
- let markerIcon;
77
- // TODO: Re-add custom pins
91
+ activateMarker(marker) {
92
+ if (this.selectedMarker) {
93
+ this.selectedMarker.setIcon(this.generateMarkerIcon(this.selectedMarker.property));
94
+ }
95
+ this.selectedMarker = marker;
96
+ if (this.selectedMarker) this.selectedMarker.setIcon(this.generateMarkerIcon(marker.property));
97
+ }
98
+
99
+ generateMarkerIcon(property) {
78
100
  if (Homeflow.get('custom_property_pin') != null) {
79
- markerIcon = Homeflow.get('custom_property_pin')(property);
101
+ return Homeflow.get('custom_property_pin')(property);
80
102
  } else {
81
- let left, left1, left2, left3, left4;
103
+ let left, left1, left2, left3, left4, left5;
82
104
  const markerIconImage = (left = Homeflow.get('custom_map_icon')) != null ? left : '/assets/leaflet/marker-icon.png';
83
105
  const markerIconSize = (left1 = Homeflow.get('custom_map_icon_size')) != null ? left1 : [25, 41];
84
106
  const markerShadowUrl = (left2 = Homeflow.get('custom_map_shadow')) != null ? left2 : '/assets/leaflet/marker-shadow.png';
85
107
  const markerShadowSize = (left3 = Homeflow.get('custom_map_shadow_icon_size')) != null ? left3 : [41, 41];
86
108
  const pinAnchor = (left4 = Homeflow.get('custom_map_icon_offset')) != null ? left4 : [12, 41];
87
- markerIcon = new L.Icon.Default({
109
+ const shadowAnchor = (left5 = Homeflow.get('custom_map_shadow_anchor')) != null ? left5 : [12, 41];
110
+
111
+ return new L.Icon.Default({
88
112
  iconUrl: markerIconImage,
89
113
  iconSize: markerIconSize,
114
+ iconAnchor: pinAnchor,
90
115
  shadowUrl: markerShadowUrl,
91
116
  shadowSize: markerShadowSize,
92
- iconAnchor: pinAnchor
117
+ shadowAnchor: shadowAnchor,
93
118
  });
94
119
  }
120
+ }
95
121
 
96
- const marker = L.marker([property.lat, property.lng], { icon: markerIcon });
122
+ generateMarker(property) {
123
+ const marker = L.marker([property.lat, property.lng], { icon: this.generateMarkerIcon(property) });
97
124
 
98
125
  // add the property to marker attributes so they can be accessed on click
99
126
  marker.property = property;
@@ -115,11 +142,12 @@ export default class DraggableMap {
115
142
  }
116
143
 
117
144
  marker.on('click', e => Homeflow.kickEvent('map_marker_clicked', e.target));
118
-
145
+ if (Homeflow.get('select_marker_on_click')) {
146
+ marker.on('click', e => store.dispatch(setSelectedMarker(e.target)));
147
+ }
119
148
  return window['map_marker_' + property.property_id] = marker;
120
149
  }
121
150
 
122
-
123
151
  generateGeoFeature(geo_feature) {
124
152
  let left, left1, left2, left3;
125
153
  const markerIconImage = (left = Homeflow.get('custom_map_icon')) != null ? left : (Homeflow.get('root_url') + 'assets/leaflet/marker-icon.png');
@@ -164,8 +192,11 @@ export default class DraggableMap {
164
192
 
165
193
  generateMap(map_options) {
166
194
  const zoomMax = Homeflow.get('custom_max_zoom') ? Homeflow.get('custom_max_zoom') : 16;
195
+ const zoomPosition = Homeflow.get('custom_map_zoom_location') ? Homeflow.get('custom_map_zoom_location') : '';
196
+
167
197
  if (map_options == null) {
168
198
  map_options = {
199
+ ...(zoomPosition !== '') && {zoomControl: false},
169
200
  minZoom: 6,
170
201
  maxZoom: zoomMax,
171
202
  scrollWheelZoom: Homeflow.get('enable_scroll_wheel_zoom'),
@@ -174,6 +205,7 @@ export default class DraggableMap {
174
205
  }
175
206
 
176
207
  this.map = new L.Map(this.element, map_options);
208
+ if (zoomPosition !== '') new L.Control.Zoom({ position: zoomPosition }).addTo(this.map);
177
209
 
178
210
  const tileLayer = Homeflow.get('leaflet_layer');
179
211
  let layer;
@@ -205,8 +237,10 @@ export default class DraggableMap {
205
237
  }
206
238
  }
207
239
 
208
- addMarkersFor(properties) {
240
+ setMarkersFor(properties) {
209
241
  let bounds;
242
+ if (this.marker_layer != null) { this.map.removeLayer(this.marker_layer); }
243
+ if (!properties) { return null }
210
244
 
211
245
  if (Homeflow.get('pin_clustering')) {
212
246
  let radius = Homeflow.get('custom_clustering_radius');
@@ -247,23 +281,69 @@ export default class DraggableMap {
247
281
  }
248
282
  }
249
283
 
284
+ buildSubPolygon(polygon, extraPolygon, style = null) {
285
+ let bounds = [];
286
+ const points = [];
287
+ polygon.forEach(ar => {
288
+ const a = new L.LatLng(ar[1], ar[0]);
289
+ bounds.push(a);
290
+ return points.push(a);
291
+ });
292
+ polygon = new L.Polygon(points,
293
+ { weight: 10, fill: true }
294
+ );
295
+ // @drawnItems.addLayer polygon
296
+ this.map.fire("draw:created", {
297
+ layer: polygon,
298
+ layerType: polygon.type,
299
+ i: 0,
300
+ placePoly: true, //Build subPolygon(s) is only used to draw place polygon or extended radius polygons so no need to update the search state with poly
301
+ additionalDrawing: extraPolygon,
302
+ polyStyle: style,
303
+ });
304
+ return new L.LatLngBounds(bounds);
305
+ }
306
+ buildSubPolygons(polygons) {
307
+ //TODO: Clear existing polygons
308
+ let bounds = null;
309
+ let extraPolygon = false;
310
+ polygons.forEach(polygon => {
311
+ const extraBounds = this.buildSubPolygon(polygon, extraPolygon);
312
+ extraPolygon = true;
313
+ if (bounds === null) {
314
+ bounds = extraBounds;
315
+ } else {
316
+ bounds.extend(extraBounds)
317
+ }
318
+ });
319
+ return bounds;
320
+ }
250
321
 
251
322
  buildPolygon() {
252
- let bounds, polygon;
253
- if (Homeflow.get('enable_draw_a_map') && (this.getSearch().poly != null)) {
254
- bounds = [];
255
- polygon = new L.Polygon.fromEncoded(this.getSearch().poly);
256
- bounds = polygon.getBounds();
323
+ const search = this.getSearch();
324
+ if (Homeflow.get('enable_draw_a_map') && (search.poly != null)) {
325
+ const polygon = new L.Polygon.fromEncoded(search.poly);
326
+ const polygonBounds = polygon.getBounds();
327
+
257
328
  this.map.fire("draw:created", {
258
329
  layer: polygon,
259
- layerType: polygon.type
260
- }
261
- );
262
- return this.map.fitBounds(bounds);
330
+ layerType: polygon.type,
331
+ placePoly: false.valueOf,
332
+ });
333
+ return this.map.fitBounds(polygonBounds);
334
+
263
335
  } else if (Homeflow.get('place') != null) {
264
336
  let center;
265
337
  const place = Homeflow.get('place');
266
- if ((place.radius != null) && (place.radius !== '')) {
338
+ if (search.expandedPolygon && Homeflow.get('enable_polygon_radius_search')) {
339
+ const expandedStyle = { weight: 2, dashArray: "8 8", color: '#000' }
340
+ if (place.polygon.length === 1) this.buildSubPolygons(place.polygon);
341
+
342
+ const bounds = this.buildSubPolygon(search.expandedPolygon, true, expandedStyle);
343
+
344
+ return this.map.fitBounds(bounds);
345
+
346
+ } else if ((place.radius != null) && (place.radius !== '')) {
267
347
  this.circle = new L.circle([place.lat, place.lng], place.radius * 1000, {
268
348
  fill: false,
269
349
  weight: 0
@@ -271,33 +351,16 @@ export default class DraggableMap {
271
351
  this.map.addLayer(this.circle);
272
352
  center = new L.LatLng(place.lat, place.lng);
273
353
  return this.map.fitBounds(this.circle.getBounds());
354
+
274
355
  } else if ((place.polygon != null) && (place.polygon.length > 0)) {
275
- bounds = [];
276
- place.polygon.forEach(polygon => {
277
- const points = [];
278
- polygon.forEach(ar => {
279
- const a = new L.LatLng(ar[1], ar[0]);
280
- bounds.push(a);
281
- return points.push(a);
282
- });
283
- polygon = new L.Polygon(points,
284
- { weight: 10, fill: true }
285
- );
286
- // @drawnItems.addLayer polygon
287
- return this.map.fire("draw:created", {
288
- layer: polygon,
289
- layerType: polygon.type,
290
- i: 0
291
- }
292
- );
293
- });
294
- return this.map.fitBounds(new L.LatLngBounds(bounds));
356
+ const placeBounds = this.buildSubPolygons(place.polygon)
357
+ return this.map.fitBounds(placeBounds);
358
+
295
359
  } else {
296
360
  center = new L.LatLng(place.lat, place.lng);
297
361
  return this.map.setView(center, 12);
298
362
  }
299
363
  } else {
300
- //@map.setView([51.505, -0.09], 12)
301
364
  return this.noLocationfound = true;
302
365
  }
303
366
  }
@@ -307,7 +370,8 @@ export default class DraggableMap {
307
370
  if (!Homeflow.get('disable_draggable_map')) {
308
371
  let url;
309
372
  const bounds = this.getSearchableBounds();
310
- if (this.mapLoadedTimes === 1) {
373
+ const has_expanded = this.getSearch().expandedPolygon;
374
+ if (this.mapLoadedTimes === 1 || has_expanded ) {
311
375
  url = `/search.ljson?${buildQueryString(this.getSearch())}/view-${bounds.toBBoxString()}&count=50`;
312
376
  } else {
313
377
  // unset place
@@ -327,20 +391,19 @@ export default class DraggableMap {
327
391
  if (Homeflow.get('map_branch_id') != null) {
328
392
  url = url + "&branch_id=" + Homeflow.get('map_branch_id');
329
393
  }
330
- Homeflow.kickEvent('before_draggable_map_updated'); //use to show some loading?
394
+ Homeflow.kickEvent('before_draggable_map_updated');
331
395
  return fetch(url)
332
396
  .then((response) => response.json())
333
397
  .then(json => {
334
398
  Homeflow.kickEvent('draggable_map_updated', json);
335
399
  this._running_update = false;
336
- if (this.marker_layer != null) { this.map.removeLayer(this.marker_layer); }
400
+
337
401
  this.properties = json.properties;
338
402
  // TODO: figure out what 'tile view' is
339
403
  // if (this.tile_view != null) {
340
404
  // this.tile_view = new Ctesius.Views.Tiles({ collection: this.collection });
341
405
  // }
342
-
343
- return this.addMarkersFor(this.properties);
406
+ return this.setMarkersFor(this.properties);
344
407
  });
345
408
  }
346
409
  }
@@ -449,30 +512,22 @@ export default class DraggableMap {
449
512
  return params.join("/");
450
513
  }
451
514
 
452
-
453
515
  updateURL() {
454
516
  let new_location = window.location.pathname.replace(/(.*\/sales|lettings).*/, '$1');
455
517
  new_location = new_location + '/' + this.searchToHomeflowUrlParams(true) + window.location.hash;
456
518
  return window.history.replaceState("NaN", document.title, new_location);
457
519
  }
458
520
 
459
-
460
-
461
521
  onPolygonDrawn(layer, with_update) {
462
522
  let geo_url, url;
463
523
  if (with_update == null) { with_update = true; }
464
524
  const s = this.getSearch();
465
-
466
- if (with_update && ((this.first != null) && (this.first === false))) {
525
+ if (with_update) {
467
526
  const encodedPoly = L.PolylineUtil.encode(this.buildDrawnPolygon(layer));
468
527
  store.dispatch(setSearchField({ poly: encodedPoly }));
469
528
  s.poly = encodedPoly;
470
- // store.dispatch(setSearchField({ poly: L.PolylineUtil.encode(this.buildDrawnPolygon(layer)) }));
471
- // s.poly = L.PolylineUtil.encode(this.buildDrawnPolygon(layer));
472
529
  }
473
530
 
474
- // debugger;
475
-
476
531
  if (s.poly) {
477
532
  url = `/search.ljson?${buildQueryString(s)}&count=50`;
478
533
  geo_url = `geo_features.ljson?${buildQueryString(s)}&count=50`;
@@ -494,7 +549,6 @@ export default class DraggableMap {
494
549
  // return this.addGeoMarkers(res);
495
550
  // });
496
551
  // }
497
- this.addMarkersFor(this.properties);
498
552
 
499
553
  // TODO: Rewrite this to use fetch() and update redux store
500
554
  fetch(url)
@@ -506,14 +560,13 @@ export default class DraggableMap {
506
560
  Homeflow.kickEvent('clear_search_box');
507
561
  Homeflow.kickEvent('draggable_map_updated', json);
508
562
  this._running_update = false;
509
- if (this.marker_layer != null) { this.map.removeLayer(this.marker_layer); }
510
563
  this.properties = json.properties;
511
564
 
512
565
  // if (this.tile_view != null) {
513
566
  // this.tile_view = new Ctesius.Views.Tiles({ collection: this.collection });
514
567
  // }
515
568
 
516
- return this.addMarkersFor(json.properties);
569
+ return this.setMarkersFor(json.properties);
517
570
  })
518
571
  // return $.get(url, (res, status, xhr) => {
519
572
  // s.set('performed_data', res);
@@ -529,7 +582,7 @@ export default class DraggableMap {
529
582
  // }
530
583
  // else { }
531
584
  // //@tile_view.collection(@collection)
532
- // return this.addMarkersFor(this.collection.models);
585
+ // return this.setMarkersFor(this.collection.models);
533
586
  // });
534
587
  }
535
588
 
@@ -27,12 +27,12 @@ export default class DrawableMap extends DraggableMap {
27
27
  if (this.drawnItems.getLayers().length === 0) {
28
28
  return super.onMapDrag();
29
29
  } else {
30
- return this.onPolygonDrawn(this.drawnItems.getLayers()[0], true);
30
+ return this.onPolygonDrawn(this.drawnItems.getLayers()[0], false);
31
31
  }
32
32
  }
33
33
 
34
34
  repositionDrawControls() {
35
- let newControlsContainer = Ctesius.getConfig('draw_controls_container');
35
+ let newControlsContainer = Homeflow.get('draw_controls_container');
36
36
  if (newControlsContainer) {
37
37
  const oldControlsContainer = this.drawControl.getContainer().parentNode;
38
38
  newControlsContainer = document.getElementById(newControlsContainer);
@@ -47,65 +47,81 @@ export default class DrawableMap extends DraggableMap {
47
47
  }
48
48
 
49
49
  generateMap(map_options) {
50
- const zoomMax = Ctesius.getConfig('custom_max_zoom') ? Ctesius.getConfig('custom_max_zoom') : 16;
50
+ const zoomMax = Homeflow.get('custom_max_zoom') ? Homeflow.get('custom_max_zoom') : 16;
51
+ const drawLocation = Homeflow.get('custom_map_draw_location') ? Homeflow.get('custom_map_draw_location') : 'bottomleft';
52
+
51
53
  map_options = {
52
54
  minZoom: 6,
53
55
  maxZoom: zoomMax,
54
- scrollWheelZoom: Ctesius.getConfig('enable_scroll_wheel_zoom'),
56
+ scrollWheelZoom: Homeflow.get('enable_scroll_wheel_zoom'),
55
57
  'zoomControl': false,
56
- dragging: !Ctesius.getConfig('disable_drag_for_drawable_map')
58
+ dragging: !Homeflow.get('disable_drag_for_drawable_map')
57
59
  };
60
+ if (!Homeflow.get('custom_map_zoom_location')) Homeflow.set('custom_map_zoom_location', 'topright');
61
+
58
62
  super.generateMap(map_options);
59
63
  this.drawnItems = new L.FeatureGroup();
60
64
  this.map.addLayer(this.drawnItems);
61
- this.titleControl = new L.Control.Title();
62
- this.map.addControl(this.titleControl);
63
- new L.Control.Zoom({ position: "topright" }).addTo(this.map);
64
-
65
-
66
- this.drawControl = new L.Control.Draw({
67
- position: 'topleft',
68
- edit: {
69
- featureGroup: this.drawnItems,
70
- remove: true,
71
- },
72
- draw: {
73
- freehand: {
65
+ if (!Homeflow.get('disable_draw_a_search')) {
66
+ this.titleControl = new L.Control.Title({position: drawLocation});
67
+ this.map.addControl(this.titleControl);
68
+
69
+ this.drawControl = new L.Control.Draw({
70
+ position: drawLocation,
71
+ edit: {
74
72
  featureGroup: this.drawnItems,
75
- simplifyFunction: Hull.parseVertices
73
+ remove: true,
74
+ },
75
+ draw: {
76
+ freehand: {
77
+ featureGroup: this.drawnItems,
78
+ simplifyFunction: Hull.parseVertices
79
+ }
76
80
  }
77
- }
78
- });
81
+ });
79
82
 
80
- this.map.addControl(this.drawControl);
83
+ this.map.addControl(this.drawControl);
84
+ }
81
85
 
82
86
  this.map.on("draw:created", e => {
83
- this.drawnItems.clearLayers();
84
- if (this.marker_layer) { this.map.removeLayer(this.marker_layer); }
85
- if (this.geo_marker_layer != null) { this.map.removeLayer(this.geo_marker_layer); }
86
- if (this.circle != null) { this.map.removeLayer(this.circle); }
87
+ const { additionalDrawing, layer, polyStyle } = e
88
+ if (!additionalDrawing) {
89
+ this.drawnItems.clearLayers();
90
+ if (this.marker_layer) { this.map.removeLayer(this.marker_layer); }
91
+ if (this.geo_marker_layer != null) { this.map.removeLayer(this.geo_marker_layer); }
92
+ if (this.circle != null) { this.map.removeLayer(this.circle); }
93
+ }
87
94
  // TODO: where does `poly` come from?
88
95
  // const poly_updater = map => Ctesius.getSearch().set('poly', decodeURIComponent(poly));
89
- const poly_updater = map => store.dispatch(setSearchField({ poly: base64.encode(poly) }));
96
+ if (!e.placePoly) {
97
+ const poly_updater = map => store.dispatch(setSearchField({ poly: base64.encode(poly) }));
98
+ Homeflow.registerEvent('properties_map_toggle_view_displayed', poly_updater, 1);
99
+ }
100
+ if (polyStyle) {
101
+ if (polyStyle.color) layer.options.color = polyStyle.color;
102
+ if (polyStyle.weight) layer.options.weight = polyStyle.weight;
103
+ if (polyStyle.dashArray) layer.options.dashArray = polyStyle.dashArray;
104
+ if (polyStyle.fillColor) layer.options.fillColor = polyStyle.fillColor;
105
+ else layer.options.fill = false;
106
+ if (polyStyle.fillOpacity) layer.options.fillOpacity = polyStyle.fillOpacity;
90
107
 
91
- Ctesius.registerEvent('properties_map_toggle_view_displayed', poly_updater, 1);
92
- const type = e.layerType;
93
- const {
94
- layer
95
- } = e;
96
- layer.options.color = Ctesius.getConfig('polygon_color');
97
- layer.options.fillColor = Ctesius.getConfig('polygon_fill_color');
98
- layer.options.fillOpacity = 0.1;
99
- if (Ctesius.getConfig('polygon_stroke_weight')) {
100
- layer.options.weight = Ctesius.getConfig('polygon_stroke_weight');
108
+ } else {
109
+ layer.options.color = Homeflow.get('polygon_color');
110
+ layer.options.fillColor = Homeflow.get('polygon_fill_color');
111
+ layer.options.fillOpacity = 0.1;
112
+ if (Homeflow.get('polygon_stroke_weight')) {
113
+ layer.options.weight = Homeflow.get('polygon_stroke_weight');
114
+ }
101
115
  }
102
116
 
103
117
  var poly = layer.encodePath();
104
118
  poly = encodeURIComponent(poly);
105
119
  this.drawnItems.addLayer(layer);
106
- this.onPolygonDrawn(layer);
107
- if (!this.first) {
120
+
121
+ this.onPolygonDrawn(layer, !e.placePoly);
122
+ if (!this.first && !e.placePoly) {
108
123
  this.updateURL();
124
+ if (this.first) { this.first = false; }
109
125
 
110
126
  // TODO: why do we need to make this request that doesn't do anything?
111
127
  // $.get(window.location);
@@ -118,8 +134,6 @@ export default class DrawableMap extends DraggableMap {
118
134
  }
119
135
 
120
136
  Homeflow.kickEvent('poly_url_refresh');
121
-
122
- if (this.first) { this.first = false; }
123
137
  });
124
138
 
125
139
 
@@ -141,7 +155,7 @@ export default class DrawableMap extends DraggableMap {
141
155
  });
142
156
 
143
157
  this.map.on("draw:drawstop", e => {
144
- if (Ctesius.getConfig('fit_bounds_after_draw')) {
158
+ if (Homeflow.get('fit_bounds_after_draw')) {
145
159
  setTimeout((() => {
146
160
  if (this.marker_layer.getLayers().length > 0) {
147
161
  const bounds = [this.drawnItems.getBounds()];
@@ -176,12 +190,13 @@ export default class DrawableMap extends DraggableMap {
176
190
  theMap.removeLayer(layer);
177
191
  }
178
192
  });
179
- this.onMapDrag();
193
+ this.onPolygonDrawn(this.drawnItems.getLayers()[0], true);
194
+
180
195
  return Homeflow.kickEvent('editbannertoggled');
181
196
  });
182
197
 
183
198
  this.map.on("draw:deletestart", e => {
184
- if (Ctesius.getConfig('instant_delete_polygon')) {
199
+ if (Homeflow.get('instant_delete_polygon')) {
185
200
  this.drawnItems.clearLayers();
186
201
  if (this.marker_layer) { this.map.removeLayer(this.marker_layer); }
187
202
  if (this.geo_marker_layer != null) { return this.map.removeLayer(this.geo_marker_layer); }
@@ -197,7 +212,8 @@ export default class DrawableMap extends DraggableMap {
197
212
  let countOfEditedLayers = 0;
198
213
  layers.eachLayer(layer => {
199
214
  const poly = encodeURIComponent(layer.encodePath());
200
- this.onPolygonDrawn(layer);
215
+
216
+ this.onPolygonDrawn(layer, true);
201
217
  this.updateURL();
202
218
  window.location.hash = "map/" + poly;
203
219
  countOfEditedLayers++;
@@ -48,9 +48,10 @@ window.$ = () => ({
48
48
  // )
49
49
  // end
50
50
 
51
- const PropertiesMap = ({ leaflet, gmapKey }) => {
51
+ const PropertiesMap = ({ leaflet, gmapKey, googleLayer }) => {
52
52
  const addLegacyMaps = () => {
53
- if (!leaflet && !document.getElementById('hfjs-gmaps') && gmapKey) {
53
+ const needsGoogle = !leaflet || googleLayer;
54
+ if (needsGoogle && !document.getElementById('hfjs-gmaps') && gmapKey) {
54
55
  const gmapScript = document.createElement('script');
55
56
  gmapScript.setAttribute('src', `https://maps.google.com/maps/api/js?key=${gmapKey}`);
56
57
  gmapScript.setAttribute('id', 'hfjs-gmaps');
@@ -63,6 +64,8 @@ const PropertiesMap = ({ leaflet, gmapKey }) => {
63
64
  script.setAttribute('id', 'hfjs-legacy-maps');
64
65
  script.setAttribute('onload', 'window.initLegacyMap()');
65
66
  document.head.appendChild(script);
67
+ } else {
68
+ window.initLegacyMap();
66
69
  }
67
70
  };
68
71
 
@@ -81,11 +84,13 @@ const PropertiesMap = ({ leaflet, gmapKey }) => {
81
84
  PropertiesMap.propTypes = {
82
85
  gmapKey: PropTypes.string,
83
86
  leaflet: PropTypes.bool,
87
+ googleLayer: PropTypes.bool,
84
88
  };
85
89
 
86
90
  PropertiesMap.defaultProps = {
87
91
  gmapKey: null,
88
92
  leaflet: false,
93
+ googleLayer: false,
89
94
  };
90
95
 
91
96
  const mapStateToProps = (state) => ({
@@ -6,8 +6,7 @@ import {
6
6
  } from 'react-router-dom';
7
7
  import PropTypes from 'prop-types';
8
8
 
9
- import PropertiesList from '../properties-list/properties-list.component';
10
- import PropertiesGrid from '../properties-grid/properties-grid.component';
9
+ import PropertiesDisplay from '../properties-display/properties-display.component';
11
10
 
12
11
  import './property-results.styles.scss';
13
12
 
@@ -22,34 +21,68 @@ const LazyPropertiesMap = (props) => (
22
21
  const PropertyResults = ({
23
22
  GridItem,
24
23
  ListItem,
24
+ MapItem,
25
25
  defaultView,
26
26
  infiniteScroll,
27
27
  noMap,
28
+ inserts,
29
+ ...other
28
30
  }) => (
29
31
  <Router>
30
32
  <Switch>
31
33
  <Route path="/list">
32
- <PropertiesList ListItem={ListItem} infiniteScroll={infiniteScroll} />
34
+ <PropertiesDisplay
35
+ Item={ListItem}
36
+ displayType="list"
37
+ infiniteScroll={infiniteScroll}
38
+ inserts={inserts}
39
+ {...other}
40
+ />
33
41
  </Route>
34
42
 
35
43
  <Route path="/grid">
36
- <PropertiesGrid GridItem={GridItem} infiniteScroll={infiniteScroll} />
44
+ <PropertiesDisplay
45
+ Item={GridItem}
46
+ displayType="grid"
47
+ infiniteScroll={infiniteScroll}
48
+ inserts={inserts}
49
+ {...other}
50
+ />
37
51
  </Route>
38
52
 
39
-
40
- {!noMap && (
41
- <Route path="/map">
53
+ <Route path="/map">
54
+ {noMap ? (
55
+ <PropertiesDisplay
56
+ Item={MapItem}
57
+ displayType="map"
58
+ infiniteScroll={infiniteScroll}
59
+ inserts={inserts}
60
+ {...other}
61
+ />
62
+ ) : (
42
63
  <LazyPropertiesMap />
43
- </Route>
44
- )}
64
+ )}
65
+ </Route>
45
66
 
46
67
  {defaultView === 'grid' ? (
47
68
  <Route>
48
- <PropertiesGrid GridItem={GridItem} infiniteScroll={infiniteScroll} />
69
+ <PropertiesDisplay
70
+ Item={GridItem}
71
+ displayType="grid"
72
+ infiniteScroll={infiniteScroll}
73
+ inserts={inserts}
74
+ {...other}
75
+ />
49
76
  </Route>
50
77
  ) : (
51
78
  <Route>
52
- <PropertiesList ListItem={ListItem} infiniteScroll={infiniteScroll} />
79
+ <PropertiesDisplay
80
+ Item={ListItem}
81
+ displayType="list"
82
+ infiniteScroll={infiniteScroll}
83
+ inserts={inserts}
84
+ {...other}
85
+ />
53
86
  </Route>
54
87
  )}
55
88
  </Switch>
@@ -59,17 +92,21 @@ const PropertyResults = ({
59
92
  PropertyResults.propTypes = {
60
93
  GridItem: PropTypes.elementType,
61
94
  ListItem: PropTypes.elementType,
95
+ MapItem: PropTypes.elementType,
62
96
  defaultView: PropTypes.string,
63
97
  infiniteScroll: PropTypes.bool,
64
98
  noMap: PropTypes.bool,
99
+ inserts: PropTypes.array,
65
100
  };
66
101
 
67
102
  PropertyResults.defaultProps = {
68
103
  GridItem: null,
69
104
  ListItem: null,
105
+ MapItem: null,
70
106
  defaultView: 'list',
71
107
  infiniteScroll: false,
72
108
  noMap: false,
109
+ inserts: null,
73
110
  };
74
111
 
75
112
  export default PropertyResults;
@@ -4,6 +4,7 @@ const INITIAL_STATE = {
4
4
  properties: [],
5
5
  savedProperties: [],
6
6
  pagination: {},
7
+ selectedMarker: null,
7
8
  propertyLinks: {
8
9
  next: '',
9
10
  previous: '',
@@ -37,11 +38,11 @@ const propertiesReducer = (state = INITIAL_STATE, action) => {
37
38
  ...state,
38
39
  propertyLinks: action.payload,
39
40
  };
40
- case PropertiesActionTypes.TOGGLE_SAVED_PROPERTY:
41
+ case PropertiesActionTypes.TOGGLE_SAVED_PROPERTY: {
41
42
  const propertyId = parseInt(action.payload.propertyId, 10);
42
43
  const newSavedProperties = [...state.savedProperties];
43
44
 
44
- const index = newSavedProperties.findIndex(({ property_id }) => property_id === propertyId);
45
+ const index = newSavedProperties.findIndex(({ property_id: searchId }) => searchId === propertyId);
45
46
 
46
47
  if (index > -1) {
47
48
  newSavedProperties.splice(index, 1);
@@ -53,22 +54,28 @@ const propertiesReducer = (state = INITIAL_STATE, action) => {
53
54
  return {
54
55
  ...state,
55
56
  savedProperties: newSavedProperties,
56
- }
57
- } else {
58
- // need to find the property from the propertiesReducer and add it here
59
- const property = state.properties.find(({ property_id }) => property_id === propertyId);
57
+ };
58
+ }
60
59
 
61
- newSavedProperties.push(property || state.property);
60
+ // need to find the property from the propertiesReducer and add it here
61
+ const property = state.properties.find(({ property_id: searchId }) => searchId === propertyId);
62
62
 
63
- if (!action.payload.userLoggedIn) {
64
- localStorage.setItem('savedProperties', JSON.stringify(newSavedProperties));
65
- }
63
+ newSavedProperties.push(property || state.property);
66
64
 
67
- return {
68
- ...state,
69
- savedProperties: newSavedProperties,
70
- }
65
+ if (!action.payload.userLoggedIn) {
66
+ localStorage.setItem('savedProperties', JSON.stringify(newSavedProperties));
71
67
  }
68
+
69
+ return {
70
+ ...state,
71
+ savedProperties: newSavedProperties,
72
+ };
73
+ }
74
+ case PropertiesActionTypes.SET_SELECTED_MARKER:
75
+ return {
76
+ ...state,
77
+ selectedMarker: action.payload,
78
+ };
72
79
  default:
73
80
  return state;
74
81
  }
@@ -15,6 +15,14 @@ describe('propertiesReducer', () => {
15
15
  expect(reducedState.pagination).toEqual({ page: 2 });
16
16
  });
17
17
 
18
+ it('Sets the selected propertymarker when it receives the SET_SELECTED_MARKER action', () => {
19
+ const state = {};
20
+ const leafletMaker = { lat: 1, lng: 2 };
21
+ const reducedState = propertiesReducer(state, { type: 'SET_SELECTED_MARKER', payload: leafletMaker });
22
+
23
+ expect(reducedState.selectedMarker).toEqual(leafletMaker);
24
+ });
25
+
18
26
  it('Sets the saved properties when it receives the SET_SAVED_PROPERTIES action', () => {
19
27
  const state = { savedProperties: [] };
20
28
  const reducedState = propertiesReducer(state, { type: 'SET_SAVED_PROPERTIES', payload: [1, 2, 3, 4, 5] });
@@ -17,6 +17,7 @@ const NormalSelect = ({
17
17
  optionClass,
18
18
  minBeds,
19
19
  maxBeds,
20
+ placeholder,
20
21
  ...otherProps
21
22
  }) => (
22
23
  <select
@@ -32,7 +33,7 @@ const NormalSelect = ({
32
33
  title={`${type} beds`}
33
34
  className={optionClass}
34
35
  >
35
- {capitalizeFirstLetter(type)}. bedrooms
36
+ {placeholder || `${capitalizeFirstLetter(type)}. bedrooms`}
36
37
  </option>
37
38
  {bedValues.map((_, i) => (
38
39
  <option
@@ -55,6 +56,7 @@ const ReactSelect = (props) => {
55
56
  className,
56
57
  styles,
57
58
  value,
59
+ placeholder,
58
60
  } = props;
59
61
 
60
62
  const bedOptions = bedValues.map(bedValue => (
@@ -69,7 +71,7 @@ const ReactSelect = (props) => {
69
71
  className={className}
70
72
  options={bedOptions}
71
73
  styles={styles}
72
- placeholder={`${capitalizeFirstLetter(type)} bedrooms`}
74
+ placeholder={placeholder || `${capitalizeFirstLetter(type)} bedrooms`}
73
75
  value={bedOptions.find(option => option.value === value)}
74
76
  setValue={() => value}
75
77
  isSearchable={false}
@@ -118,12 +120,14 @@ BedroomsSelect.propTypes = {
118
120
  maxBeds: PropTypes.number,
119
121
  setSearchField: PropTypes.func.isRequired,
120
122
  optionClass: PropTypes.string,
123
+ placeholder: PropTypes.string,
121
124
  };
122
125
 
123
126
  BedroomsSelect.defaultProps = {
124
127
  bedValues: defaultBedrooms,
125
128
  optionClass: '',
126
- }
129
+ placeholder: null,
130
+ };
127
131
 
128
132
  const mapStateToProps = ({ search: { currentSearch } }) => ({
129
133
  minBeds: currentSearch.minBeds,
@@ -27,7 +27,9 @@ export const buildQueryString = (search) => {
27
27
  if (Array.isArray(value) && !value.length) continue;
28
28
  // only include either q or place ID
29
29
  if (key === 'q' && !search.isQuerySearch
30
- || key === 'placeId' && search.isQuerySearch) continue;
30
+ || key === 'placeId' && search.isQuerySearch
31
+ || key === 'place' && search.isQuerySearch
32
+ || key === 'poly' && search.isQuerySearch) continue;
31
33
 
32
34
  // Don't include branch_id if a search term has been entered
33
35
  if (key === 'branch_id' && search.isQuerySearch) continue;
@@ -45,6 +45,11 @@ class SearchForm extends Component {
45
45
  q: place.name + (place.county_name ? `, ${place.county_name}` : ''),
46
46
  });
47
47
  }
48
+ const expandedPolygon = Homeflow.get('expanded_polygon');
49
+ if (expandedPolygon) {
50
+ setSearchField({ expandedPolygon });
51
+ setInitialSearch({ expandedPolygon });
52
+ }
48
53
 
49
54
  if (defaultChannel) setSearchField({ channel: defaultChannel });
50
55
 
@@ -55,7 +60,6 @@ class SearchForm extends Component {
55
60
 
56
61
  handleSubmit(e) {
57
62
  const { search, submitCallback } = this.props;
58
-
59
63
  e.preventDefault();
60
64
 
61
65
  if (submitCallback) submitCallback();