Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
Merge pull request #749 from open-apparel-registry/ki/handle-marker-c…
Browse files Browse the repository at this point in the history
…licks

Use selected marker for selected facility & set map view conditionally

Connects #739
  • Loading branch information
Kelly Innes committed Aug 28, 2019
2 parents 07571a1 + 2a87040 commit af0667a
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]
### Added
- Adjust marker icon on selecting a new facility on the vector tiles layer [#749](https://github.com/open-apparel-registry/open-apparel-registry/pull/749)

### Changed

Expand Down
76 changes: 59 additions & 17 deletions src/app/src/components/VectorTileFacilitiesLayer.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { bool, func, number, string } from 'prop-types';
import { connect } from 'react-redux';
import VectorGridDefault from 'react-leaflet-vectorgrid';
import { withLeaflet } from 'react-leaflet';
import L from 'leaflet';
import isEmpty from 'lodash/isEmpty';
import get from 'lodash/get';

import { createQueryStringFromSearchFilters } from '../util/util';

const VectorGrid = withLeaflet(VectorGridDefault);

const createMarkerIcon = iconUrl => L.icon({
iconUrl,
iconSize: [30, 40],
iconAnchor: [15, 40],
});

const selectedMarkerIcon = createMarkerIcon('/images/selectedmarker.png');
const unselectedMarkerIcon = createMarkerIcon('/images/marker.png');

function useUpdateTileURL(tileURL, performingNewSearch, resetButtonClickCount) {
const [
vectorTileURLWithQueryParams,
Expand Down Expand Up @@ -46,13 +56,41 @@ function useUpdateTileURL(tileURL, performingNewSearch, resetButtonClickCount) {
return vectorTileURLWithQueryParams;
}

const useUpdateTileLayerWithMarkerForSelectedOARID = (oarID) => {
const tileLayerRef = useRef(null);

const [currentSelectedMarkerID, setCurrentSelectedMarkerID] = useState(oarID);

useEffect(() => {
if (tileLayerRef && (oarID !== currentSelectedMarkerID)) {
const tileLayer = get(
tileLayerRef,
'current.leafletElement',
);

tileLayer.setFeatureStyle(currentSelectedMarkerID, {
icon: unselectedMarkerIcon,
});

tileLayer.setFeatureStyle(oarID, {
icon: selectedMarkerIcon,
});

setCurrentSelectedMarkerID(oarID);
}
}, [oarID, currentSelectedMarkerID, setCurrentSelectedMarkerID, tileLayerRef]);

return tileLayerRef;
};

const VectorTileFacilitiesLayer = ({
tileURL,
handleClick,
handleMarkerClick,
fetching,
resetButtonClickCount,
tileCacheKey,
getNewCacheKey,
oarID,
}) => {
const vectorTileURL = useUpdateTileURL(
tileURL,
Expand All @@ -61,6 +99,8 @@ const VectorTileFacilitiesLayer = ({
getNewCacheKey,
);

const vectorTileLayerRef = useUpdateTileLayerWithMarkerForSelectedOARID(oarID);

if (!tileCacheKey) {
// We throw an error here if the tile cache key is missing.
// This crashes the map, intentionally, but an ErrorBoundary
Expand All @@ -70,33 +110,42 @@ const VectorTileFacilitiesLayer = ({

return (
<VectorGrid
ref={vectorTileLayerRef}
key={vectorTileURL}
url={vectorTileURL}
type="protobuf"
rendererFactory={L.canvas.tile}
vectorTileLayerStyles={{
facilities: {
icon: L.icon({
iconUrl: '/images/marker.png',
iconSize: [30, 40],
iconAnchor: [15, 40],
}),
facilities(properties) {
const facilityID = get(properties, 'id', null);

return {
icon: (oarID && facilityID === oarID)
? selectedMarkerIcon
: unselectedMarkerIcon,
};
},
}}
subdomains=""
zIndex={100}
interactive
onClick={handleClick}
onClick={handleMarkerClick}
getFeatureId={f => get(f, 'properties.id', null)}
/>
);
};

VectorTileFacilitiesLayer.defaultProps = {
oarID: null,
};

VectorTileFacilitiesLayer.propTypes = {
handleClick: func.isRequired,
handleMarkerClick: func.isRequired,
tileURL: string.isRequired,
tileCacheKey: string.isRequired,
fetching: bool.isRequired,
resetButtonClickCount: number.isRequired,
oarID: string,
};

const createURLWithQueryString = (qs, key) =>
Expand Down Expand Up @@ -127,13 +176,6 @@ function mapStateToProps({
};
}

function mapDispatchToProps() {
return {
handleClick: ({ layer }) => window.console.log(layer),
};
}

export default connect(
mapStateToProps,
mapDispatchToProps,
)(VectorTileFacilitiesLayer);
152 changes: 147 additions & 5 deletions src/app/src/components/VectorTileFacilitiesMap.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import { bool, number, string } from 'prop-types';
import { arrayOf, bool, func, number, shape, string } from 'prop-types';
import { connect } from 'react-redux';
import { Map as ReactLeafletMap, ZoomControl } from 'react-leaflet';
import ReactLeafletGoogleLayer from 'react-leaflet-google-layer';
Expand All @@ -9,15 +9,23 @@ import { CopyToClipboard } from 'react-copy-to-clipboard';
import { toast } from 'react-toastify';
import noop from 'lodash/noop';
import get from 'lodash/get';
import head from 'lodash/head';
import last from 'lodash/last';
import delay from 'lodash/delay';

import Button from './Button';
import VectorTileFacilitiesLayer from './VectorTileFacilitiesLayer';

import { COUNTRY_CODES } from '../util/constants';

import { makeFacilityDetailLink } from '../util/util';

import { facilityDetailsPropType } from '../util/propTypes';

import {
initialCenter,
initialZoom,
detailsZoomLevel,
GOOGLE_CLIENT_SIDE_API_KEY,
} from '../util/constants.facilitiesMap';

Expand All @@ -33,9 +41,95 @@ const mapComponentStyles = Object.freeze({
}),
});

function useUpdateLeafletMapImperatively(resetButtonClickCount) {
function useUpdateLeafletMapImperatively(
resetButtonClickCount,
{ oarID, data, error, fetching },
) {
const mapRef = useRef(null);

// Set the map view on a facility location if the user has arrived
// directly from a URL containing a valid OAR ID
const [
shouldSetViewOnReceivingData,
setShouldSetViewOnReceivingData,
] = useState(!!oarID);

useEffect(() => {
if (shouldSetViewOnReceivingData) {
if (data) {
const leafletMap = get(mapRef, 'current.leafletElement', null);

const facilityLocation = get(
data,
'geometry.coordinates',
null,
);

if (leafletMap && facilityLocation) {
leafletMap.setView(
{
lng: head(facilityLocation),
lat: last(facilityLocation),
},
detailsZoomLevel,
);
}

setShouldSetViewOnReceivingData(false);
} else if (error) {
setShouldSetViewOnReceivingData(false);
}
}
}, [
shouldSetViewOnReceivingData,
setShouldSetViewOnReceivingData,
data,
error,
]);

// Set the map view on the facility location if it is not within the
// current viewport bbox
const [appIsGettingFacilityData, setAppIsGettingFacilityData] = useState(fetching);

useEffect(() => {
if (shouldSetViewOnReceivingData) {
noop();
} else if (fetching && !appIsGettingFacilityData) {
setAppIsGettingFacilityData(true);
} else if (!fetching && appIsGettingFacilityData && data) {
const leafletMap = get(mapRef, 'current.leafletElement', null);
const facilityLocation = get(data, 'geometry.coordinates', null);

delay(
() => {
if (leafletMap && facilityLocation) {
const facilityLatLng = {
lng: head(facilityLocation),
lat: last(facilityLocation),
};

const mapBoundsContainsFacility = leafletMap
.getBounds()
.contains(facilityLatLng);

if (!mapBoundsContainsFacility) {
leafletMap.setView(facilityLatLng);
}
}

setAppIsGettingFacilityData(false);
},
0,
);
}
}, [
fetching,
appIsGettingFacilityData,
setAppIsGettingFacilityData,
data,
shouldSetViewOnReceivingData,
]);

// Reset the map state when the reset button is clicked
const [
currentResetButtonClickCount,
Expand Down Expand Up @@ -65,8 +159,20 @@ function VectorTileFacilitiesMap({
resetButtonClickCount,
clientInfoFetched,
countryCode,
handleMarkerClick,
match: {
params: { oarID },
},
facilityDetailsData,
errorFetchingFacilityDetailsData,
fetchingDetailsData,
}) {
const mapRef = useUpdateLeafletMapImperatively(resetButtonClickCount);
const mapRef = useUpdateLeafletMapImperatively(resetButtonClickCount, {
oarID,
data: facilityDetailsData,
fetching: fetchingDetailsData,
error: errorFetchingFacilityDetailsData,
});

if (!clientInfoFetched) {
return null;
Expand Down Expand Up @@ -108,28 +214,64 @@ function VectorTileFacilitiesMap({
</CopyToClipboard>
</Control>
<ZoomControl position="bottomright" />
<VectorTileFacilitiesLayer />
<VectorTileFacilitiesLayer
handleMarkerClick={handleMarkerClick}
oarID={oarID}
/>
</ReactLeafletMap>
);
}

VectorTileFacilitiesMap.defaultProps = {
facilityDetailsData: null,
errorFetchingFacilityDetailsData: null,
};

VectorTileFacilitiesMap.propTypes = {
resetButtonClickCount: number.isRequired,
clientInfoFetched: bool.isRequired,
countryCode: string.isRequired,
handleMarkerClick: func.isRequired,
match: shape({
params: shape({
oarID: string,
}),
}).isRequired,
facilityDetailsData: facilityDetailsPropType,
errorFetchingFacilityDetailsData: arrayOf(string),
fetchingDetailsData: bool.isRequired,
};

function mapStateToProps({
ui: {
facilitiesSidebarTabSearch: { resetButtonClickCount },
},
clientInfo: { fetched, countryCode },
facilities: {
singleFacility: { data, error, fetching },
},
}) {
return {
resetButtonClickCount,
clientInfoFetched: fetched,
countryCode: countryCode || COUNTRY_CODES.default,
facilityDetailsData: data,
errorFetchingFacilityDetailsData: error,
fetchingDetailsData: fetching,
};
}

function mapDispatchToProps(_, { history: { push } }) {
return {
handleMarkerClick: (e) => {
const oarID = get(e, 'layer.properties.id', null);

return oarID ? push(makeFacilityDetailLink(oarID)) : noop();
},
};
}

export default connect(mapStateToProps)(VectorTileFacilitiesMap);
export default connect(
mapStateToProps,
mapDispatchToProps,
)(VectorTileFacilitiesMap);

0 comments on commit af0667a

Please sign in to comment.