From d8db8f6f07c9049098c6c0979b82c8edc6eb132f Mon Sep 17 00:00:00 2001 From: Shan He Date: Sat, 28 Aug 2021 23:10:28 -0700 Subject: [PATCH] [Chore] Refactored map control and decoupled action components (#1552) * chore: Refactored map control and decoupled action components Signed-off-by: Giuseppe Macri --- .../demo-app/src/factories/map-control.js | 2 +- package.json | 2 +- src/components/index.js | 16 +- src/components/map/layer-selector-panel.d.ts | 15 + src/components/map/layer-selector-panel.js | 83 +++ src/components/map/locale-panel.d.ts | 12 + src/components/map/locale-panel.js | 73 +++ src/components/map/map-control-panel.d.ts | 11 + src/components/map/map-control-panel.js | 78 +++ src/components/map/map-control-toolbar.js | 13 + src/components/map/map-control-tooltip.d.ts | 8 + src/components/map/map-control-tooltip.js | 20 + src/components/map/map-control.d.ts | 146 ++--- src/components/map/map-control.js | 601 ++---------------- src/components/map/map-draw-panel.d.ts | 16 + src/components/map/map-draw-panel.js | 101 +++ src/components/map/map-legend-panel.d.ts | 20 + src/components/map/map-legend-panel.js | 76 +++ src/components/map/map-legend.d.ts | 6 +- src/components/map/map-legend.js | 117 ++-- src/components/map/split-map-button.d.ts | 19 + src/components/map/split-map-button.js | 58 ++ src/components/map/toggle-3d-button.d.ts | 16 + src/components/map/toggle-3d-button.js | 47 ++ test/browser/components/kepler-gl-test.js | 6 + .../components/map/map-control-test.js | 6 +- .../browser/components/map/map-legend-test.js | 5 +- test/helpers/mock-map-styles.js | 78 +++ test/helpers/mock-state.js | 10 +- 29 files changed, 931 insertions(+), 730 deletions(-) create mode 100644 src/components/map/layer-selector-panel.d.ts create mode 100644 src/components/map/layer-selector-panel.js create mode 100644 src/components/map/locale-panel.d.ts create mode 100644 src/components/map/locale-panel.js create mode 100644 src/components/map/map-control-panel.d.ts create mode 100644 src/components/map/map-control-panel.js create mode 100644 src/components/map/map-control-toolbar.js create mode 100644 src/components/map/map-control-tooltip.d.ts create mode 100644 src/components/map/map-control-tooltip.js create mode 100644 src/components/map/map-draw-panel.d.ts create mode 100644 src/components/map/map-draw-panel.js create mode 100644 src/components/map/map-legend-panel.d.ts create mode 100644 src/components/map/map-legend-panel.js create mode 100644 src/components/map/split-map-button.d.ts create mode 100644 src/components/map/split-map-button.js create mode 100644 src/components/map/toggle-3d-button.d.ts create mode 100644 src/components/map/toggle-3d-button.js create mode 100644 test/helpers/mock-map-styles.js diff --git a/examples/demo-app/src/factories/map-control.js b/examples/demo-app/src/factories/map-control.js index c662f515c2..9cb4e1ede5 100644 --- a/examples/demo-app/src/factories/map-control.js +++ b/examples/demo-app/src/factories/map-control.js @@ -29,7 +29,7 @@ function CustomMapControlFactory(...deps) { const StyledMapControlOverlay = styled.div` position: absolute; top: ${props => props.top}px; - right: 0px; + right: 0; z-index: 1; `; diff --git a/package.json b/package.json index ac41d8cf83..eb25f6a97f 100644 --- a/package.json +++ b/package.json @@ -283,4 +283,4 @@ "volta": { "node": "12.19.0" } -} +} \ No newline at end of file diff --git a/src/components/index.js b/src/components/index.js index 9dc84ce8f6..891c1dc483 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -87,12 +87,15 @@ export {default as MapPopoverFactory} from './map/map-popover'; export {default as MapControlFactory} from './map/map-control'; export {default as LayerHoverInfoFactory} from './map/layer-hover-info'; export {default as CoordinateInfoFactory} from './map/coordinate-info'; -export { - Toggle3dButtonFactory, - MapDrawPanelFactory, - SplitMapButtonFactory, - MapLegendPanelFactory -} from './map/map-control'; +export {default as LayerSelectorPanelFactory} from './map/layer-selector-panel'; +export {default as LocalePanelFactory} from './map/locale-panel'; +export {default as MapControlPanelFactory} from './map/map-control-panel'; +export {default as MapControlTooltipFactory} from './map/map-control-tooltip'; +export {default as MapLegendFactory} from './map/map-legend'; +export {default as MapDrawPanelFactory} from './map/map-draw-panel'; +export {default as SplitMapButtonFactory} from './map/split-map-button'; +export {default as MapLegendPanelFactory} from './map/map-legend-panel'; +export {default as Toggle3dButtonFactory} from './map/toggle-3d-button'; // // modal factories export {default as ModalDialogFactory} from './modals/modal-dialog'; @@ -187,6 +190,7 @@ export {default as MapLegend} from 'components/map/map-legend'; export * from './common/styled-components'; import * as Icons from './common/icons'; + export {Icons}; // Individual Component from Dependency Tree diff --git a/src/components/map/layer-selector-panel.d.ts b/src/components/map/layer-selector-panel.d.ts new file mode 100644 index 0000000000..b588112ee0 --- /dev/null +++ b/src/components/map/layer-selector-panel.d.ts @@ -0,0 +1,15 @@ +import React from 'react'; +import {Layer} from 'layers'; +import {MapControls} from 'reducers/ui-state-updaters'; + +export type LayerSelectorPanelProps = { + onMapToggleLayer: (layerId: string) => void; + onToggleMapControl: (control: string) => void; + layers: ReadonlyArray; + layersToRender: {[key: string]: boolean}; + isSplit: boolean; + mapControls: MapControls; + readOnly: boolean; +}; + +export type LayerSelectorPanelComponent = React.FunctionComponent; diff --git a/src/components/map/layer-selector-panel.js b/src/components/map/layer-selector-panel.js new file mode 100644 index 0000000000..cf4f03c4cf --- /dev/null +++ b/src/components/map/layer-selector-panel.js @@ -0,0 +1,83 @@ +import React, {useCallback, useMemo} from 'react'; +import {MapControlButton} from 'components/common/styled-components'; +import {Layers} from '../common/icons'; +import MapLayerSelector from '../common/map-layer-selector'; +import MapControlTooltipFactory from './map-control-tooltip'; +import MapControlPanelFactory from './map-control-panel'; + +LayerSelectorPanelFactory.deps = [MapControlTooltipFactory, MapControlPanelFactory]; + +function LayerSelectorPanelFactory(MapControlTooltip, MapControlPanel) { + /** @type {import('./layer-selector-panel').LayerSelectorPanelComponent} */ + const LayerSelectorPanel = ({ + onMapToggleLayer, + onToggleMapControl, + layers, + layersToRender, + isSplit, + mapControls, + readOnly + }) => { + const visibleLayers = mapControls?.visibleLayers || {}; + const {active: isActive, show, disableClose} = visibleLayers; + + const legendLayers = useMemo( + () => + layers + .filter(({config}) => config.isVisible) + .map(({id, config}) => ({ + id, + name: config.label, + // layer + isVisible: layersToRender[id] + })), + [layers, layersToRender] + ); + + const isVisible = useMemo(() => isSplit && show && readOnly !== true, [ + isSplit, + show, + readOnly + ]); + + const onToggleMenuPanel = useCallback( + event => { + event.preventDefault(); + onToggleMapControl('visibleLayers'); + }, + [onToggleMapControl] + ); + + return isVisible ? ( + (!isActive ? ( + + + + + ) : ( + + + + )) + ) : null; + }; + + LayerSelectorPanel.displayName = 'LayerSelectorPanel'; + + return React.memo(LayerSelectorPanel); +} + +export default LayerSelectorPanelFactory; diff --git a/src/components/map/locale-panel.d.ts b/src/components/map/locale-panel.d.ts new file mode 100644 index 0000000000..fe691529a0 --- /dev/null +++ b/src/components/map/locale-panel.d.ts @@ -0,0 +1,12 @@ +import React from 'react'; +import {MapControls} from '../../reducers'; + +export type LocalePanelProps = { + availableLocales: ReadnlyArray; + onSetLocale: (locale: string) => void; + locale: string; + onToggleMenuPanel: () => void; + mapControls: MapControls; +}; + +export type LocalePanelComponent = React.FunctionComponent; diff --git a/src/components/map/locale-panel.js b/src/components/map/locale-panel.js new file mode 100644 index 0000000000..ddef051905 --- /dev/null +++ b/src/components/map/locale-panel.js @@ -0,0 +1,73 @@ +import React, {useCallback} from 'react'; +import ToolbarItem from 'components/common/toolbar-item'; +import {MapControlButton} from 'components/common/styled-components'; +import MapControlTooltipFactory from './map-control-tooltip'; +import MapControlPanelFactory from './map-control-panel'; +import MapControlToolbarFactory from './map-control-toolbar'; + +LocalePanelFactory.deps = [ + MapControlTooltipFactory, + MapControlPanelFactory, + MapControlToolbarFactory +]; + +function LocalePanelFactory(MapControlTooltip, MapControlPanel, MapControlToolbar) { + /** @type {import('./locale-panel').LocalePanelComponent} */ + const LocalePanel = React.memo( + ({availableLocales, onToggleMenuPanel, onSetLocale, locale: currentLocal, mapControls}) => { + const {active: isActive, disableClose} = mapControls.mapLocale || {}; + + const onClickItem = useCallback( + locale => { + onSetLocale(locale); + }, + [onSetLocale] + ); + + const onClickButton = useCallback( + e => { + e.preventDefault(); + onToggleMenuPanel(); + }, + [onToggleMenuPanel] + ); + const getLabel = useCallback(locale => `toolbar.${locale}`, []); + + if (!mapControls.mapLocale) { + return null; + } + return ( +
+ {isActive ? ( + + {availableLocales.map(locale => ( + onClickItem(locale)} + label={getLabel(locale)} + active={currentLocal === locale} + /> + ))} + + ) : null} + + {currentLocal.toUpperCase()} + + +
+ ); + } + ); + + LocalePanel.displayName = 'LocalePanel'; + + return LocalePanel; +} + +export default LocalePanelFactory; diff --git a/src/components/map/map-control-panel.d.ts b/src/components/map/map-control-panel.d.ts new file mode 100644 index 0000000000..91854f5198 --- /dev/null +++ b/src/components/map/map-control-panel.d.ts @@ -0,0 +1,11 @@ +import React from 'react'; + +export type MapControlPanelProps = { + header?: string; + scale?: number; + onClick: () => void; + isExport?: boolean; + logoComponent?: Element; +}; + +export type MapControlPanelComponent = React.FunctionComponent; diff --git a/src/components/map/map-control-panel.js b/src/components/map/map-control-panel.js new file mode 100644 index 0000000000..6fb0ef8aea --- /dev/null +++ b/src/components/map/map-control-panel.js @@ -0,0 +1,78 @@ +import React from 'react'; +import styled from 'styled-components'; +import {FormattedMessage} from 'localization'; +import {IconRoundSmall} from 'components/common/styled-components'; +import {Close} from 'components/common/icons'; + +const StyledMapControlPanel = styled.div` + background-color: ${props => props.theme.mapPanelBackgroundColor}; + flex-grow: 1; + z-index: 1; + p { + margin-bottom: 0; + } +`; + +const StyledMapControlPanelContent = styled.div.attrs({ + className: 'map-control__panel-content' +})` + ${props => props.theme.dropdownScrollBar}; + max-height: 500px; + min-height: 100px; + min-width: ${props => props.theme.mapControl.width}px; + overflow: auto; +`; + +const StyledMapControlPanelHeader = styled.div.attrs({ + className: 'map-control__panel-header' +})` + display: flex; + justify-content: space-between; + background-color: ${props => props.theme.mapPanelHeaderBackgroundColor}; + height: 32px; + padding: 6px 12px; + font-size: 11px; + color: ${props => props.theme.titleTextColor}; + position: relative; + + button { + width: 18px; + height: 18px; + } +`; + +function MapControlPanelFactory() { + /** @type {import('./map-control-panel').MapControlPanelComponent} */ + const MapControlPanel = React.memo( + ({children, header, onClick, scale = 1, isExport, logoComponent}) => ( + + + {isExport && logoComponent ? ( + logoComponent + ) : header ? ( + + + + ) : null} + {isExport ? null : ( + + + + )} + + {children} + + ) + ); + + MapControlPanel.displayName = 'MapControlPanel'; + + return MapControlPanel; +} + +export default MapControlPanelFactory; diff --git a/src/components/map/map-control-toolbar.js b/src/components/map/map-control-toolbar.js new file mode 100644 index 0000000000..186f2f036a --- /dev/null +++ b/src/components/map/map-control-toolbar.js @@ -0,0 +1,13 @@ +import styled from 'styled-components'; +import VerticalToolbar from '../common/vertical-toolbar'; + +function MapControlToolbar() { + const StyledToolbar = styled(VerticalToolbar)` + position: absolute; + right: 32px; + `; + + return StyledToolbar; +} + +export default MapControlToolbar; diff --git a/src/components/map/map-control-tooltip.d.ts b/src/components/map/map-control-tooltip.d.ts new file mode 100644 index 0000000000..7d274ee4e4 --- /dev/null +++ b/src/components/map/map-control-tooltip.d.ts @@ -0,0 +1,8 @@ +import React from 'react'; + +export type MapControlTooltipProps = { + id: string; + message: string; +}; + +export type MapControlTooltipComponent = React.FunctionComponent; diff --git a/src/components/map/map-control-tooltip.js b/src/components/map/map-control-tooltip.js new file mode 100644 index 0000000000..c41da9cd81 --- /dev/null +++ b/src/components/map/map-control-tooltip.js @@ -0,0 +1,20 @@ +import React from 'react'; +import {Tooltip} from '..'; +import {FormattedMessage} from 'localization'; + +function MapControlTooltipFactory() { + /** @type {import('./map-control-tooltip').MapControlTooltipComponent} */ + const MapControlTooltip = React.memo(({id, message}) => ( + + + + + + )); + + MapControlTooltip.displayName = 'MapControlTooltip'; + + return MapControlTooltip; +} + +export default MapControlTooltipFactory; diff --git a/src/components/map/map-control.d.ts b/src/components/map/map-control.d.ts index cd31bfea63..1cf47de3e8 100644 --- a/src/components/map/map-control.d.ts +++ b/src/components/map/map-control.d.ts @@ -1,110 +1,40 @@ -import {Element} from 'react'; +import React from 'react'; +import {Editor, MapControls} from '../../reducers'; +import {Datasets} from 'reducers/vis-state-updaters'; +import {FeatureFlags} from '../context'; import {Layer} from 'layers'; -import {Editor} from 'reducers/vis-state-updaters'; -export type LocalePanelProps = { - availableLocales: ReadonlyArray, - onSetLocale: (locale: string) => void, - activeLocale: string, - isActive: boolean, - onToggleMenuPanel: () => void, - disableClose?: boolean -}; - -export type LocalePanelComponent = React.FunctionComponent; - -export type LayerSelectorPanelProps = { - items: ReadonlyArray, - onMapToggleLayer: (layerId: string) => void, - isActive: boolean, - toggleMenuPanel: () => void, - disableClose?: boolean -}; - -export type LayerSelectorPanelComponent = React.FunctionComponent; - -export type MapControlTooltipProps = { - id: string, - message: string -}; - -export type MapControlTooltipComponent = React.FunctionComponent; - -interface MapDrawPanelIcons { - visible: ComponentType, - hidden: ComponentType, - polygon: ComponentType, - cursor: ComponentType, - innerPolygon: ComponentType, - rectangle: ComponentType -} - -export type MapDrawPanelProps = { - editor: Editor, - isActive: boolean, - onToggleMenuPanel: () => void, - onSetEditorMode: (mode: string) => void, - onToggleEditorVisibility: () => void, - actionIcons: MapDrawPanelIcons -}; - -export type MapDrawPanelComponent = React.FunctionComponent; -export function MapDrawPanelFactory(): MapDrawPanelComponent; - -export type MapControlPanelProps = { - children: Element, - header?: string, - scale?: number, - onClick: () => void, - isExport?: boolean, - disableClose?: boolean, - logoComponent?: Element -}; - -export type MapControlPanelComponent = React.FunctionComponent; - -interface MapLegendPanelIcons { - legend: ComponentType -} - -export type MapLegendPanelProps = { - layers: ReadonlyArray, - isActive: boolean, - scale: number, - onToggleMenuPanel: () => void, - isExport: boolean, - disableClose?: boolean, - logoComponent: Element, - actionIcons: MapLegendPanelIcons -}; - -export type MapLegendPanelComponent = React.FunctionComponent; -export function MapLegendPanelFactory(): MapLegendPanelComponent; - -interface Toggle3dButtonIcons { - cube: ComponentType -} - -export type Toggle3dButtonProps = { - dragRotate: boolean, - onTogglePerspective: () => void, - actionIcons: Toggle3dButtonIcons -}; - -export type Toggle3dButtonComponent = React.FunctionComponent; -export function Toggle3dButtonFactory(): Toggle3dButtonComponent; - -interface SplitMapButtonIcons { - delete: ComponentType, - split: ComponentType -} - -export type SplitMapButtonProps = { - isSplit: boolean, - mapIndex: number, - onToggleSplitMap: (index?: number) => void, - actionIcons: SplitMapButtonIcons -}; - -export type SplitMapButtonComponent = React.FunctionComponent; -export function SplitMapButtonFactory(): SplitMapButtonComponent; +export type MapControlComponentProps = Partial; + +export type MapControlProps = { + availableLocales: ReadonlyArray; + featureFlags: FeatureFlags; + datasets: Datasets; + dragRotate: boolean; + globe: boolean; + isSplit: boolean; + layers: Layer[]; + layersToRender: {[key: string]: boolean}; + mapIndex: number; + mapControls: MapControls; + onTogglePerspective: () => void; + onToggleGlobe: () => void; + onToggleSplitMap: () => void; + onToggleMapControl: (control: string) => void; + onSetEditorMode: (mode: string) => void; + onToggleEditorVisibility: () => void; + top: number; + onSetLocale: () => void; + locale: string; + logoComponent: React.FC | React.ReactNode; + + // optional + readOnly?: boolean; + scale?: number; + mapLayers?: {[key: string]: boolean}; + editor: Editor; + actionComponents: React.FC[] | React.Component[]; +}; + +export type MapControl = React.FC; +export function MapControlFactory(): MapControl; diff --git a/src/components/map/map-control.js b/src/components/map/map-control.js index bb3e184f08..6c82568c2a 100644 --- a/src/components/map/map-control.js +++ b/src/components/map/map-control.js @@ -18,35 +18,17 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React, {Component, useCallback} from 'react'; -import PropTypes from 'prop-types'; -import {createSelector} from 'reselect'; +import React from 'react'; import styled from 'styled-components'; -import {FormattedMessage} from 'localization'; -import classnames from 'classnames'; - -import {IconRoundSmall, MapControlButton, Tooltip} from 'components/common/styled-components'; -import MapLayerSelector from 'components/common/map-layer-selector'; import KeplerGlLogo from 'components/common/logo'; -import MapLegend from './map-legend'; -import { - Close, - Cube3d, - CursorClick, - Delete, - DrawPolygon, - EyeSeen, - EyeUnseen, - Layers, - Legend, - Polygon, - Rectangle, - Split -} from 'components/common/icons'; -import VerticalToolbar from 'components/common/vertical-toolbar'; -import ToolbarItem from 'components/common/toolbar-item'; -import {EDITOR_MODES} from 'constants/default-settings'; -import {LOCALE_CODES} from 'localization/locales'; + +// factories +import SplitMapButtonFactory from './split-map-button'; +import Toggle3dButtonFactory from './toggle-3d-button'; +import LayerSelectorPanelFactory from './layer-selector-panel'; +import MapLegendPanelFactory from './map-legend-panel'; +import MapDrawPanelFactory from './map-draw-panel'; +import LocalePanelFactory from './locale-panel'; const StyledMapControl = styled.div` right: 0; @@ -54,545 +36,60 @@ const StyledMapControl = styled.div` z-index: 10; margin-top: ${props => props.top || 0}px; position: absolute; + display: grid; + row-gap: 8px; + justify-items: end; `; -const StyledMapControlAction = styled.div` - padding: 4px 0; - display: flex; - justify-content: flex-end; -`; - -const StyledMapControlPanel = styled.div` - background-color: ${props => props.theme.mapPanelBackgroundColor}; - flex-grow: 1; - z-index: 1; - p { - margin-bottom: 0; - } -`; - -const StyledMapControlPanelContent = styled.div.attrs({ - className: 'map-control__panel-content' -})` - ${props => props.theme.dropdownScrollBar}; - max-height: 500px; - min-height: 100px; - min-width: ${props => props.theme.mapControl.width}px; - overflow: auto; -`; - -const StyledMapControlPanelHeader = styled.div.attrs({ - className: 'map-control__panel-header' -})` - display: flex; - justify-content: space-between; - background-color: ${props => props.theme.mapPanelHeaderBackgroundColor}; - height: 32px; - padding: 6px 12px; - font-size: 11px; - color: ${props => props.theme.titleTextColor}; - position: relative; - - button { - width: 18px; - height: 18px; - } -`; - -const ActionPanel = ({className = '', children}) => ( - {children} -); - -ActionPanel.displayName = 'ActionPanel'; - -/** @type {import('./map-control').MapControlTooltipComponent} */ -const MapControlTooltip = React.memo(({id, message}) => ( - - - - - -)); - -MapControlTooltip.displayName = 'MapControlTooltip'; - -const MapLegendTooltip = ({id, message}) => ( - - - - - -); - -/** @type {import('./map-control').LayerSelectorPanelComponent} */ -const LayerSelectorPanel = React.memo( - ({items, onMapToggleLayer, isActive, toggleMenuPanel, disableClose}) => - !isActive ? ( - ( { - e.preventDefault(); - toggleMenuPanel(); - }} - className="map-control-button toggle-layer" - data-tip - data-for="toggle-layer" - > - - - ) - ) : ( - ( - - ) - ) -); - -LayerSelectorPanel.displayName = 'LayerSelectorPanel'; - -/** @type {import('./map-control').MapControlPanelComponent} */ -const MapControlPanel = React.memo( - ({children, header, onClick, scale = 1, isExport, disableClose = false, logoComponent}) => ( - - - {isExport && logoComponent ? ( - logoComponent - ) : header ? ( - - - - ) : null} - {isExport ? null : ( - - - - )} - - {children} - - ) -); - -MapControlPanel.displayName = 'MapControlPanel'; - -MapLegendPanelFactory.deps = []; -export function MapLegendPanelFactory() { - const defaultActionIcons = { - legend: Legend - }; - /** @type {import('./map-control').MapLegendPanelComponent} */ - const MapLegendPanel = ({ - layers, - isActive, - scale, - onToggleMenuPanel, - isExport, - disableClose, - logoComponent, - actionIcons = defaultActionIcons - }) => - !isActive ? ( - ( { - e.preventDefault(); - onToggleMenuPanel(); - }} - > - - - ) - ) : ( - ( - - ) - ); - - MapLegendPanel.displayName = 'MapControlPanel'; - return MapLegendPanel; -} - -SplitMapButtonFactory.deps = []; -export function SplitMapButtonFactory() { - const defaultActionIcons = { - delete: Delete, - split: Split - }; - /** @type {import('./map-control').SplitMapButtonComponent} */ - const SplitMapButton = React.memo( - ({isSplit, mapIndex, onToggleSplitMap, actionIcons = defaultActionIcons}) => ( - { - e.preventDefault(); - onToggleSplitMap(isSplit ? mapIndex : undefined); - }} - key={`split-${isSplit}`} - className={classnames('map-control-button', 'split-map', {'close-map': isSplit})} - data-tip - data-for="action-toggle" - > - {isSplit ? : } - - - ) - ); - - SplitMapButton.displayName = 'SplitMapButton'; - return SplitMapButton; -} - -Toggle3dButtonFactory.deps = []; -export function Toggle3dButtonFactory() { - const defaultActionIcons = { - cube: Cube3d - }; - /** @type {import('./map-control').Toggle3dButtonComponent} */ - const Toggle3dButton = React.memo( - ({dragRotate, onTogglePerspective, actionIcons = defaultActionIcons}) => ( - { - e.preventDefault(); - onTogglePerspective(); - }} - active={dragRotate} - data-tip - data-for="action-3d" - > - - - - ) - ); - - Toggle3dButton.displayName = 'Toggle3dButton'; - return Toggle3dButton; -} -const StyledToolbar = styled(VerticalToolbar)` - position: absolute; - right: 32px; -`; - -MapDrawPanelFactory.deps = []; -export function MapDrawPanelFactory() { - const defaultActionIcons = { - visible: EyeSeen, - hidden: EyeUnseen, - polygon: DrawPolygon, - cursor: CursorClick, - innerPolygon: Polygon, - rectangle: Rectangle - }; - /** @type {import('./map-control').MapDrawPanelComponent} */ - const MapDrawPanel = React.memo( - ({ - editor, - isActive, - onToggleMenuPanel, - onSetEditorMode, - onToggleEditorVisibility, - actionIcons = defaultActionIcons - }) => { - return ( -
- {isActive ? ( - - onSetEditorMode(EDITOR_MODES.EDIT)} - label="toolbar.select" - icon={actionIcons.cursor} - active={editor.mode === EDITOR_MODES.EDIT} - /> - onSetEditorMode(EDITOR_MODES.DRAW_POLYGON)} - label="toolbar.polygon" - icon={actionIcons.innerPolygon} - active={editor.mode === EDITOR_MODES.DRAW_POLYGON} - /> - onSetEditorMode(EDITOR_MODES.DRAW_RECTANGLE)} - label="toolbar.rectangle" - icon={actionIcons.rectangle} - active={editor.mode === EDITOR_MODES.DRAW_RECTANGLE} - /> - - - ) : null} - { - e.preventDefault(); - onToggleMenuPanel(); - }} - active={isActive} - data-tip - data-for="map-draw" - > - - - -
- ); - } - ); - - MapDrawPanel.displayName = 'MapDrawPanel'; - return MapDrawPanel; -} - -/** @type {import('./map-control').LocalePanelComponent} */ -const LocalePanel = React.memo( - ({availableLocales, isActive, onToggleMenuPanel, onSetLocale, activeLocale, disableClose}) => { - const onClickItem = useCallback( - locale => { - onSetLocale(locale); - }, - [onSetLocale] - ); - - const onClickButton = useCallback( - e => { - e.preventDefault(); - onToggleMenuPanel(); - }, - [onToggleMenuPanel] - ); - const getLabel = useCallback(locale => `toolbar.${locale}`, []); - - return ( -
- {isActive ? ( - - {availableLocales.map(locale => ( - onClickItem(locale)} - label={getLabel(locale)} - active={activeLocale === locale} - /> - ))} - - ) : null} - - {activeLocale.toUpperCase()} - - -
- ); - } -); - -LocalePanel.displayName = 'LocalePanel'; - const LegendLogo = ; + MapControlFactory.deps = [ MapDrawPanelFactory, Toggle3dButtonFactory, SplitMapButtonFactory, - MapLegendPanelFactory + MapLegendPanelFactory, + LayerSelectorPanelFactory, + LocalePanelFactory ]; -function MapControlFactory(MapDrawPanel, Toggle3dButton, SplitMapButton, MapLegendPanel) { - class MapControl extends Component { - static propTypes = { - datasets: PropTypes.object.isRequired, - dragRotate: PropTypes.bool.isRequired, - isSplit: PropTypes.bool.isRequired, - layers: PropTypes.arrayOf(PropTypes.object), - layersToRender: PropTypes.object.isRequired, - mapIndex: PropTypes.number.isRequired, - mapControls: PropTypes.object.isRequired, - onTogglePerspective: PropTypes.func.isRequired, - onToggleSplitMap: PropTypes.func.isRequired, - onToggleMapControl: PropTypes.func.isRequired, - onSetEditorMode: PropTypes.func.isRequired, - onToggleEditorVisibility: PropTypes.func.isRequired, - top: PropTypes.number.isRequired, - onSetLocale: PropTypes.func.isRequired, - locale: PropTypes.string.isRequired, - logoComponent: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), - - // optional - readOnly: PropTypes.bool, - scale: PropTypes.number, - mapLayers: PropTypes.object, - editor: PropTypes.object - }; - static defaultProps = { - isSplit: false, - top: 0, - mapIndex: 0, - logoComponent: LegendLogo - }; - - layerSelector = props => props.layers; - layersToRenderSelector = props => props.layersToRender; - layerPanelItemsSelector = createSelector( - this.layerSelector, - this.layersToRenderSelector, - (layers, layersToRender) => - layers - .filter(l => l.config.isVisible) - .map(layer => ({ - id: layer.id, - name: layer.config.label, - // layer - isVisible: layersToRender[layer.id] - })) +function MapControlFactory( + MapDrawPanel, + Toggle3dButton, + SplitMapButton, + MapLegendPanel, + LayerSelectorPanel, + LocalePanel +) { + const DEFAULT_ACTIONS = [ + SplitMapButton, + LayerSelectorPanel, + Toggle3dButton, + MapLegendPanel, + MapDrawPanel, + LocalePanel + ]; + + /** @type {import('./map-control').MapControl} */ + const MapControl = ({actionComponents = DEFAULT_ACTIONS, ...props}) => { + return ( + + {actionComponents.map((ActionComponent, index) => ( + + ))} + ); + }; - render() { - const { - dragRotate, - layers, - layersToRender, - isSplit, - isExport, - mapIndex, - mapControls, - onTogglePerspective, - onToggleSplitMap, - onMapToggleLayer, - onToggleMapControl, - editor, - scale, - readOnly, - locale, - top, - logoComponent - } = this.props; - - const { - visibleLayers = {}, - mapLegend = {}, - toggle3d = {}, - splitMap = {}, - mapDraw = {}, - mapLocale = {} - } = mapControls; - - return ( - - {/* Split Map */} - {splitMap.show && readOnly !== true ? ( - - - - ) : null} - - {/* Map Layers */} - {isSplit && visibleLayers.show && readOnly !== true ? ( - - onToggleMapControl('visibleLayers')} - disableClose={visibleLayers.disableClose} - /> - - ) : null} - - {/* 3D Map */} - {toggle3d.show ? ( - - - - ) : null} - - {/* Map Legend */} - {mapLegend.show ? ( - - layersToRender[l.id])} - scale={scale} - isExport={isExport} - onMapToggleLayer={onMapToggleLayer} - isActive={mapLegend.active} - onToggleMenuPanel={() => onToggleMapControl('mapLegend')} - disableClose={mapLegend.disableClose} - logoComponent={logoComponent} - /> - - ) : null} - - {mapDraw.show ? ( - - onToggleMapControl('mapDraw')} - onSetEditorMode={this.props.onSetEditorMode} - onToggleEditorVisibility={this.props.onToggleEditorVisibility} - /> - - ) : null} - - {mapLocale.show ? ( - - onToggleMapControl('mapLocale')} - disableClose={mapLocale.disableClose} - /> - - ) : null} - - ); - } - } + MapControl.defaultProps = { + isSplit: false, + top: 0, + mapIndex: 0, + logoComponent: LegendLogo + }; MapControl.displayName = 'MapControl'; - return MapControl; + return React.memo(MapControl); } export default MapControlFactory; diff --git a/src/components/map/map-draw-panel.d.ts b/src/components/map/map-draw-panel.d.ts new file mode 100644 index 0000000000..4519d3ba09 --- /dev/null +++ b/src/components/map/map-draw-panel.d.ts @@ -0,0 +1,16 @@ +import React from 'react'; +import {Editor, MapControls} from '../../reducers'; +import {MapDrawPanelIcons} from './map-control'; + +export type MapDrawPanelProps = { + editor: Editor, + mapControls: MapControls, + onToggleMapControl: (control: string) => void; + onSetEditorMode: (mode: string) => void, + onToggleEditorVisibility: () => void, + actionIcons: MapDrawPanelIcons +}; + +export type MapDrawPanelComponent = React.FunctionComponent; +export function MapDrawPanelFactory(): MapDrawPanelComponent; + diff --git a/src/components/map/map-draw-panel.js b/src/components/map/map-draw-panel.js new file mode 100644 index 0000000000..80058fbcbe --- /dev/null +++ b/src/components/map/map-draw-panel.js @@ -0,0 +1,101 @@ +import React, {useCallback} from 'react'; +import {EDITOR_MODES} from 'constants/default-settings'; +import { + CursorClick, + DrawPolygon, + EyeSeen, + EyeUnseen, + Polygon, + Rectangle +} from 'components/common/icons'; +import {MapControlButton} from 'components/common/styled-components'; +import ToolbarItem from 'components/common/toolbar-item'; +import MapControlTooltipFactory from './map-control-tooltip'; +import MapControlPanelFactory from './map-control-panel'; +import MapControlToolbarFactory from './map-control-toolbar'; + +MapDrawPanelFactory.deps = [ + MapControlTooltipFactory, + MapControlPanelFactory, + MapControlToolbarFactory +]; +function MapDrawPanelFactory(MapControlTooltip, MapControlPanel, MapControlToolbar) { + const defaultActionIcons = { + visible: EyeSeen, + hidden: EyeUnseen, + polygon: DrawPolygon, + cursor: CursorClick, + innerPolygon: Polygon, + rectangle: Rectangle + }; + /** @type {import('./map-draw-panel').MapDrawPanelComponent} */ + const MapDrawPanel = React.memo( + ({ + editor, + mapControls, + onToggleMapControl, + onSetEditorMode, + onToggleEditorVisibility, + actionIcons = defaultActionIcons + }) => { + // const {mapDraw} = mapControls; + // const {active: isActive} = mapDraw; + const isActive = mapControls?.mapDraw?.active; + const onToggleMenuPanel = useCallback(() => onToggleMapControl('mapDraw'), [ + onToggleMapControl + ]); + return ( +
+ {isActive ? ( + + onSetEditorMode(EDITOR_MODES.EDIT)} + label="toolbar.select" + icon={actionIcons.cursor} + active={editor.mode === EDITOR_MODES.EDIT} + /> + onSetEditorMode(EDITOR_MODES.DRAW_POLYGON)} + label="toolbar.polygon" + icon={actionIcons.innerPolygon} + active={editor.mode === EDITOR_MODES.DRAW_POLYGON} + /> + onSetEditorMode(EDITOR_MODES.DRAW_RECTANGLE)} + label="toolbar.rectangle" + icon={actionIcons.rectangle} + active={editor.mode === EDITOR_MODES.DRAW_RECTANGLE} + /> + + + ) : null} + { + e.preventDefault(); + onToggleMenuPanel(); + }} + active={isActive} + data-tip + data-for="map-draw" + > + + + +
+ ); + } + ); + + MapDrawPanel.displayName = 'MapDrawPanel'; + return MapDrawPanel; +} + +export default MapDrawPanelFactory; diff --git a/src/components/map/map-legend-panel.d.ts b/src/components/map/map-legend-panel.d.ts new file mode 100644 index 0000000000..00cab679c0 --- /dev/null +++ b/src/components/map/map-legend-panel.d.ts @@ -0,0 +1,20 @@ +import React, {Element} from 'react'; +import {Layer} from 'layers'; +import {MapControls} from '../../reducers'; + +interface MapLegendPanelIcons { + legend: ComponentType +} + +export type MapLegendPanelProps = { + layers: ReadonlyArray; + scale: number; + onToggleMapControl: (control: string) => void; + isExport: boolean; + logoComponent: Element; + actionIcons: MapLegendPanelIcons; + mapControls: MapControls; +}; + +export type MapLegendPanelComponent = React.FunctionComponent; +export function MapLegendPanelFactory(): MapLegendPanelComponent; diff --git a/src/components/map/map-legend-panel.js b/src/components/map/map-legend-panel.js new file mode 100644 index 0000000000..96f3e6d2f8 --- /dev/null +++ b/src/components/map/map-legend-panel.js @@ -0,0 +1,76 @@ +import React, {useCallback} from 'react'; +import {Legend} from 'components/common/icons'; +import {FormattedMessage} from 'localization'; +import {MapControlButton, Tooltip} from 'components/common/styled-components'; +import MapControlTooltipFactory from './map-control-tooltip'; +import MapControlPanelFactory from './map-control-panel'; +import MapLegendFactory from './map-legend'; + +const MapLegendTooltip = ({id, message}) => ( + + + + + +); + +MapLegendPanelFactory.deps = [MapControlTooltipFactory, MapControlPanelFactory, MapLegendFactory]; + +function MapLegendPanelFactory(MapControlTooltip, MapControlPanel, MapLegend) { + const defaultActionIcons = { + legend: Legend + }; + + /** @type {import('./map-legend-panel').MapLegendPanelComponent} */ + const MapLegendPanel = ({ + layers, + mapControls, + scale, + onToggleMapControl, + isExport, + logoComponent, + actionIcons = defaultActionIcons + }) => { + const {mapLegend} = mapControls; + const {active: isActive, disableClose} = mapLegend; + const onToggleMenuPanel = useCallback(() => onToggleMapControl('mapLegend'), [ + onToggleMapControl + ]); + + const onClick = useCallback( + e => { + e.preventDefault(); + onToggleMenuPanel(); + }, + [onToggleMenuPanel] + ); + + return !isActive ? ( + ( + + + ) + ) : ( + ( + + ) + ); + }; + + MapLegendPanel.displayName = 'MapControlPanel'; + return MapLegendPanel; +} + +export default MapLegendPanelFactory; diff --git a/src/components/map/map-legend.d.ts b/src/components/map/map-legend.d.ts index ebafc8bf25..98d136a35c 100644 --- a/src/components/map/map-legend.d.ts +++ b/src/components/map/map-legend.d.ts @@ -1,5 +1,6 @@ import {FunctionComponent} from 'react'; import {Layer, LayerConfig, VisualChannel, VisualChannelDescription} from 'layers'; +import {MapLegendPanelComponent} from './map-legend-panel'; export type SingleColorLegendProps = { width: number, @@ -25,5 +26,6 @@ export type MapLegendProps = { } }; -const MapLegend: FunctionComponent; -export default MapLegend; +export const MapLegend: FunctionComponent; + +export function MapLegendFactory(): MapLegend; diff --git a/src/components/map/map-legend.js b/src/components/map/map-legend.js index 2b7f2c27bc..7a71798ccb 100644 --- a/src/components/map/map-legend.js +++ b/src/components/map/map-legend.js @@ -143,60 +143,65 @@ LayerColorLegend.displayName = 'LayerColorLegend'; const isColorChannel = visualChannel => [CHANNEL_SCALES.color, CHANNEL_SCALES.colorAggr].includes(visualChannel.channelScaleType); -/** @type {typeof import('./map-legend').default }> */ -const MapLegend = ({layers = [], width, options}) => ( -
- {layers.map((layer, index) => { - if (!layer.isValidToSave() || layer.config.hidden) { - return null; - } - const containerW = width || DIMENSIONS.mapControl.width; - const colorChannels = Object.values(layer.visualChannels).filter(isColorChannel); - const nonColorChannels = Object.values(layer.visualChannels).filter( - vc => !isColorChannel(vc) - ); - - return ( - - {options?.showLayerName !== false ? ( -
{layer.config.label}
- ) : null} - {colorChannels.map(colorChannel => - !colorChannel.condition || colorChannel.condition(layer.config) ? ( - - ) : null - )} - {nonColorChannels.map(visualChannel => { - const matchCondition = - !visualChannel.condition || visualChannel.condition(layer.config); - const enabled = layer.config[visualChannel.field] || visualChannel.defaultMeasure; - - const description = layer.getVisualChannelDescription(visualChannel.key); - - return matchCondition && enabled ? ( - - ) : null; - })} -
- ); - })} -
-); +function MapLegendFactory() { + /** @type {typeof import('./map-legend').MapLegend }> */ + const MapLegend = ({layers = [], width, options}) => ( +
+ {layers.map((layer, index) => { + if (!layer.isValidToSave() || layer.config.hidden) { + return null; + } + const containerW = width || DIMENSIONS.mapControl.width; + const colorChannels = Object.values(layer.visualChannels).filter(isColorChannel); + const nonColorChannels = Object.values(layer.visualChannels).filter( + vc => !isColorChannel(vc) + ); + + return ( + + {options?.showLayerName !== false ? ( +
{layer.config.label}
+ ) : null} + {colorChannels.map(colorChannel => + !colorChannel.condition || colorChannel.condition(layer.config) ? ( + + ) : null + )} + {nonColorChannels.map(visualChannel => { + const matchCondition = + !visualChannel.condition || visualChannel.condition(layer.config); + const enabled = layer.config[visualChannel.field] || visualChannel.defaultMeasure; + + const description = layer.getVisualChannelDescription(visualChannel.key); + + return matchCondition && enabled ? ( + + ) : null; + })} +
+ ); + })} +
+ ); + + MapLegend.displayName = 'MapLegend'; + + return MapLegend; +} -/** @type {typeof import('./map-legend').default }> */ -export default MapLegend; +export default MapLegendFactory; diff --git a/src/components/map/split-map-button.d.ts b/src/components/map/split-map-button.d.ts new file mode 100644 index 0000000000..cb7dc1819a --- /dev/null +++ b/src/components/map/split-map-button.d.ts @@ -0,0 +1,19 @@ +import React from 'react'; +import {MapControls} from '../../reducers'; + +interface SplitMapButtonIcons { + delete: ComponentType; + split: ComponentType; +} + +export type SplitMapButtonProps = { + isSplit: boolean; + mapIndex: number; + onToggleSplitMap: (index?: number) => void; + actionIcons: SplitMapButtonIcons; + readOnly: boolean; + mapControls: MapControls; +}; + +export type SplitMapButtonComponent = React.FunctionComponent; +export function SplitMapButtonFactory(): SplitMapButtonComponent; diff --git a/src/components/map/split-map-button.js b/src/components/map/split-map-button.js new file mode 100644 index 0000000000..4374493b42 --- /dev/null +++ b/src/components/map/split-map-button.js @@ -0,0 +1,58 @@ +import React, {useCallback, useMemo} from 'react'; +import classnames from 'classnames'; +import {MapControlButton} from 'components/common/styled-components'; +import {Delete, Split} from 'components/common/icons'; +import MapControlTooltipFactory from './map-control-tooltip'; +import MapControlPanelFactory from './map-control-panel'; + +SplitMapButtonFactory.deps = [MapControlTooltipFactory, MapControlPanelFactory]; + +function SplitMapButtonFactory(MapControlTooltip) { + const defaultActionIcons = { + delete: Delete, + split: Split + }; + + /** @type {import('./split-map-button').SplitMapButtonComponent} */ + const SplitMapButton = ({ + isSplit, + mapIndex, + onToggleSplitMap, + actionIcons = defaultActionIcons, + mapControls, + readOnly + }) => { + const splitMap = mapControls?.splitMap || {}; + const onClick = useCallback( + event => { + event.preventDefault(); + onToggleSplitMap(isSplit ? mapIndex : undefined); + }, + [isSplit, mapIndex, onToggleSplitMap] + ); + + const isVisible = useMemo(() => splitMap.show && readOnly !== true, [splitMap.show, readOnly]); + + return isVisible ? ( + ( + {isSplit ? : } + + ) + ) : null; + }; + + SplitMapButton.displayName = 'SplitMapButton'; + return React.memo(SplitMapButton); +} + +export default SplitMapButtonFactory; diff --git a/src/components/map/toggle-3d-button.d.ts b/src/components/map/toggle-3d-button.d.ts new file mode 100644 index 0000000000..f98ceef60c --- /dev/null +++ b/src/components/map/toggle-3d-button.d.ts @@ -0,0 +1,16 @@ +import React from 'react'; +import {MapControls} from '../../reducers'; + +interface Toggle3dButtonIcons { + cube: ComponentType; +} + +export type Toggle3dButtonProps = { + dragRotate: boolean; + onTogglePerspective: () => void; + actionIcons: Toggle3dButtonIcons; + mapControls: MapControls; +}; + +export type Toggle3dButtonComponent = React.FunctionComponent; +export function Toggle3dButtonFactory(): Toggle3dButtonComponent; diff --git a/src/components/map/toggle-3d-button.js b/src/components/map/toggle-3d-button.js new file mode 100644 index 0000000000..6bd0113bf0 --- /dev/null +++ b/src/components/map/toggle-3d-button.js @@ -0,0 +1,47 @@ +import React, {useCallback, useMemo} from 'react'; +import {Cube3d} from 'components/common/icons'; +import {MapControlButton} from 'components/common/styled-components'; +import MapControlTooltipFactory from './map-control-tooltip'; +import MapControlPanelFactory from './map-control-panel'; + +Toggle3dButtonFactory.deps = [MapControlTooltipFactory, MapControlPanelFactory]; + +function Toggle3dButtonFactory(MapControlTooltip) { + const defaultActionIcons = { + cube: Cube3d + }; + /** @type {import('./toggle-3d-button').Toggle3dButtonComponent} */ + const Toggle3dButton = ({ + dragRotate, + onTogglePerspective, + actionIcons = defaultActionIcons, + mapControls + }) => { + const onClick = useCallback( + event => { + event.preventDefault(); + onTogglePerspective(); + }, + [onTogglePerspective] + ); + + const isVisible = useMemo(() => { + return (mapControls?.toggle3d || {}).show; + }, [mapControls]); + + return isVisible ? ( + ( + + + ) + ) : null; + }; + + Toggle3dButton.displayName = 'Toggle3dButton'; + return React.memo(Toggle3dButton); +} + +export default Toggle3dButtonFactory; diff --git a/test/browser/components/kepler-gl-test.js b/test/browser/components/kepler-gl-test.js index ffcfb4c179..e5d40bf644 100644 --- a/test/browser/components/kepler-gl-test.js +++ b/test/browser/components/kepler-gl-test.js @@ -64,6 +64,7 @@ const initialState = { const mockStore = configureStore(); test('Components -> KeplerGl -> Mount', t => { + drainTasksForTesting(); // mount with empty store const store = mockStore(initialState); let wrapper; @@ -94,6 +95,7 @@ test('Components -> KeplerGl -> Mount', t => { }); test('Components -> KeplerGl -> Mount -> readOnly', t => { + drainTasksForTesting(); // mount with readOnly true const initialStateReadonly = { keplerGl: { @@ -136,6 +138,7 @@ test('Components -> KeplerGl -> Mount -> readOnly', t => { }); test('Components -> KeplerGl -> Mount -> Plot', t => { + drainTasksForTesting(); // mount with readOnly true const initialStatePlots = { keplerGl: { @@ -181,6 +184,7 @@ test('Components -> KeplerGl -> Mount -> Plot', t => { }); test('Components -> KeplerGl -> Mount -> Split Maps', t => { + drainTasksForTesting(); // mount with readOnly true const initialStateSplitMap = { keplerGl: { @@ -224,6 +228,7 @@ test('Components -> KeplerGl -> Mount -> Split Maps', t => { test('Components -> KeplerGl -> Mount -> Load default map style task', t => { // mount with empty store + drainTasksForTesting(); const store = mockStore(initialState); t.doesNotThrow(() => { @@ -336,6 +341,7 @@ test('Components -> KeplerGl -> Mount -> Load default map style task', t => { }); test('Components -> KeplerGl -> Mount -> Load custom map style task', t => { + drainTasksForTesting(); // mount with empty store const store = mockStore(initialState); // mount without id or a kepler.gl state diff --git a/test/browser/components/map/map-control-test.js b/test/browser/components/map/map-control-test.js index c2da60e8ec..c0d800ed32 100644 --- a/test/browser/components/map/map-control-test.js +++ b/test/browser/components/map/map-control-test.js @@ -23,9 +23,11 @@ import {shallow} from 'enzyme'; import sinon from 'sinon'; import test from 'tape'; import MapControlFactory from 'components/map/map-control'; +import {appInjector} from 'components'; + +const MapControl = appInjector.get(MapControlFactory); test('MapControlFactory - display all options', t => { - const MapControl = MapControlFactory(); const onToggleSplitMap = sinon.spy(); const onTogglePerspective = sinon.spy(); const onToggleMapControl = sinon.spy(); @@ -58,7 +60,7 @@ test('MapControlFactory - display all options', t => { /> ); - t.equal($.find('ActionPanel').length, 5, 'Should show 5 action panels'); + t.equal($.find('.map-control-action').length, 6, 'Should show 6 action panels'); t.end(); }); diff --git a/test/browser/components/map/map-legend-test.js b/test/browser/components/map/map-legend-test.js index a03a0de35f..c4e7e63bf4 100644 --- a/test/browser/components/map/map-legend-test.js +++ b/test/browser/components/map/map-legend-test.js @@ -23,7 +23,7 @@ import test from 'tape'; import {mount} from 'enzyme'; import cloneDeep from 'lodash.clonedeep'; -import MapLegend, { +import MapLegendFactory, { StyledMapControlLegend, LayerColorLegend, VisualChannelMetric, @@ -37,6 +37,9 @@ import { expectedSavedLayer2 as geojsonLayer } from 'test/helpers/mock-state'; import {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils'; +import {appInjector} from 'components'; + +const MapLegend = appInjector.get(MapLegendFactory); test('Components -> MapLegend.render', t => { t.doesNotThrow(() => { diff --git a/test/helpers/mock-map-styles.js b/test/helpers/mock-map-styles.js new file mode 100644 index 0000000000..f49bcec6ae --- /dev/null +++ b/test/helpers/mock-map-styles.js @@ -0,0 +1,78 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const MOCK_MAP_STYLE = { + id: 'dark', + label: 'Dark', + url: 'mapbox://styles/xxxxx/abcdefg', + icon: 'https://my.icon.net/kepler.gl/test/taro.png', + layerGroups: [ + { + slug: 'water', + defaultVisibility: true + }, + { + slug: 'land', + defaultVisibility: true + } + ], + style: { + version: 8, + name: 'Mock-Map-Style', + metadata: {}, + center: [-73.96109142750214, 40.74028310359776], + zoom: 12.624759221889015, + bearing: 23.936999544510627, + pitch: 0, + light: { + anchor: 'viewport' + }, + sources: { + composite: { + url: 'mapbox://mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v7,eklimcz.00zz64mq', + type: 'vector' + } + }, + sprite: 'mapbox://sprites', + glyphs: 'mapbox://fonts', + layers: [ + { + id: 'background', + type: 'background', + layout: {}, + paint: { + 'background-color': '#09101D' + }, + interactive: true + }, + { + id: 'water', + type: 'fill', + source: 'composite', + 'source-layer': 'water', + layout: {}, + paint: { + 'fill-color': '#112330' + }, + interactive: true + } + ] + } +}; diff --git a/test/helpers/mock-state.js b/test/helpers/mock-state.js index 5199afaf6b..7d6f108bdb 100644 --- a/test/helpers/mock-state.js +++ b/test/helpers/mock-state.js @@ -20,6 +20,8 @@ import test from 'tape-catch'; import cloneDeep from 'lodash.clonedeep'; +import {drainTasksForTesting} from 'react-palm/tasks'; + import {VizColorPalette} from 'constants/custom-color-ranges'; import {getInitialInputStyle} from 'reducers/map-style-updaters'; @@ -53,6 +55,7 @@ import { import tripGeojson, {tripDataInfo} from 'test/fixtures/trip-geojson'; import {processCsvData, processGeojson} from 'processors/data-processor'; import {COMPARE_TYPES} from 'constants/tooltip'; +import {MOCK_MAP_STYLE} from './mock-map-styles'; const geojsonFields = cloneDeep(fields); const geojsonRows = cloneDeep(rows); @@ -95,6 +98,10 @@ function mockStateWithFileUpload() { // load csv and geojson const updatedState = applyActions(keplerGlReducer, initialState, [ + { + action: MapStyleActions.loadMapStyles, + payload: [{dark: MOCK_MAP_STYLE}] + }, { action: VisStateActions.updateVisData, payload: [[{info: csvInfo, data: {fields: testFields, rows: testAllData}}]] @@ -104,7 +111,8 @@ function mockStateWithFileUpload() { payload: [[{info: geojsonInfo, data: {fields: geojsonFields, rows: geojsonRows}}]] } ]); - + // cleanup tasks created during loadMapStyles + drainTasksForTesting(); // replace layer id and color with controlled value for testing updatedState.visState.layers.forEach((l, i) => { l.id = `${l.type}-${i}`;