From 498af1dac479e70c5e9fc693f05892adfe3393c7 Mon Sep 17 00:00:00 2001 From: Igor Dykhta Date: Mon, 20 May 2024 23:07:21 +0300 Subject: [PATCH] [fix] Disable polygon filter menu for non-polygon features Signed-off-by: Ihor Dykhta --- src/components/src/common/action-panel.tsx | 35 +++++++++++++++---- src/components/src/common/checkbox.tsx | 14 ++++++-- .../src/editor/feature-action-panel.tsx | 17 ++++++--- src/localization/src/translations/en.ts | 1 + src/utils/src/filter-utils.ts | 8 +++++ .../editor/feature-action-panel-test.js | 3 ++ 6 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/components/src/common/action-panel.tsx b/src/components/src/common/action-panel.tsx index 38d908c2f8..87d55034b0 100644 --- a/src/components/src/common/action-panel.tsx +++ b/src/components/src/common/action-panel.tsx @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project +import classnames from 'classnames'; import React, {useCallback, PropsWithChildren, ElementType, CSSProperties, ReactNode} from 'react'; import styled from 'styled-components'; -import classnames from 'classnames'; import {ArrowRight} from './icons'; import Checkbox from './switch'; +import TippyTooltip from './tippy-tooltip'; export type ActionPanelProps = PropsWithChildren<{ color?: string; @@ -21,6 +22,8 @@ export type ActionPanelItemProps = PropsWithChildren<{ onClick?: () => void; isSelection?: boolean; isActive?: boolean; + isDisabled?: boolean; + tooltipText?: string | null; style?: CSSProperties; }>; @@ -42,7 +45,6 @@ const StyledItem = styled.div` position: relative; ${props => (props.color ? `border-left: 3px solid rgb(${props.color});` : '')} :hover { - cursor: pointer; color: ${props => props.theme.textColorHl}; .nested-group { display: block; @@ -123,19 +125,32 @@ export const ActionPanelItem = React.memo( onClick, isSelection, isActive, + isDisabled, + tooltipText, style }: ActionPanelItemProps) => { const onClickCallback = useCallback( event => { + if (isDisabled) { + return; + } event.preventDefault(); event.stopPropagation(); onClick?.(); }, - [onClick] + [onClick, isDisabled] ); - return ( - + const content = ( + {Icon ? (
@@ -145,6 +160,7 @@ export const ActionPanelItem = React.memo(
-
{React.Children.map(children, renderChildren)}
+ {!isDisabled ? ( +
{React.Children.map(children, renderChildren)}
+ ) : null} ) : null}
); + return tooltipText ? ( +
{tooltipText}
}>{content}
+ ) : ( + content + ); } ); diff --git a/src/components/src/common/checkbox.tsx b/src/components/src/common/checkbox.tsx index 9ddb2f725d..da835d6787 100644 --- a/src/components/src/common/checkbox.tsx +++ b/src/components/src/common/checkbox.tsx @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project +import classnames from 'classnames'; +import pick from 'lodash.pick'; import React, {ChangeEventHandler, Component, FocusEventHandler, ReactNode} from 'react'; import styled from 'styled-components'; -import pick from 'lodash.pick'; -import classnames from 'classnames'; function noop() { return; @@ -33,12 +33,21 @@ const HiddenInput = styled.input` interface StyledCheckboxProps { type?: string; + disabled?: boolean; } const StyledCheckbox = styled.div` display: flex; min-height: ${props => props.theme.switchHeight}px; margin-left: ${props => (props.type === 'radio' ? 0 : props.theme.switchLabelMargin)}px; + ${props => + props.disabled + ? ` + cursor: not-allowed; + pointer-events: none; + opacity: 0.5; + ` + : ''} `; interface CheckboxProps { @@ -107,6 +116,7 @@ export default class Checkbox extends Component { diff --git a/src/components/src/editor/feature-action-panel.tsx b/src/components/src/editor/feature-action-panel.tsx index 83e0206840..b53ed20cdd 100644 --- a/src/components/src/editor/feature-action-panel.tsx +++ b/src/components/src/editor/feature-action-panel.tsx @@ -3,17 +3,19 @@ import React, {useCallback, useState, ComponentType} from 'react'; import {useIntl} from 'react-intl'; - -import ActionPanel, {ActionPanelItem} from '../common/action-panel'; -import styled from 'styled-components'; -import classnames from 'classnames'; -import {Trash, Layers, Copy, Checkmark} from '../common/icons'; import copy from 'copy-to-clipboard'; import {useDismiss, useFloating, useInteractions} from '@floating-ui/react'; +import classnames from 'classnames'; +import styled from 'styled-components'; + import {Layer} from '@kepler.gl/layers'; import {Filter} from '@kepler.gl/types'; import {Feature} from '@nebula.gl/edit-modes'; import {Datasets} from '@kepler.gl/table'; +import {canApplyFeatureFilter} from '@kepler.gl/utils'; + +import ActionPanel, {ActionPanelItem} from '../common/action-panel'; +import {Trash, Layers, Copy, Checkmark} from '../common/icons'; const LAYOVER_OFFSET = 4; @@ -89,6 +91,7 @@ export function PureFeatureActionPanelFactory(): React.FC {layers.length ? ( layers.map((layer, index) => ( diff --git a/src/localization/src/translations/en.ts b/src/localization/src/translations/en.ts index 53812c0544..f4cfbde76b 100644 --- a/src/localization/src/translations/en.ts +++ b/src/localization/src/translations/en.ts @@ -250,6 +250,7 @@ export default { }, editor: { filterLayer: 'Filter Layers', + filterLayerDisabled: 'Non-polygon geometries cannot be used for filtering', copyGeometry: 'Copy Geometry', noLayersToFilter: 'No layers to filter' }, diff --git a/src/utils/src/filter-utils.ts b/src/utils/src/filter-utils.ts index aa18509c92..c9b10a31ce 100644 --- a/src/utils/src/filter-utils.ts +++ b/src/utils/src/filter-utils.ts @@ -446,12 +446,20 @@ export const getPolygonFilterFunctor = (layer, filter, dataContainer) => { } }; +/** + * Check if a GeoJSON feature filter can be applied to a layer + */ +export function canApplyFeatureFilter(feature: Feature | null): boolean { + return Boolean(feature?.geometry && ['Polygon', 'MultiPolygon'].includes(feature.geometry.type)); +} + /** * @param param An object that represents a row record. * @param param.index Index of the row in data container. * @returns Returns true to keep the element, or false otherwise. */ type filterFunction = (data: {index: number}) => boolean; + /** * @param field dataset Field * @param dataId Dataset id diff --git a/test/browser/components/editor/feature-action-panel-test.js b/test/browser/components/editor/feature-action-panel-test.js index 43c5681bb6..c64b551dea 100644 --- a/test/browser/components/editor/feature-action-panel-test.js +++ b/test/browser/components/editor/feature-action-panel-test.js @@ -31,6 +31,8 @@ test('FeatureActionPanel -> display layers', t => { } }; + const selectedFeature = {type: 'Feature', geometry: {type: 'Polygon', coordinates: []}}; + const onToggleLayer = sinon.spy(); const onDeleteFeature = sinon.spy(); @@ -43,6 +45,7 @@ test('FeatureActionPanel -> display layers', t => { className="action-item-test" layers={layers} datasets={datasets} + selectedFeature={selectedFeature} onToggleLayer={onToggleLayer} onDeleteFeature={onDeleteFeature} position={{x: 0, y: 0}}