diff --git a/examples/editor/example.js b/examples/editor/example.js index 3df47f25c..ca2ac2f12 100644 --- a/examples/editor/example.js +++ b/examples/editor/example.js @@ -3,8 +3,8 @@ import * as React from 'react'; import DeckGL from '@deck.gl/react'; import { EditableGeoJsonLayer } from '@nebula.gl/layers'; -import { Toolbox } from '@nebula.gl/editor'; -import { ViewMode } from '@nebula.gl/edit-modes'; +import { Toolbox, OverlappingSelection } from '@nebula.gl/editor'; +import { SelectMode } from '@nebula.gl/edit-modes'; import { StaticMap } from 'react-map-gl'; const MAPBOX_ACCESS_TOKEN = @@ -22,7 +22,9 @@ export function Example() { features: [ { type: 'Feature', - properties: {}, + properties: { + name: 'My feature 1', + }, geometry: { type: 'Polygon', coordinates: [ @@ -38,7 +40,9 @@ export function Example() { }, { type: 'Feature', - properties: {}, + properties: { + name: 'My feature 2', + }, geometry: { type: 'Polygon', coordinates: [ @@ -54,18 +58,19 @@ export function Example() { }, ], }); - const [selectedFeatureIndexes, setSelectedFeatureIndexes] = React.useState([0]); - const [mode, setMode] = React.useState(() => ViewMode); + const [selection, setSelection] = React.useState({ selectedIndexes: [0] }); + const [mode, setMode] = React.useState(() => SelectMode); const [modeConfig, setModeConfig] = React.useState({}); const layer = new EditableGeoJsonLayer({ data: geoJson, mode, modeConfig, - selectedFeatureIndexes, + selectedFeatureIndexes: selection.selectedIndexes, onEdit: ({ updatedData }) => { setGeoJson(updatedData); }, + onSelectionChanged: setSelection, }); return ( @@ -77,14 +82,6 @@ export function Example() { }} layers={[layer]} getCursor={layer.getCursor.bind(layer)} - onClick={(info) => { - if (mode === ViewMode) - if (info) { - setSelectedFeatureIndexes([info.index]); - } else { - setSelectedFeatureIndexes([]); - } - }} > @@ -103,6 +100,8 @@ export function Example() { } onSetGeoJson={setGeoJson} /> + + ); } diff --git a/examples/editor/yarn.lock b/examples/editor/yarn.lock index 7d787c8d8..0c1b01ca8 100644 --- a/examples/editor/yarn.lock +++ b/examples/editor/yarn.lock @@ -824,6 +824,22 @@ mjolnir.js "^2.3.0" probe.gl "^3.2.1" +"@deck.gl/geo-layers@^8.0.6": + version "8.2.8" + resolved "https://registry.yarnpkg.com/@deck.gl/geo-layers/-/geo-layers-8.2.8.tgz#75412741f35e74baa5ca677b060d1525bd358796" + integrity sha512-1JVw9MAVmxmCxwkclJvEaUtsBDk4CAQs7/GXKNRXmFJ04xHd+ls0PaXqy+Qxc3q/nckorwx0c3vUDhwgcxu/og== + dependencies: + "@loaders.gl/3d-tiles" "^2.2.3" + "@loaders.gl/loader-utils" "^2.2.3" + "@loaders.gl/mvt" "^2.2.3" + "@loaders.gl/terrain" "^2.2.3" + "@loaders.gl/tiles" "^2.2.3" + "@math.gl/culling" "^3.2.1" + "@math.gl/web-mercator" "^3.2.1" + h3-js "^3.6.0" + long "^3.2.0" + math.gl "^3.2.1" + "@deck.gl/layers@^8.0.6": version "8.1.0" resolved "https://registry.yarnpkg.com/@deck.gl/layers/-/layers-8.1.0.tgz#46580b7cb1da85c01941793f955afb726751ba08" @@ -849,6 +865,29 @@ dependencies: prop-types "^15.6.0" +"@loaders.gl/3d-tiles@^2.2.3": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@loaders.gl/3d-tiles/-/3d-tiles-2.2.8.tgz#20588105d6bd4b4cb21362e4ca268725d3e63d8c" + integrity sha512-amPwjwQkz44Z3c/bFIp3Xd8BRHeoAyiKzCH2Kk/sVBRFZ4H7PPRp85HKRThAOE6URWLseUto7+VyU48wL0ucAA== + dependencies: + "@loaders.gl/core" "2.2.8" + "@loaders.gl/draco" "2.2.8" + "@loaders.gl/gltf" "2.2.8" + "@loaders.gl/loader-utils" "2.2.8" + "@loaders.gl/math" "2.2.8" + "@loaders.gl/tiles" "2.2.8" + "@math.gl/core" "^3.2.0" + "@math.gl/geospatial" "^3.2.0" + "@probe.gl/stats" "^3.3.0" + +"@loaders.gl/core@2.2.8": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@loaders.gl/core/-/core-2.2.8.tgz#fb2060f3b90d3babe0de2a128fc5227f921c08ad" + integrity sha512-PM74zQA6Kn6NJCMoRdEDENy9jQWQyMkICKZ6o8UGa6/oi3dD4LO5GhFZSwU6W/Lxl/IeUwaZBQ3MqfT3aXA0Dg== + dependencies: + "@babel/runtime" "^7.3.1" + "@loaders.gl/loader-utils" "2.2.8" + "@loaders.gl/core@^2.1.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@loaders.gl/core/-/core-2.1.1.tgz#5dd79f9bc72f0aa7ea565dea419e3063edebb26f" @@ -857,6 +896,32 @@ "@babel/runtime" "^7.3.1" "@loaders.gl/loader-utils" "2.1.1" +"@loaders.gl/draco@2.2.8": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@loaders.gl/draco/-/draco-2.2.8.tgz#869fba552b520d5764ce9d030ed5ee8346a5d91b" + integrity sha512-naU52eLF9e3l84ffGURXnsmFKNaggCqjEaF+6eg7trZjPzFv5HEQKSmi6oA3HwKPxzR4yvvu/UgyI/xOOwPrvw== + dependencies: + "@babel/runtime" "^7.3.1" + "@loaders.gl/loader-utils" "2.2.8" + draco3d "^1.3.4" + +"@loaders.gl/gltf@2.2.8": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@loaders.gl/gltf/-/gltf-2.2.8.tgz#b4e28992737a7ed4020086760a7d27d98873235d" + integrity sha512-Zd31KpKsrA/7dMqwGYR7XT5vqbmEfAGJpdhYYLG9mAQUIXjvi46qm91cFg6vEnTfS+nzpupHb7aMxG3ljP2cxQ== + dependencies: + "@loaders.gl/core" "2.2.8" + "@loaders.gl/draco" "2.2.8" + "@loaders.gl/images" "2.2.8" + "@loaders.gl/loader-utils" "2.2.8" + +"@loaders.gl/images@2.2.8": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@loaders.gl/images/-/images-2.2.8.tgz#7db7781fcc3aa60f3cf1109370ea87ac5bf78ad0" + integrity sha512-qFKdtD8Wo7wmqFJsaqBm5owrzl2vDMlTYzGaqnpNjqVCEmjp8f6mj+511FyXmE4vuPqiC/zOFg/pAKXi39bHRQ== + dependencies: + "@loaders.gl/loader-utils" "2.2.8" + "@loaders.gl/images@^2.1.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@loaders.gl/images/-/images-2.1.1.tgz#4c327aea783b7721ebc25bd0752dd2e6fea6820f" @@ -872,6 +937,55 @@ "@babel/runtime" "^7.3.1" "@probe.gl/stats" "^3.2.1" +"@loaders.gl/loader-utils@2.2.8", "@loaders.gl/loader-utils@^2.2.3": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@loaders.gl/loader-utils/-/loader-utils-2.2.8.tgz#9f12f3460260b15e961fa46c3d3c867d7bf48bee" + integrity sha512-3F2VgxJPxjuZwAr8fDBkZzx3Wg275XdVaWnyYQ3uyZQ22yGEkrzKc+TlgcqSrxbO5pHrtS5PdS7dmyoJo2+Bdg== + dependencies: + "@babel/runtime" "^7.3.1" + "@probe.gl/stats" "^3.3.0" + +"@loaders.gl/math@2.2.8": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@loaders.gl/math/-/math-2.2.8.tgz#581f2e4e5898b5d755b6708949cd35e4eeb5d517" + integrity sha512-Yd52cSG3SfJegA2lXGpemQLzXjxOOKFt/NMuc/iHF66n7a53mVkgjqd6ox6qCE7G/pmnbtAFOkNlxX9vPZLaQw== + dependencies: + "@loaders.gl/images" "2.2.8" + "@loaders.gl/loader-utils" "2.2.8" + "@math.gl/core" "^3.2.0" + +"@loaders.gl/mvt@^2.2.3": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@loaders.gl/mvt/-/mvt-2.2.8.tgz#137cc6a7e91b84e0f82412db493d0434d31dc967" + integrity sha512-c9/70PCs9N8+9X4goiAnSQYoKJKq1wylCrtnzQGskaWBrpF1McyIcsv6nARbFDGlnXPmk2Ao/kDpRUDlA2IgRg== + dependencies: + "@loaders.gl/loader-utils" "2.2.8" + "@mapbox/vector-tile" "^1.3.1" + pbf "^3.2.1" + +"@loaders.gl/terrain@^2.2.3": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@loaders.gl/terrain/-/terrain-2.2.8.tgz#4cf19c24eb79c938995bc9c8b14fb3ce55e12c87" + integrity sha512-ltfPo4SLd4q6VDqaXScoKrQnILvQbOPdDQrEphuHHBl7tOJaVT1oWoP4vPautTV5ncMXnJN6tZq39ppsVXGtBg== + dependencies: + "@babel/runtime" "^7.3.1" + "@loaders.gl/loader-utils" "2.2.8" + "@mapbox/martini" "^0.2.0" + +"@loaders.gl/tiles@2.2.8", "@loaders.gl/tiles@^2.2.3": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@loaders.gl/tiles/-/tiles-2.2.8.tgz#155351c8d014ac47b5fce923dfc16bee3889a84f" + integrity sha512-5P5D08NP5nCUbs1lYA4UdkaxSgQ9pqzdZY1RPTg1et3/bxGkh5KmzpNxBab20reZRaC+lFGbzWgbG33eEgQnwA== + dependencies: + "@loaders.gl/core" "2.2.8" + "@loaders.gl/loader-utils" "2.2.8" + "@loaders.gl/math" "2.2.8" + "@math.gl/core" "^3.2.0" + "@math.gl/culling" "^3.2.0" + "@math.gl/geospatial" "^3.2.0" + "@math.gl/web-mercator" "^3.2.0" + "@probe.gl/stats" "^3.3.0" + "@luma.gl/constants@8.1.0": version "8.1.0" resolved "https://registry.yarnpkg.com/@luma.gl/constants/-/constants-8.1.0.tgz#ca69f5439ce173c1d8db38e9058612e046e99588" @@ -970,6 +1084,11 @@ resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz#f60b6a55a5d8e5ee908347d2ce4250b15103dc8e" integrity sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg== +"@mapbox/martini@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@mapbox/martini/-/martini-0.2.0.tgz#1af70211fbe994abf26e37f1388ca69c02cd43b4" + integrity sha512-7hFhtkb0KTLEls+TRw/rWayq5EeHtTaErgm/NskVoXmtgAQu/9D299aeyj6mzAR/6XUnYRp2lU+4IcrYRFjVsQ== + "@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2" @@ -1005,6 +1124,32 @@ "@babel/runtime" "^7.0.0" gl-matrix "^3.0.0" +"@math.gl/core@3.2.2", "@math.gl/core@^3.2.0": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@math.gl/core/-/core-3.2.2.tgz#929eccc724b8fafce92e3762988c2f07859232a9" + integrity sha512-R1UQwg699pfReLHy0N+ach2KdGqH4XsniEPKM7H0WbeDhEvuoMRWB9oNtIw9qPQV39lBv3Dzplaw5DPscwgMPQ== + dependencies: + "@babel/runtime" "^7.0.0" + gl-matrix "^3.0.0" + +"@math.gl/culling@^3.2.0", "@math.gl/culling@^3.2.1": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@math.gl/culling/-/culling-3.2.2.tgz#8eaf51de5c409635d2a29b051a97ad81da2d55c0" + integrity sha512-j3Y0FLqyk0FKmL7JX8zONP2jdhG6KAPLcipnAfwVKMfDsbAHddbMm7iUNqWsXy9txd8k3KUs/TgFz3xDIz/KBQ== + dependencies: + "@babel/runtime" "^7.0.0" + "@math.gl/core" "3.2.2" + gl-matrix "^3.0.0" + +"@math.gl/geospatial@^3.2.0": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@math.gl/geospatial/-/geospatial-3.2.2.tgz#1ffd9a0f037083837c20acfbea2882e7c48741a9" + integrity sha512-pQUMd6DPK3+j72A914wCeS+G3hdnVDnn4W+u+0FexmeyXhbegV0UcEh+NVDh3Q3T3aXc6O1kzNta0RWC3mLngw== + dependencies: + "@babel/runtime" "^7.0.0" + "@math.gl/core" "3.2.2" + gl-matrix "^3.0.0" + "@math.gl/web-mercator@^3.1.3": version "3.1.3" resolved "https://registry.yarnpkg.com/@math.gl/web-mercator/-/web-mercator-3.1.3.tgz#3a6b2aeb6c68b331f26269c2fa1905cacc40e84b" @@ -1013,6 +1158,14 @@ "@babel/runtime" "^7.0.0" gl-matrix "^3.0.0" +"@math.gl/web-mercator@^3.2.0", "@math.gl/web-mercator@^3.2.1": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@math.gl/web-mercator/-/web-mercator-3.2.2.tgz#93e66ded7f3004b92bc931da3e32ef244189679b" + integrity sha512-uZbLlST7gbfJV227Ev1oShpYQGon9Nk/i0AoM81mVoEYtd+XxKuoDBYaRIdPNVkkALwF9xFG1lwxHqblrs3v5w== + dependencies: + "@babel/runtime" "^7.0.0" + gl-matrix "^3.0.0" + "@probe.gl/stats@3.2.1", "@probe.gl/stats@^3.2.1": version "3.2.1" resolved "https://registry.yarnpkg.com/@probe.gl/stats/-/stats-3.2.1.tgz#4e95c75513229ebb01384045e89524a466c022eb" @@ -1020,6 +1173,13 @@ dependencies: "@babel/runtime" "^7.0.0" +"@probe.gl/stats@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@probe.gl/stats/-/stats-3.3.0.tgz#66f684ead7cee1f2aad5ee5e9d297e84e08c5536" + integrity sha512-CV4c3EgallqZTO88u34/u9L5asL0nCVP1BEkb4qcXlh8Qz2Vmygbyjz1ViQsct6rSi2lJ52lo6W0PnlpZJJvcA== + dependencies: + "@babel/runtime" "^7.0.0" + "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" @@ -2206,6 +2366,11 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" +draco3d@^1.3.4: + version "1.3.6" + resolved "https://registry.yarnpkg.com/draco3d/-/draco3d-1.3.6.tgz#e4d9e7d846637775328c903721c932b953dce331" + integrity sha512-zZoH5JNcdWDrUb2ks2mbzGDUUPvDaDf1ysTJS2St+3/F/8XcKAX4VKgzPjTP7MfHegHQ7Udv8ovS+R3AgXlH7g== + duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" @@ -2775,6 +2940,11 @@ grid-index@^1.1.0: resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA== +h3-js@^3.6.0: + version "3.6.4" + resolved "https://registry.yarnpkg.com/h3-js/-/h3-js-3.6.4.tgz#eb14f63a1fe1efec04194266271ec0af9c021566" + integrity sha512-wMu0Y+vdh4xx2WT1jqy4QDBgJupjBfHsGaMtMsFocdZdIsfxLFufzjGcmReOSfKQ+twRO2XjXAmDY9h1nq99EA== + hammerjs@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" @@ -3450,6 +3620,11 @@ loglevel@^1.6.6: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.7.tgz#b3e034233188c68b889f5b862415306f565e2c56" integrity sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A== +long@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" + integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -3537,6 +3712,13 @@ math.gl@^3.1.2, math.gl@^3.1.3: dependencies: "@math.gl/core" "3.1.3" +math.gl@^3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/math.gl/-/math.gl-3.2.2.tgz#d19e68b903b43ecbde677f8086a03b91fbab3c33" + integrity sha512-lkM9y3DirpOBMpCCrsOMV3AWkxtwql8fvF98EUN0SLOMWAIjnkQBS9NPleN5uvqpnjSVZd3Y3cn+fbIuA4npmQ== + dependencies: + "@math.gl/core" "3.2.2" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" diff --git a/modules/edit-modes/src/index.ts b/modules/edit-modes/src/index.ts index ecde715de..a9f8130f5 100644 --- a/modules/edit-modes/src/index.ts +++ b/modules/edit-modes/src/index.ts @@ -1,3 +1,6 @@ +// Utils +import * as utils from './utils'; + export type { EditMode } from './lib/edit-mode'; export type { GeoJsonEditModeType } from './lib/geojson-edit-mode'; export type { GeoJsonEditModeConstructor } from './lib/geojson-edit-mode'; @@ -32,6 +35,7 @@ export { ImmutableFeatureCollection } from './lib/immutable-feature-collection'; // Other modes export { ViewMode } from './lib/view-mode'; +export { SelectMode } from './lib/select-mode'; export { MeasureDistanceMode } from './lib/measure-distance-mode'; export { MeasureAreaMode } from './lib/measure-area-mode'; export { MeasureAngleMode } from './lib/measure-angle-mode'; @@ -44,6 +48,7 @@ export { default as _memoize } from './memoize'; export type { ScreenCoordinates, EditAction, + SelectionContext, Pick, ClickEvent, PointerMoveEvent, @@ -81,6 +86,4 @@ export type { AnyGeoJson, } from './geojson-types'; -// Utils -import * as utils from './utils'; export { utils }; diff --git a/modules/edit-modes/src/lib/select-mode.ts b/modules/edit-modes/src/lib/select-mode.ts new file mode 100644 index 000000000..294a00485 --- /dev/null +++ b/modules/edit-modes/src/lib/select-mode.ts @@ -0,0 +1,31 @@ +import { ClickEvent, ModeProps, PointerMoveEvent } from '../types'; +import { FeatureCollection } from '../geojson-types'; +import { GeoJsonEditMode } from './geojson-edit-mode'; + +export class SelectMode extends GeoJsonEditMode { + handleClick( + { picks, screenCoords }: ClickEvent, + { data, onSelectionChanged }: ModeProps + ): void { + picks = picks || []; + const selectedIndexes = new Set(picks.map(({ object }) => data.features.indexOf(object))); + + onSelectionChanged({ + selectedIndexes: [...selectedIndexes].sort(), + selectContext: { + screenCoords, + }, + }); + } + + handlePointerMove(event: PointerMoveEvent, props: ModeProps): void { + const picks = (event && event.picks) || []; + + let cursor = null; + if (picks.length) { + cursor = 'pointer'; + } + + props.onUpdateCursor(cursor); + } +} diff --git a/modules/edit-modes/src/types.ts b/modules/edit-modes/src/types.ts index 45c597b78..5acd7f8a3 100644 --- a/modules/edit-modes/src/types.ts +++ b/modules/edit-modes/src/types.ts @@ -9,6 +9,13 @@ export type EditAction = { editContext: any; }; +export type SelectionContext = { + selectedIndexes: number[]; + selectContext?: { + screenCoords: ScreenCoordinates; + }; +}; + // Represents an object "picked" from the screen. This usually reflects an object under the cursor export type Pick = { object: any; @@ -128,4 +135,6 @@ export type ModeProps = { // Callback used to update cursor onUpdateCursor: (cursor: string | null | undefined) => void; + + onSelectionChanged: (event: SelectionContext) => void; }; diff --git a/modules/editor/src/index.ts b/modules/editor/src/index.ts index 4835a404a..4f7bc424c 100644 --- a/modules/editor/src/index.ts +++ b/modules/editor/src/index.ts @@ -3,3 +3,4 @@ export { ExportModal } from './export-modal'; export { ExportComponent } from './export-component'; export { ImportModal } from './import-modal'; export { ImportComponent } from './import-component'; +export { OverlappingSelection } from './overlapping-selection'; diff --git a/modules/editor/src/overlapping-selection.tsx b/modules/editor/src/overlapping-selection.tsx new file mode 100644 index 000000000..5a2b81d59 --- /dev/null +++ b/modules/editor/src/overlapping-selection.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import styled from 'styled-components'; +import { SelectionContext } from '@nebula.gl/edit-modes'; + +const Button = styled.button<{ active?: boolean; kind?: string }>` + color: #fff; + background: ${({ kind, active }) => + kind === 'danger' ? 'rgb(180, 40, 40)' : active ? 'rgb(0, 105, 217)' : 'rgb(90, 98, 94)'}; + font-size: 1em; + font-weight: 400; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, + 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + border: 1px solid transparent; + border-radius: 0.25em; + margin: 0.05em; + padding: 0.1em 0.2em; + :hover { + background: rgb(128, 137, 133); + } +`; + +export type Props = { + data: any; + selection: SelectionContext; + onSetSelection: (selection: SelectionContext) => unknown; +}; + +export function OverlappingSelection({ data, selection, onSetSelection }: Props) { + const { selectedIndexes, selectContext } = selection; + if (!selectedIndexes || selectedIndexes.length < 2) { + return null; + } + + const left = selectContext ? selectContext.screenCoords[0] : 30; + const top = selectContext ? selectContext.screenCoords[1] : 30; + + return ( +
+ {selectedIndexes.map((index) => { + const feature = data.features[index]; + return ( + + ); + })} +
+ ); +} diff --git a/modules/editor/src/toolbox.tsx b/modules/editor/src/toolbox.tsx index 655ccc495..b14d4a732 100644 --- a/modules/editor/src/toolbox.tsx +++ b/modules/editor/src/toolbox.tsx @@ -1,11 +1,12 @@ import * as React from 'react'; import { - ViewMode, + SelectMode, DrawPointMode, DrawLineStringMode, DrawPolygonMode, DrawCircleFromCenterMode, DrawRectangleMode, + ModifyMode, MeasureDistanceMode, MeasureAngleMode, MeasureAreaMode, @@ -58,15 +59,17 @@ export type Props = { mode: any; modeConfig: any; geoJson: any; + selectedIndexes: number[]; onSetMode: (mode: any) => unknown; onSetModeConfig: (modeConfig: any) => unknown; onSetGeoJson: (geojson: any) => unknown; + onSetSelectedIndexes: (selectedIndexes: number[]) => unknown; onImport: (imported: any) => unknown; }; const MODE_GROUPS = [ { - modes: [{ mode: ViewMode, content: }], + modes: [{ mode: SelectMode, content: }], }, { modes: [{ mode: DrawPointMode, content: }], @@ -86,6 +89,9 @@ const MODE_GROUPS = [ { mode: DrawCircleFromCenterMode, content: }, ], }, + { + modes: [{ mode: ModifyMode, content: }], + }, { modes: [ { mode: MeasureDistanceMode, content: }, diff --git a/modules/layers/src/layers/editable-geojson-layer.ts b/modules/layers/src/layers/editable-geojson-layer.ts index c72d6c1d0..9cfec940b 100644 --- a/modules/layers/src/layers/editable-geojson-layer.ts +++ b/modules/layers/src/layers/editable-geojson-layer.ts @@ -26,6 +26,7 @@ import { SnappableMode, TransformMode, EditAction, + SelectionContext, ClickEvent, StartDraggingEvent, StopDraggingEvent, @@ -106,6 +107,8 @@ const defaultProps = { // Edit and interaction events onEdit: () => {}, + onSelectionChanged: () => {}, + pickable: true, pickingRadius: 10, pickingDepth: 5, @@ -191,7 +194,8 @@ const modeNameMapping = { type Props = { mode: string | GeoJsonEditModeConstructor | GeoJsonEditModeType; - onEdit: (arg0: EditAction) => void; + onEdit: (event: EditAction) => unknown; + onSelectionChanged?: (event: SelectionContext) => unknown; // TODO: type the rest [key: string]: any; @@ -346,6 +350,11 @@ export default class EditableGeoJsonLayer extends EditableLayer { onUpdateCursor: (cursor: string | null | undefined) => { this.setState({ cursor }); }, + onSelectionChanged: (selectedIndexes) => { + if (props.onSelectionChanged) { + props.onSelectionChanged(selectedIndexes); + } + }, }; } diff --git a/modules/main/src/index.ts b/modules/main/src/index.ts index bbb53b833..28947c45a 100644 --- a/modules/main/src/index.ts +++ b/modules/main/src/index.ts @@ -55,6 +55,7 @@ export { ImmutableFeatureCollection } from '@nebula.gl/edit-modes'; // Other modes export { ViewMode } from '@nebula.gl/edit-modes'; +export { SelectMode } from '@nebula.gl/edit-modes'; export { MeasureDistanceMode } from '@nebula.gl/edit-modes'; export { MeasureAreaMode } from '@nebula.gl/edit-modes'; export { MeasureAngleMode } from '@nebula.gl/edit-modes';