diff --git a/src/components/src/dnd-context.tsx b/src/components/src/dnd-context.tsx index c9c810ae8c9..9893a184e17 100644 --- a/src/components/src/dnd-context.tsx +++ b/src/components/src/dnd-context.tsx @@ -59,6 +59,7 @@ function DndContextFactory( onToggleEnableConfig={nop} onDuplicateLayer={nop} onRemoveLayer={nop} + onZoomToLayer={nop} layerType={layer.type} allowDuplicate={false} isDragNDropEnabled={false} diff --git a/src/components/src/side-panel/layer-manager.tsx b/src/components/src/side-panel/layer-manager.tsx index 8775d5e1348..32e5a876f57 100644 --- a/src/components/src/side-panel/layer-manager.tsx +++ b/src/components/src/side-panel/layer-manager.tsx @@ -20,7 +20,7 @@ import InfoHelperFactory from '../common/info-helper'; import {LAYER_BLENDINGS, OVERLAY_BLENDINGS, PANEL_VIEW_TOGGLES} from '@kepler.gl/constants'; import {Layer, LayerClassesType} from '@kepler.gl/layers'; -import {UIStateActions, VisStateActions, ActionHandler} from '@kepler.gl/actions'; +import {UIStateActions, VisStateActions, MapStateActions, ActionHandler} from '@kepler.gl/actions'; import {SidePanelItem} from '../types'; import {PanelListView} from '@kepler.gl/types'; import {Datasets} from '@kepler.gl/table'; @@ -45,6 +45,7 @@ type LayerManagerProps = { overlayBlending: string; uiStateActions: typeof UIStateActions; visStateActions: typeof VisStateActions; + mapStateActions: typeof MapStateActions; showAddDataModal: () => void; removeDataset: ActionHandler; showDatasetTable: ActionHandler; @@ -174,6 +175,7 @@ function LayerManagerFactory( removeDataset, uiStateActions, visStateActions, + mapStateActions, panelListView, panelMetadata } = this.props; @@ -218,6 +220,7 @@ function LayerManagerFactory( layerClasses={this.props.layerClasses} uiStateActions={uiStateActions} visStateActions={visStateActions} + mapStateActions={mapStateActions} showDeleteDataset /> ) : ( @@ -229,6 +232,7 @@ function LayerManagerFactory( layerOrder={layerOrder} uiStateActions={uiStateActions} visStateActions={visStateActions} + mapStateActions={mapStateActions} layerClasses={this.props.layerClasses} /> )} diff --git a/src/components/src/side-panel/layer-panel/dataset-layer-group.tsx b/src/components/src/side-panel/layer-panel/dataset-layer-group.tsx index 23fafb69538..8a1b4e08a93 100644 --- a/src/components/src/side-panel/layer-panel/dataset-layer-group.tsx +++ b/src/components/src/side-panel/layer-panel/dataset-layer-group.tsx @@ -5,7 +5,7 @@ import React, {useMemo} from 'react'; import DatasetLayerSectionFactory from './dataset-layer-section'; import {Layer, LayerClassesType} from '@kepler.gl/layers'; -import {UIStateActions, VisStateActions, ActionHandler} from '@kepler.gl/actions'; +import {UIStateActions, VisStateActions, ActionHandler, MapStateActions} from '@kepler.gl/actions'; import {KeplerTable, Datasets} from '@kepler.gl/table'; type DatasetLayerGroupProps = { @@ -19,6 +19,7 @@ type DatasetLayerGroupProps = { updateTableColor: ActionHandler; uiStateActions: typeof UIStateActions; visStateActions: typeof VisStateActions; + mapStateActions: typeof MapStateActions; }; DatasetLayerGroupFactory.deps = [DatasetLayerSectionFactory]; @@ -37,7 +38,8 @@ function DatasetLayerGroupFactory( layerOrder, layerClasses, uiStateActions, - visStateActions + visStateActions, + mapStateActions } = props; const datasetLayerSectionData = useMemo(() => { @@ -69,6 +71,7 @@ function DatasetLayerGroupFactory( layerClasses={layerClasses} uiStateActions={uiStateActions} visStateActions={visStateActions} + mapStateActions={mapStateActions} /> ))} diff --git a/src/components/src/side-panel/layer-panel/dataset-layer-section.tsx b/src/components/src/side-panel/layer-panel/dataset-layer-section.tsx index eba9d5e825f..7d202448358 100644 --- a/src/components/src/side-panel/layer-panel/dataset-layer-section.tsx +++ b/src/components/src/side-panel/layer-panel/dataset-layer-section.tsx @@ -7,7 +7,7 @@ import styled from 'styled-components'; import SourceDataCatalogFactory from '../common/source-data-catalog'; import LayerListFactory from './layer-list'; import {Layer, LayerClassesType} from '@kepler.gl/layers'; -import {UIStateActions, ActionHandler, VisStateActions} from '@kepler.gl/actions'; +import {UIStateActions, ActionHandler, VisStateActions, MapStateActions} from '@kepler.gl/actions'; import {KeplerTable, Datasets} from '@kepler.gl/table'; type DatasetLayerSectionProps = { @@ -21,7 +21,8 @@ type DatasetLayerSectionProps = { updateTableColor: ActionHandler; removeDataset: ActionHandler; uiStateActions: typeof UIStateActions; - visStateActions: typeof VisStateActions; + visStateActions: typeof VisStateActions; + mapStateActions: typeof MapStateActions; }; const DatasetLayerSectionWrapper = styled.div.attrs({ @@ -48,7 +49,8 @@ function DatasetLayerSectionFactory( layerOrder, layerClasses, uiStateActions, - visStateActions + visStateActions, + mapStateActions } = props; const datasetCatalog = useMemo(() => { @@ -71,6 +73,7 @@ function DatasetLayerSectionFactory( layerClasses={layerClasses} uiStateActions={uiStateActions} visStateActions={visStateActions} + mapStateActions={mapStateActions} isSortable={false} /> diff --git a/src/components/src/side-panel/layer-panel/layer-list.tsx b/src/components/src/side-panel/layer-panel/layer-list.tsx index 1eb1af46e6a..8bb672ceff9 100644 --- a/src/components/src/side-panel/layer-panel/layer-list.tsx +++ b/src/components/src/side-panel/layer-panel/layer-list.tsx @@ -7,7 +7,7 @@ import classnames from 'classnames'; import {Layer, LayerClassesType} from '@kepler.gl/layers'; import {Datasets} from '@kepler.gl/table'; -import {UIStateActions, VisStateActions} from '@kepler.gl/actions'; +import {UIStateActions, VisStateActions, MapStateActions} from '@kepler.gl/actions'; import {useSortable, SortableContext, verticalListSortingStrategy} from '@dnd-kit/sortable'; import {CSS} from '@dnd-kit/utilities'; @@ -23,6 +23,7 @@ export type LayerListProps = { isSortable?: boolean; uiStateActions: typeof UIStateActions; visStateActions: typeof VisStateActions; + mapStateActions: typeof MapStateActions; }; export type LayerListFactoryDeps = [typeof LayerPanelFactory]; @@ -117,6 +118,7 @@ function LayerListFactory(LayerPanel: ReturnType) { layerOrder, uiStateActions, visStateActions, + mapStateActions, layerClasses, isSortable = true } = props; @@ -159,10 +161,11 @@ function LayerListFactory(LayerPanel: ReturnType) { layerVisConfigChange: visStateActions.layerVisConfigChange, layerTextLabelChange: visStateActions.layerTextLabelChange, removeLayer: visStateActions.removeLayer, + zoomToLayer: mapStateActions.fitBounds, duplicateLayer: visStateActions.duplicateLayer, layerSetIsValid: visStateActions.layerSetIsValid }), - [visStateActions] + [visStateActions, mapStateActions] ); const panelProps = useMemo( diff --git a/src/components/src/side-panel/layer-panel/layer-panel-header.tsx b/src/components/src/side-panel/layer-panel/layer-panel-header.tsx index c3034b92bb2..e5e38d75ffe 100644 --- a/src/components/src/side-panel/layer-panel/layer-panel-header.tsx +++ b/src/components/src/side-panel/layer-panel/layer-panel-header.tsx @@ -20,7 +20,8 @@ import { Trash, VertDots, WarningSign, - Reset + Reset, + Crosshairs } from '../../common/icons'; import {InlineInput, StyledPanelHeader} from '../../common/styled-components'; @@ -54,6 +55,7 @@ export type LayerPanelHeaderProps = { onUpdateLayerLabel: ChangeEventHandler; onToggleEnableConfig: MouseEventHandler; onRemoveLayer: MouseEventHandler; + onZoomToLayer: MouseEventHandler; onDuplicateLayer: MouseEventHandler; onResetIsValid: MouseEventHandler; isConfigActive: boolean; @@ -71,6 +73,7 @@ export type LayerPanelHeaderProps = { enableConfig: ComponentType>; resetIsValid: ComponentType>; duplicate: ComponentType>; + crosshairs: ComponentType>; }; listeners?: React.ElementType; }; @@ -278,6 +281,7 @@ export function LayerPanelHeaderActionSectionFactory( onToggleEnableConfig, onDuplicateLayer, onRemoveLayer, + onZoomToLayer, showRemoveLayer, isEditingLabel, // TODO: may not contain all necessary icons for all actions, e.g. actionIcons.duplicate. Need to to merge rather than replace @@ -305,6 +309,13 @@ export function LayerPanelHeaderActionSectionFactory( IconComponent={actionIcons.duplicate} disabled={!allowDuplicate} /> + {isValid ? ( ; layerTextLabelChange: ActionHandler; removeLayer: ActionHandler; + zoomToLayer: ActionHandler; duplicateLayer: ActionHandler; listeners?: React.ElementType; }; @@ -119,6 +120,12 @@ function LayerPanelFactory( this.props.removeLayer(this.props.layer.id); }; + _zoomToLayer: MouseEventHandler = e => { + e?.stopPropagation(); + const bounds = this.props.layer?.meta?.bounds; + bounds && this.props.zoomToLayer(bounds); + }; + _duplicateLayer: MouseEventHandler = e => { e?.stopPropagation(); this.props.duplicateLayer(this.props.layer.id); @@ -153,6 +160,7 @@ function LayerPanelFactory( onResetIsValid={this._resetIsValid} onUpdateLayerLabel={this._updateLayerLabel} onRemoveLayer={this._removeLayer} + onZoomToLayer={this._zoomToLayer} onDuplicateLayer={this._duplicateLayer} isDragNDropEnabled={isDraggable} listeners={listeners} diff --git a/src/localization/src/translations/cn.ts b/src/localization/src/translations/cn.ts index 913a9812f0d..19abb3faaf1 100644 --- a/src/localization/src/translations/cn.ts +++ b/src/localization/src/translations/cn.ts @@ -175,6 +175,7 @@ export default { hide: '隐藏', show: '显示', removeLayer: '删除图层', + zoomToLayer: '缩放☞图层', duplicateLayer: '复制图层', layerSettings: '图层设置', closePanel: '关闭当前面板', diff --git a/src/localization/src/translations/en.ts b/src/localization/src/translations/en.ts index a7687b34c16..084a7e7b9bd 100644 --- a/src/localization/src/translations/en.ts +++ b/src/localization/src/translations/en.ts @@ -202,6 +202,7 @@ export default { show: 'show', removeLayer: 'Remove layer', duplicateLayer: 'Duplicate layer', + zoomToLayer: 'Zoom to layer', resetAfterError: 'Try to enable the layer after an error', layerSettings: 'Layer settings', closePanel: 'Close current panel', diff --git a/test/browser/components/side-panel/layer-list.spec.js b/test/browser/components/side-panel/layer-list.spec.js index c2da052accb..3d424a10e2b 100644 --- a/test/browser/components/side-panel/layer-list.spec.js +++ b/test/browser/components/side-panel/layer-list.spec.js @@ -7,7 +7,7 @@ import cloneDeep from 'lodash.clonedeep'; import {fireEvent, screen} from '@testing-library/react'; import {dataTestIds} from '@kepler.gl/constants'; import {appInjector, LayerListFactory} from '@kepler.gl/components'; -import {VisStateActions, UIStateActions, addDataToMap, keplerGlInit} from '@kepler.gl/actions'; +import {VisStateActions, UIStateActions,MapStateActions, addDataToMap, keplerGlInit} from '@kepler.gl/actions'; import {processCsvData} from '@kepler.gl/processors'; import {keplerGlReducerCore as keplerGlReducer} from '@kepler.gl/reducers'; @@ -86,7 +86,8 @@ const defaultProps = { layerOrder: StateWMultiH3Layers.visState.layerOrder, layers: StateWMultiH3Layers.visState.layers, uiStateActions: UIStateActions, - visStateActions: VisStateActions + visStateActions: VisStateActions, + mapStateActions: MapStateActions }; // jest.mock('@kepler.gl/actions'); diff --git a/test/browser/components/side-panel/layer-manager-test.js b/test/browser/components/side-panel/layer-manager-test.js index 7caa31b4708..fa6a779d78f 100644 --- a/test/browser/components/side-panel/layer-manager-test.js +++ b/test/browser/components/side-panel/layer-manager-test.js @@ -18,7 +18,7 @@ import { import {mountWithTheme, IntlWrapper} from 'test/helpers/component-utils'; -import {VisStateActions, UIStateActions} from '@kepler.gl/actions'; +import {VisStateActions, UIStateActions, MapStateActions} from '@kepler.gl/actions'; import {StateWMultiH3Layers} from 'test/helpers/mock-state'; @@ -52,6 +52,7 @@ const defaultProps = { layerPanelListView: 'list', uiStateActions: UIStateActions, visStateActions: VisStateActions, + mapStateActions: MapStateActions, layerBlending: 'normal', overlayBlending: 'normal' }; diff --git a/test/browser/components/side-panel/layer-panel-header-test.js b/test/browser/components/side-panel/layer-panel-header-test.js index 73f22ed2bb2..824e64c0f27 100644 --- a/test/browser/components/side-panel/layer-panel-header-test.js +++ b/test/browser/components/side-panel/layer-panel-header-test.js @@ -19,7 +19,8 @@ const defaultProps = { onToggleVisibility: nop, onUpdateLayerLabel: nop, onToggleEnableConfig: nop, - onRemoveLayer: nop + onRemoveLayer: nop, + onZoomToLayer: nop, }; test('Components -> LayerPanelHeader.mount -> no prop', t => { diff --git a/test/browser/components/side-panel/side-panel-test.js b/test/browser/components/side-panel/side-panel-test.js index 9878abf7a43..56413b67bd5 100644 --- a/test/browser/components/side-panel/side-panel-test.js +++ b/test/browser/components/side-panel/side-panel-test.js @@ -20,7 +20,7 @@ import { appInjector } from '@kepler.gl/components'; -import {VisStateActions, MapStyleActions, UIStateActions} from '@kepler.gl/actions'; +import {VisStateActions, MapStyleActions, UIStateActions, MapStateActions} from '@kepler.gl/actions'; import {IntlWrapper, mountWithTheme} from 'test/helpers/component-utils'; @@ -58,6 +58,7 @@ const defaultProps = { width: 300, uiStateActions: UIStateActions, visStateActions: VisStateActions, + mapStateActions: MapStateActions, mapStyleActions: MapStyleActions, availableProviders: {} };