Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/components/src/bottom-widget.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project

import React, {forwardRef, useMemo, useCallback} from 'react';
import React, {forwardRef, memo, useMemo, useCallback} from 'react';
import styled, {withTheme, IStyledComponent} from 'styled-components';

import {FILTER_VIEW_TYPES, EXPORT_VIDEO_ID} from '@kepler.gl/constants';
Expand Down Expand Up @@ -232,10 +232,15 @@ export default function BottomWidgetFactory(
);
};

return withTheme(
forwardRef((props: BottomWidgetThemedProps, ref: React.ForwardedRef<HTMLDivElement>) => (
<BottomWidget {...props} rootRef={ref} />
))
const MemoizedBottomWidget = memo(BottomWidget);

const ForwardedBottomWidget = forwardRef(
(props: BottomWidgetThemedProps, ref: React.ForwardedRef<HTMLDivElement>) => (
<MemoizedBottomWidget {...props} rootRef={ref} />
)
);
ForwardedBottomWidget.displayName = 'BottomWidget';

return withTheme(ForwardedBottomWidget);
}
/* eslint-enable complexity */
13 changes: 12 additions & 1 deletion src/components/src/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,18 @@ export const ERROR_MSG = {

const mapStateToProps = (state: any, props: ContainerProps) => ({state, ...props});
const dispatchToProps = (dispatch: Dispatch<any>) => ({dispatch});
const connector = connect(mapStateToProps, dispatchToProps);
const connector = connect(mapStateToProps, dispatchToProps, null, {
areStatesEqual: (next: any, prev: any, nextOwnProps: ContainerProps) => {
const getState = nextOwnProps.getState || ((s: any) => s.keplerGl);
const id = nextOwnProps.id || 'map';
const nextInstance = getState(next)?.[id];
const prevInstance = getState(prev)?.[id];
if (!prevInstance && !nextInstance) {
return next === prev;
}
return nextInstance === prevInstance;
}
});

type ContainerProps = {
id: string;
Expand Down
158 changes: 118 additions & 40 deletions src/components/src/kepler-gl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,32 +207,79 @@ export function getVisibleDatasets(datasets) {
return filterObjectByPredicate(datasets, key => key !== GEOCODER_DATASET_NAME);
}

export const sidePanelSelector = (props: KeplerGLProps, availableProviders, filteredDatasets) => ({
appName: props.appName ? props.appName : DEFAULT_KEPLER_GL_PROPS.appName,
version: props.version ? props.version : DEFAULT_KEPLER_GL_PROPS.version,
appWebsite: props.appWebsite,
mapStyle: props.mapStyle,
onSaveMap: props.onSaveMap,
uiState: props.uiState,
mapStyleActions: props.mapStyleActions,
visStateActions: props.visStateActions,
uiStateActions: props.uiStateActions,
mapStateActions: props.mapStateActions,

datasets: filteredDatasets,
filters: props.visState.filters,
layers: props.visState.layers,
layerOrder: props.visState.layerOrder,
layerClasses: props.visState.layerClasses,
interactionConfig: props.visState.interactionConfig,
mapInfo: props.visState.mapInfo,
layerBlending: props.visState.layerBlending,
overlayBlending: props.visState.overlayBlending,

width: props.sidePanelWidth ? props.sidePanelWidth : DEFAULT_KEPLER_GL_PROPS.width,
availableProviders,
mapSaved: props.providerState.mapSaved
});
export const sidePanelSelector = createSelector(
[
(props: KeplerGLProps) => props.appName,
(props: KeplerGLProps) => props.version,
(props: KeplerGLProps) => props.appWebsite,
(props: KeplerGLProps) => props.mapStyle,
(props: KeplerGLProps) => props.onSaveMap,
(props: KeplerGLProps) => props.uiState,
(props: KeplerGLProps) => props.mapStyleActions,
(props: KeplerGLProps) => props.visStateActions,
(props: KeplerGLProps) => props.uiStateActions,
(props: KeplerGLProps) => props.mapStateActions,
(props: KeplerGLProps) => props.visState.filters,
(props: KeplerGLProps) => props.visState.layers,
(props: KeplerGLProps) => props.visState.layerOrder,
(props: KeplerGLProps) => props.visState.layerClasses,
(props: KeplerGLProps) => props.visState.interactionConfig,
(props: KeplerGLProps) => props.visState.mapInfo,
(props: KeplerGLProps) => props.visState.layerBlending,
(props: KeplerGLProps) => props.visState.overlayBlending,
(props: KeplerGLProps) => props.sidePanelWidth,
(props: KeplerGLProps) => props.providerState.mapSaved,
(_props: KeplerGLProps, availableProviders) => availableProviders,
(_props: KeplerGLProps, _availableProviders, filteredDatasets) => filteredDatasets
],
(
appName,
version,
appWebsite,
mapStyle,
onSaveMap,
uiState,
mapStyleActions,
visStateActions,
uiStateActions,
mapStateActions,
filters,
layers,
layerOrder,
layerClasses,
interactionConfig,
mapInfo,
layerBlending,
overlayBlending,
sidePanelWidth,
mapSaved,
availableProviders,
filteredDatasets
) => ({
appName: appName ? appName : DEFAULT_KEPLER_GL_PROPS.appName,
version: version ? version : DEFAULT_KEPLER_GL_PROPS.version,
appWebsite,
mapStyle,
onSaveMap,
uiState,
mapStyleActions,
visStateActions,
uiStateActions,
mapStateActions,
datasets: filteredDatasets,
filters,
layers,
layerOrder,
layerClasses,
interactionConfig,
mapInfo,
layerBlending,
overlayBlending,
width: sidePanelWidth ? sidePanelWidth : DEFAULT_KEPLER_GL_PROPS.width,
availableProviders,
mapSaved
})
);

export const plotContainerSelector = (props: KeplerGLProps) => ({
width: props.width,
Expand All @@ -255,16 +302,41 @@ export const plotContainerSelector = (props: KeplerGLProps) => ({
export const isSplitSelector = (props: KeplerGLProps) =>
props.visState.splitMaps && props.visState.splitMaps.length > 1;

export const bottomWidgetSelector = (props: KeplerGLProps, theme) => ({
filters: props.visState.filters,
datasets: props.visState.datasets,
uiState: props.uiState,
layers: props.visState.layers,
animationConfig: props.visState.animationConfig,
visStateActions: props.visStateActions,
toggleModal: props.uiStateActions.toggleModal,
sidePanelWidth: props.uiState.readOnly ? 0 : props.sidePanelWidth + theme.sidePanel.margin.left
});
export const bottomWidgetSelector = createSelector(
[
(props: KeplerGLProps) => props.visState.filters,
(props: KeplerGLProps) => props.visState.datasets,
(props: KeplerGLProps) => props.uiState,
(props: KeplerGLProps) => props.visState.layers,
(props: KeplerGLProps) => props.visState.animationConfig,
(props: KeplerGLProps) => props.visStateActions,
(props: KeplerGLProps) => props.uiStateActions.toggleModal,
(props: KeplerGLProps) => props.uiState.readOnly,
(props: KeplerGLProps) => props.sidePanelWidth,
(_props: KeplerGLProps, theme) => theme
],
(
filters,
datasets,
uiState,
layers,
animationConfig,
visStateActions,
toggleModal,
readOnly,
sidePanelWidth,
theme
) => ({
filters,
datasets,
uiState,
layers,
animationConfig,
visStateActions,
toggleModal,
sidePanelWidth: readOnly ? 0 : sidePanelWidth + theme.sidePanel.margin.left
})
);

export const modalContainerSelector = (props: KeplerGLProps, rootNode) => ({
appName: props.appName ? props.appName : DEFAULT_KEPLER_GL_PROPS.appName,
Expand Down Expand Up @@ -413,10 +485,16 @@ export const attributionSelector = createSelector(
}
);

export const notificationPanelSelector = (props: KeplerGLProps) => ({
removeNotification: props.uiStateActions.removeNotification,
notifications: props.uiState.notifications
});
export const notificationPanelSelector = createSelector(
[
(props: KeplerGLProps) => props.uiStateActions.removeNotification,
(props: KeplerGLProps) => props.uiState.notifications
],
(removeNotification, notifications) => ({
removeNotification,
notifications
})
);

export const DEFAULT_KEPLER_GL_PROPS = {
mapStyles: [],
Expand Down
6 changes: 4 additions & 2 deletions src/components/src/loading-indicator.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project

import React, {PropsWithChildren, useRef, useEffect} from 'react';
import React, {PropsWithChildren, useRef, useEffect, memo} from 'react';
import styled, {withTheme, keyframes} from 'styled-components';

import {getNumRasterTilesBeingLoaded, getNumVectorTilesBeingLoaded} from '@kepler.gl/layers';
Expand Down Expand Up @@ -109,4 +109,6 @@ const LoadingIndicator: React.FC<LoadingIndicatorProps & {theme: any}> = ({
);
};

export default withTheme(LoadingIndicator) as React.FC<PropsWithChildren<LoadingIndicatorProps>>;
const MemoizedLoadingIndicator = memo(LoadingIndicator);

export default withTheme(MemoizedLoadingIndicator) as React.FC<PropsWithChildren<LoadingIndicatorProps>>;
8 changes: 4 additions & 4 deletions src/components/src/map-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ type AttributionProps = {
baseMapLibraryConfig: BaseMapLibraryConfig;
};

export const Attribution: React.FC<AttributionProps> = ({
export const Attribution: React.FC<AttributionProps> = React.memo(({
showBaseMapLibLogo = true,
showOsmBasemapAttribution = false,
datasetAttributions,
Expand Down Expand Up @@ -311,7 +311,7 @@ export const Attribution: React.FC<AttributionProps> = ({
]);

return memoizedComponents;
};
});

const StyledAttributionLogoContainer = styled.div<{$left: number}>`
position: absolute;
Expand Down Expand Up @@ -339,7 +339,7 @@ type AttributionLogosProps = {

const LOGO_LEFT_ADJUSTMENT = 3;

export const AttributionLogos: React.FC<AttributionLogosProps> = ({
export const AttributionLogos: React.FC<AttributionLogosProps> = React.memo(({
logos,
activeSidePanel,
sidePanelWidth
Expand All @@ -365,7 +365,7 @@ export const AttributionLogos: React.FC<AttributionLogosProps> = ({
))}
</StyledAttributionLogoContainer>
);
};
});

MapContainerFactory.deps = [MapPopoverFactory, MapControlFactory, EditorFactory];

Expand Down
63 changes: 61 additions & 2 deletions src/components/src/map/map-control.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project

import React from 'react';
import React, {memo} from 'react';
import styled from 'styled-components';
import KeplerGlLogo from '../common/logo';

Expand Down Expand Up @@ -143,7 +143,66 @@ function MapControlFactory(

MapControl.displayName = 'MapControl';

return MapControl;
const areMapControlPropsEqual = (prev: MapControlProps, next: MapControlProps): boolean => {
const keys = Object.keys(next) as (keyof MapControlProps)[];
for (const key of keys) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't some deepEqual simplify this?

if (prev[key] === next[key]) continue;

if (key === 'layers') {
const pl = prev.layers;
const nl = next.layers;
if (!pl || !nl || pl.length !== nl.length) return false;
for (let i = 0; i < nl.length; i++) {
if (pl[i] === nl[i]) continue;
if (pl[i].id !== nl[i].id) return false;
if (pl[i].config.isVisible !== nl[i].config.isVisible) return false;
if (pl[i].config.label !== nl[i].config.label) return false;
if (pl[i].config.isConfigActive !== nl[i].config.isConfigActive) return false;
}
continue;
}

if (key === 'datasets') {
const pd = prev.datasets;
const nd = next.datasets;
if (!pd || !nd) return false;
const pKeys = Object.keys(pd);
const nKeys = Object.keys(nd);
if (pKeys.length !== nKeys.length) return false;
for (const dk of nKeys) {
if (!pd[dk]) return false;
if (pd[dk] === nd[dk]) continue;
if (pd[dk].id !== nd[dk].id) return false;
if (pd[dk].label !== nd[dk].label) return false;
if (pd[dk].color !== nd[dk].color) return false;
}
continue;
}

if (key === 'layersToRender') {
const pl = prev.layersToRender;
const nl = next.layersToRender;
if (!pl || !nl) return false;
const pKeys = Object.keys(pl);
const nKeys = Object.keys(nl);
if (pKeys.length !== nKeys.length) return false;
for (const lk of nKeys) {
if (pl[lk] !== nl[lk]) return false;
}
continue;
}

return false;
}
return true;
};

const MemoizedMapControl = memo(MapControl, areMapControlPropsEqual) as React.NamedExoticComponent<MapControlProps> & {
defaultActionComponents: MapControlProps['actionComponents'];
};
(MemoizedMapControl as any).defaultActionComponents = DEFAULT_ACTIONS;

return MemoizedMapControl;
}

export default MapControlFactory;
Loading
Loading