Skip to content

Commit

Permalink
[Feat] (Editor) Replace react-map-gl-draw with Nebula (UN-2619) (#412)
Browse files Browse the repository at this point in the history
  • Loading branch information
igorDykhta committed Dec 4, 2022
1 parent c53d81f commit e932e11
Show file tree
Hide file tree
Showing 50 changed files with 1,703 additions and 558 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
"@deck.gl/test-utils": "^8.6.0",
"@loaders.gl/polyfills": "^3.0.9",
"@luma.gl/test-utils": "^8.5.10",
"@nebula.gl/layers": "1.0.2-alpha.0",
"@probe.gl/env": "^3.0.1",
"@probe.gl/test-utils": "^3.0.1",
"@testing-library/react-hooks": "^3.4.2",
Expand Down
16 changes: 10 additions & 6 deletions src/actions/src/vis-state-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
LayerVisConfig,
ColorUI,
Feature,
FeatureSelectionContext,
InteractionConfig,
Filter
} from '@kepler.gl/types';
Expand Down Expand Up @@ -1081,20 +1082,23 @@ export function setPolygonFilterLayer(
}

export type SetSelectedFeatureUpdaterAction = {
feature: Feature;
feature: Feature | null;
selectionContext?: FeatureSelectionContext;
};

/**
* Set the current feature to be edited/deleted
* Set the current feature to be edited/deleted,
* and the context of how the feature was selected.
* @memberof visStateActions
* @param feature
* @param selectionContext
* @returns action
*/
export function setSelectedFeature(
feature: Feature
): Merge<SetSelectedFeatureUpdaterAction, {type: typeof ActionTypes.SET_SELECTED_FEATURE}> {
export function setSelectedFeature(feature: Feature | null, selectionContext?: FeatureSelectionContext): Merge<SetSelectedFeatureUpdaterAction, {type: typeof ActionTypes.SET_SELECTED_FEATURE}> {
return {
type: ActionTypes.SET_SELECTED_FEATURE,
feature
feature,
selectionContext
};
}

Expand Down
3 changes: 1 addition & 2 deletions src/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"@kepler.gl/table": "3.0.0-alpha.0",
"@kepler.gl/types": "3.0.0-alpha.0",
"@kepler.gl/utils": "3.0.0-alpha.0",
"@nebula.gl/edit-modes": "0.14.0",
"@nebula.gl/edit-modes": "1.0.2-alpha.0",
"@tippyjs/react": "^4.2.0",
"@types/classnames": "^2.3.1",
"@types/d3-array": "^2.0.0",
Expand Down Expand Up @@ -104,7 +104,6 @@
"react-json-pretty": "^2.2.0",
"react-lifecycles-compat": "^3.0.4",
"react-map-gl": "^5.0.3",
"react-map-gl-draw": "0.14.8",
"react-markdown": "^5.0.2",
"react-modal": "^3.8.1",
"react-onclickoutside": "^6.7.1",
Expand Down
2 changes: 2 additions & 0 deletions src/components/src/common/item-selector/item-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export type ItemSelectorProps = {
fixedOptions?: ReadonlyArray<string | number | boolean | object> | null;
erasable?: boolean;
showArrow?: boolean;
searchOptions?: (value: any, opt: any) => any;
searchable?: boolean;
displayOption?: string | ((opt: any) => any);
getOptionValue?: string | ((opt: any) => any);
Expand Down Expand Up @@ -297,6 +298,7 @@ class ItemSelectorUnmemoized extends Component<ItemSelectorProps> {
customListItemComponent={this.props.DropDownLineItemRenderComponent}
displayOption={Accessor.generateOptionToStringFor(this.props.displayOption)}
searchable={this.props.searchable}
searchOptions={this.props.searchOptions}
showOptionsWhenEmpty
selectedItems={toArray(this.props.selectedItems)}
light={this.props.inputTheme === 'light'}
Expand Down
8 changes: 6 additions & 2 deletions src/components/src/common/toolbar-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const StyledDiv = styled.div.attrs(props => ({
background-color: ${props =>
props.active ? props.theme.toolbarItemBgdHover : props.theme.dropdownListBgd};
svg {
.toolbar-item__svg-container {
margin-bottom: 4px;
}
.toolbar-item__title {
Expand Down Expand Up @@ -87,7 +87,11 @@ const ToolbarItem = React.memo((props: ToolbarItemProps) => (
props.onClick?.(e);
}}
>
{props.icon && <props.icon />}
{props.icon && (
<div className="toolbar-item__svg-container">
<props.icon />
</div>
)}
<div className="toolbar-item__title">
<FormattedMessage id={props.label} />
</div>
Expand Down
106 changes: 25 additions & 81 deletions src/components/src/editor/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import React, {Component, CSSProperties, KeyboardEvent} from 'react';
import styled from 'styled-components';
import {Editor as Draw} from 'react-map-gl-draw';
import window from 'global/window';
import classnames from 'classnames';
import get from 'lodash.get';
Expand All @@ -33,18 +32,12 @@ import {
EDITOR_MODES,
KeyEvent
} from '@kepler.gl/constants';
import {Layer} from '@kepler.gl/layers';

import {DEFAULT_RADIUS, getStyle as getFeatureStyle} from './feature-styles';
import {getStyle as getEditHandleStyle, getEditHandleShape} from './handle-style';
import {Filter} from '@kepler.gl/types';
import {Layer, EditorLayerUtils} from '@kepler.gl/layers';
import {Filter, FeatureSelectionContext} from '@kepler.gl/types';
import {Feature} from '@nebula.gl/edit-modes';
import {MjolnirGestureEvent} from 'mjolnir.js';
import {Datasets} from '@kepler.gl/table';

const StyledWrapper = styled.div`
cursor: ${(props: {editor: {mode: string}}) =>
props.editor.mode === EDITOR_MODES.EDIT ? 'pointer' : 'crosshair'};
const StyledWrapper = styled.div<{editor: any}>`
position: relative;
`;

Expand All @@ -56,15 +49,13 @@ interface EditorProps {
filters: Filter[];
layers: Layer[];
datasets: Datasets;
editor: {selectedFeature: Feature; mode: string};
editor: {selectedFeature: Feature; mode: string, selectionContext?: FeatureSelectionContext};
layersToRender: Record<string, Layer>;
index: number;
className: string;
clickRadius: number;
style: CSSProperties;
isEnabled: boolean;
onSelect: (f: Feature | null) => void;
onUpdate: (f: Feature[]) => void;
onSetEditorMode: (m: any) => void;
onDeleteFeature: (f: Feature) => void;
onTogglePolygonFilter: (l: Layer, f: Feature) => void;
}
Expand All @@ -73,16 +64,11 @@ export default function EditorFactory(
FeatureActionPanel: React.FC<FeatureActionPanelProps>
): React.ComponentClass<EditorProps> {
class EditorUnmemoized extends Component<EditorProps> {
static defaultProps = {
clickRadius: DEFAULT_RADIUS
};
static defaultProps = {};

static displayName = 'Editor';

state = {
showActions: false,
lastPosition: null
};
state = {};

componentDidMount() {
window.addEventListener('keydown', this._onKeyPressed);
Expand Down Expand Up @@ -125,66 +111,36 @@ export default function EditorFactory(
);

_onKeyPressed = (event: KeyboardEvent) => {
const {isEnabled} = this.props;

if (!isEnabled) {
return;
}

switch (event.keyCode) {
case KeyEvent.DOM_VK_DELETE:
case KeyEvent.DOM_VK_BACK_SPACE:
this._onDeleteSelectedFeature();
break;
case KeyEvent.DOM_VK_ESCAPE:
// reset active drawing
if (EditorLayerUtils.isDrawingActive(true, this.props.editor.mode)) {
this.props.onSetEditorMode(EDITOR_MODES.EDIT);
}

this.props.onSelect(null);
break;
default:
break;
}
};

_onSelect = ({
selectedFeatureId,
sourceEvent
}: {
selectedFeatureId: string | number;
sourceEvent: MjolnirGestureEvent;
}) => {
const allFeatures = this.allFeaturesSelector(this.props);
this.setState(
{
...(sourceEvent.rightButton
? {
showActions: true,
lastPosition: {
x: sourceEvent.changedPointers[0].offsetX,
y: sourceEvent.changedPointers[0].offsetY
}
}
: null)
},
() => {
this.props.onSelect(allFeatures.find(f => f.id === selectedFeatureId));
}
);
};

_onDeleteSelectedFeature = () => {
if (this.state.showActions) {
this.setState({showActions: false});
}

const {editor} = this.props;
const {selectedFeature = {}} = editor;
this.props.onDeleteFeature(selectedFeature);
this.props.onDeleteFeature(editor.selectedFeature || {});
};

_closeFeatureAction = () => {
this.setState({showActions: false});
// reset selection context
const {selectedFeature} = this.props.editor;
this.props.onSelect(selectedFeature);
};

_onToggleLayer = (layer: Layer) => {
_togglePolygonFilter = (layer: Layer) => {
const {selectedFeature} = this.props.editor;
if (!selectedFeature) {
return;
Expand All @@ -194,37 +150,25 @@ export default function EditorFactory(
};

render() {
const {className, clickRadius, datasets, editor, onUpdate, style} = this.props;

const {lastPosition, showActions} = this.state;
const selectedFeatureId = get(editor, ['selectedFeature', 'id']);
const {className, datasets, editor, style, index} = this.props;
const {selectedFeature, selectionContext} = editor;
const currentFilter = this.currentFilterSelector(this.props);
const availableLayers = this.availableLayersSeletor(this.props);
const allFeatures = this.allFeaturesSelector(this.props);

const {rightClick, position, mapIndex} = selectionContext || {};

return (
<StyledWrapper editor={editor} className={classnames('editor', className)} style={style}>
<Draw
clickRadius={clickRadius}
mode={editor.mode}
features={allFeatures}
selectedFeatureId={selectedFeatureId}
onSelect={this._onSelect}
onUpdate={onUpdate}
getEditHandleShape={getEditHandleShape}
getFeatureStyle={getFeatureStyle}
getEditHandleStyle={getEditHandleStyle}
/>
{showActions && Boolean(selectedFeatureId) ? (
{Boolean(rightClick) && selectedFeature && index === mapIndex ? (
<FeatureActionPanel
selectedFeature={get(editor, ['selectedFeature'])}
selectedFeature={selectedFeature}
datasets={datasets}
layers={availableLayers}
currentFilter={currentFilter}
onClose={this._closeFeatureAction}
onDeleteFeature={this._onDeleteSelectedFeature}
onToggleLayer={this._onToggleLayer}
position={lastPosition}
onToggleLayer={this._togglePolygonFilter}
position={position || null}
/>
) : null}
</StyledWrapper>
Expand Down
37 changes: 26 additions & 11 deletions src/components/src/editor/feature-action-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const LAYOVER_OFFSET = 4;

const StyledActionsLayer = styled.div`
position: absolute;
.layer-panel-item-disabled {
color: ${props => props.theme.textColor};
}
`;

PureFeatureActionPanelFactory.deps = [];
Expand Down Expand Up @@ -93,26 +96,38 @@ export function PureFeatureActionPanelFactory(): React.FC<FeatureActionPanelProp
label={intl.formatMessage({id: 'editor.filterLayer', defaultMessage: 'Filter layers'})}
Icon={Layers}
>
{layers.map((layer, index) => (
{layers.length ? (
layers.map((layer, index) => (
<ActionPanelItem
key={index}
label={layer.config.label}
// @ts-ignore
color={datasets[layer.config.dataId].color}
isSelection={true}
isActive={layerId.includes(layer.id)}
onClick={() => onToggleLayer(layer)}
className="layer-panel-item"
/>
))
) : (
<ActionPanelItem
key={index}
label={layer.config.label}
// @ts-ignore
color={datasets[layer.config.dataId].color}
isSelection={true}
isActive={layerId.includes(layer.id)}
onClick={() => onToggleLayer(layer)}
className="layer-panel-item"
key={'no-layers'}
label={intl.formatMessage({
id: 'editor.noLayersToFilter',
defaultMessage: 'No layers to filter'
})}
isSelection={false}
isActive={false}
className="layer-panel-item-disabled"
/>
))}
)}
</ActionPanelItem>
<ActionPanelItem
label={intl.formatMessage({id: 'editor.copyGeometry', defaultMessage: 'Copy Geometry'})}
className="delete-panel-item"
Icon={copied ? Checkmark : Copy}
onClick={copyGeometry}
/>

<ActionPanelItem
label={intl.formatMessage({id: 'tooltip.delete', defaultMessage: 'Delete'})}
className="delete-panel-item"
Expand Down
Loading

0 comments on commit e932e11

Please sign in to comment.