Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fix] Disable polygon filter menu for non-polygon features #2652

Merged
merged 1 commit into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions src/components/src/common/action-panel.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -21,6 +22,8 @@ export type ActionPanelItemProps = PropsWithChildren<{
onClick?: () => void;
isSelection?: boolean;
isActive?: boolean;
isDisabled?: boolean;
tooltipText?: string | null;
style?: CSSProperties;
}>;

Expand All @@ -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;
Expand Down Expand Up @@ -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 (
<StyledItem className={className} onClick={onClickCallback} color={color} style={style}>
const content = (
<StyledItem
className={className}
onClick={onClickCallback}
color={color}
style={{
...(isDisabled ? {cursor: 'not-allowed', opacity: 0.5} : {cursor: 'pointer'}),
...style
}}
>
{Icon ? (
<div className="icon">
<Icon height="16px" />
Expand All @@ -145,6 +160,7 @@ export const ActionPanelItem = React.memo(
<StyledCheckedbox
type="checkbox"
checked={Boolean(isActive)}
disabled={Boolean(isDisabled)}
id={`switch-${label}`}
secondary
label={label}
Expand All @@ -157,11 +173,18 @@ export const ActionPanelItem = React.memo(
<div className="label-icon">
<ArrowRight height="16px" />
</div>
<div className="nested-group">{React.Children.map(children, renderChildren)}</div>
{!isDisabled ? (
<div className="nested-group">{React.Children.map(children, renderChildren)}</div>
) : null}
</div>
) : null}
</StyledItem>
);
return tooltipText ? (
<TippyTooltip render={() => <div>{tooltipText}</div>}>{content}</TippyTooltip>
) : (
content
);
}
);

Expand Down
14 changes: 12 additions & 2 deletions src/components/src/common/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -33,12 +33,21 @@ const HiddenInput = styled.input`

interface StyledCheckboxProps {
type?: string;
disabled?: boolean;
}

const StyledCheckbox = styled.div<StyledCheckboxProps>`
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 {
Expand Down Expand Up @@ -107,6 +116,7 @@ export default class Checkbox extends Component<CheckboxProps> {
<StyledCheckbox
type={this.props.type}
className={classnames('kg-checkbox', this.props.className)}
disabled={this.props.disabled}
>
<HiddenInput {...inputProps} />
<LabelElement className="kg-checkbox__label" {...labelProps}>
Expand Down
17 changes: 12 additions & 5 deletions src/components/src/editor/feature-action-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -89,6 +91,7 @@ export function PureFeatureActionPanelFactory(): React.FC<FeatureActionPanelProp
return null;
}

const isFilterLayerDisabled = !canApplyFeatureFilter(selectedFeature as any);
return (
<StyledActionsLayer
ref={refs.setFloating}
Expand All @@ -104,6 +107,10 @@ export function PureFeatureActionPanelFactory(): React.FC<FeatureActionPanelProp
className="editor-layers-list"
label={intl.formatMessage({id: 'editor.filterLayer', defaultMessage: 'Filter layers'})}
Icon={actionIcons.layer}
isDisabled={isFilterLayerDisabled}
tooltipText={
isFilterLayerDisabled ? intl.formatMessage({id: 'editor.filterLayerDisabled'}) : null
}
>
{layers.length ? (
layers.map((layer, index) => (
Expand Down
1 change: 1 addition & 0 deletions src/localization/src/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
},
Expand Down
8 changes: 8 additions & 0 deletions src/utils/src/filter-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions test/browser/components/editor/feature-action-panel-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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}}
Expand Down
Loading