diff --git a/CHANGELOG.md b/CHANGELOG.md index 73bde87a3..d531de746 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Not released +- Upgrade storybook and fix custom-component stories [#303](https://github.com/CartoDB/carto-react/pull/303) +- Name refactor in DrawingTool for FeatureSelection [#271](https://github.com/CartoDB/carto-react/pull/271) +- Add compatibility between Google Maps and FeatureSelectionLayer [#271](https://github.com/CartoDB/carto-react/pull/271) +- Update deck.gl version to 8.7.0-beta.2 and integrate new deck.gl MaskExtension, required by FeatureSelectionLayer [#271](https://github.com/CartoDB/carto-react/pull/271) + ## 1.2 ### 1.2.1-alpha.8 (2022-01-27) diff --git a/package.json b/package.json index 64920a517..5d70f68b6 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,13 @@ "packages/*" ], "devDependencies": { - "@deck.gl/carto": "^8.7.0-alpha.11", - "@deck.gl/core": "^8.7.0-alpha.11", - "@deck.gl/extensions": "^8.7.0-alpha.11", - "@deck.gl/geo-layers": "^8.7.0-alpha.11", - "@deck.gl/google-maps": "^8.7.0-alpha.11", - "@deck.gl/layers": "^8.7.0-alpha.11", - "@deck.gl/mesh-layers": "^8.7.0-alpha.11", + "@deck.gl/carto": "^8.7.0-beta.2", + "@deck.gl/core": "^8.7.0-beta.2", + "@deck.gl/extensions": "^8.7.0-beta.2", + "@deck.gl/geo-layers": "^8.7.0-beta.2", + "@deck.gl/google-maps": "^8.7.0-beta.2", + "@deck.gl/layers": "^8.7.0-beta.2", + "@deck.gl/mesh-layers": "^8.7.0-beta.2", "@material-ui/core": "^4.11.3", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.57", @@ -53,6 +53,8 @@ "test:coverage": "lerna run --parallel test:coverage && node scripts/mergeCoverage.js", "link-all": "lerna exec --parallel yarn link", "unlink-all": "lerna exec --parallel yarn unlink", + "link-deck": "yarn link @deck.gl/core @deck.gl/layers @deck.gl/geo-layers @deck.gl/google-maps @deck.gl/carto @deck.gl/extensions", + "unlink-deck": "yarn unlink @deck.gl/core @deck.gl/layers @deck.gl/geo-layers @deck.gl/google-maps @deck.gl/carto @deck.gl/extensions", "storybook:start": "lerna --scope @carto/react-ui exec -- npm run storybook:start", "storybook:build": "lerna --scope @carto/react-ui exec -- npm run storybook:build", "storybook:publish": "lerna --scope @carto/react-ui exec -- npm run storybook:publish", diff --git a/packages/react-api/__tests__/hooks/mask-extension-util.test.js b/packages/react-api/__tests__/hooks/mask-extension-util.test.js new file mode 100644 index 000000000..8fc94a777 --- /dev/null +++ b/packages/react-api/__tests__/hooks/mask-extension-util.test.js @@ -0,0 +1,29 @@ +import { MASK_ID } from '@carto/react-core/'; +import { MaskExtension } from '@deck.gl/extensions'; +import { getMaskExtensionProps } from '../../src/hooks/maskExtensionUtil'; + +describe('mask-extension-util', () => { + test('correct values without maskPolygon', () => { + const myMaskPolygon = undefined; + const { maskId, maskEnabled, extensions } = getMaskExtensionProps(myMaskPolygon); + + expect(maskId).toEqual(MASK_ID); + expect(maskEnabled).toEqual(false); + expect(extensions[0]).toBeInstanceOf(MaskExtension); + }); + + test('correct values width maskPolygon', () => { + const myMaskPolygon = [ + [-41.484375, 35.17380831799959], + [0.3515625, 35.17380831799959], + [0.3515625, 53.330872983017066], + [-41.484375, 53.330872983017066], + [-41.484375, 35.17380831799959] + ]; + const { maskId, maskEnabled, extensions } = getMaskExtensionProps(myMaskPolygon); + + expect(maskId).toEqual(MASK_ID); + expect(maskEnabled).toEqual(true); + expect(extensions[0]).toBeInstanceOf(MaskExtension); + }); +}); diff --git a/packages/react-api/__tests__/hooks/useCartoLayerProps.test.js b/packages/react-api/__tests__/hooks/useCartoLayerProps.test.js index 4cbc80e63..92bc2e2bd 100644 --- a/packages/react-api/__tests__/hooks/useCartoLayerProps.test.js +++ b/packages/react-api/__tests__/hooks/useCartoLayerProps.test.js @@ -1,5 +1,6 @@ -import { DataFilterExtension } from '@deck.gl/extensions'; +import { DataFilterExtension, MaskExtension } from '@deck.gl/extensions'; import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto'; +import { MASK_ID } from '@carto/react-core/'; import { renderHook } from '@testing-library/react-hooks'; import useCartoLayerProps from '../../src/hooks/useCartoLayerProps'; import { mockReduxHooks, mockClear } from '../mockReduxHooks'; @@ -19,7 +20,9 @@ describe('useCartoLayerProps', () => { 'filterRange', 'updateTriggers', 'getFilterValue', - 'extensions' + 'extensions', + 'maskId', + 'maskEnabled' ]; describe('when maps_api_version is V2', () => { @@ -188,11 +191,12 @@ describe('useCartoLayerProps', () => { ]); }); - test('extensions should have an unique instance of DataFilterExtension', () => { + test('extensions should have an instance of DataFilterExtension and MaskExtension', () => { const { result } = renderHook(() => useCartoLayerProps({})); - expect(result.current.extensions.length).toBe(1); + expect(result.current.extensions.length).toBe(2); expect(result.current.extensions[0]).toBeInstanceOf(DataFilterExtension); + expect(result.current.extensions[1]).toBeInstanceOf(MaskExtension); }); test(`filter size should be ${MAX_GPU_FILTERS}`, () => { @@ -206,6 +210,17 @@ describe('useCartoLayerProps', () => { expect(result.current.updateTriggers).toHaveProperty('getFilterValue'); }); + + test('maskEnabled should be present and be false by default', () => { + const { result } = renderHook(() => useCartoLayerProps({})); + expect(result.current.maskEnabled).toEqual(false); + }); + + test('maskId should be present and should have the correct value', () => { + const { result } = renderHook(() => useCartoLayerProps({})); + + expect(result.current.maskId).toEqual(MASK_ID); + }); }); mockClear(); diff --git a/packages/react-api/package.json b/packages/react-api/package.json index 94fa2bcc6..3c93a3947 100644 --- a/packages/react-api/package.json +++ b/packages/react-api/package.json @@ -64,12 +64,12 @@ "@babel/runtime": "^7.13.9" }, "peerDependencies": { - "@carto/react-core": "^1.1.4", - "@carto/react-redux": "^1.1.4", - "@carto/react-workers": "^1.1.4", - "@deck.gl/carto": "^8.7.0-alpha.11", - "@deck.gl/core": "^8.7.0-alpha.11", - "@deck.gl/extensions": "^8.7.0-alpha.11", + "@carto/react-core": "^1.2.1-alpha.8", + "@carto/react-redux": "^1.2.1-alpha.8", + "@carto/react-workers": "^1.2.1-alpha.8", + "@deck.gl/carto": "^8.7.0-beta.2", + "@deck.gl/core": "^8.7.0-beta.2", + "@deck.gl/extensions": "^8.7.0-beta.2", "react": "^17.0.1", "react-dom": "^17.0.1", "react-redux": "^7.2.2", diff --git a/packages/react-api/src/hooks/dataFilterExtensionUtil.js b/packages/react-api/src/hooks/dataFilterExtensionUtil.js index f9c29944c..026385f53 100644 --- a/packages/react-api/src/hooks/dataFilterExtensionUtil.js +++ b/packages/react-api/src/hooks/dataFilterExtensionUtil.js @@ -3,6 +3,7 @@ import { _buildFeatureFilter, _FilterTypes } from '@carto/react-core/'; // Don't change this value to maintain compatibility with builder export const MAX_GPU_FILTERS = 4; +const dataFilterExtension = new DataFilterExtension({ filterSize: MAX_GPU_FILTERS }); function getFiltersByType(filters) { const filtersWithoutTimeType = {}; @@ -84,6 +85,6 @@ export function getDataFilterExtensionProps(filters = {}) { filterRange: getFilterRange(timeFilter), updateTriggers: getUpdateTriggers(filtersWithoutTimeType, timeFilter), getFilterValue: getFilterValue(filtersWithoutTimeType, timeFilter), - extensions: [new DataFilterExtension({ filterSize: MAX_GPU_FILTERS })] + extensions: [dataFilterExtension] }; } diff --git a/packages/react-api/src/hooks/maskExtensionUtil.js b/packages/react-api/src/hooks/maskExtensionUtil.js new file mode 100644 index 000000000..250e7cd00 --- /dev/null +++ b/packages/react-api/src/hooks/maskExtensionUtil.js @@ -0,0 +1,12 @@ +import { MASK_ID } from '@carto/react-core/'; +import { MaskExtension } from '@deck.gl/extensions'; + +const maskExtension = new MaskExtension(); + +export function getMaskExtensionProps(maskPolygon) { + return { + maskId: MASK_ID, + maskEnabled: Boolean(maskPolygon && maskPolygon.length), + extensions: [maskExtension] + }; +} diff --git a/packages/react-api/src/hooks/useCartoLayerProps.js b/packages/react-api/src/hooks/useCartoLayerProps.js index adc689770..c9dea764a 100644 --- a/packages/react-api/src/hooks/useCartoLayerProps.js +++ b/packages/react-api/src/hooks/useCartoLayerProps.js @@ -4,6 +4,7 @@ import { selectSpatialFilter } from '@carto/react-redux'; import useGeojsonFeatures from './useGeojsonFeatures'; import useTileFeatures from './useTileFeatures'; import { getDataFilterExtensionProps } from './dataFilterExtensionUtil'; +import { getMaskExtensionProps } from './maskExtensionUtil'; export default function useCartoLayerProps({ source, @@ -50,6 +51,11 @@ export default function useCartoLayerProps({ } const dataFilterExtensionProps = getDataFilterExtensionProps(source?.filters); + const maskExtensionProps = getMaskExtensionProps(spatialFilter?.geometry?.coordinates); + const extensions = [ + ...dataFilterExtensionProps.extensions, + ...maskExtensionProps.extensions + ]; return { ...props, @@ -59,6 +65,8 @@ export default function useCartoLayerProps({ connection: source?.connection, credentials: source?.credentials, clientId: 'carto-for-react', - ...dataFilterExtensionProps + ...dataFilterExtensionProps, + ...maskExtensionProps, + extensions }; } diff --git a/packages/react-api/src/types.d.ts b/packages/react-api/src/types.d.ts index beb05599e..a1dd3b691 100644 --- a/packages/react-api/src/types.d.ts +++ b/packages/react-api/src/types.d.ts @@ -1,4 +1,4 @@ -import { DataFilterExtension } from '@deck.gl/extensions'; +import { DataFilterExtension, MaskExtension } from '@deck.gl/extensions'; import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto'; import { FeatureCollection } from 'geojson'; @@ -33,10 +33,12 @@ export type UseCartoLayerFilterProps = { onDataLoad?: Function; getFilterValue: Function; filterRange: [number, number]; - extensions: DataFilterExtension[]; + extensions: [DataFilterExtension, MaskExtension]; updateTriggers: { getFilterValue: Record; }; + maskEnabled: boolean; + maskId: string; } & SourceProps; export type ExecuteSQLResponse = Promise; diff --git a/packages/react-auth/package.json b/packages/react-auth/package.json index cf169060e..c0b8e4e10 100644 --- a/packages/react-auth/package.json +++ b/packages/react-auth/package.json @@ -64,7 +64,7 @@ "@babel/runtime": "^7.13.9" }, "peerDependencies": { - "@carto/react-core": "^1.1.4", + "@carto/react-core": "^1.2.1-alpha.8", "react": "^17.0.1", "react-dom": "^17.0.1" } diff --git a/packages/react-basemaps/package.json b/packages/react-basemaps/package.json index 66197968d..8cd8346e0 100644 --- a/packages/react-basemaps/package.json +++ b/packages/react-basemaps/package.json @@ -64,8 +64,8 @@ "@babel/runtime": "^7.13.9" }, "peerDependencies": { - "@carto/react-core": "^1.1.4", - "@deck.gl/google-maps": "^8.7.0-alpha.11", + "@carto/react-core": "^1.2.1-alpha.8", + "@deck.gl/google-maps": "^8.7.0-beta.2", "react": "^17.0.1", "react-dom": "^17.0.1" } diff --git a/packages/react-basemaps/src/basemaps/GoogleMap.js b/packages/react-basemaps/src/basemaps/GoogleMap.js index 856c897b4..035f9f1ff 100644 --- a/packages/react-basemaps/src/basemaps/GoogleMap.js +++ b/packages/react-basemaps/src/basemaps/GoogleMap.js @@ -67,7 +67,7 @@ export function GoogleMap(props) { const mapNotConnected = containerRef.current.children.length === 0; if (!window.cartoGmap || mapNotConnected) { const map = new window.google.maps.Map(containerRef.current, options); - const deckOverlay = new GoogleMapsOverlay({ layers, getTooltip }); + const deckOverlay = new GoogleMapsOverlay({ getTooltip }); const handleViewportChange = () => { const center = map.getCenter(); @@ -104,6 +104,7 @@ export function GoogleMap(props) { window.cartoGmap = map; window.cartoDeck = deckOverlay; + window.cartoDeck.setMap(window.cartoGmap); } else { const { center, heading, tilt, zoom, ...rest } = options; const newViewState = { @@ -120,12 +121,16 @@ export function GoogleMap(props) { window.cartoGmap.setZoom(zoom); } window.cartoGmap.setOptions(rest); - window.cartoDeck.setProps({ layers, getTooltip }); + window.cartoDeck.setProps({ getTooltip }); } - - window.cartoDeck.setMap(window.cartoGmap); }; + // Set layers should be outside of the useEffect to avoid problems with layers that changes + // dinamically with move events like the FeatureSelectionLayer + if (window.cartoDeck) { + window.cartoDeck.setProps({ layers }); + } + useEffect(() => { if (!document.querySelector('#gmaps')) { const script = document.createElement(`script`); diff --git a/packages/react-core/src/index.d.ts b/packages/react-core/src/index.d.ts index 5664d26ec..0f16f9d5b 100644 --- a/packages/react-core/src/index.d.ts +++ b/packages/react-core/src/index.d.ts @@ -27,4 +27,4 @@ export { AggregationFunctions, GroupByFeature, HistogramFeature, Viewport, TileF export { GroupDateTypes } from './operations/GroupDateTypes'; export { groupValuesByDateColumn } from './operations/groupByDate'; -export { DRAW_MODES, EDIT_MODES } from './utils/drawingToolConstants'; +export { FEATURE_SELECTION_MODES, EDIT_MODES, MASK_ID } from './utils/featureSelectionConstants'; diff --git a/packages/react-core/src/index.js b/packages/react-core/src/index.js index 34ea2ee2f..0eb88a4db 100644 --- a/packages/react-core/src/index.js +++ b/packages/react-core/src/index.js @@ -35,4 +35,8 @@ export { geojsonFeatures } from './filters/geojsonFeatures'; export { GroupDateTypes } from './operations/GroupDateTypes'; export { groupValuesByDateColumn } from './operations/groupByDate'; -export { DRAW_MODES, EDIT_MODES } from './utils/drawingToolConstants'; +export { + FEATURE_SELECTION_MODES, + EDIT_MODES, + MASK_ID +} from './utils/featureSelectionConstants'; diff --git a/packages/react-core/src/utils/drawingToolConstants.js b/packages/react-core/src/utils/featureSelectionConstants.js similarity index 56% rename from packages/react-core/src/utils/drawingToolConstants.js rename to packages/react-core/src/utils/featureSelectionConstants.js index 9c639a0ee..b815f73e9 100644 --- a/packages/react-core/src/utils/drawingToolConstants.js +++ b/packages/react-core/src/utils/featureSelectionConstants.js @@ -1,4 +1,5 @@ -export const DRAW_MODES = Object.freeze({ +// Don't rename values. These values come from nebula. +export const FEATURE_SELECTION_MODES = Object.freeze({ POLYGON: 'DrawPolygonMode', RECTANGLE: 'DrawRectangleMode', CIRCLE: 'DrawCircleFromCenterMode', @@ -8,3 +9,5 @@ export const DRAW_MODES = Object.freeze({ export const EDIT_MODES = Object.freeze({ EDIT: 'edit' }); + +export const MASK_ID = 'feature_selection_mask'; diff --git a/packages/react-redux/__tests__/cartoSlice.test.js b/packages/react-redux/__tests__/cartoSlice.test.js index 9858b4a3b..d3cef6e26 100644 --- a/packages/react-redux/__tests__/cartoSlice.test.js +++ b/packages/react-redux/__tests__/cartoSlice.test.js @@ -183,12 +183,12 @@ describe('carto slice', () => { }); }); - describe('drawing tool mode', () => { - test('should set drawing tool mode', () => { + describe('feature selection mode', () => { + test('should set feature selection mode', () => { const MODE = 'abracadabra'; - store.dispatch(cartoSlice.setDrawingToolMode(MODE)); + store.dispatch(cartoSlice.setFeatureSelectionMode(MODE)); const state = store.getState(); - expect(state.carto.drawingToolMode).toEqual(MODE); + expect(state.carto.featureSelectionMode).toEqual(MODE); }); }); diff --git a/packages/react-redux/package.json b/packages/react-redux/package.json index 0ab888b97..bfab08650 100644 --- a/packages/react-redux/package.json +++ b/packages/react-redux/package.json @@ -63,10 +63,10 @@ "@babel/runtime": "^7.13.9" }, "peerDependencies": { - "@carto/react-core": "^1.1.4", - "@carto/react-workers": "^1.1.4", - "@deck.gl/carto": "^8.7.0-alpha.11", - "@deck.gl/core": "^8.7.0-alpha.11", + "@carto/react-core": "^1.2.1-alpha.8", + "@carto/react-workers": "^1.2.1-alpha.8", + "@deck.gl/carto": "^8.7.0-beta.2", + "@deck.gl/core": "^8.7.0-beta.2", "@reduxjs/toolkit": "^1.5.0" } } diff --git a/packages/react-redux/src/slices/cartoSlice.d.ts b/packages/react-redux/src/slices/cartoSlice.d.ts index 58258067f..e3f6902de 100644 --- a/packages/react-redux/src/slices/cartoSlice.d.ts +++ b/packages/react-redux/src/slices/cartoSlice.d.ts @@ -62,8 +62,8 @@ declare enum CartoActions { CLEAR_FILTERS = 'carto/clearFilters', SET_FEATURES_READY = 'carto/setFeaturesReady', SET_CREDENTIALS = 'carto/setCredentials', - SET_DRAWING_TOOL_MODE = 'carto/setDrawingToolMode', - SET_DRAWING_TOOL_ENABLED = 'carto/setDrawingToolEnabled' + SET_FEATURE_SELECTION_MODE = 'carto/setFeatureSelectionMode', + SET_FEATURE_SELECTION_ENABLED = 'carto/setFeatureSelectionEnabled', } export function createCartoSlice( @@ -141,14 +141,16 @@ export function setCredentials(credentials: Credentials): { payload: Credentials; }; -export function setDrawingToolMode(mode: string): { - type: CartoActions.SET_DRAWING_TOOL_MODE; +export function setFeatureSelectionMode(mode: string): { + type: CartoActions.SET_FEATURE_SELECTION_MODE; payload: string; }; -export function setDrawingToolEnabled(enabled: boolean): { - type: CartoActions.SET_DRAWING_TOOL_MODE; +export function setFeatureSelectionEnabled(enabled: boolean): { + type: CartoActions.SET_FEATURE_SELECTION_ENABLED; payload: boolean; }; export function selectSpatialFilter(state: any, sourceId?: string): object | null; + +export function selectFeatureSelectionMode(state: any): string | null; diff --git a/packages/react-redux/src/slices/cartoSlice.js b/packages/react-redux/src/slices/cartoSlice.js index 69c2fa5de..870093c93 100644 --- a/packages/react-redux/src/slices/cartoSlice.js +++ b/packages/react-redux/src/slices/cartoSlice.js @@ -3,7 +3,7 @@ import { WebMercatorViewport } from '@deck.gl/core'; import { debounce } from '@carto/react-core'; import { removeWorker } from '@carto/react-workers'; import { setDefaultCredentials } from '@deck.gl/carto'; -import { DRAW_MODES } from '@carto/react-core'; +import { FEATURE_SELECTION_MODES } from '@carto/react-core'; /** * @@ -50,8 +50,8 @@ export const createCartoSlice = (initialState) => { // Auto import dataSources }, spatialFilter: null, - drawingToolMode: DRAW_MODES.POLYGON, - drawingToolEnabled: false, + featureSelectionMode: FEATURE_SELECTION_MODES.POLYGON, + featureSelectionEnabled: false, featuresReady: {}, ...initialState }, @@ -162,11 +162,11 @@ export const createCartoSlice = (initialState) => { }; setDefaultCredentials(state.credentials); }, - setDrawingToolMode: (state, action) => { - state.drawingToolMode = action.payload; + setFeatureSelectionMode: (state, action) => { + state.featureSelectionMode = action.payload; }, - setDrawingToolEnabled: (state, action) => { - state.drawingToolEnabled = action.payload; + setFeatureSelectionEnabled: (state, action) => { + state.featureSelectionEnabled = action.payload; } } }); @@ -298,10 +298,10 @@ export const selectSpatialFilter = (state, sourceId) => { }; /** - * Redux selector to select the selected drawing tool mode based on if it's enabled + * Redux selector to select the feature selection mode based on if it's enabled */ -export const selectDrawingToolMode = (state) => - state.carto.drawingToolEnabled && state.carto.drawingToolMode; +export const selectFeatureSelectionMode = (state) => + (state.carto.featureSelectionEnabled && state.carto.featureSelectionMode) || null; /** * Redux selector to know if features from a certain source are ready @@ -362,19 +362,19 @@ export const setCredentials = (data) => ({ }); /** - * Action to set drawing tool mode + * Action to set feature selection mode * @param {boolean} mode */ -export const setDrawingToolMode = (mode) => ({ - type: 'carto/setDrawingToolMode', +export const setFeatureSelectionMode = (mode) => ({ + type: 'carto/setFeatureSelectionMode', payload: mode }); /** - * Action to set if drawing tool is enabled + * Action to set if feature selection tool is enabled * @param {boolean} enabled */ -export const setDrawingToolEnabled = (enabled) => ({ - type: 'carto/setDrawingToolEnabled', +export const setFeatureSelectionEnabled = (enabled) => ({ + type: 'carto/setFeatureSelectionEnabled', payload: enabled }); diff --git a/packages/react-redux/src/types.d.ts b/packages/react-redux/src/types.d.ts index 55e7bdfc1..05bc0cc67 100644 --- a/packages/react-redux/src/types.d.ts +++ b/packages/react-redux/src/types.d.ts @@ -1,7 +1,7 @@ import { Credentials } from '@carto/react-api'; import { OauthApp } from '@carto/react-auth'; import { CartoBasemapsNames } from '@carto/react-basemaps'; -import { DRAW_MODES, Viewport } from '@carto/react-core'; +import { Viewport } from '@carto/react-core'; import { Geometry } from 'geojson'; export type ViewState = { @@ -45,8 +45,8 @@ export type CartoState = { dataSources: { [key: string]: string }, spatialFilter: Geometry, featuresReady: { [key: string]: boolean }, - drawingToolEnabled: boolean, - drawingToolMode: typeof DRAW_MODES + featureSelectionEnabled: boolean, + featureSelectionMode: string } & InitialCartoState; export type InitialOauthState = { diff --git a/packages/react-ui/__tests__/widgets/DrawingToolWidgetUI.test.js b/packages/react-ui/__tests__/widgets/FeatureSelectionWidgetUI.test.js similarity index 68% rename from packages/react-ui/__tests__/widgets/DrawingToolWidgetUI.test.js rename to packages/react-ui/__tests__/widgets/FeatureSelectionWidgetUI.test.js index bff137409..80c6b05e4 100644 --- a/packages/react-ui/__tests__/widgets/DrawingToolWidgetUI.test.js +++ b/packages/react-ui/__tests__/widgets/FeatureSelectionWidgetUI.test.js @@ -1,6 +1,6 @@ import React from 'react'; import { render, queryByAttribute, fireEvent } from '@testing-library/react'; -import DrawingToolWidgetUI from '../../src/widgets/DrawingToolWidgetUI'; +import FeatureSelectionWidgetUI from '../../src/widgets/FeatureSelectionWidgetUI'; import { capitalize } from '@material-ui/core'; import CursorIcon from '../../src/assets/CursorIcon'; import PolygonIcon from '../../src/assets/PolygonIcon'; @@ -10,48 +10,61 @@ const POLYGON_ICON_ID = 'polygon-icon'; const RECTANGLE_ICON_ID = 'rectangle-icon'; -const DRAW_MODES = [ +const FEATURE_SELECTION_MODES = [ { id: 'polygon', label: 'polygon', icon: }, { id: 'rectangle', label: 'rectangle', icon: } ]; const EDIT_MODES = [{ id: 'edit', label: 'Edit geometry', icon: }]; -const CommonDrawingToolWidgetUI = (props) => ( - ( + ); const getById = queryByAttribute.bind(null, 'id'); -describe('DrawingToolWidgetUI', () => { +describe('FeatureSelectionWidgetUI', () => { beforeAll(() => jest.spyOn(console, 'error').mockImplementation(() => jest.fn())); afterAll(() => jest.restoreMocks()); test('renders selectedMode correctly', () => { const { container } = render( - + ); expect(getById(container, POLYGON_ICON_ID)).toBeInTheDocument(); const { container: container2 } = render( - + ); expect(getById(container2, RECTANGLE_ICON_ID)).toBeInTheDocument(); }); - test('throw error if selectedMode is not found neither in draw modes or edit modes', () => { + test('throw error if selectedMode is not found neither in select modes or edit modes', () => { expect(() => - render() - ).toThrowError('Selected mode provided is not found neither in drawing or edit mode'); + render( + + ) + ).toThrowError( + 'Selected mode provided is not found neither in selecting or edit mode' + ); }); test('activate selected mode event is correctly raised', () => { const onEnabledChange = jest.fn(); const rendered = render( - + ); const selectedModeBtn = getById(rendered.container, POLYGON_ICON_ID); @@ -61,33 +74,35 @@ describe('DrawingToolWidgetUI', () => { expect(onEnabledChange).toHaveBeenCalledWith(true); rendered.rerender( - + ); fireEvent.click(selectedModeBtn); expect(onEnabledChange).toHaveBeenCalledWith(false); }); - test('drawModes and editModes are rendered correctly in modes menu', () => { - const rendered = render(); + test('selectionModes and editModes are rendered correctly in modes menu', () => { + const rendered = render(); const menuBtn = rendered.getByTitle('Select a mode'); // Open menu fireEvent.click(menuBtn); // Once the menu is opened, check everything is okey rendered - [...DRAW_MODES, ...EDIT_MODES].forEach(({ label }) => + [...FEATURE_SELECTION_MODES, ...EDIT_MODES].forEach(({ label }) => expect(rendered.getByText(capitalize(label))).toBeDefined() ); }); test('select mode event is correctly raised', () => { const onSelectMode = jest.fn(); - const rendered = render(); + const rendered = render( + + ); const menuBtn = rendered.getByTitle('Select a mode'); // Open menu fireEvent.click(menuBtn); - const anotherMode = DRAW_MODES[1]; + const anotherMode = FEATURE_SELECTION_MODES[1]; const anotherModeBtn = rendered.getByText(capitalize(anotherMode.label)); expect(anotherModeBtn).toBeDefined(); @@ -98,7 +113,7 @@ describe('DrawingToolWidgetUI', () => { const GEOMETRY = { geometry: 1 }; test('geometry is rendered correctly', () => { - const rendered = render(); + const rendered = render(); expect(rendered.getByText('Feature')).toBeDefined(); }); @@ -106,7 +121,7 @@ describe('DrawingToolWidgetUI', () => { const onSelectGeometry = jest.fn(); const rendered = render( - @@ -128,7 +143,7 @@ describe('DrawingToolWidgetUI', () => { const onDeleteGeometry = jest.fn(); const rendered = render( - diff --git a/packages/react-ui/package.json b/packages/react-ui/package.json index 4f4a40997..0c1211a2a 100644 --- a/packages/react-ui/package.json +++ b/packages/react-ui/package.json @@ -75,7 +75,7 @@ "@babel/runtime": "^7.13.9" }, "peerDependencies": { - "@carto/react-core": "^1.1.4", + "@carto/react-core": "^1.2.1-alpha.8", "@material-ui/core": "^4.11.3", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.57", diff --git a/packages/react-ui/src/index.js b/packages/react-ui/src/index.js index 86055a716..4bd400816 100644 --- a/packages/react-ui/src/index.js +++ b/packages/react-ui/src/index.js @@ -7,7 +7,7 @@ import PieWidgetUI from './widgets/PieWidgetUI'; import LegendWidgetUI, { LEGEND_TYPES } from './widgets/legend/LegendWidgetUI'; import ScatterPlotWidgetUI from './widgets/ScatterPlotWidgetUI'; import TimeSeriesWidgetUI from './widgets/TimeSeriesWidgetUI/TimeSeriesWidgetUI'; -import DrawingToolWidgetUI from './widgets/DrawingToolWidgetUI'; +import FeatureSelectionWidgetUI from './widgets/FeatureSelectionWidgetUI'; import { CHART_TYPES } from './widgets/TimeSeriesWidgetUI/utils/constants'; import TableWidgetUI from './widgets/TableWidgetUI/TableWidgetUI'; import NoDataAlert from './widgets/NoDataAlert'; @@ -17,7 +17,7 @@ import RectangleIcon from './assets/RectangleIcon'; import LassoIcon from './assets/LassoIcon'; import CircleIcon from './assets/CircleIcon'; -const drawingToolIcons = { +const featureSelectionIcons = { CursorIcon, PolygonIcon, RectangleIcon, @@ -34,11 +34,11 @@ export { PieWidgetUI, ScatterPlotWidgetUI, TimeSeriesWidgetUI, - DrawingToolWidgetUI, + FeatureSelectionWidgetUI, CHART_TYPES as TIME_SERIES_CHART_TYPES, TableWidgetUI, LegendWidgetUI, LEGEND_TYPES, NoDataAlert, - drawingToolIcons + featureSelectionIcons }; diff --git a/packages/react-ui/src/types.d.ts b/packages/react-ui/src/types.d.ts index 05b6096de..fbc17f3db 100644 --- a/packages/react-ui/src/types.d.ts +++ b/packages/react-ui/src/types.d.ts @@ -122,14 +122,14 @@ export type NoDataAlert = { body: string; }; -export type DrawingToolWidgetUIData = { +export type FeatureSelectionWidgetUIData = { id: string; label: string; icon: React.ReactElement; }; -export type DrawingToolWidgetUI = { - drawModes: DrawingToolWidgetUIData[]; - editModes: DrawingToolWidgetUIData[]; +export type FeatureSelectionWidgetUI = { + selectionModes: FeatureSelectionWidgetUIData[]; + editModes: FeatureSelectionWidgetUIData[]; selectedMode: string; onSelectMode?: Function; activated?: boolean; diff --git a/packages/react-ui/src/widgets/DrawingToolWidgetUI.js b/packages/react-ui/src/widgets/FeatureSelectionWidgetUI.js similarity index 88% rename from packages/react-ui/src/widgets/DrawingToolWidgetUI.js rename to packages/react-ui/src/widgets/FeatureSelectionWidgetUI.js index 795b6bc7d..ed39a68f8 100644 --- a/packages/react-ui/src/widgets/DrawingToolWidgetUI.js +++ b/packages/react-ui/src/widgets/FeatureSelectionWidgetUI.js @@ -16,9 +16,9 @@ import { import { ArrowDropDown } from '@material-ui/icons'; import PropTypes from 'prop-types'; -function DrawingToolWidgetUI({ +function FeatureSelectionWidgetUI({ className, - drawModes, + selectionModes, editModes, selectedMode, onSelectMode, @@ -37,7 +37,10 @@ function DrawingToolWidgetUI({ enabled={enabled} > ({ ...mode, isEdit: true }))]} + modes={[ + ...selectionModes, + ...editModes.map((mode) => ({ ...mode, isEdit: true })) + ]} selectedMode={selectedMode} enabled={enabled} onEnabledChange={onEnabledChange} @@ -45,7 +48,7 @@ function DrawingToolWidgetUI({ /> modeId === selectedMode); if (!foundMode) { throw new Error( - 'Selected mode provided is not found neither in drawing or edit mode' + 'Selected mode provided is not found neither in selecting or edit mode' ); } return foundMode; @@ -191,7 +194,7 @@ function SelectedModeViewer({ } }, [modes, selectedMode]); - const tooltipTitle = isEdit ? label : `Draw a ${label}`; + const tooltipTitle = isEdit ? label : `Select a ${label}`; const onEnabledChangeWrapper = () => onEnabledChange(!enabled); @@ -218,7 +221,7 @@ const useModesSelectorStyles = makeStyles((theme) => ({ })); function ModesSelector({ - drawModes, + selectionModes, editModes, selectedMode, onSelectMode, @@ -245,7 +248,7 @@ function ModesSelector({ handleClose(); }; - const hasDrawModes = !!drawModes.length; + const hasSelectionModes = !!selectionModes.length; const hasEditModes = !!editModes.length; const MenuItemWrapper = forwardRef(({ mode, isEnabled }, ref) => ( @@ -296,11 +299,11 @@ function ModesSelector({ }} > - Choose a draw mode + Choose a selection mode - {hasDrawModes && drawModes.map(createMenuItemWrapper)} - {hasDrawModes && hasEditModes && } - {hasDrawModes && editModes.map(createMenuItemWrapper)} + {hasSelectionModes && selectionModes.map(createMenuItemWrapper)} + {hasSelectionModes && hasEditModes && } + {hasSelectionModes && editModes.map(createMenuItemWrapper)} ); diff --git a/packages/react-ui/storybook/stories/widgetsUI/DrawingToolWidgetUI.stories.js b/packages/react-ui/storybook/stories/widgetsUI/FeatureSelectionWidgetUI.stories.js similarity index 81% rename from packages/react-ui/storybook/stories/widgetsUI/DrawingToolWidgetUI.stories.js rename to packages/react-ui/storybook/stories/widgetsUI/FeatureSelectionWidgetUI.stories.js index b421514d1..b8bc249b5 100644 --- a/packages/react-ui/storybook/stories/widgetsUI/DrawingToolWidgetUI.stories.js +++ b/packages/react-ui/storybook/stories/widgetsUI/FeatureSelectionWidgetUI.stories.js @@ -1,14 +1,13 @@ import { Box } from '@material-ui/core'; import React, { useState } from 'react'; -import DrawingToolWidgetUI from '../../../src/widgets/DrawingToolWidgetUI'; -import { buildReactPropsAsString } from '../../utils'; +import FeatureSelectionWidgetUI from '../../../src/widgets/FeatureSelectionWidgetUI'; import CursorIcon from '../../../src/assets/CursorIcon'; import PolygonIcon from '../../../src/assets/PolygonIcon'; import RectangleIcon from '../../../src/assets/RectangleIcon'; const options = { - title: 'Custom Components/DrawingToolWidgetUI', - component: DrawingToolWidgetUI, + title: 'Custom Components/FeatureSelectionWidgetUI', + component: FeatureSelectionWidgetUI, argTypes: { enabled: { control: { type: 'boolean' } @@ -27,7 +26,7 @@ export default options; // MODES -const DRAW_MODES = [ +const FEATURE_SELECTION_MODES = [ { id: 'polygon', label: 'polygon', icon: }, { id: 'rectangle', label: 'rectangle', icon: } ]; @@ -36,12 +35,12 @@ const EDIT_MODES = [{ id: 'edit', label: 'Edit geometry', icon: } const Template = (args) => { const [enabled, setEnabled] = useState(args.enabled ?? false); - const [selectedMode, setSelectedMode] = useState(DRAW_MODES[0].id); + const [selectedMode, setSelectedMode] = useState(FEATURE_SELECTION_MODES[0].id); return ( - selectDrawingToolMode(state)); - const spatialFilterGeometry = useSelector((state) => selectSpatialFilter(state)); - - const isEdit = isEditMode(selectedMode); - const hasGeometry = !!spatialFilterGeometry; - const isSelected = selectedFeatureIndex !== null; - - useEffect(() => { - // When the user changes mode or finishes drawing a geometry, remove selected feature - if (!isEdit && isSelected) { - setSelectedFeatureIndex(null); - } - }, [isEdit, spatialFilterGeometry, isSelected]); - - const mode = useMemo(() => { - if (selectedMode) { - if (isEdit && isSelected) { - return EditMode; - } else if (!isEdit && !isSelected) { - return nebulaModes[selectedMode]; - } - } - - return ViewMode; - }, [isSelected, isEdit, selectedMode]); - - const primaryAsRgba = formatRGBA(hexToRgb(theme.palette.primary.main)); - const secondaryAsRgba = formatRGBA(hexToRgb(theme.palette.secondary.main)); - - const mainColor = hasGeometry && !isSelected ? secondaryAsRgba : primaryAsRgba; - - return new EditableGeoJsonLayer({ - id: 'DrawingToolLayer', - pickable: !!selectedMode, - data: { - type: 'FeatureCollection', - features: spatialFilterGeometry ? [spatialFilterGeometry] : [] - }, - mode, - selectedFeatureIndexes: isFinite(selectedFeatureIndex) ? [selectedFeatureIndex] : [], - onEdit: ({ updatedData, editType }) => { - // Once the geometry is drawed, disable the tool - if (editType === 'addFeature') { - dispatch(setDrawingToolEnabled(false)); - } - - // Do not update spatial filter if - // 1. updatedData is empty - // 2. editType includes tentative, that means it's being drawn - if (updatedData.features.length !== 0 && !editType.includes('Tentative')) { - const [lastFeature] = updatedData.features.slice(-1); - if (lastFeature) { - dispatch( - addSpatialFilter({ - geometry: lastFeature - }) - ); - } - } - }, - onClick: ({ index, object }) => { - if (isEdit && object?.geometry.type === 'Polygon') { - setSelectedFeatureIndex(index); - } - }, - // Styles once geometry is created or it's being edited - getLineColor: mainColor, - getFillColor: isEdit ? [...mainColor.slice(0, 3), 255 * 0.08] : [0, 0, 0, 0], - // Styles while drawing geometry - getTentativeFillColor: [...primaryAsRgba.slice(0, 3), 255 * 0.08], - getTentativeLineColor: primaryAsRgba, - // Point styles while drawing - getEditHandlePointColor: [0xff, 0xff, 0xff], - getEditHandlePointOutlineColor: primaryAsRgba, - editHandlePointStrokeWidth: 5, - getEditHandlePointRadius: 2 - }); -} - -// Aux -function isEditMode(mode) { - return Object.values(EDIT_MODES).indexOf(mode) !== -1; -} - -function formatRGBA(cssRgba) { - const [r, g, b, alpha] = cssRgba.replace(/[rgba() ]/g, '').split(','); - - return [r, g, b, alpha ? alpha * 255 : 255].map(Number); -} diff --git a/packages/react-widgets/src/layers/EditableCartoGeoJsonLayer.js b/packages/react-widgets/src/layers/EditableCartoGeoJsonLayer.js new file mode 100644 index 000000000..fb8c2d069 --- /dev/null +++ b/packages/react-widgets/src/layers/EditableCartoGeoJsonLayer.js @@ -0,0 +1,44 @@ +import { EditableGeoJsonLayer } from '@nebula.gl/layers'; + +// EditableGeoJsonLayer extends EditableLayer and it's its code https://github.com/uber/nebula.gl/blob/master/modules/layers/src/layers/editable-layer.ts#L75 +// We've overwritten _addEventHandlers and _removeEventHandlers to be able to use an eventManager sent it as a property instead of use +// the default (this.context.deck.eventManager). We need to do this to get the compatibility with Google maps because GoogleMapsOverlay +// uses its own event system + +const EVENT_TYPES = ['anyclick', 'pointermove', 'panstart', 'panmove', 'panend', 'keyup']; + +export default class EditableCartoGeoJsonLayer extends EditableGeoJsonLayer { + updateState({ props, oldProps, context, changeFlags }) { + if (props.eventManager !== oldProps.eventManager) { + this._removeEventHandlers(props.eventManager); + this._removeEventHandlers(oldProps.eventManager); + this._addEventHandlers(); + } + super.updateState({ props, oldProps, context, changeFlags }); + } + + _addEventHandlers() { + const eventManager = this._getEventManager(); + const { eventHandler } = this.state._editableLayerState; + + for (const eventType of EVENT_TYPES) { + eventManager.on(eventType, eventHandler, { + // give nebula a higher priority so that it can stop propagation to deck.gl's map panning handlers + priority: 100 + }); + } + } + + _removeEventHandlers(_eventManager = undefined) { + const eventManager = _eventManager || this._getEventManager(); + const { eventHandler } = this.state._editableLayerState; + + for (const eventType of EVENT_TYPES) { + eventManager.off(eventType, eventHandler); + } + } + + _getEventManager() { + return this.props.eventManager || this.context.deck.eventManager; + } +} diff --git a/packages/react-widgets/src/layers/FeatureSelectionLayer.js b/packages/react-widgets/src/layers/FeatureSelectionLayer.js new file mode 100644 index 000000000..2771835f0 --- /dev/null +++ b/packages/react-widgets/src/layers/FeatureSelectionLayer.js @@ -0,0 +1,129 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import * as nebulaModes from '@nebula.gl/edit-modes'; +import { + addSpatialFilter, + selectSpatialFilter, + setFeatureSelectionEnabled, + selectFeatureSelectionMode +} from '@carto/react-redux'; +import { EDIT_MODES } from '@carto/react-core'; +import { hexToRgb, useTheme } from '@material-ui/core'; +import EditableCartoGeoJsonLayer from './EditableCartoGeoJsonLayer'; +import useEventManager from './useEventManager'; +import MaskLayer from './MaskLayer'; + +const { ViewMode, TranslateMode, ModifyMode, CompositeMode } = nebulaModes; + +const EditMode = new CompositeMode([new TranslateMode(), new ModifyMode()]); + +export default function FeatureSelectionLayer( + { eventManager, mask } = { eventManager: null, mask: true } +) { + const dispatch = useDispatch(); + const theme = useTheme(); + const [selectedFeatureIndex, setSelectedFeatureIndex] = useState(null); + const selectedMode = useSelector((state) => selectFeatureSelectionMode(state)); + const spatialFilterGeometry = useSelector((state) => selectSpatialFilter(state)); + + const isEdit = isEditMode(selectedMode); + const hasGeometry = !!spatialFilterGeometry; + const isSelected = selectedFeatureIndex !== null; + + const customEventManager = useEventManager({ + eventManager, + isEdit, + isSelected, + selectedMode + }); + + useEffect(() => { + // When the user changes mode or finishes drawing a geometry, remove selected feature + if (!isEdit && isSelected) { + setSelectedFeatureIndex(null); + } + }, [isEdit, spatialFilterGeometry, isSelected]); + + const mode = useMemo(() => { + if (selectedMode) { + if (isEdit && isSelected) { + return EditMode; + } else if (!isEdit && !isSelected) { + return nebulaModes[selectedMode]; + } + } + + return ViewMode; + }, [isSelected, isEdit, selectedMode]); + + const primaryAsRgba = formatRGBA(hexToRgb(theme.palette.primary.main)); + const secondaryAsRgba = formatRGBA(hexToRgb(theme.palette.secondary.main)); + + const mainColor = hasGeometry && !isSelected ? secondaryAsRgba : primaryAsRgba; + + return [ + mask && MaskLayer(), + // @ts-ignore + new EditableCartoGeoJsonLayer({ + eventManager: customEventManager, + id: 'FeatureSelectionLayer', + pickable: !!selectedMode, + data: { + type: 'FeatureCollection', + features: spatialFilterGeometry ? [spatialFilterGeometry] : [] + }, + mode, + // @ts-ignore + selectedFeatureIndexes: isFinite(selectedFeatureIndex) + ? [selectedFeatureIndex] + : [], + onEdit: ({ updatedData, editType }) => { + // Once the geometry is drawed, disable the tool + if (editType === 'addFeature') { + dispatch(setFeatureSelectionEnabled(false)); + } + + // Do not update spatial filter if + // 1. updatedData is empty + // 2. editType includes tentative, that means it's being drawn + if (updatedData.features.length !== 0 && !editType.includes('Tentative')) { + const [lastFeature] = updatedData.features.slice(-1); + if (lastFeature) { + dispatch( + addSpatialFilter({ + geometry: lastFeature + }) + ); + } + } + }, + onClick: ({ index, object }) => { + if (isEdit && object?.geometry.type === 'Polygon') { + setSelectedFeatureIndex(index); + } + }, + // Styles once geometry is created or it's being edited + getLineColor: mainColor, + getFillColor: isEdit ? [...mainColor.slice(0, 3), 255 * 0.08] : [0, 0, 0, 0], + // Styles while drawing geometry + getTentativeFillColor: [...primaryAsRgba.slice(0, 3), 255 * 0.08], + getTentativeLineColor: primaryAsRgba, + // Point styles while drawing + getEditHandlePointColor: [0xff, 0xff, 0xff], + getEditHandlePointOutlineColor: primaryAsRgba, + editHandlePointStrokeWidth: 5, + getEditHandlePointRadius: 2 + }) + ]; +} + +// Aux +function isEditMode(mode) { + return Object.values(EDIT_MODES).indexOf(mode) !== -1; +} + +function formatRGBA(cssRgba) { + const [r, g, b, alpha] = cssRgba.replace(/[rgba() ]/g, '').split(','); + + return [r, g, b, alpha ? alpha * 255 : 255].map(Number); +} diff --git a/packages/react-widgets/src/layers/MaskLayer.js b/packages/react-widgets/src/layers/MaskLayer.js new file mode 100644 index 000000000..e99d2eae4 --- /dev/null +++ b/packages/react-widgets/src/layers/MaskLayer.js @@ -0,0 +1,19 @@ +import { useSelector } from 'react-redux'; +import { OPERATION } from '@deck.gl/core'; +import { SolidPolygonLayer } from '@deck.gl/layers'; +import { MASK_ID } from '@carto/react-core/'; +import { selectSpatialFilter } from '@carto/react-redux/'; + +export default function MaskLayer() { + const spatialFilterGeometry = useSelector((state) => selectSpatialFilter(state)); + const maskData = !!spatialFilterGeometry + ? [{ polygon: spatialFilterGeometry?.geometry.coordinates }] + : []; + + return new SolidPolygonLayer({ + id: MASK_ID, + operation: OPERATION.MASK, + data: maskData, + getFillColor: [255, 255, 255, 255] + }); +} diff --git a/packages/react-widgets/src/layers/useEventManager.js b/packages/react-widgets/src/layers/useEventManager.js new file mode 100644 index 000000000..6574a8ce8 --- /dev/null +++ b/packages/react-widgets/src/layers/useEventManager.js @@ -0,0 +1,42 @@ +import { EventManager } from 'mjolnir.js'; +import { useEffect, useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { BASEMAPS } from '@carto/react-basemaps/'; +import { FEATURE_SELECTION_MODES } from '@carto/react-core/'; + +// When we're using Google as a base map, Nebula layer doesn't work becase Nebula uses the Deck Gl eventManager +// so in this case we're defining a new EventManager with the Google maps div. On the other hand, in Builder +// we don't use the c4r GoogleMap component so we need to send a EventManager as parameter +export default function useEventManager({ + eventManager, + isEdit, + isSelected, + selectedMode +}) { + const isGmaps = useSelector((state) => BASEMAPS[state.carto.basemap].type === 'gmaps'); + const cartoGmap = window.cartoGmap; + + const customEventManager = useMemo(() => { + if (eventManager) { + return eventManager; + } + + if (isGmaps && cartoGmap) { + return new EventManager(cartoGmap.getDiv()); + } + // If Google is not the base map we use the default Nebula EventManager + return undefined; + }, [cartoGmap, eventManager, isGmaps]); + + // Disable the map interactivity if we're using Google as a base map and we're editing + // the geometry or drawing a lasso. Nebula does it automatically when the base map is not Google + useEffect(() => { + if (isGmaps && cartoGmap) { + const disableDragAndDrop = + (isEdit && isSelected) || selectedMode === FEATURE_SELECTION_MODES.LASSO_TOOL; + cartoGmap.setOptions({ gestureHandling: disableDragAndDrop ? 'none' : 'auto' }); + } + }, [cartoGmap, isEdit, isGmaps, isSelected, selectedMode]); + + return customEventManager; +} diff --git a/packages/react-widgets/src/widgets/DrawingToolWidget.js b/packages/react-widgets/src/widgets/FeatureSelectionWidget.js similarity index 55% rename from packages/react-widgets/src/widgets/DrawingToolWidget.js rename to packages/react-widgets/src/widgets/FeatureSelectionWidget.js index 71a2fa0c3..90f5e4e25 100644 --- a/packages/react-widgets/src/widgets/DrawingToolWidget.js +++ b/packages/react-widgets/src/widgets/FeatureSelectionWidget.js @@ -1,35 +1,35 @@ -import { DrawingToolWidgetUI, drawingToolIcons } from '@carto/react-ui'; +import { FeatureSelectionWidgetUI, featureSelectionIcons } from '@carto/react-ui'; import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; import { - setDrawingToolMode, + setFeatureSelectionMode, addSpatialFilter, removeSpatialFilter, - setDrawingToolEnabled + setFeatureSelectionEnabled } from '@carto/react-redux'; import { - DRAW_MODES as DRAW_MODES_KEYS, + FEATURE_SELECTION_MODES, EDIT_MODES as EDIT_MODES_KEYS } from '@carto/react-core'; const { PolygonIcon, RectangleIcon, CircleIcon, LassoIcon, CursorIcon } = - drawingToolIcons; + featureSelectionIcons; -const DRAW_MODES_MAP = { - [DRAW_MODES_KEYS.POLYGON]: { +const FEATURE_SELECTION_MODES_MAP = { + [FEATURE_SELECTION_MODES.POLYGON]: { label: 'Polygon', icon: }, - [DRAW_MODES_KEYS.RECTANGLE]: { + [FEATURE_SELECTION_MODES.RECTANGLE]: { label: 'Rectangle', icon: }, - [DRAW_MODES_KEYS.CIRCLE]: { + [FEATURE_SELECTION_MODES.CIRCLE]: { label: 'Circle', icon: }, - [DRAW_MODES_KEYS.LASSO_TOOL]: { + [FEATURE_SELECTION_MODES.LASSO_TOOL]: { label: 'Lasso tool', icon: } @@ -42,22 +42,22 @@ const EDIT_MODES_MAP = { } }; -function DrawingToolWidget({ +function FeatureSelectionWidget({ className, - drawModes: drawModesKeys, + selectionModes: selectionModesKeys, editModes: editModesKeys, tooltipPlacement }) { const dispatch = useDispatch(); const geometry = useSelector((state) => state.carto.spatialFilter); - const selectedMode = useSelector((state) => state.carto.drawingToolMode); - const enabled = useSelector((state) => state.carto.drawingToolEnabled); + const selectedMode = useSelector((state) => state.carto.featureSelectionMode); + const enabled = useSelector((state) => state.carto.featureSelectionEnabled); - const drawModes = useMemo(() => { - return drawModesKeys - .filter((key) => DRAW_MODES_MAP[key]) - .map((key) => ({ id: key, ...DRAW_MODES_MAP[key] })); - }, [drawModesKeys]); + const selectionModes = useMemo(() => { + return selectionModesKeys + .filter((key) => FEATURE_SELECTION_MODES_MAP[key]) + .map((key) => ({ id: key, ...FEATURE_SELECTION_MODES_MAP[key] })); + }, [selectionModesKeys]); const editModes = useMemo(() => { return editModesKeys @@ -66,11 +66,11 @@ function DrawingToolWidget({ }, [editModesKeys]); const handleEnabledChange = (newEnabled) => { - dispatch(setDrawingToolEnabled(newEnabled)); + dispatch(setFeatureSelectionEnabled(newEnabled)); }; const handleSelectMode = (newSelectedMode) => { - dispatch(setDrawingToolMode(newSelectedMode)); + dispatch(setFeatureSelectionMode(newSelectedMode)); // If the user update selectedMode, activate it by default handleEnabledChange(true); }; @@ -94,9 +94,9 @@ function DrawingToolWidget({ }; return ( -