mirror of
https://github.com/HSLdevcom/digitransit-ui
synced 2025-07-27 15:05:15 +02:00
372 lines
12 KiB
JavaScript
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 & 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 & 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 };
|