in src/components/kepler-gl.js [105:436]
function KeplerGlFactory(
BottomWidget,
GeoCoderPanel,
MapContainer,
ModalContainer,
SidePanel,
PlotContainer,
NotificationPanel
) {
/** @typedef {import('./kepler-gl').KeplerGlProps} KeplerGlProps */
/** @augments React.Component<KeplerGlProps> */
class KeplerGL extends Component {
static defaultProps = {
mapStyles: [],
mapStylesReplaceDefault: false,
mapboxApiUrl: DEFAULT_MAPBOX_API_URL,
width: 800,
height: 800,
appName: KEPLER_GL_NAME,
version: KEPLER_GL_VERSION,
sidePanelWidth: DIMENSIONS.sidePanel.width,
theme: {},
cloudProviders: [],
readOnly: false
};
componentDidMount() {
this._validateMapboxToken();
this._loadMapStyle(this.props.mapStyles);
this._handleResize(this.props);
}
componentDidUpdate(prevProps) {
if (
// if dimension props has changed
this.props.height !== prevProps.height ||
this.props.width !== prevProps.width ||
// react-map-gl will dispatch updateViewport after this._handleResize is called
// here we check if this.props.mapState.height is sync with props.height
this.props.height !== this.props.mapState.height
) {
this._handleResize(this.props);
}
}
root = createRef();
static contextType = RootContext;
/* selectors */
themeSelector = props => props.theme;
availableThemeSelector = createSelector(this.themeSelector, theme =>
typeof theme === 'object'
? {
...basicTheme,
...theme
}
: theme === THEME.light
? themeLT
: theme === THEME.base
? themeBS
: theme
);
availableProviders = createSelector(
props => props.cloudProviders,
providers =>
Array.isArray(providers) && providers.length
? {
hasStorage: providers.some(p => p.hasPrivateStorage()),
hasShare: providers.some(p => p.hasSharingUrl())
}
: {}
);
localeMessagesSelector = createSelector(
props => props.localeMessages,
customMessages => (customMessages ? mergeMessages(messages, customMessages) : messages)
);
/* private methods */
_validateMapboxToken() {
const {mapboxApiAccessToken} = this.props;
if (!validateToken(mapboxApiAccessToken)) {
Console.warn(MISSING_MAPBOX_TOKEN);
}
}
_handleResize({width, height}) {
if (!Number.isFinite(width) || !Number.isFinite(height)) {
Console.warn('width and height is required');
return;
}
this.props.mapStateActions.updateMap({
width: width / (1 + Number(this.props.mapState.isSplit)),
height
});
}
_loadMapStyle = () => {
const defaultStyles = Object.values(this.props.mapStyle.mapStyles);
// add id to custom map styles if not given
const customStyles = (this.props.mapStyles || []).map(ms => ({
...ms,
id: ms.id || generateHashId()
}));
const allStyles = [...customStyles, ...defaultStyles].reduce(
(accu, style) => {
const hasStyleObject = style.style && typeof style.style === 'object';
accu[hasStyleObject ? 'toLoad' : 'toRequest'][style.id] = style;
return accu;
},
{toLoad: {}, toRequest: {}}
);
this.props.mapStyleActions.loadMapStyles(allStyles.toLoad);
this.props.mapStyleActions.requestMapStyles(allStyles.toRequest);
};
render() {
const {
// props
id,
appName,
version,
appWebsite,
onSaveMap,
onViewStateChange,
onDeckInitialized,
width,
height,
mapboxApiAccessToken,
mapboxApiUrl,
getMapboxRef,
deckGlProps,
// redux state
mapStyle,
mapState,
uiState,
visState,
providerState,
// actions,
visStateActions,
mapStateActions,
mapStyleActions,
uiStateActions,
providerActions,
// readOnly override
readOnly
} = this.props;
const availableProviders = this.availableProviders(this.props);
const {
filters,
layers,
splitMaps, // this will store support for split map view is necessary
layerOrder,
layerBlending,
layerClasses,
interactionConfig,
datasets,
layerData,
hoverInfo,
clicked,
mousePos,
animationConfig,
mapInfo
} = visState;
const notificationPanelFields = {
removeNotification: uiStateActions.removeNotification,
notifications: uiState.notifications
};
const sideFields = {
appName,
version,
appWebsite,
datasets,
filters,
layers,
layerOrder,
layerClasses,
interactionConfig,
mapStyle,
mapInfo,
layerBlending,
onSaveMap,
uiState,
mapStyleActions,
visStateActions,
uiStateActions,
width: this.props.sidePanelWidth,
availableProviders,
mapSaved: providerState.mapSaved
};
const mapFields = {
datasets,
getMapboxRef,
mapboxApiAccessToken,
mapboxApiUrl,
mapState,
uiState,
editor: visState.editor,
mapStyle,
mapControls: uiState.mapControls,
layers,
layerOrder,
layerData,
layerBlending,
filters,
interactionConfig,
hoverInfo,
clicked,
mousePos,
readOnly: uiState.readOnly,
onDeckInitialized,
onViewStateChange,
uiStateActions,
visStateActions,
mapStateActions,
animationConfig,
deckGlProps
};
const isSplit = splitMaps && splitMaps.length > 1;
const containerW = mapState.width * (Number(isSplit) + 1);
const mapContainers = !isSplit
? [<MapContainer key={0} index={0} {...mapFields} mapLayers={null} />]
: splitMaps.map((settings, index) => (
<MapContainer
key={index}
index={index}
{...mapFields}
mapLayers={splitMaps[index].layers}
/>
));
const isExportingImage = uiState.exportImage.exporting;
const theme = this.availableThemeSelector(this.props);
const localeMessages = this.localeMessagesSelector(this.props);
return (
<RootContext.Provider value={this.root}>
<IntlProvider locale={uiState.locale} messages={localeMessages[uiState.locale]}>
<ThemeProvider theme={theme}>
<GlobalStyle
width={width}
height={height}
className="kepler-gl"
id={`kepler-gl__${id}`}
ref={this.root}
>
<NotificationPanel {...notificationPanelFields} />
{!uiState.readOnly && !readOnly && <SidePanel {...sideFields} />}
<div className="maps" style={{display: 'flex'}}>
{mapContainers}
</div>
{isExportingImage && (
<PlotContainer
width={width}
height={height}
exportImageSetting={uiState.exportImage}
mapFields={mapFields}
addNotification={uiStateActions.addNotification}
setExportImageSetting={uiStateActions.setExportImageSetting}
setExportImageDataUri={uiStateActions.setExportImageDataUri}
setExportImageError={uiStateActions.setExportImageError}
splitMaps={splitMaps}
/>
)}
{interactionConfig.geocoder.enabled && (
<GeoCoderPanel
isGeocoderEnabled={interactionConfig.geocoder.enabled}
mapboxApiAccessToken={mapboxApiAccessToken}
mapState={mapState}
updateVisData={visStateActions.updateVisData}
removeDataset={visStateActions.removeDataset}
updateMap={mapStateActions.updateMap}
/>
)}
<BottomWidget
filters={filters}
datasets={datasets}
uiState={uiState}
layers={layers}
animationConfig={animationConfig}
visStateActions={visStateActions}
sidePanelWidth={
uiState.readOnly ? 0 : this.props.sidePanelWidth + theme.sidePanel.margin.left
}
containerW={containerW}
/>
<ModalContainer
mapStyle={mapStyle}
visState={visState}
mapState={mapState}
uiState={uiState}
mapboxApiAccessToken={mapboxApiAccessToken}
mapboxApiUrl={mapboxApiUrl}
visStateActions={visStateActions}
uiStateActions={uiStateActions}
mapStyleActions={mapStyleActions}
providerActions={providerActions}
rootNode={this.root.current}
containerW={containerW}
containerH={mapState.height}
providerState={this.props.providerState}
// User defined cloud provider props
cloudProviders={this.props.cloudProviders}
onExportToCloudSuccess={this.props.onExportToCloudSuccess}
onLoadCloudMapSuccess={this.props.onLoadCloudMapSuccess}
onLoadCloudMapError={this.props.onLoadCloudMapError}
onExportToCloudError={this.props.onExportToCloudError}
/>
</GlobalStyle>
</ThemeProvider>
</IntlProvider>
</RootContext.Provider>
);
}
}
return keplerGlConnect(mapStateToProps, makeMapDispatchToProps)(withTheme(KeplerGL));
}