diff --git a/src/components/bottom-widget.d.ts b/src/components/bottom-widget.d.ts new file mode 100644 index 0000000000..06855afa97 --- /dev/null +++ b/src/components/bottom-widget.d.ts @@ -0,0 +1,33 @@ +import React from 'react'; +import {Datasets, Filter, AnimationConfig} from 'reducers/vis-state-updaters'; +import {UiState} from 'reducers/ui-state-updaters'; +import * as UIStateActions from 'actions/ui-state-actions'; +import * as VisStateActions from 'actions/vis-state-actions'; +import {Layer} from 'layers'; + +export type BottomWidgetProps = { + datasets: Datasets; + filters: Filter[]; + layers: Layer[]; + animationConfig: AnimationConfig; + uiStateActions: typeof UIStateActions; + visStateActions: typeof VisStateActions; + rootRef: React.ForwardedRef; + containerW: number; + uiState: UiState; + sidePanelWidth: number; + toggleModal: typeof UIStateActions.toggleModal; + children?: React.ReactNode; +}; + +export function BottomWidgetFactory( + TimeWidget: typeof React.Component, + AnimationControl: typeof React.Component, + FilterAnimationController: typeof React.Component, + LayerAnimationController: typeof React.Component +): any; + +export default BottomWidgetFactory; + +export function LayerAnimationControllerFactory(): React.Component; +export function FilterAnimationControllerFactory(): React.Component; diff --git a/src/components/index.js b/src/components/index.js index 208e6a381a..9dc84ce8f6 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -35,7 +35,8 @@ export {default as KeplerGl, default, injectComponents, ContainerFactory} from ' // factories export {default as KeplerGlFactory, DEFAULT_KEPLER_GL_PROPS} from './kepler-gl'; -export {default as SidePanelFactory, PanelTitleFactory} from './side-panel'; +export {default as SidePanelFactory} from './side-panel'; +export {default as PanelTitleFactory} from './side-panel/panel-title'; export {default as MapContainerFactory} from './map-container'; export { default as BottomWidgetFactory, @@ -54,7 +55,8 @@ export { } from './side-panel/panel-header'; export {default as PanelHeaderActionFactory} from './side-panel/panel-header-action'; export {CollapseButtonFactory, default as SidebarFactory} from './side-panel/side-bar'; -export {default as PanelToggleFactory, PanelTabFactory} from './side-panel/panel-toggle'; +export {default as PanelToggleFactory} from './side-panel/panel-toggle'; +export {default as PanelTabFactory} from './side-panel/panel-tab'; export {AddDataButtonFactory, default as LayerManagerFactory} from './side-panel/layer-manager'; export {default as LayerPanelFactory} from './side-panel/layer-panel/layer-panel'; @@ -80,7 +82,6 @@ export {default as MapManagerFactory} from './side-panel/map-manager'; export {default as LayerGroupSelectorFactory} from './side-panel/map-style-panel/map-layer-selector'; export {default as MapStyleSelectorFactory} from './side-panel/map-style-panel/map-style-selector'; export {default as CustomPanelsFactory} from './side-panel/custom-panel'; - // // map factories export {default as MapPopoverFactory} from './map/map-popover'; export {default as MapControlFactory} from './map/map-control'; diff --git a/src/components/kepler-gl.d.ts b/src/components/kepler-gl.d.ts index e3bbd4bd11..c800e41846 100644 --- a/src/components/kepler-gl.d.ts +++ b/src/components/kepler-gl.d.ts @@ -1,5 +1,4 @@ -import {ComponentClass, RefType} from 'react'; -import {LoaderObject} from '@loaders.gl/loader-utils'; +import {ComponentClass} from 'react'; import {OnErrorCallBack, OnSuccessCallBack} from 'actions/provider-actions'; import {MapState} from 'reducers/map-state-updaters'; import {MapStyle} from 'reducers/map-style-updaters'; @@ -13,44 +12,44 @@ import * as UIStateActions from 'actions/ui-state-actions'; import * as ProviderActions from 'actions/provider-actions'; export type KeplerGlProps = { - id: string, - appWebsite: any, - onSaveMap?: () => void, - onViewStateChange?: () => void, - onDeckInitialized?: () => void, - onKeplerGlInitialized?: () => void, - mapboxApiAccessToken: string, - mapboxApiUrl: string, - getMapboxRef: () => React.RefType, - mapStyles: {id: string, style?: object}[], - mapStylesReplaceDefault: boolean, - mapboxApiUrl: string, - width: number, - height: number, - appName: string, - version: string, - sidePanelWidth: number, - theme: object, - cloudProviders: object[], - deckGlProps?: object, - onLoadCloudMapSuccess?: OnSuccessCallBack, - onLoadCloudMapError?: OnErrorCallBack, - onExportToCloudSuccess?:OnSuccessCallBack, - onExportToCloudError?: OnErrorCallBack, - readOnly?: boolean + id: string; + appWebsite: any; + onSaveMap?: () => void; + onViewStateChange?: () => void; + onDeckInitialized?: () => void; + onKeplerGlInitialized?: () => void; + mapboxApiAccessToken: string; + mapboxApiUrl: string; + getMapboxRef: () => React.RefType; + mapStyles: {id: string; style?: object}[]; + mapStylesReplaceDefault: boolean; + mapboxApiUrl: string; + width: number; + height: number; + appName: string; + version: string; + sidePanelWidth: number; + theme: object; + cloudProviders: object[]; + deckGlProps?: object; + onLoadCloudMapSuccess?: OnSuccessCallBack; + onLoadCloudMapError?: OnErrorCallBack; + onExportToCloudSuccess?: OnSuccessCallBack; + onExportToCloudError?: OnErrorCallBack; + readOnly?: boolean; }; export type UnconnectedKeplerGlProps = KeplerGlProps & { - mapState: MapState, - mapStyle: MapStyle, - uiState: UiState, - visState: VisState, - providerState: ProviderState, - visStateActions: typeof VisStateActions, - uiStateActions: typeof UIStateActions, - mapStateActions: typeof MapStateActions, - mapStyleActions: typeof MapStyleActions, - providerActions: typeof ProviderActions + mapState: MapState; + mapStyle: MapStyle; + uiState: UiState; + visState: VisState; + providerState: ProviderState; + visStateActions: typeof VisStateActions; + uiStateActions: typeof UIStateActions; + mapStateActions: typeof MapStateActions; + mapStyleActions: typeof MapStyleActions; + providerActions: typeof ProviderActions; }; export type KeplerGlComponent = ComponentClass; diff --git a/src/components/modal-container.d.ts b/src/components/modal-container.d.ts index d6de5a1d51..5c2148bd98 100644 --- a/src/components/modal-container.d.ts +++ b/src/components/modal-container.d.ts @@ -10,7 +10,6 @@ import {MapStyle} from 'reducers/map-style-updaters'; import {UiState} from 'reducers/ui-state-updaters'; import {VisState} from 'reducers/vis-state-updaters'; import {ProviderState} from 'reducers/provider-state-updaters'; -import {OnSuccessCallBack} from 'actions'; export type ModalContainerProps = { appName: string; diff --git a/src/components/side-panel.d.ts b/src/components/side-panel.d.ts new file mode 100644 index 0000000000..fb05355dd4 --- /dev/null +++ b/src/components/side-panel.d.ts @@ -0,0 +1,33 @@ +import {ComponentType} from 'react'; +import {Datasets, Filter, InteractionConfig, MapStyle} from '../reducers'; +import {Layer, LayerClassesType} from '../layers'; + +import * as MapStyleActions from 'actions/map-style-actions'; +import * as VisStateActions from 'actions/vis-state-actions'; +import * as MapStateActions from 'actions/map-state-actions'; + +export type SidePanelItem = { + id: stirng; + label: string; + iconComponent: ComponentType; +}; + +export type SidePanelProps = { + filters: Filter; + interactionConfig: InteractionConfig; + layerBlending: stirng; + layers: Layer; + layerClasses: LayerClassesType; + mapStyle: MapStyle; + width: number; + datasets: Datasets; + visStateActions: typeof VisStateActions; + mapStateActions: typeof MapStateActions; + mapStyleActions: typeof MapStyleActions; + availableProviders: object; + mapSaved?: string | null; + panels: SidePanelItem[]; +}; + +export const SidePanel: PureComponent; +export default function SidePanelFactory(): PureComponent; diff --git a/src/components/side-panel.js b/src/components/side-panel.js index ef45f2c5e3..6d49e3810b 100644 --- a/src/components/side-panel.js +++ b/src/components/side-panel.js @@ -18,34 +18,35 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React, {PureComponent} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {FormattedMessage} from 'localization'; -import PropTypes from 'prop-types'; -import styled from 'styled-components'; -import get from 'lodash.get'; - -import SidebarFactory from './side-panel/side-bar'; -import PanelHeaderFactory from './side-panel/panel-header'; -import LayerManagerFactory from './side-panel/layer-manager'; -import FilterManagerFactory from './side-panel/filter-manager'; -import InteractionManagerFactory from './side-panel/interaction-manager'; -import MapManagerFactory from './side-panel/map-manager'; -import PanelToggleFactory from './side-panel/panel-toggle'; -import CustomPanelsFactory from './side-panel/custom-panel'; import { - ADD_DATA_ID, - ADD_MAP_STYLE_ID, EXPORT_DATA_ID, EXPORT_MAP_ID, SHARE_MAP_ID, SIDEBAR_PANELS, OVERWRITE_MAP_ID, SAVE_MAP_ID, - EXPORT_IMAGE_ID + EXPORT_IMAGE_ID, + ADD_DATA_ID, + ADD_MAP_STYLE_ID } from 'constants/default-settings'; -const SidePanelContent = styled.div` +import SidebarFactory from './side-panel/side-bar'; +import PanelHeaderFactory from './side-panel/panel-header'; +import PanelToggleFactory from './side-panel/panel-toggle'; +import LayerManagerFactory from './side-panel/layer-manager'; +import FilterManagerFactory from './side-panel/filter-manager'; +import InteractionManagerFactory from './side-panel/interaction-manager'; +import MapManagerFactory from './side-panel/map-manager'; +import CustomPanelsFactory from './side-panel/custom-panel'; +import PanelTitleFactory from './side-panel/panel-title'; + +import styled from 'styled-components'; +import get from 'lodash.get'; + +export const StyledSidePanelContent = styled.div` ${props => props.theme.sidePanelScrollBar}; flex-grow: 1; padding: ${props => props.theme.sidePanelInnerPadding}px; @@ -59,15 +60,6 @@ const SidePanelContent = styled.div` } `; -export const PanelTitleFactory = () => styled.div` - color: ${props => props.theme.titleTextColor}; - font-size: ${props => props.theme.sidePanelTitleFontsize}; - line-height: ${props => props.theme.sidePanelTitleLineHeight}; - font-weight: 400; - letter-spacing: 1.25px; - margin-bottom: 14px; -`; - SidePanelFactory.deps = [ SidebarFactory, PanelHeaderFactory, @@ -81,7 +73,6 @@ SidePanelFactory.deps = [ ]; /** - * * Vertical sidebar containing input components for the rendering layers */ export default function SidePanelFactory( @@ -95,223 +86,168 @@ export default function SidePanelFactory( MapManager, CustomPanels ) { - const getCustomPanelProps = get(CustomPanels, ['defaultProps', 'getProps']) || (() => ({})); - - class SidePanel extends PureComponent { - static propTypes = { - filters: PropTypes.arrayOf(PropTypes.any).isRequired, - interactionConfig: PropTypes.object.isRequired, - layerBlending: PropTypes.string.isRequired, - layers: PropTypes.arrayOf(PropTypes.any).isRequired, - layerClasses: PropTypes.object.isRequired, - mapStyle: PropTypes.object.isRequired, - width: PropTypes.number.isRequired, - datasets: PropTypes.object.isRequired, - visStateActions: PropTypes.object.isRequired, - mapStyleActions: PropTypes.object.isRequired, - availableProviders: PropTypes.object, - mapSaved: PropTypes.string, - panels: PropTypes.arrayOf(PropTypes.object) - }; - - static defaultProps = { - panels: SIDEBAR_PANELS, - uiState: {}, - visStateActions: {}, - mapStyleActions: {}, - uiStateActions: {}, - availableProviders: {} - }; - - /* component private functions */ - _onOpenOrClose = () => { - this.props.uiStateActions.toggleSidePanel( - this.props.uiState.activeSidePanel ? null : 'layer' - ); - }; - - _showDatasetTable = dataId => { - this.props.visStateActions.showDatasetTable(dataId); - }; - - _showAddDataModal = () => { - this.props.uiStateActions.toggleModal(ADD_DATA_ID); - }; - - _showAddMapStyleModal = () => { - this.props.uiStateActions.toggleModal(ADD_MAP_STYLE_ID); - }; - - _removeDataset = key => { - // this will show the modal dialog to confirm deletion - this.props.uiStateActions.openDeleteModal(key); - }; - - _onClickExportImage = () => this.props.uiStateActions.toggleModal(EXPORT_IMAGE_ID); + // inject components + const SIDEBAR_COMPONENTS = { + layer: LayerManager, + filter: FilterManager, + interaction: InteractionManager, + map: MapManager + }; + + // We should defined sidebar panels here but keeping them for backward compatible + const fullPanels = SIDEBAR_PANELS.map(component => ({ + ...component, + component: SIDEBAR_COMPONENTS[component.id] + })); - _onClickExportData = () => this.props.uiStateActions.toggleModal(EXPORT_DATA_ID); - - _onClickExportMap = () => this.props.uiStateActions.toggleModal(EXPORT_MAP_ID); - - _onClickSaveToStorage = () => - this.props.uiStateActions.toggleModal(this.props.mapSaved ? OVERWRITE_MAP_ID : SAVE_MAP_ID); + const getCustomPanelProps = get(CustomPanels, ['defaultProps', 'getProps']) || (() => ({})); - _onClickSaveAsToStorage = () => { - // add (copy) to file name - this.props.visStateActions.setMapInfo({ - title: `${this.props.mapInfo.title || 'Kepler.gl'} (Copy)` + /** @type {typeof import('./side-panel').SidePanel} */ + // eslint-disable-next-line max-statements + const SidePanel = props => { + const { + appName, + appWebsite, + availableProviders, + datasets, + filters, + layers, + layerBlending, + layerClasses, + layerOrder, + interactionConfig, + panels, + mapInfo, + mapSaved, + mapStateActions, + mapStyle, + mapStyleActions, + onSaveMap, + uiState, + uiStateActions, + visStateActions, + version, + width + } = props; + const {openDeleteModal, toggleModal, toggleSidePanel} = uiStateActions; + const {activeSidePanel} = uiState; + const {setMapInfo, showDatasetTable} = visStateActions; + const {hasShare, hasStorage} = availableProviders; + + const {title} = mapInfo; + + const isOpen = Boolean(activeSidePanel); + + const _onOpenOrClose = useCallback(() => toggleSidePanel(activeSidePanel ? null : 'layer'), [ + activeSidePanel, + toggleSidePanel + ]); + + const onClickExportImage = useCallback(() => toggleModal(EXPORT_IMAGE_ID), [toggleModal]); + const onClickExportData = useCallback(() => toggleModal(EXPORT_DATA_ID), [toggleModal]); + const onClickExportMap = useCallback(() => toggleModal(EXPORT_MAP_ID), [toggleModal]); + const onClickSaveToStorage = useCallback( + () => toggleModal(mapSaved ? OVERWRITE_MAP_ID : SAVE_MAP_ID), + [mapSaved, toggleModal] + ); + const onClickSaveAsToStorage = useCallback(() => { + setMapInfo({ + title: `${title || 'Kepler.gl'} (Copy)` }); - this.props.uiStateActions.toggleModal(SAVE_MAP_ID); - }; - - _onClickShareMap = () => this.props.uiStateActions.toggleModal(SHARE_MAP_ID); - - // eslint-disable-next-line complexity - render() { - const { - appName, - appWebsite, - version, - datasets, - filters, - layers, - layerBlending, - layerClasses, - uiState, - layerOrder, - interactionConfig, - visStateActions, - mapStyleActions, - uiStateActions, - availableProviders, - panels - } = this.props; - - const {activeSidePanel} = uiState; - const isOpen = Boolean(activeSidePanel); - - const layerManagerActions = { - addLayer: visStateActions.addLayer, - layerConfigChange: visStateActions.layerConfigChange, - layerColorUIChange: visStateActions.layerColorUIChange, - layerTextLabelChange: visStateActions.layerTextLabelChange, - layerVisualChannelConfigChange: visStateActions.layerVisualChannelConfigChange, - layerTypeChange: visStateActions.layerTypeChange, - layerVisConfigChange: visStateActions.layerVisConfigChange, - updateLayerBlending: visStateActions.updateLayerBlending, - updateLayerOrder: visStateActions.reorderLayer, - showDatasetTable: this._showDatasetTable, - showAddDataModal: this._showAddDataModal, - removeLayer: visStateActions.removeLayer, - duplicateLayer: visStateActions.duplicateLayer, - removeDataset: this._removeDataset, - openModal: uiStateActions.toggleModal - }; - - const filterManagerActions = { - addFilter: visStateActions.addFilter, - removeFilter: visStateActions.removeFilter, - setFilter: visStateActions.setFilter, - showDatasetTable: this._showDatasetTable, - showAddDataModal: this._showAddDataModal, - toggleAnimation: visStateActions.toggleFilterAnimation, - enlargeFilter: visStateActions.enlargeFilter, - toggleFilterFeature: visStateActions.toggleFilterFeature - }; - - const interactionManagerActions = { - onConfigChange: visStateActions.interactionConfigChange - }; - - const mapManagerActions = { - addMapStyleUrl: mapStyleActions.addMapStyleUrl, - onConfigChange: mapStyleActions.mapConfigChange, - onStyleChange: mapStyleActions.mapStyleChange, - onBuildingChange: mapStyleActions.mapBuildingChange, - set3dBuildingColor: mapStyleActions.set3dBuildingColor, - showAddMapStyleModal: this._showAddMapStyleModal - }; - - return ( -
- - - - -
- - id === activeSidePanel) || {}).label} - /> - - {activeSidePanel === 'layer' && ( - - )} - {activeSidePanel === 'filter' && ( - - )} - {activeSidePanel === 'interaction' && ( - - )} - {activeSidePanel === 'map' && ( - - )} - -
-
-
-
- ); - } - } + toggleModal(SAVE_MAP_ID); + }, [title, setMapInfo, toggleModal]); + const onClickShareMap = useCallback(() => toggleModal(SHARE_MAP_ID), [toggleModal]); + const onShowDatasetTable = useCallback(dataId => showDatasetTable(dataId), [showDatasetTable]); + const onShowAddDataModal = useCallback(() => toggleModal(ADD_DATA_ID), [toggleModal]); + const onShowAddMapStyleModal = useCallback(() => toggleModal(ADD_MAP_STYLE_ID), [toggleModal]); + const onRemoveDataset = useCallback(dataId => openDeleteModal(dataId), [openDeleteModal]); + const onSaveToStorage = useMemo(() => ((hasStorage ? onClickSaveToStorage : null)), [ + hasStorage, + onClickSaveToStorage + ]); + const onSaveAsToStorage = useMemo( + () => ((hasStorage && mapSaved ? onClickSaveAsToStorage : null)), + [hasStorage, mapSaved, onClickSaveAsToStorage] + ); + const currentPanel = useMemo(() => panels.find(({id}) => id === activeSidePanel) || {}, [ + activeSidePanel, + panels + ]); + const onShareMap = useMemo(() => ((hasShare ? onClickShareMap : null)), [ + hasShare, + onClickShareMap + ]); + const customPanelProps = useMemo(() => getCustomPanelProps(props), [props]); + + const PanelComponent = currentPanel.component; + + return ( + + + {/* the next two components should be moved into one */} + {/* but i am keeping them because of backward compatibility */} + + +
+ + + + {PanelComponent ? ( + + ) : null} + +
+
+
+ ); + }; + + SidePanel.defaultProps = { + panels: fullPanels, + sidebarComponents: SIDEBAR_COMPONENTS, + uiState: {}, + visStateActions: {}, + mapStyleActions: {}, + uiStateActions: {}, + availableProviders: {}, + mapInfo: {} + }; return SidePanel; } diff --git a/src/components/side-panel/filter-manager.js b/src/components/side-panel/filter-manager.js index 4c1164fa3e..afb89641c3 100644 --- a/src/components/side-panel/filter-manager.js +++ b/src/components/side-panel/filter-manager.js @@ -29,18 +29,15 @@ import FilterPanelFactory from './filter-panel/filter-panel'; FilterManagerFactory.deps = [SourceDataCatalogFactory, FilterPanelFactory]; function FilterManagerFactory(SourceDataCatalog, FilterPanel) { - const FilterManager = ({ - filters = [], - datasets, - layers, - showDatasetTable, - addFilter, - setFilter, - removeFilter, - enlargeFilter, - toggleAnimation, - toggleFilterFeature - }) => { + const FilterManager = ({filters = [], datasets, layers, showDatasetTable, visStateActions}) => { + const { + addFilter, + enlargeFilter, + removeFilter, + setFilter, + toggleAnimation, + toggleFilterFeature + } = visStateActions; const isAnyFilterAnimating = filters.some(f => f.isAnimating); const hadEmptyFilter = filters.some(f => !f.name); const hadDataset = Object.keys(datasets).length; @@ -94,14 +91,9 @@ function FilterManagerFactory(SourceDataCatalog, FilterPanel) { FilterManager.propTypes = { datasets: PropTypes.object, layers: PropTypes.arrayOf(PropTypes.any).isRequired, - addFilter: PropTypes.func.isRequired, - removeFilter: PropTypes.func.isRequired, - enlargeFilter: PropTypes.func.isRequired, - toggleAnimation: PropTypes.func.isRequired, - toggleFilterFeature: PropTypes.func.isRequired, - setFilter: PropTypes.func.isRequired, filters: PropTypes.arrayOf(PropTypes.any).isRequired, - showDatasetTable: PropTypes.func, + showDatasetTable: PropTypes.func.isRequired, + visStateActions: PropTypes.object.isRequired, // fields can be undefined when dataset is not selected fields: PropTypes.arrayOf(PropTypes.any) diff --git a/src/components/side-panel/interaction-manager.js b/src/components/side-panel/interaction-manager.js index 46c9b51f49..ac21dafcba 100644 --- a/src/components/side-panel/interaction-manager.js +++ b/src/components/side-panel/interaction-manager.js @@ -24,18 +24,21 @@ import InteractionPanelFactory from './interaction-panel/interaction-panel'; InteractionManagerFactory.deps = [InteractionPanelFactory]; function InteractionManagerFactory(InteractionPanel) { - const InteractionManager = ({interactionConfig, datasets, onConfigChange}) => ( -
- {Object.keys(interactionConfig).map(key => ( - - ))} -
- ); + const InteractionManager = ({interactionConfig, datasets, visStateActions}) => { + const {interactionConfigChange: onConfigChange} = visStateActions; + return ( +
+ {Object.keys(interactionConfig).map(key => ( + + ))} +
+ ); + }; return InteractionManager; } diff --git a/src/components/side-panel/layer-manager.js b/src/components/side-panel/layer-manager.js index 1f020aef22..067a1b43e4 100644 --- a/src/components/side-panel/layer-manager.js +++ b/src/components/side-panel/layer-manager.js @@ -139,21 +139,10 @@ function LayerManagerFactory(AddDataButton, LayerPanel, SourceDataCatalog) { layerBlending: PropTypes.string.isRequired, layerClasses: PropTypes.object.isRequired, layers: PropTypes.arrayOf(PropTypes.any).isRequired, + visStateActions: PropTypes.object.isRequired, // functions - addLayer: PropTypes.func.isRequired, - layerColorUIChange: PropTypes.func.isRequired, - layerConfigChange: PropTypes.func.isRequired, - layerTextLabelChange: PropTypes.func.isRequired, - layerVisualChannelConfigChange: PropTypes.func.isRequired, - layerTypeChange: PropTypes.func.isRequired, - layerVisConfigChange: PropTypes.func.isRequired, - openModal: PropTypes.func.isRequired, - removeLayer: PropTypes.func.isRequired, - duplicateLayer: PropTypes.func.isRequired, removeDataset: PropTypes.func.isRequired, - showDatasetTable: PropTypes.func.isRequired, - updateLayerBlending: PropTypes.func.isRequired, - updateLayerOrder: PropTypes.func.isRequired + showDatasetTable: PropTypes.func.isRequired }; state = { isSorting: false @@ -173,11 +162,13 @@ function LayerManagerFactory(AddDataButton, LayerPanel, SourceDataCatalog) { ); _addEmptyNewLayer = () => { - this.props.addLayer(); + const {visStateActions} = this.props; + visStateActions.addLayer(); }; _handleSort = ({oldIndex, newIndex}) => { - this.props.updateLayerOrder(arrayMove(this.props.layerOrder, oldIndex, newIndex)); + const {visStateActions} = this.props; + visStateActions.reorderLayer(arrayMove(this.props.layerOrder, oldIndex, newIndex)); this.setState({isSorting: false}); }; @@ -187,27 +178,38 @@ function LayerManagerFactory(AddDataButton, LayerPanel, SourceDataCatalog) { _updateBeforeSortStart = ({index}) => { // if layer config is active, close it - const {layerOrder, layers, layerConfigChange} = this.props; + const {layerOrder, layers, visStateActions} = this.props; const layerIdx = layerOrder[index]; if (layers[layerIdx].config.isConfigActive) { - layerConfigChange(layers[layerIdx], {isConfigActive: false}); + visStateActions.layerConfigChange(layers[layerIdx], {isConfigActive: false}); } }; render() { - const {layers, datasets, layerOrder, openModal, intl} = this.props; + const { + layers, + datasets, + intl, + layerOrder, + showAddDataModal, + showDatasetTable, + removeDataset, + uiStateActions, + visStateActions + } = this.props; + const {toggleModal: openModal} = uiStateActions; const defaultDataset = Object.keys(datasets)[0]; const layerTypeOptions = this.layerTypeOptionsSelector(this.props); const layerActions = { - layerColorUIChange: this.props.layerColorUIChange, - layerConfigChange: this.props.layerConfigChange, - layerVisualChannelConfigChange: this.props.layerVisualChannelConfigChange, - layerTypeChange: this.props.layerTypeChange, - layerVisConfigChange: this.props.layerVisConfigChange, - layerTextLabelChange: this.props.layerTextLabelChange, - removeLayer: this.props.removeLayer, - duplicateLayer: this.props.duplicateLayer + layerColorUIChange: visStateActions.layerColorUIChange, + layerConfigChange: visStateActions.layerConfigChange, + layerVisualChannelConfigChange: visStateActions.layerVisualChannelConfigChange, + layerTypeChange: visStateActions.layerTypeChange, + layerVisConfigChange: visStateActions.layerVisConfigChange, + layerTextLabelChange: visStateActions.layerTextLabelChange, + removeLayer: visStateActions.removeLayer, + duplicateLayer: visStateActions.duplicateLayer }; const panelProps = { @@ -220,11 +222,11 @@ function LayerManagerFactory(AddDataButton, LayerPanel, SourceDataCatalog) {
- +
diff --git a/src/components/side-panel/map-manager.js b/src/components/side-panel/map-manager.js index aca98dd0c6..05ca1dcbd6 100644 --- a/src/components/side-panel/map-manager.js +++ b/src/components/side-panel/map-manager.js @@ -37,8 +37,7 @@ function MapManagerFactory(MapStyleSelector, LayerGroupSelector) { class MapManager extends Component { static propTypes = { mapStyle: PropTypes.object.isRequired, - onConfigChange: PropTypes.func.isRequired, - onStyleChange: PropTypes.func.isRequired, + mapStyleActions: PropTypes.object.isRequired, showAddMapStyleModal: PropTypes.func.isRequired }; @@ -47,19 +46,21 @@ function MapManagerFactory(MapStyleSelector, LayerGroupSelector) { }; buildingColorSelector = props => props.mapStyle.threeDBuildingColor; - setColorSelector = props => props.set3dBuildingColor; + setColorSelector = props => props.mapStyleActions.set3dBuildingColor; _toggleSelecting = () => { this.setState({isSelecting: !this.state.isSelecting}); }; _selectStyle = val => { - this.props.onStyleChange(val); + const {mapStyleActions} = this.props; + const {mapStyleChange} = mapStyleActions; + mapStyleChange(val); this._toggleSelecting(); }; render() { - const {mapStyle, intl} = this.props; + const {mapStyle, intl, mapStyleActions, showAddMapStyleModal} = this.props; const currentStyle = mapStyle.mapStyles[mapStyle.styleType] || {}; const editableLayers = (currentStyle.layerGroups || []).map(lg => lg.slug); const hasBuildingLayer = mapStyle.visibleLayerGroups['3d building']; @@ -92,17 +93,13 @@ function MapManagerFactory(MapStyleSelector, LayerGroupSelector) { layers={mapStyle.visibleLayerGroups} editableLayers={editableLayers} topLayers={mapStyle.topLayerGroups} - onChange={this.props.onConfigChange} + onChange={mapStyleActions.mapConfigChange} /> ) : null} - diff --git a/src/components/side-panel/panel-tab.js b/src/components/side-panel/panel-tab.js new file mode 100644 index 0000000000..9f3e430659 --- /dev/null +++ b/src/components/side-panel/panel-tab.js @@ -0,0 +1,42 @@ +import React from 'react'; +import styled from 'styled-components'; +import {Tooltip} from 'components/common/styled-components'; +import {FormattedMessage} from 'localization'; + +export const StyledPanelTab = styled.div.attrs({ + className: 'side-panel__tab' +})` + align-items: flex-end; + border-bottom-style: solid; + border-bottom-width: 2px; + border-bottom-color: ${props => + props.active ? props.theme.panelToggleBorderColor : 'transparent'}; + color: ${props => (props.active ? props.theme.subtextColorActive : props.theme.panelTabColor)}; + display: flex; + justify-content: center; + margin-right: ${props => props.theme.panelToggleMarginRight}px; + padding-bottom: ${props => props.theme.panelToggleBottomPadding}px; + width: ${props => props.theme.panelTabWidth}; + + :hover { + cursor: pointer; + color: ${props => props.theme.textColorHl}; + } +`; + +export function PanelTabFactory() { + const PanelTab = ({isActive, onClick, panel}) => ( + + + + + + + + + ); + + return PanelTab; +} + +export default PanelTabFactory; diff --git a/src/components/side-panel/panel-title.js b/src/components/side-panel/panel-title.js new file mode 100644 index 0000000000..ccccb8b13f --- /dev/null +++ b/src/components/side-panel/panel-title.js @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +const PanelTitleFactory = () => styled.div` + color: ${props => props.theme.titleTextColor}; + font-size: ${props => props.theme.sidePanelTitleFontsize}; + line-height: ${props => props.theme.sidePanelTitleLineHeight}; + font-weight: 400; + letter-spacing: 1.25px; + margin-bottom: 14px; +`; + +export default PanelTitleFactory; diff --git a/src/components/side-panel/panel-toggle.js b/src/components/side-panel/panel-toggle.js index 0b715061ed..8941ff7f17 100644 --- a/src/components/side-panel/panel-toggle.js +++ b/src/components/side-panel/panel-toggle.js @@ -18,11 +18,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React from 'react'; +import React, {useCallback} from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; -import {FormattedMessage} from 'localization'; -import {Tooltip} from 'components/common/styled-components'; +import PanelTabFactory from './panel-tab'; const propTypes = { panels: PropTypes.arrayOf(PropTypes.object), @@ -40,53 +39,29 @@ const PanelHeaderBottom = styled.div.attrs({ min-height: 30px; `; -export function PanelTabFactory() { - const PanelTab = styled.div.attrs({ - className: 'side-panel__tab' - })` - align-items: flex-end; - border-bottom-style: solid; - border-bottom-width: 2px; - border-bottom-color: ${props => - props.active ? props.theme.panelToggleBorderColor : 'transparent'}; - color: ${props => (props.active ? props.theme.subtextColorActive : props.theme.panelTabColor)}; - display: flex; - justify-content: center; - margin-right: ${props => props.theme.panelToggleMarginRight}px; - padding-bottom: ${props => props.theme.panelToggleBottomPadding}px; - width: ${props => props.theme.panelTabWidth}; - - :hover { - cursor: pointer; - color: ${props => props.theme.textColorHl}; - } - `; - - return PanelTab; -} - PanelToggleFactory.deps = [PanelTabFactory]; function PanelToggleFactory(PanelTab) { - const PanelToggle = ({panels, activePanel, togglePanel}) => ( - - {panels.map(panel => ( - togglePanel(panel.id)} - > - - - - - - - - ))} - - ); + const PanelToggle = ({activePanel, panels, togglePanel}) => { + const onClick = useCallback( + panel => { + const callback = panel.onClick || togglePanel; + callback(panel.id); + }, + [togglePanel] + ); + return ( + + {panels.map(panel => ( + onClick(panel)} + /> + ))} + + ); + }; PanelToggle.propTypes = propTypes; return PanelToggle; diff --git a/src/constants/default-settings.js b/src/constants/default-settings.js index 59d998745b..5236eb2ac0 100644 --- a/src/constants/default-settings.js +++ b/src/constants/default-settings.js @@ -164,22 +164,26 @@ export const SIDEBAR_PANELS = [ { id: 'layer', label: 'sidebar.panels.layer', - iconComponent: Layers + iconComponent: Layers, + onClick: null }, { id: 'filter', label: 'sidebar.panels.filter', - iconComponent: FilterFunnel + iconComponent: FilterFunnel, + onClick: null }, { id: 'interaction', label: 'sidebar.panels.interaction', - iconComponent: CursorClick + iconComponent: CursorClick, + onClick: null }, { id: 'map', label: 'sidebar.panels.basemap', - iconComponent: Settings + iconComponent: Settings, + onClick: null } ]; diff --git a/test/browser/components/side-panel/filter-manager-test.js b/test/browser/components/side-panel/filter-manager-test.js index 2ede41aeea..b011f04166 100644 --- a/test/browser/components/side-panel/filter-manager-test.js +++ b/test/browser/components/side-panel/filter-manager-test.js @@ -46,12 +46,14 @@ const defaultProps = { datasets: {}, layers: [], showDatasetTable: nop, - addFilter: nop, - setFilter: nop, - removeFilter: nop, - enlargeFilter: nop, - toggleAnimation: nop, - toggleFilterFeature: nop + visStateActions: { + addFilter: nop, + setFilter: nop, + removeFilter: nop, + enlargeFilter: nop, + toggleAnimation: nop, + toggleFilterFeature: nop + } }; // components @@ -60,12 +62,14 @@ const filterManagerProps = { datasets: StateWFilters.visState.datasets, layers: StateWFilters.visState.layers, showDatasetTable: nop, - addFilter: nop, - setFilter: nop, - removeFilter: nop, - enlargeFilter: nop, - toggleAnimation: nop, - toggleFilterFeature: nop + visStateActions: { + addFilter: nop, + setFilter: nop, + removeFilter: nop, + enlargeFilter: nop, + toggleAnimation: nop, + toggleFilterFeature: nop + } }; test('Components -> FilterManager.mount -> no prop', t => { @@ -97,11 +101,18 @@ test('Components -> FilterManager.mount -> no prop', t => { test('Components -> FilterManager.mount -> with prop', t => { // mount const addFilter = sinon.spy(); + const newProps = { + ...filterManagerProps, + visStateActions: { + ...filterManagerProps.visStateActions, + addFilter + } + }; let wrapper; t.doesNotThrow(() => { wrapper = mountWithTheme( - + ); }, 'FilterManager should not fail without props'); @@ -126,7 +137,7 @@ test('Components -> FilterManager.mount -> with prop', t => { t.deepEqual( addFilter.args, [[Object.keys(StateWFilters.visState.datasets)[0]]], - 'Should call addfilter with 1st dataset' + 'Should call addFilter with 1st dataset' ); t.end();