SearchPage: open mobile SearchMap in modal

This commit is contained in:
Vesa Luusua 2017-08-18 15:44:55 +03:00
parent fc4336f0f7
commit 234b2a69d7
5 changed files with 155 additions and 11 deletions

View file

@ -0,0 +1,62 @@
import React, { PropTypes } from 'react';
const MapIcon = props => {
const { className } = props;
return (
<svg
className={className}
width="27"
height="27"
viewBox="0 0 27 27"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<filter
x="-3.8%"
y="-10.4%"
width="107.7%"
height="129.2%"
filterUnits="objectBoundingBox"
id="a"
>
<feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feColorMatrix
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"
in="shadowBlurOuter1"
result="shadowMatrixOuter1"
/>
<feMerge>
<feMergeNode in="shadowMatrixOuter1" /><feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<g
filter="url(#a)"
transform="translate(-41 -12)"
strokeWidth="1.4"
fill="none"
fillRule="evenodd"
strokeLinecap="round"
strokeLinejoin="round"
>
<path
d="M51.667 32L46 27.952V15l5.667 4.048L57.333 15 63 19.048V32l-5.667-4.048zM51.7 19v13M57.3 28V15"
/>
</g>
</svg>
);
};
const { string } = PropTypes;
MapIcon.defaultProps = {
className: null,
};
MapIcon.propTypes = {
className: string,
};
export default MapIcon;

View file

@ -73,8 +73,46 @@
flex-grow: 1;
}
.openMobileMap {
/* Font */
@apply --marketplaceH4FontStyles;
/* Positioning */
position: fixed;
bottom: 48px;
left: 50vw;
transform: translateX(-50%);
margin-top: 0;
margin-bottom: 0;
/* Button's layout */
padding: 12px 24px;
background-color: var(--matterColorLight);
border-radius: 24px;
border: 0;
box-shadow: var(--boxShadow);
cursor: pointer;
transition: var(--transitionStyleButton);
& .openMobileMapIcon {
stroke: var(--matterColor);
}
&:hover {
color: var(--marketplaceColor);
box-shadow: var(--boxShadowPopupLight);
& .openMobileMapIcon {
stroke: var(--marketplaceColor);
}
}
@media (--viewportMedium) {
display: none;
}
}
.mapPanel {
display: none;
@media (--viewportMedium) {
/**
@ -94,6 +132,8 @@
}
.map {
width: 100vw;
height: 100vh;
@media (--viewportMedium) {
/* Map is fixed so that it doesn't scroll along search results */

View file

@ -14,7 +14,9 @@ import { getListingsById } from '../../ducks/marketplaceData.duck';
import { logout, authenticationInProgress } from '../../ducks/Auth.duck';
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
import { SearchMap, ModalInMobile, PageLayout, SearchResultsPanel, Topbar } from '../../components';
import { searchListings, searchMapListings } from './SearchPage.duck';
import MapIcon from './MapIcon';
import css from './SearchPage.css';
// Pagination page size might need to be dynamic on responsive page layouts
@ -24,6 +26,7 @@ const RESULT_PAGE_SIZE = 12;
const SHARETRIBE_API_MAX_PAGE_SIZE = 100;
const MAX_SEARCH_RESULT_PAGES_ON_MAP = 5; // 100 * 5 = 500 listings are shown on a map.
const DEBOUNCE_MAP_BOUNDS_CHANGE = 500; // bounds_change event is fired too often while dragging
const MODAL_BREAKPOINT = 768; /* Search is in modal on mobile layout */
const pickSearchParamsOnly = params => {
const { address, origin, bounds } = params || {};
@ -34,11 +37,16 @@ export class SearchPageComponent extends Component {
constructor(props) {
super(props);
this.state = {
isSearchMapOpenOnMobile: false,
};
// Initiating map creates 'bounds_changes' event
// we listen to that event to make new searches
// So, if the search comes from location search input,
// we need to by pass 2nd search created by initial 'bounds_changes' event
this.useLocationSearchBounds = true;
this.modalOpenedBoundsChange = false;
this.onBoundsChanged = debounce(this.onBoundsChanged.bind(this), DEBOUNCE_MAP_BOUNDS_CHANGE);
this.fetchMoreListingsToMap = this.fetchMoreListingsToMap.bind(this);
@ -71,9 +79,10 @@ export class SearchPageComponent extends Component {
latlngBounds: ['bounds'],
});
// If boundsChanged url param is given or original location search is rendered once,
// If boundsChanged url param is given (and we have not just opened mobile map modal)
// or original location search is rendered once,
// we start to react to 'bounds_changed' event by generating new searches
if (boundsChanged || (!this.useLocationSearchBounds && !boundsChanged)) {
if ((boundsChanged && !this.modalOpenedBoundsChange) || !this.useLocationSearchBounds) {
const viewportBounds = googleMap.getBounds();
const bounds = googleBoundsToSDKBounds(viewportBounds);
const origin = googleLatLngToSDKLatLng(viewportBounds.getCenter());
@ -82,6 +91,7 @@ export class SearchPageComponent extends Component {
history.push(createResourceLocatorString('SearchPage', flattenedRoutes, {}, searchParams));
} else {
this.useLocationSearchBounds = false;
this.modalOpenedBoundsChange = false;
}
}
@ -189,6 +199,18 @@ export class SearchPageComponent extends Component {
</h2>
);
const searchMap = (
<SearchMap
bounds={bounds}
center={origin}
listings={mapListings || []}
onBoundsChanged={this.onBoundsChanged}
isOpenOnModal={this.state.isSearchMapOpenOnMobile}
/>
);
const showSearchMapInMobile = this.state.isSearchMapOpenOnMobile ? searchMap : null;
const searchMapMaybe = window.innerWidth < MODAL_BREAKPOINT ? showSearchMapInMobile : searchMap;
const searchParamsForPagination = parse(location.search);
return (
@ -227,20 +249,28 @@ export class SearchPageComponent extends Component {
search={searchParamsForPagination}
/>
</div>
<button
className={css.openMobileMap}
onClick={() => {
this.useLocationSearchBounds = true;
this.modalOpenedBoundsChange = true;
this.setState({ isSearchMapOpenOnMobile: true });
}}
>
<MapIcon className={css.openMobileMapIcon} />
<FormattedMessage id="SearchPage.openMapView" />
</button>
</div>
<ModalInMobile
className={css.mapPanel}
id="SearchPage.map"
isModalOpenOnMobile={false}
isModalOpenOnMobile={this.state.isSearchMapOpenOnMobile}
onClose={() => this.setState({ isSearchMapOpenOnMobile: false })}
showAsModalMaxWidth={MODAL_BREAKPOINT}
onManageDisableScrolling={onManageDisableScrolling}
>
<div className={css.map}>
<SearchMap
bounds={bounds}
center={origin}
listings={mapListings || []}
onBoundsChanged={this.onBoundsChanged}
/>
{searchMapMaybe}
</div>
</ModalInMobile>
</div>

View file

@ -61,11 +61,21 @@ exports[`SearchPageComponent matches snapshot 1`] = `
rootClassName={null}
search={Object {}} />
</div>
<button
onClick={[Function]}>
<MapIcon
className={null} />
<FormattedMessage
id="SearchPage.openMapView"
values={Object {}} />
</button>
</div>
<withViewport(ModalInMobileComponent)
id="SearchPage.map"
isModalOpenOnMobile={false}
onManageDisableScrolling={[Function]}>
onClose={[Function]}
onManageDisableScrolling={[Function]}
showAsModalMaxWidth={768}>
<div>
<SearchMapComponent
bounds={null}
@ -76,6 +86,7 @@ exports[`SearchPageComponent matches snapshot 1`] = `
}
}
className=""
isOpenOnModal={false}
listings={Array []}
mapRootClassName={null}
onBoundsChanged={[Function]}

View file

@ -251,6 +251,7 @@
"SearchPage.loadingResults": "Loading search results…",
"SearchPage.loadingResults": "Loading search results…",
"SearchPage.noResults": "Could not find any listings.",
"SearchPage.openMapView": "Map view",
"SearchPage.searchError": "Search failed. Please try again.",
"SearchResultsPanel.nextPage": "Next page",
"SearchResultsPanel.previousPage": "Previous page",