digitransit-ui/app/component/MapLayersDialogContent.js

372 lines
12 KiB
JavaScript

/* eslint react/forbid-prop-types: 0 */
import PropTypes from 'prop-types';
import React, { Fragment } from 'react';
import { intlShape } from 'react-intl';
import { matchShape, routerShape } from 'found';
import connectToStores from 'fluxible-addons-react/connectToStores';
import { configShape, mapLayerOptionsShape } from '../util/shapes';
import { isKeyboardSelectionEvent } from '../util/browser';
import Icon from './Icon';
import Checkbox from './Checkbox';
import GeoJsonStore from '../store/GeoJsonStore';
import MapLayerStore, { mapLayerShape } from '../store/MapLayerStore';
import { updateMapLayers } from '../action/MapLayerActions';
import { addAnalyticsEvent } from '../util/analyticsUtils';
import withGeojsonObjects from './map/withGeojsonObjects';
import { getTransportModes, showRentalVehiclesOfType } from '../util/modeUtils';
import { TransportMode } from '../constants';
const transportModeconfigShape = PropTypes.shape({
availableForSelection: PropTypes.bool,
});
const geoJsonConfigShape = PropTypes.shape({
layers: PropTypes.arrayOf(
PropTypes.shape({
url: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(PropTypes.string),
]).isRequired,
name: PropTypes.shape({
en: PropTypes.string,
fi: PropTypes.string.isRequired,
sv: PropTypes.string,
}),
}),
),
});
const mapLayersconfigShape = PropTypes.shape({
vehicleRental: PropTypes.shape({
networks: PropTypes.object,
}),
geoJson: geoJsonConfigShape,
parkAndRide: PropTypes.shape({
showParkAndRide: PropTypes.bool,
}),
parkAndRideForBikes: PropTypes.shape({
showParkAndRideForBikes: PropTypes.bool,
}),
transportModes: PropTypes.shape({
bus: transportModeconfigShape,
citybike: transportModeconfigShape,
ferry: transportModeconfigShape,
rail: transportModeconfigShape,
subway: transportModeconfigShape,
tram: transportModeconfigShape,
scooter: transportModeconfigShape,
}),
mapLayers: PropTypes.shape({
tooltip: PropTypes.shape({
en: PropTypes.string,
fi: PropTypes.string.isRequired,
sv: PropTypes.string,
}),
}),
vehicles: PropTypes.bool,
});
const sendLayerChangeAnalytic = (name, enable) => {
const action = enable ? 'ShowMapLayer' : 'HideMapLayer';
addAnalyticsEvent({
category: 'Map',
action,
name,
});
};
class MapLayersDialogContent extends React.Component {
static propTypes = {
mapLayers: mapLayerShape.isRequired,
mapLayerOptions: mapLayerOptionsShape,
setOpen: PropTypes.func.isRequired,
updateMapLayers: PropTypes.func.isRequired,
lang: PropTypes.string.isRequired,
open: PropTypes.bool.isRequired,
geoJson: geoJsonConfigShape,
};
static defaultProps = {
mapLayerOptions: null,
geoJson: undefined,
};
handlePanelState(open) {
if (open === this.props.open) {
return;
}
this.props.setOpen(open);
}
updateSetting = newSetting => {
this.props.updateMapLayers({
...newSetting,
});
};
updateStopSetting = newSetting => {
const stop = {
...newSetting,
};
this.updateSetting({ stop });
};
updateGeoJsonSetting = newSetting => {
const geoJson = {
...this.props.mapLayers.geoJson,
...newSetting,
};
this.updateSetting({ geoJson });
};
render() {
const {
citybike,
parkAndRide,
parkAndRideForBikes,
stop,
geoJson,
vehicles,
scooter,
} = this.props.mapLayers;
let arr;
if (this.props.geoJson) {
arr = Object.entries(this.props.geoJson)?.map(([k, v]) => {
return { url: k, ...v };
});
}
const isTransportModeEnabled = transportMode =>
transportMode && transportMode.availableForSelection;
const { config } = this.context;
const transportModes = getTransportModes(config);
return (
<Fragment>
<button
className="panel-close"
onClick={() => this.handlePanelState(false)}
onKeyDown={e =>
isKeyboardSelectionEvent(e) && this.handlePanelState(false)
}
type="button"
>
<Icon img="icon-icon_close" />
</button>
<span className="map-layer-header">
{this.context.intl.formatMessage({
id: 'select-map-layers-header',
defaultMessage: 'Bubble Dialog Header',
})}
</span>
<div className="checkbox-grouping" />
{config.vehicles && (
<div className="checkbox-grouping">
<Checkbox
large
checked={
!this.props.mapLayerOptions
? vehicles
: !!this.props.mapLayerOptions?.vehicles?.isLocked &&
!!this.props.mapLayerOptions?.vehicles?.isSelected
}
disabled={!!this.props.mapLayerOptions?.vehicles?.isLocked}
defaultMessage="Moving vehicles"
labelId="map-layer-vehicles"
onChange={e => {
this.updateSetting({ vehicles: e.target.checked });
sendLayerChangeAnalytic('Vehicles', e.target.checked);
}}
/>
</div>
)}
<div className="checkbox-grouping">
{isTransportModeEnabled(transportModes.bus) && (
<Checkbox
large
checked={stop.bus}
disabled={!!this.props.mapLayerOptions?.stop?.bus?.isLocked}
defaultMessage="Bus stop"
labelId="map-layer-stop-bus"
onChange={e => {
this.updateStopSetting({ bus: e.target.checked });
sendLayerChangeAnalytic('BusStop', e.target.checked);
}}
/>
)}
{isTransportModeEnabled(transportModes.tram) && (
<Checkbox
large
checked={stop.tram}
disabled={!!this.props.mapLayerOptions?.stop?.tram?.isLocked}
defaultMessage="Tram stop"
labelId="map-layer-stop-tram"
onChange={e => {
this.updateStopSetting({ tram: e.target.checked });
sendLayerChangeAnalytic('TramStop', e.target.checked);
}}
/>
)}
{isTransportModeEnabled(transportModes.ferry) && (
<Checkbox
large
checked={stop.ferry}
disabled={!!this.props.mapLayerOptions?.stop?.ferry?.isLocked}
defaultMessage="Ferry"
labelId="map-layer-stop-ferry"
onChange={e => {
this.updateStopSetting({ ferry: e.target.checked });
sendLayerChangeAnalytic('FerryStop', e.target.checked);
}}
/>
)}
{showRentalVehiclesOfType(
config.vehicleRental?.networks,
config,
TransportMode.Citybike,
) && (
<Checkbox
large
checked={citybike}
disabled={!!this.props.mapLayerOptions?.citybike?.isLocked}
defaultMessage="Citybike station"
labelId="map-layer-citybike"
onChange={e => {
this.updateSetting({ citybike: e.target.checked });
sendLayerChangeAnalytic('Citybike', e.target.checked);
}}
/>
)}
{showRentalVehiclesOfType(
config.vehicleRental?.networks,
config,
TransportMode.Scooter,
) && (
<Checkbox
large
checked={scooter}
disabled={!!this.props.mapLayerOptions?.scooter?.isLocked}
defaultMessage="Scooters"
labelId="map-layer-scooter"
onChange={e => {
this.updateSetting({ scooter: e.target.checked });
sendLayerChangeAnalytic('Scooter', e.target.checked);
}}
/>
)}
{isTransportModeEnabled(transportModes.funicular) && (
<Checkbox
large
checked={stop.funicular}
disabled={!!this.props.mapLayerOptions?.stop?.funicular?.isLocked}
defaultMessage="Funicular"
labelId="map-layer-stop-funicular"
onChange={e => {
this.updateStopSetting({ funicular: e.target.checked });
sendLayerChangeAnalytic('FunicularStop', e.target.checked);
}}
/>
)}
{config.parkAndRide?.showParkAndRide && (
<Checkbox
large
checked={parkAndRide}
disabled={!!this.props.mapLayerOptions?.parkAndRide?.isLocked}
defaultMessage="Park &amp; ride"
labelId="map-layer-park-and-ride"
onChange={e => {
this.updateSetting({ parkAndRide: e.target.checked });
sendLayerChangeAnalytic('ParkAndRide', e.target.checked);
}}
/>
)}
{config.parkAndRide?.showParkAndRideForBikes && (
<Checkbox
large
checked={parkAndRideForBikes}
disabled={
!!this.props.mapLayerOptions?.parkAndRideForBikes?.isLocked
}
defaultMessage="Park &amp; ride bike parking"
labelId="map-layer-park-and-ride-bike"
onChange={e => {
this.updateSetting({ parkAndRideForBikes: e.target.checked });
sendLayerChangeAnalytic(
'ParkAndRideForBikes',
e.target.checked,
);
}}
/>
)}
</div>
{arr && Array.isArray(arr) && (
<div className="checkbox-grouping">
{arr.map(gj => (
<Checkbox
large
checked={
(gj.isOffByDefault && geoJson[gj.url] === true) ||
(!gj.isOffByDefault && geoJson[gj.url] !== false)
}
defaultMessage={gj.name[this.props.lang]}
key={gj.url}
onChange={e => {
const newSetting = {};
newSetting[gj.url] = e.target.checked;
this.updateGeoJsonSetting(newSetting);
sendLayerChangeAnalytic('Zones', e.target.checked);
}}
/>
))}
</div>
)}
</Fragment>
);
}
}
MapLayersDialogContent.contextTypes = {
config: configShape.isRequired,
intl: intlShape.isRequired,
router: routerShape.isRequired,
match: matchShape.isRequired,
};
/**
* Retrieves the list of geojson layers in use from the configuration or
* the geojson store. If no layers exist in these sources, the
* defaultValue is returned.
*
* @param {*} config the configuration for the software installation.
* @param {*} store the geojson store.
* @param {*} defaultValue the default value, defaults to undefined.
*/
export const getGeoJsonLayersOrDefault = (
config,
store,
defaultValue = undefined,
) => {
return (
(Array.isArray(config.geoJson?.layers) && config.geoJson.layers) ||
(store && Array.isArray(store.layers) && store.layers) ||
defaultValue
);
};
const connectedComponent = connectToStores(
withGeojsonObjects(MapLayersDialogContent),
[GeoJsonStore, MapLayerStore, 'PreferencesStore'],
({ config, executeAction, getStore }) => ({
config: {
...config,
geoJson: {
layers: getGeoJsonLayersOrDefault(config, getStore(GeoJsonStore)),
},
},
updateMapLayers: mapLayers =>
executeAction(updateMapLayers, { ...mapLayers }),
lang: getStore('PreferencesStore').getLanguage(),
}),
{
config: mapLayersconfigShape,
executeAction: PropTypes.func,
},
);
export { connectedComponent as default, MapLayersDialogContent as Component };