@accelint/map-toolkit 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/catalog-info.yaml +3 -3
- package/dist/camera/store.js +1 -1
- package/dist/camera/store.js.map +1 -1
- package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
- package/dist/deckgl/base-map/index.d.ts +2 -2
- package/dist/deckgl/base-map/index.js +24 -7
- package/dist/deckgl/base-map/index.js.map +1 -1
- package/dist/deckgl/base-map/provider.d.ts +2 -2
- package/dist/deckgl/base-map/provider.js +2 -4
- package/dist/deckgl/base-map/provider.js.map +1 -1
- package/dist/deckgl/index.js +3 -3
- package/dist/deckgl/shapes/display-shape-layer/index.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/store.js +7 -1
- package/dist/deckgl/shapes/display-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +2 -2
- package/dist/deckgl/shapes/draw-shape-layer/index.js +3 -3
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +7 -7
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +2 -2
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +2 -2
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +2 -2
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +2 -2
- package/dist/deckgl/shapes/draw-shape-layer/store.js +37 -3
- package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/constants.js +2 -1
- package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/index.js +30 -12
- package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +6 -6
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +10 -4
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js +129 -0
- package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js.map +1 -0
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/store.js +19 -4
- package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +4 -3
- package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +1 -1
- package/dist/deckgl/shapes/index.js +4 -4
- package/dist/deckgl/shapes/shared/constants.js +5 -5
- package/dist/deckgl/shapes/shared/constants.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/layer-config.js +1 -1
- package/dist/hotkey-manager/dist/react/use-hotkey.js +39 -0
- package/dist/hotkey-manager/dist/react/use-hotkey.js.map +1 -0
- package/dist/shared/cleanup.d.ts +58 -0
- package/dist/shared/cleanup.js +93 -0
- package/dist/shared/cleanup.js.map +1 -0
- package/dist/shared/create-map-store.d.ts +12 -0
- package/dist/shared/create-map-store.js +8 -3
- package/dist/shared/create-map-store.js.map +1 -1
- package/dist/viewport/viewport-size.d.ts +2 -2
- package/package.json +10 -8
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
'use client';
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { DRAW_SHAPE_LAYER_ID } from "./constants.js";
|
|
17
17
|
import { DEFAULT_TENTATIVE_COLORS, EMPTY_FEATURE_COLLECTION } from "../shared/constants.js";
|
|
18
|
+
import { cancelDrawingFromLayer, completeDrawingFromLayer, drawStore } from "./store.js";
|
|
19
|
+
import { MapContext } from "../../base-map/provider.js";
|
|
18
20
|
import { useShiftZoomDisable } from "../shared/hooks/use-shift-zoom-disable.js";
|
|
19
21
|
import { getDefaultEditableLayerProps } from "../shared/utils/layer-config.js";
|
|
20
|
-
import { DRAW_SHAPE_LAYER_ID } from "./constants.js";
|
|
21
22
|
import { getModeInstance, triggerDoubleClickFinish } from "./modes/index.js";
|
|
22
|
-
import { cancelDrawingFromLayer, completeDrawingFromLayer, drawStore } from "./store.js";
|
|
23
23
|
import { useContext, useEffect } from "react";
|
|
24
24
|
import { jsx } from "react/jsx-runtime";
|
|
25
25
|
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
import { formatCircleTooltip } from "../../shared/constants.js";
|
|
15
14
|
import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
|
|
15
|
+
import { formatCircleTooltip } from "../../shared/constants.js";
|
|
16
16
|
import { computeCircleMeasurements } from "../../shared/utils/geometry-measurements.js";
|
|
17
17
|
import { DrawCircleFromCenterMode } from "@deck.gl-community/editable-layers";
|
|
18
18
|
|
|
19
19
|
//#region src/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.ts
|
|
20
20
|
/**
|
|
21
|
-
* Extends DrawCircleFromCenterMode to display
|
|
21
|
+
* Extends DrawCircleFromCenterMode to display radius and area tooltip.
|
|
22
22
|
*
|
|
23
|
-
* Shows the
|
|
23
|
+
* Shows the radius and area of the circle being drawn based on the radius
|
|
24
24
|
* from center point to cursor position. The tooltip updates in real-time as
|
|
25
25
|
* the cursor moves, displaying measurements in the configured distance units.
|
|
26
26
|
*
|
|
@@ -30,7 +30,7 @@ import { DrawCircleFromCenterMode } from "@deck.gl-community/editable-layers";
|
|
|
30
30
|
*
|
|
31
31
|
* ## Drawing Flow
|
|
32
32
|
* 1. Click to set center point
|
|
33
|
-
* 2. Move cursor to set radius (tooltip shows
|
|
33
|
+
* 2. Move cursor to set radius (tooltip shows radius and area)
|
|
34
34
|
* 3. Click to finish the circle
|
|
35
35
|
*
|
|
36
36
|
* @example
|
|
@@ -46,7 +46,7 @@ var DrawCircleModeWithTooltip = class extends DrawCircleFromCenterMode {
|
|
|
46
46
|
tooltip = null;
|
|
47
47
|
/**
|
|
48
48
|
* Handle pointer move events to update the tooltip with circle measurements.
|
|
49
|
-
* Calculates
|
|
49
|
+
* Calculates radius and area based on the distance from center to cursor.
|
|
50
50
|
*
|
|
51
51
|
* @param event - Pointer move event with cursor position
|
|
52
52
|
* @param props - Mode properties including distance units configuration
|
|
@@ -61,10 +61,10 @@ var DrawCircleModeWithTooltip = class extends DrawCircleFromCenterMode {
|
|
|
61
61
|
const { mapCoords } = event;
|
|
62
62
|
const distanceUnits = props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;
|
|
63
63
|
const centerPoint = clickSequence[clickSequence.length - 1];
|
|
64
|
-
const {
|
|
64
|
+
const { radius, area } = computeCircleMeasurements(centerPoint, mapCoords, distanceUnits);
|
|
65
65
|
this.tooltip = {
|
|
66
66
|
position: mapCoords,
|
|
67
|
-
text: formatCircleTooltip(
|
|
67
|
+
text: formatCircleTooltip(radius, area, getDistanceUnitAbbreviation(distanceUnits))
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draw-circle-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n DrawCircleFromCenterMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatCircleTooltip } from '../../shared/constants';\nimport { computeCircleMeasurements } from '../../shared/utils/geometry-measurements';\n\n/**\n * Extends DrawCircleFromCenterMode to display
|
|
1
|
+
{"version":3,"file":"draw-circle-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n DrawCircleFromCenterMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatCircleTooltip } from '../../shared/constants';\nimport { computeCircleMeasurements } from '../../shared/utils/geometry-measurements';\n\n/**\n * Extends DrawCircleFromCenterMode to display radius and area tooltip.\n *\n * Shows the radius and area of the circle being drawn based on the radius\n * from center point to cursor position. The tooltip updates in real-time as\n * the cursor moves, displaying measurements in the configured distance units.\n *\n * ## Usage\n * This mode is automatically used by DrawShapeLayer when drawing circles.\n * The mode is cached at module level to prevent deck.gl assertion failures.\n *\n * ## Drawing Flow\n * 1. Click to set center point\n * 2. Move cursor to set radius (tooltip shows radius and area)\n * 3. Click to finish the circle\n *\n * @example\n * ```typescript\n * import { DrawCircleModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawCircleModeWithTooltip();\n * ```\n */\nexport class DrawCircleModeWithTooltip extends DrawCircleFromCenterMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n\n /**\n * Handle pointer move events to update the tooltip with circle measurements.\n * Calculates radius and area based on the distance from center to cursor.\n *\n * @param event - Pointer move event with cursor position\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n const centerPoint = clickSequence[clickSequence.length - 1] as [\n number,\n number,\n ];\n const edgePoint = mapCoords as [number, number];\n\n const { radius, area } = computeCircleMeasurements(\n centerPoint,\n edgePoint,\n distanceUnits,\n );\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n this.tooltip = {\n position: mapCoords,\n text: formatCircleTooltip(radius, area, unitAbbrev),\n };\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,4BAAb,cAA+C,yBAAyB;;CAEtE,AAAQ,UAA0B;;;;;;;;CASlC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EAErC,MAAM,cAAc,cAAc,cAAc,SAAS;EAMzD,MAAM,EAAE,QAAQ,SAAS,0BACvB,aAHgB,WAKhB,cACD;AAGD,OAAK,UAAU;GACb,UAAU;GACV,MAAM,oBAAoB,QAAQ,MAJjB,4BAA4B,cAAc,CAIR;GACpD;;;;;;;CAQH,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
import { formatDistanceTooltip, formatEllipseTooltip } from "../../shared/constants.js";
|
|
15
14
|
import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
|
|
16
|
-
import {
|
|
15
|
+
import { formatDistanceTooltip, formatEllipseTooltip } from "../../shared/constants.js";
|
|
17
16
|
import { distance } from "@turf/turf";
|
|
17
|
+
import { DrawEllipseUsingThreePointsMode } from "@deck.gl-community/editable-layers";
|
|
18
18
|
|
|
19
19
|
//#region src/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.ts
|
|
20
20
|
/**
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
import { formatDistanceTooltip } from "../../shared/constants.js";
|
|
15
14
|
import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
|
|
16
|
-
import {
|
|
15
|
+
import { formatDistanceTooltip } from "../../shared/constants.js";
|
|
17
16
|
import { distance } from "@turf/turf";
|
|
17
|
+
import { DrawLineStringMode } from "@deck.gl-community/editable-layers";
|
|
18
18
|
|
|
19
19
|
//#region src/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.ts
|
|
20
20
|
/**
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
import { formatDistanceTooltip } from "../../shared/constants.js";
|
|
15
14
|
import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
|
|
16
|
-
import {
|
|
15
|
+
import { formatDistanceTooltip } from "../../shared/constants.js";
|
|
17
16
|
import { distance } from "@turf/turf";
|
|
17
|
+
import { DrawPolygonMode } from "@deck.gl-community/editable-layers";
|
|
18
18
|
|
|
19
19
|
//#region src/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.ts
|
|
20
20
|
/**
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
import { formatRectangleTooltip } from "../../shared/constants.js";
|
|
15
14
|
import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
|
|
16
|
-
import {
|
|
15
|
+
import { formatRectangleTooltip } from "../../shared/constants.js";
|
|
17
16
|
import { area, bbox, bboxPolygon, convertArea, destination, distance } from "@turf/turf";
|
|
17
|
+
import { DrawRectangleMode } from "@deck.gl-community/editable-layers";
|
|
18
18
|
import { featureCollection, point as point$1 } from "@turf/helpers";
|
|
19
19
|
|
|
20
20
|
//#region src/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.ts
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
import { createMapStore } from "../../../shared/create-map-store.js";
|
|
17
17
|
import { MapModeEvents } from "../../../map-mode/events.js";
|
|
18
|
-
import { DrawShapeEvents } from "./events.js";
|
|
19
|
-
import { DRAW_CURSOR_MAP, DRAW_SHAPE_LAYER_ID, DRAW_SHAPE_MODE } from "./constants.js";
|
|
20
18
|
import { releaseModeAndCursor, requestModeAndCursor } from "../shared/utils/mode-utils.js";
|
|
19
|
+
import { DRAW_CURSOR_MAP, DRAW_SHAPE_LAYER_ID, DRAW_SHAPE_MODE } from "./constants.js";
|
|
20
|
+
import { DrawShapeEvents } from "./events.js";
|
|
21
21
|
import { convertFeatureToShape } from "./utils/feature-conversion.js";
|
|
22
22
|
import { Broadcast } from "@accelint/bus";
|
|
23
23
|
|
|
@@ -158,6 +158,40 @@ const drawStore = createMapStore({
|
|
|
158
158
|
}
|
|
159
159
|
});
|
|
160
160
|
/**
|
|
161
|
+
* Manually clear the drawing state for a specific map instance.
|
|
162
|
+
*
|
|
163
|
+
* Removes the drawing store instance for the given map ID, canceling any
|
|
164
|
+
* active drawing operation and releasing mode/cursor ownership. This is
|
|
165
|
+
* typically called automatically during cleanup, but can be used manually
|
|
166
|
+
* when needed.
|
|
167
|
+
*
|
|
168
|
+
* ## When to Use
|
|
169
|
+
* - Cleanup after programmatically managing drawing state
|
|
170
|
+
* - Force-reset drawing state in error conditions
|
|
171
|
+
* - Testing and debugging
|
|
172
|
+
*
|
|
173
|
+
* ## Side Effects
|
|
174
|
+
* - Cancels active drawing (if any)
|
|
175
|
+
* - Releases map mode and cursor
|
|
176
|
+
* - Emits 'shapes:draw-canceled' event
|
|
177
|
+
* - Removes store instance from memory
|
|
178
|
+
*
|
|
179
|
+
* @param mapId - Unique identifier for the map instance
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* import { clearDrawingState } from '@accelint/map-toolkit/deckgl/shapes';
|
|
184
|
+
*
|
|
185
|
+
* // Clear drawing state when unmounting a map
|
|
186
|
+
* function cleanup(mapId: UniqueId) {
|
|
187
|
+
* clearDrawingState(mapId);
|
|
188
|
+
* }
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
function clearDrawingState(mapId) {
|
|
192
|
+
drawStore.clear(mapId);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
161
195
|
* Complete the drawing operation with a GeoJSON feature.
|
|
162
196
|
*
|
|
163
197
|
* Called internally by the DrawShapeLayer component when the user finishes
|
|
@@ -219,5 +253,5 @@ function cancelDrawingFromLayer(mapId) {
|
|
|
219
253
|
}
|
|
220
254
|
|
|
221
255
|
//#endregion
|
|
222
|
-
export { cancelDrawingFromLayer, completeDrawingFromLayer, drawStore };
|
|
256
|
+
export { cancelDrawingFromLayer, clearDrawingState, completeDrawingFromLayer, drawStore };
|
|
223
257
|
//# sourceMappingURL=store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","names":["DEFAULT_DRAWING_STATE: DrawingState"],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/store.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\n/**\n * Draw Shape Store\n *\n * Manages drawing state for shape creation.\n *\n * @example\n * ```tsx\n * import { drawStore } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * function DrawControls({ mapId }) {\n * const { state, draw, cancel } = drawStore.use(mapId);\n *\n * return (\n * <div>\n * <p>Drawing: {state.activeShapeType ?? 'none'}</p>\n * <button onClick={() => draw('Polygon')}>Draw Polygon</button>\n * <button onClick={cancel}>Cancel</button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { MapModeEvents } from '@/map-mode/events';\nimport { createMapStore } from '@/shared/create-map-store';\nimport {\n releaseModeAndCursor,\n requestModeAndCursor,\n} from '../shared/utils/mode-utils';\nimport {\n DRAW_CURSOR_MAP,\n DRAW_SHAPE_LAYER_ID,\n DRAW_SHAPE_MODE,\n} from './constants';\nimport { DrawShapeEvents } from './events';\nimport { convertFeatureToShape } from './utils/feature-conversion';\nimport type { UniqueId } from '@accelint/core';\nimport type { Feature } from 'geojson';\nimport type { MapModeEventType } from '@/map-mode/types';\nimport type { Shape, ShapeFeatureType } from '../shared/types';\nimport type { DrawShapeEvent, ShapeDrawnEvent } from './events';\nimport type { DrawFunction, DrawingState, DrawShapeOptions } from './types';\n\n/**\n * Typed event bus instances\n */\nconst drawShapeBus = Broadcast.getInstance<DrawShapeEvent>();\nconst mapModeBus = Broadcast.getInstance<MapModeEventType>();\n\n/**\n * Default drawing state\n */\nconst DEFAULT_DRAWING_STATE: DrawingState = {\n activeShapeType: null,\n tentativeFeature: null,\n styleDefaults: null,\n circleDefaults: null,\n};\n\n/**\n * Actions for draw shape store\n */\ntype DrawShapeActions = {\n /** Start drawing a shape of the specified type */\n draw: DrawFunction;\n /** Cancel the current drawing operation */\n cancel: () => void;\n};\n\n/**\n * Start drawing a shape\n */\nfunction startDrawing(\n mapId: UniqueId,\n state: DrawingState,\n shapeType: ShapeFeatureType,\n options: DrawShapeOptions | undefined,\n notify: () => void,\n setState: (updates: Partial<DrawingState>) => void,\n): void {\n // Already drawing - cancel first\n if (state.activeShapeType) {\n cancelDrawingInternal(mapId, state, notify, setState);\n }\n\n // Update state with new object reference\n setState({\n activeShapeType: shapeType,\n tentativeFeature: null,\n styleDefaults: options?.styleDefaults ?? null,\n circleDefaults: options?.circleDefaults ?? null,\n });\n\n // Request map mode and cursor using shared utilities\n const cursor = DRAW_CURSOR_MAP[shapeType];\n requestModeAndCursor(mapId, DRAW_SHAPE_MODE, cursor, DRAW_SHAPE_LAYER_ID);\n\n // Emit drawing started event\n drawShapeBus.emit(DrawShapeEvents.drawing, {\n shapeType,\n mapId,\n });\n\n notify();\n}\n\n/**\n * Complete drawing and create a shape\n */\nfunction completeDrawingInternal(\n mapId: UniqueId,\n state: DrawingState,\n feature: Feature,\n notify: () => void,\n setState: (updates: Partial<DrawingState>) => void,\n): Shape {\n if (!state.activeShapeType) {\n throw new Error('Cannot complete drawing - not currently drawing');\n }\n\n const shapeType = state.activeShapeType;\n const styleDefaults = state.styleDefaults;\n\n // Convert feature to Shape\n const shape = convertFeatureToShape(feature, shapeType, styleDefaults);\n\n // Reset state with new object reference\n setState({\n activeShapeType: null,\n tentativeFeature: null,\n styleDefaults: null,\n circleDefaults: null,\n });\n\n // Release mode and cursor using shared utilities\n releaseModeAndCursor(mapId, DRAW_SHAPE_LAYER_ID);\n\n // Emit shape drawn event\n drawShapeBus.emit(DrawShapeEvents.drawn, {\n shape,\n mapId,\n } as unknown as ShapeDrawnEvent['payload']);\n\n notify();\n\n return shape;\n}\n\n/**\n * Cancel the current drawing operation\n */\nfunction cancelDrawingInternal(\n mapId: UniqueId,\n state: DrawingState,\n notify: () => void,\n setState: (updates: Partial<DrawingState>) => void,\n): void {\n if (!state.activeShapeType) {\n return; // Nothing to cancel\n }\n\n const shapeType = state.activeShapeType;\n\n // Reset state with new object reference\n setState({\n activeShapeType: null,\n tentativeFeature: null,\n styleDefaults: null,\n circleDefaults: null,\n });\n\n // Release mode and cursor using shared utilities\n releaseModeAndCursor(mapId, DRAW_SHAPE_LAYER_ID);\n\n // Emit canceled event\n drawShapeBus.emit(DrawShapeEvents.canceled, {\n shapeType,\n mapId,\n });\n\n notify();\n}\n\n/**\n * Draw shape store\n */\nexport const drawStore = createMapStore<DrawingState, DrawShapeActions>({\n defaultState: { ...DEFAULT_DRAWING_STATE },\n\n actions: (mapId, { get, set, notify }) => ({\n draw: (shapeType: ShapeFeatureType, options?: DrawShapeOptions) => {\n startDrawing(mapId, get(), shapeType, options, notify, set);\n },\n\n cancel: () => {\n cancelDrawingInternal(mapId, get(), notify, set);\n },\n }),\n\n bus: (mapId, { get }) => {\n // Listen for mode authorization requests - REJECT when drawing (protected mode)\n const unsubAuth = mapModeBus.on(\n MapModeEvents.changeAuthorization,\n (event) => {\n const { authId, id } = event.payload;\n\n // Filter: only handle if targeted at this map\n if (id !== mapId) {\n return;\n }\n\n // If we're actively drawing, reject the mode change request\n if (get().activeShapeType) {\n mapModeBus.emit(MapModeEvents.changeDecision, {\n authId,\n approved: false,\n owner: DRAW_SHAPE_LAYER_ID,\n reason: 'Drawing in progress - cancel drawing first',\n id: mapId,\n });\n }\n },\n );\n\n return () => {\n unsubAuth();\n };\n },\n\n onCleanup: (mapId, state) => {\n // Cancel any active drawing before cleanup\n if (state.activeShapeType) {\n // Release mode and cursor using shared utilities\n releaseModeAndCursor(mapId, DRAW_SHAPE_LAYER_ID);\n\n // Emit canceled event\n drawShapeBus.emit(DrawShapeEvents.canceled, {\n shapeType: state.activeShapeType,\n mapId,\n });\n }\n },\n});\n\n// =============================================================================\n// Convenience exports\n// =============================================================================\n\n/**\n * Get the current drawing state for a map instance.\n *\n * Returns the drawing state (active shape type, style defaults, etc.) for the\n * specified map ID. Returns null if no drawing store instance exists for that map.\n *\n * ## Use Cases\n * - Check if a map is currently in drawing mode\n * - Access drawing state outside of React components\n * - Inspect state for debugging purposes\n *\n * @param mapId - Unique identifier for the map instance\n * @returns The drawing state, or null if no store instance exists\n *\n * @example\n * ```typescript\n * import { getDrawingState } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * const state = getDrawingState('map-1');\n * if (state?.activeShapeType) {\n * console.log(`Currently drawing: ${state.activeShapeType}`);\n * }\n * ```\n */\nexport function getDrawingState(mapId: UniqueId): DrawingState | null {\n if (!drawStore.exists(mapId)) {\n return null;\n }\n return drawStore.get(mapId);\n}\n\n/**\n * React hook for accessing drawing state and actions.\n *\n * Provides access to the drawing store for a specific map instance, including\n * the current state and draw/cancel actions. Uses `useSyncExternalStore` for\n * concurrent-safe React subscriptions.\n *\n * ## Comparison with useDrawShape\n * - `useDrawingState`: Low-level store access without event callbacks\n * - `useDrawShape`: High-level API with onCreate/onCancel callbacks\n *\n * Use `useDrawingState` when you need direct store access without event handling.\n * Use `useDrawShape` (recommended) for most drawing interactions.\n *\n * @param mapId - Unique identifier for the map instance\n * @returns Object containing drawing state and actions (draw, cancel)\n *\n * @example\n * ```tsx\n * import { useDrawingState } from '@accelint/map-toolkit/deckgl/shapes';\n * import { ShapeFeatureType } from '@accelint/map-toolkit/deckgl/shapes/shared/types';\n *\n * function DrawingStatus({ mapId }: { mapId: UniqueId }) {\n * const { state, draw, cancel } = useDrawingState(mapId);\n *\n * return (\n * <div>\n * <p>Status: {state.activeShapeType ?? 'Not drawing'}</p>\n * <button onClick={() => draw(ShapeFeatureType.Polygon)}>\n * Start Drawing\n * </button>\n * {state.activeShapeType && (\n * <button onClick={cancel}>Cancel</button>\n * )}\n * </div>\n * );\n * }\n * ```\n */\nexport function useDrawingState(\n mapId: UniqueId,\n): { state: DrawingState } & DrawShapeActions {\n return drawStore.use(mapId);\n}\n\n/**\n * Manually clear the drawing state for a specific map instance.\n *\n * Removes the drawing store instance for the given map ID, canceling any\n * active drawing operation and releasing mode/cursor ownership. This is\n * typically called automatically during cleanup, but can be used manually\n * when needed.\n *\n * ## When to Use\n * - Cleanup after programmatically managing drawing state\n * - Force-reset drawing state in error conditions\n * - Testing and debugging\n *\n * ## Side Effects\n * - Cancels active drawing (if any)\n * - Releases map mode and cursor\n * - Emits 'shapes:draw-canceled' event\n * - Removes store instance from memory\n *\n * @param mapId - Unique identifier for the map instance\n *\n * @example\n * ```typescript\n * import { clearDrawingState } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * // Clear drawing state when unmounting a map\n * function cleanup(mapId: UniqueId) {\n * clearDrawingState(mapId);\n * }\n * ```\n */\nexport function clearDrawingState(mapId: UniqueId): void {\n drawStore.clear(mapId);\n}\n\n/**\n * Complete the drawing operation with a GeoJSON feature.\n *\n * Called internally by the DrawShapeLayer component when the user finishes\n * drawing a shape. Converts the raw EditableGeoJsonLayer feature to a Shape\n * object, resets drawing state, releases mode/cursor, and emits the drawn event.\n *\n * ## Internal API\n * This function is exported for use by the DrawShapeLayer component and should\n * not be called directly from application code. Use the `draw` action from\n * `useDrawShape` or `useDrawingState` instead.\n *\n * @param mapId - Unique identifier for the map instance\n * @param feature - The completed GeoJSON feature from EditableGeoJsonLayer\n * @returns The newly created Shape object\n * @throws Error if not currently drawing\n *\n * @example\n * ```typescript\n * // Internal usage in DrawShapeLayer\n * const handleEdit = ({ updatedData, editType }: EditAction) => {\n * if (editType === 'addFeature') {\n * const feature = updatedData.features[updatedData.features.length - 1];\n * if (feature) {\n * completeDrawingFromLayer(mapId, feature);\n * }\n * }\n * };\n * ```\n */\nexport function completeDrawingFromLayer(\n mapId: UniqueId,\n feature: Feature,\n): Shape {\n const state = drawStore.get(mapId);\n return completeDrawingInternal(\n mapId,\n state,\n feature,\n () => {\n /* notify handled by set */\n },\n (updates) => drawStore.set(mapId, updates),\n );\n}\n\n/**\n * Cancel the current drawing operation from the layer.\n *\n * Called internally by the DrawShapeLayer component when the user presses ESC\n * or the drawing is otherwise canceled. Resets drawing state, releases mode/cursor,\n * and emits the canceled event.\n *\n * ## Internal API\n * This function is exported for use by the DrawShapeLayer component and should\n * not be called directly from application code. Use the `cancel` action from\n * `useDrawShape` or `useDrawingState` instead.\n *\n * @param mapId - Unique identifier for the map instance\n *\n * @example\n * ```typescript\n * // Internal usage in DrawShapeLayer\n * const handleEdit = ({ editType }: EditAction) => {\n * if (editType === 'cancelFeature') {\n * cancelDrawingFromLayer(mapId);\n * }\n * };\n * ```\n */\nexport function cancelDrawingFromLayer(mapId: UniqueId): void {\n drawStore.actions(mapId).cancel();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,MAAM,eAAe,UAAU,aAA6B;AAC5D,MAAM,aAAa,UAAU,aAA+B;;;;AAK5D,MAAMA,wBAAsC;CAC1C,iBAAiB;CACjB,kBAAkB;CAClB,eAAe;CACf,gBAAgB;CACjB;;;;AAeD,SAAS,aACP,OACA,OACA,WACA,SACA,QACA,UACM;AAEN,KAAI,MAAM,gBACR,uBAAsB,OAAO,OAAO,QAAQ,SAAS;AAIvD,UAAS;EACP,iBAAiB;EACjB,kBAAkB;EAClB,eAAe,SAAS,iBAAiB;EACzC,gBAAgB,SAAS,kBAAkB;EAC5C,CAAC;CAGF,MAAM,SAAS,gBAAgB;AAC/B,sBAAqB,OAAO,iBAAiB,QAAQ,oBAAoB;AAGzE,cAAa,KAAK,gBAAgB,SAAS;EACzC;EACA;EACD,CAAC;AAEF,SAAQ;;;;;AAMV,SAAS,wBACP,OACA,OACA,SACA,QACA,UACO;AACP,KAAI,CAAC,MAAM,gBACT,OAAM,IAAI,MAAM,kDAAkD;CAGpE,MAAM,YAAY,MAAM;CACxB,MAAM,gBAAgB,MAAM;CAG5B,MAAM,QAAQ,sBAAsB,SAAS,WAAW,cAAc;AAGtE,UAAS;EACP,iBAAiB;EACjB,kBAAkB;EAClB,eAAe;EACf,gBAAgB;EACjB,CAAC;AAGF,sBAAqB,OAAO,oBAAoB;AAGhD,cAAa,KAAK,gBAAgB,OAAO;EACvC;EACA;EACD,CAA0C;AAE3C,SAAQ;AAER,QAAO;;;;;AAMT,SAAS,sBACP,OACA,OACA,QACA,UACM;AACN,KAAI,CAAC,MAAM,gBACT;CAGF,MAAM,YAAY,MAAM;AAGxB,UAAS;EACP,iBAAiB;EACjB,kBAAkB;EAClB,eAAe;EACf,gBAAgB;EACjB,CAAC;AAGF,sBAAqB,OAAO,oBAAoB;AAGhD,cAAa,KAAK,gBAAgB,UAAU;EAC1C;EACA;EACD,CAAC;AAEF,SAAQ;;;;;AAMV,MAAa,YAAY,eAA+C;CACtE,cAAc,EAAE,GAAG,uBAAuB;CAE1C,UAAU,OAAO,EAAE,KAAK,KAAK,cAAc;EACzC,OAAO,WAA6B,YAA+B;AACjE,gBAAa,OAAO,KAAK,EAAE,WAAW,SAAS,QAAQ,IAAI;;EAG7D,cAAc;AACZ,yBAAsB,OAAO,KAAK,EAAE,QAAQ,IAAI;;EAEnD;CAED,MAAM,OAAO,EAAE,UAAU;EAEvB,MAAM,YAAY,WAAW,GAC3B,cAAc,sBACb,UAAU;GACT,MAAM,EAAE,QAAQ,OAAO,MAAM;AAG7B,OAAI,OAAO,MACT;AAIF,OAAI,KAAK,CAAC,gBACR,YAAW,KAAK,cAAc,gBAAgB;IAC5C;IACA,UAAU;IACV,OAAO;IACP,QAAQ;IACR,IAAI;IACL,CAAC;IAGP;AAED,eAAa;AACX,cAAW;;;CAIf,YAAY,OAAO,UAAU;AAE3B,MAAI,MAAM,iBAAiB;AAEzB,wBAAqB,OAAO,oBAAoB;AAGhD,gBAAa,KAAK,gBAAgB,UAAU;IAC1C,WAAW,MAAM;IACjB;IACD,CAAC;;;CAGP,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmJF,SAAgB,yBACd,OACA,SACO;AAEP,QAAO,wBACL,OAFY,UAAU,IAAI,MAAM,EAIhC,eACM,KAGL,YAAY,UAAU,IAAI,OAAO,QAAQ,CAC3C;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BH,SAAgB,uBAAuB,OAAuB;AAC5D,WAAU,QAAQ,MAAM,CAAC,QAAQ"}
|
|
1
|
+
{"version":3,"file":"store.js","names":["DEFAULT_DRAWING_STATE: DrawingState"],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/store.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\n/**\n * Draw Shape Store\n *\n * Manages drawing state for shape creation.\n *\n * @example\n * ```tsx\n * import { drawStore } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * function DrawControls({ mapId }) {\n * const { state, draw, cancel } = drawStore.use(mapId);\n *\n * return (\n * <div>\n * <p>Drawing: {state.activeShapeType ?? 'none'}</p>\n * <button onClick={() => draw('Polygon')}>Draw Polygon</button>\n * <button onClick={cancel}>Cancel</button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { MapModeEvents } from '@/map-mode/events';\nimport { createMapStore } from '@/shared/create-map-store';\nimport {\n releaseModeAndCursor,\n requestModeAndCursor,\n} from '../shared/utils/mode-utils';\nimport {\n DRAW_CURSOR_MAP,\n DRAW_SHAPE_LAYER_ID,\n DRAW_SHAPE_MODE,\n} from './constants';\nimport { DrawShapeEvents } from './events';\nimport { convertFeatureToShape } from './utils/feature-conversion';\nimport type { UniqueId } from '@accelint/core';\nimport type { Feature } from 'geojson';\nimport type { MapModeEventType } from '@/map-mode/types';\nimport type { Shape, ShapeFeatureType } from '../shared/types';\nimport type { DrawShapeEvent, ShapeDrawnEvent } from './events';\nimport type { DrawFunction, DrawingState, DrawShapeOptions } from './types';\n\n/**\n * Typed event bus instances\n */\nconst drawShapeBus = Broadcast.getInstance<DrawShapeEvent>();\nconst mapModeBus = Broadcast.getInstance<MapModeEventType>();\n\n/**\n * Default drawing state\n */\nconst DEFAULT_DRAWING_STATE: DrawingState = {\n activeShapeType: null,\n tentativeFeature: null,\n styleDefaults: null,\n circleDefaults: null,\n};\n\n/**\n * Actions for draw shape store\n */\ntype DrawShapeActions = {\n /** Start drawing a shape of the specified type */\n draw: DrawFunction;\n /** Cancel the current drawing operation */\n cancel: () => void;\n};\n\n/**\n * Start drawing a shape\n */\nfunction startDrawing(\n mapId: UniqueId,\n state: DrawingState,\n shapeType: ShapeFeatureType,\n options: DrawShapeOptions | undefined,\n notify: () => void,\n setState: (updates: Partial<DrawingState>) => void,\n): void {\n // Already drawing - cancel first\n if (state.activeShapeType) {\n cancelDrawingInternal(mapId, state, notify, setState);\n }\n\n // Update state with new object reference\n setState({\n activeShapeType: shapeType,\n tentativeFeature: null,\n styleDefaults: options?.styleDefaults ?? null,\n circleDefaults: options?.circleDefaults ?? null,\n });\n\n // Request map mode and cursor using shared utilities\n const cursor = DRAW_CURSOR_MAP[shapeType];\n requestModeAndCursor(mapId, DRAW_SHAPE_MODE, cursor, DRAW_SHAPE_LAYER_ID);\n\n // Emit drawing started event\n drawShapeBus.emit(DrawShapeEvents.drawing, {\n shapeType,\n mapId,\n });\n\n notify();\n}\n\n/**\n * Complete drawing and create a shape\n */\nfunction completeDrawingInternal(\n mapId: UniqueId,\n state: DrawingState,\n feature: Feature,\n notify: () => void,\n setState: (updates: Partial<DrawingState>) => void,\n): Shape {\n if (!state.activeShapeType) {\n throw new Error('Cannot complete drawing - not currently drawing');\n }\n\n const shapeType = state.activeShapeType;\n const styleDefaults = state.styleDefaults;\n\n // Convert feature to Shape\n const shape = convertFeatureToShape(feature, shapeType, styleDefaults);\n\n // Reset state with new object reference\n setState({\n activeShapeType: null,\n tentativeFeature: null,\n styleDefaults: null,\n circleDefaults: null,\n });\n\n // Release mode and cursor using shared utilities\n releaseModeAndCursor(mapId, DRAW_SHAPE_LAYER_ID);\n\n // Emit shape drawn event\n drawShapeBus.emit(DrawShapeEvents.drawn, {\n shape,\n mapId,\n } as unknown as ShapeDrawnEvent['payload']);\n\n notify();\n\n return shape;\n}\n\n/**\n * Cancel the current drawing operation\n */\nfunction cancelDrawingInternal(\n mapId: UniqueId,\n state: DrawingState,\n notify: () => void,\n setState: (updates: Partial<DrawingState>) => void,\n): void {\n if (!state.activeShapeType) {\n return; // Nothing to cancel\n }\n\n const shapeType = state.activeShapeType;\n\n // Reset state with new object reference\n setState({\n activeShapeType: null,\n tentativeFeature: null,\n styleDefaults: null,\n circleDefaults: null,\n });\n\n // Release mode and cursor using shared utilities\n releaseModeAndCursor(mapId, DRAW_SHAPE_LAYER_ID);\n\n // Emit canceled event\n drawShapeBus.emit(DrawShapeEvents.canceled, {\n shapeType,\n mapId,\n });\n\n notify();\n}\n\n/**\n * Draw shape store\n */\nexport const drawStore = createMapStore<DrawingState, DrawShapeActions>({\n defaultState: { ...DEFAULT_DRAWING_STATE },\n\n actions: (mapId, { get, set, notify }) => ({\n draw: (shapeType: ShapeFeatureType, options?: DrawShapeOptions) => {\n startDrawing(mapId, get(), shapeType, options, notify, set);\n },\n\n cancel: () => {\n cancelDrawingInternal(mapId, get(), notify, set);\n },\n }),\n\n bus: (mapId, { get }) => {\n // Listen for mode authorization requests - REJECT when drawing (protected mode)\n const unsubAuth = mapModeBus.on(\n MapModeEvents.changeAuthorization,\n (event) => {\n const { authId, id } = event.payload;\n\n // Filter: only handle if targeted at this map\n if (id !== mapId) {\n return;\n }\n\n // If we're actively drawing, reject the mode change request\n if (get().activeShapeType) {\n mapModeBus.emit(MapModeEvents.changeDecision, {\n authId,\n approved: false,\n owner: DRAW_SHAPE_LAYER_ID,\n reason: 'Drawing in progress - cancel drawing first',\n id: mapId,\n });\n }\n },\n );\n\n return () => {\n unsubAuth();\n };\n },\n\n onCleanup: (mapId, state) => {\n // Cancel any active drawing before cleanup\n if (state.activeShapeType) {\n // Release mode and cursor using shared utilities\n releaseModeAndCursor(mapId, DRAW_SHAPE_LAYER_ID);\n\n // Emit canceled event\n drawShapeBus.emit(DrawShapeEvents.canceled, {\n shapeType: state.activeShapeType,\n mapId,\n });\n }\n },\n});\n\n// =============================================================================\n// Convenience exports\n// =============================================================================\n\n/**\n * Get the current drawing state for a map instance.\n *\n * Returns the drawing state (active shape type, style defaults, etc.) for the\n * specified map ID. Returns null if no drawing store instance exists for that map.\n *\n * ## Use Cases\n * - Check if a map is currently in drawing mode\n * - Access drawing state outside of React components\n * - Inspect state for debugging purposes\n *\n * @param mapId - Unique identifier for the map instance\n * @returns The drawing state, or null if no store instance exists\n *\n * @example\n * ```typescript\n * import { getDrawingState } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * const state = getDrawingState('map-1');\n * if (state?.activeShapeType) {\n * console.log(`Currently drawing: ${state.activeShapeType}`);\n * }\n * ```\n */\nexport function getDrawingState(mapId: UniqueId): DrawingState | null {\n if (!drawStore.exists(mapId)) {\n return null;\n }\n return drawStore.get(mapId);\n}\n\n/**\n * React hook for accessing drawing state and actions.\n *\n * Provides access to the drawing store for a specific map instance, including\n * the current state and draw/cancel actions. Uses `useSyncExternalStore` for\n * concurrent-safe React subscriptions.\n *\n * ## Comparison with useDrawShape\n * - `useDrawingState`: Low-level store access without event callbacks\n * - `useDrawShape`: High-level API with onCreate/onCancel callbacks\n *\n * Use `useDrawingState` when you need direct store access without event handling.\n * Use `useDrawShape` (recommended) for most drawing interactions.\n *\n * @param mapId - Unique identifier for the map instance\n * @returns Object containing drawing state and actions (draw, cancel)\n *\n * @example\n * ```tsx\n * import { useDrawingState } from '@accelint/map-toolkit/deckgl/shapes';\n * import { ShapeFeatureType } from '@accelint/map-toolkit/deckgl/shapes/shared/types';\n *\n * function DrawingStatus({ mapId }: { mapId: UniqueId }) {\n * const { state, draw, cancel } = useDrawingState(mapId);\n *\n * return (\n * <div>\n * <p>Status: {state.activeShapeType ?? 'Not drawing'}</p>\n * <button onClick={() => draw(ShapeFeatureType.Polygon)}>\n * Start Drawing\n * </button>\n * {state.activeShapeType && (\n * <button onClick={cancel}>Cancel</button>\n * )}\n * </div>\n * );\n * }\n * ```\n */\nexport function useDrawingState(\n mapId: UniqueId,\n): { state: DrawingState } & DrawShapeActions {\n return drawStore.use(mapId);\n}\n\n/**\n * Manually clear the drawing state for a specific map instance.\n *\n * Removes the drawing store instance for the given map ID, canceling any\n * active drawing operation and releasing mode/cursor ownership. This is\n * typically called automatically during cleanup, but can be used manually\n * when needed.\n *\n * ## When to Use\n * - Cleanup after programmatically managing drawing state\n * - Force-reset drawing state in error conditions\n * - Testing and debugging\n *\n * ## Side Effects\n * - Cancels active drawing (if any)\n * - Releases map mode and cursor\n * - Emits 'shapes:draw-canceled' event\n * - Removes store instance from memory\n *\n * @param mapId - Unique identifier for the map instance\n *\n * @example\n * ```typescript\n * import { clearDrawingState } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * // Clear drawing state when unmounting a map\n * function cleanup(mapId: UniqueId) {\n * clearDrawingState(mapId);\n * }\n * ```\n */\nexport function clearDrawingState(mapId: UniqueId): void {\n drawStore.clear(mapId);\n}\n\n/**\n * Complete the drawing operation with a GeoJSON feature.\n *\n * Called internally by the DrawShapeLayer component when the user finishes\n * drawing a shape. Converts the raw EditableGeoJsonLayer feature to a Shape\n * object, resets drawing state, releases mode/cursor, and emits the drawn event.\n *\n * ## Internal API\n * This function is exported for use by the DrawShapeLayer component and should\n * not be called directly from application code. Use the `draw` action from\n * `useDrawShape` or `useDrawingState` instead.\n *\n * @param mapId - Unique identifier for the map instance\n * @param feature - The completed GeoJSON feature from EditableGeoJsonLayer\n * @returns The newly created Shape object\n * @throws Error if not currently drawing\n *\n * @example\n * ```typescript\n * // Internal usage in DrawShapeLayer\n * const handleEdit = ({ updatedData, editType }: EditAction) => {\n * if (editType === 'addFeature') {\n * const feature = updatedData.features[updatedData.features.length - 1];\n * if (feature) {\n * completeDrawingFromLayer(mapId, feature);\n * }\n * }\n * };\n * ```\n */\nexport function completeDrawingFromLayer(\n mapId: UniqueId,\n feature: Feature,\n): Shape {\n const state = drawStore.get(mapId);\n return completeDrawingInternal(\n mapId,\n state,\n feature,\n () => {\n /* notify handled by set */\n },\n (updates) => drawStore.set(mapId, updates),\n );\n}\n\n/**\n * Cancel the current drawing operation from the layer.\n *\n * Called internally by the DrawShapeLayer component when the user presses ESC\n * or the drawing is otherwise canceled. Resets drawing state, releases mode/cursor,\n * and emits the canceled event.\n *\n * ## Internal API\n * This function is exported for use by the DrawShapeLayer component and should\n * not be called directly from application code. Use the `cancel` action from\n * `useDrawShape` or `useDrawingState` instead.\n *\n * @param mapId - Unique identifier for the map instance\n *\n * @example\n * ```typescript\n * // Internal usage in DrawShapeLayer\n * const handleEdit = ({ editType }: EditAction) => {\n * if (editType === 'cancelFeature') {\n * cancelDrawingFromLayer(mapId);\n * }\n * };\n * ```\n */\nexport function cancelDrawingFromLayer(mapId: UniqueId): void {\n drawStore.actions(mapId).cancel();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,MAAM,eAAe,UAAU,aAA6B;AAC5D,MAAM,aAAa,UAAU,aAA+B;;;;AAK5D,MAAMA,wBAAsC;CAC1C,iBAAiB;CACjB,kBAAkB;CAClB,eAAe;CACf,gBAAgB;CACjB;;;;AAeD,SAAS,aACP,OACA,OACA,WACA,SACA,QACA,UACM;AAEN,KAAI,MAAM,gBACR,uBAAsB,OAAO,OAAO,QAAQ,SAAS;AAIvD,UAAS;EACP,iBAAiB;EACjB,kBAAkB;EAClB,eAAe,SAAS,iBAAiB;EACzC,gBAAgB,SAAS,kBAAkB;EAC5C,CAAC;CAGF,MAAM,SAAS,gBAAgB;AAC/B,sBAAqB,OAAO,iBAAiB,QAAQ,oBAAoB;AAGzE,cAAa,KAAK,gBAAgB,SAAS;EACzC;EACA;EACD,CAAC;AAEF,SAAQ;;;;;AAMV,SAAS,wBACP,OACA,OACA,SACA,QACA,UACO;AACP,KAAI,CAAC,MAAM,gBACT,OAAM,IAAI,MAAM,kDAAkD;CAGpE,MAAM,YAAY,MAAM;CACxB,MAAM,gBAAgB,MAAM;CAG5B,MAAM,QAAQ,sBAAsB,SAAS,WAAW,cAAc;AAGtE,UAAS;EACP,iBAAiB;EACjB,kBAAkB;EAClB,eAAe;EACf,gBAAgB;EACjB,CAAC;AAGF,sBAAqB,OAAO,oBAAoB;AAGhD,cAAa,KAAK,gBAAgB,OAAO;EACvC;EACA;EACD,CAA0C;AAE3C,SAAQ;AAER,QAAO;;;;;AAMT,SAAS,sBACP,OACA,OACA,QACA,UACM;AACN,KAAI,CAAC,MAAM,gBACT;CAGF,MAAM,YAAY,MAAM;AAGxB,UAAS;EACP,iBAAiB;EACjB,kBAAkB;EAClB,eAAe;EACf,gBAAgB;EACjB,CAAC;AAGF,sBAAqB,OAAO,oBAAoB;AAGhD,cAAa,KAAK,gBAAgB,UAAU;EAC1C;EACA;EACD,CAAC;AAEF,SAAQ;;;;;AAMV,MAAa,YAAY,eAA+C;CACtE,cAAc,EAAE,GAAG,uBAAuB;CAE1C,UAAU,OAAO,EAAE,KAAK,KAAK,cAAc;EACzC,OAAO,WAA6B,YAA+B;AACjE,gBAAa,OAAO,KAAK,EAAE,WAAW,SAAS,QAAQ,IAAI;;EAG7D,cAAc;AACZ,yBAAsB,OAAO,KAAK,EAAE,QAAQ,IAAI;;EAEnD;CAED,MAAM,OAAO,EAAE,UAAU;EAEvB,MAAM,YAAY,WAAW,GAC3B,cAAc,sBACb,UAAU;GACT,MAAM,EAAE,QAAQ,OAAO,MAAM;AAG7B,OAAI,OAAO,MACT;AAIF,OAAI,KAAK,CAAC,gBACR,YAAW,KAAK,cAAc,gBAAgB;IAC5C;IACA,UAAU;IACV,OAAO;IACP,QAAQ;IACR,IAAI;IACL,CAAC;IAGP;AAED,eAAa;AACX,cAAW;;;CAIf,YAAY,OAAO,UAAU;AAE3B,MAAI,MAAM,iBAAiB;AAEzB,wBAAqB,OAAO,oBAAoB;AAGhD,gBAAa,KAAK,gBAAgB,UAAU;IAC1C,WAAW,MAAM;IACjB;IACD,CAAC;;;CAGP,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiHF,SAAgB,kBAAkB,OAAuB;AACvD,WAAU,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCxB,SAAgB,yBACd,OACA,SACO;AAEP,QAAO,wBACL,OAFY,UAAU,IAAI,MAAM,EAIhC,eACM,KAGL,YAAY,UAAU,IAAI,OAAO,QAAQ,CAC3C;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BH,SAAgB,uBAAuB,OAAuB;AAC5D,WAAU,QAAQ,MAAM,CAAC,QAAQ"}
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
'use client';
|
|
15
15
|
|
|
16
|
-
import { MapContext } from "../../base-map/provider.js";
|
|
17
16
|
import { DrawShapeEvents } from "./events.js";
|
|
18
17
|
import { drawStore } from "./store.js";
|
|
18
|
+
import { MapContext } from "../../base-map/provider.js";
|
|
19
19
|
import { useContext, useMemo } from "react";
|
|
20
20
|
import { useBus } from "@accelint/bus/react";
|
|
21
21
|
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
'use client';
|
|
15
15
|
|
|
16
|
-
import { DEFAULT_STYLE_PROPERTIES } from "../../shared/constants.js";
|
|
17
16
|
import { ShapeFeatureType } from "../../shared/types.js";
|
|
18
17
|
import { DEFAULT_DISTANCE_UNITS } from "../../../../shared/units.js";
|
|
18
|
+
import { DEFAULT_STYLE_PROPERTIES } from "../../shared/constants.js";
|
|
19
19
|
import { getLogger } from "@accelint/logger";
|
|
20
20
|
import { uuid } from "@accelint/core";
|
|
21
21
|
import { centroid, distance } from "@turf/turf";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","names":["EDIT_CURSOR_MAP: Record<EditMode, CSSCursorType>"],"sources":["../../../../src/deckgl/shapes/edit-shape-layer/constants.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport type { CSSCursorType } from '@/map-cursor/types';\nimport type { EditMode } from './types';\n\n// Re-export edit event type sets from shared constants\nexport {\n COMPLETION_EDIT_TYPES,\n CONTINUOUS_EDIT_TYPES,\n} from '../shared/constants';\n\n/**\n * Mode name for the map-mode integration\n */\nexport const EDIT_SHAPE_MODE = 'edit-shape';\n\n/**\n * Identifier for the edit shape layer.\n * Used as the owner for map-mode/cursor and as the default layer ID.\n */\nexport const EDIT_SHAPE_LAYER_ID = 'edit-shape-layer';\n\n/**\n * Cursor mapping for each edit mode\n */\nexport const EDIT_CURSOR_MAP: Record<EditMode, CSSCursorType> = {\n view: 'default',\n 'bounding-transform': 'crosshair',\n 'vertex-transform': 'crosshair',\n 'circle-transform': 'crosshair',\n translate: 'crosshair',\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,kBAAkB;;;;;AAM/B,MAAa,sBAAsB;;;;AAKnC,MAAaA,kBAAmD;CAC9D,MAAM;CACN,sBAAsB;CACtB,oBAAoB;CACpB,oBAAoB;CACpB,WAAW;
|
|
1
|
+
{"version":3,"file":"constants.js","names":["EDIT_CURSOR_MAP: Record<EditMode, CSSCursorType>"],"sources":["../../../../src/deckgl/shapes/edit-shape-layer/constants.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport type { CSSCursorType } from '@/map-cursor/types';\nimport type { EditMode } from './types';\n\n// Re-export edit event type sets from shared constants\nexport {\n COMPLETION_EDIT_TYPES,\n CONTINUOUS_EDIT_TYPES,\n} from '../shared/constants';\n\n/**\n * Mode name for the map-mode integration\n */\nexport const EDIT_SHAPE_MODE = 'edit-shape';\n\n/**\n * Identifier for the edit shape layer.\n * Used as the owner for map-mode/cursor and as the default layer ID.\n */\nexport const EDIT_SHAPE_LAYER_ID = 'edit-shape-layer';\n\n/**\n * Cursor mapping for each edit mode\n */\nexport const EDIT_CURSOR_MAP: Record<EditMode, CSSCursorType> = {\n view: 'default',\n 'bounding-transform': 'crosshair',\n 'vertex-transform': 'crosshair',\n 'circle-transform': 'crosshair',\n translate: 'crosshair',\n 'point-translate': 'crosshair',\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,kBAAkB;;;;;AAM/B,MAAa,sBAAsB;;;;AAKnC,MAAaA,kBAAmD;CAC9D,MAAM;CACN,sBAAsB;CACtB,oBAAoB;CACpB,oBAAoB;CACpB,WAAW;CACX,mBAAmB;CACpB"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { EditShapeLayerProps } from "./types.js";
|
|
14
|
-
import * as
|
|
14
|
+
import * as react_jsx_runtime3 from "react/jsx-runtime";
|
|
15
15
|
|
|
16
16
|
//#region src/deckgl/shapes/edit-shape-layer/index.d.ts
|
|
17
17
|
|
|
@@ -57,7 +57,7 @@ declare function EditShapeLayer({
|
|
|
57
57
|
id,
|
|
58
58
|
mapId,
|
|
59
59
|
unit
|
|
60
|
-
}: EditShapeLayerProps):
|
|
60
|
+
}: EditShapeLayerProps): react_jsx_runtime3.JSX.Element | null;
|
|
61
61
|
//#endregion
|
|
62
62
|
export { EditShapeLayer };
|
|
63
63
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -13,17 +13,19 @@
|
|
|
13
13
|
|
|
14
14
|
'use client';
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { ShapeFeatureType } from "../shared/types.js";
|
|
17
17
|
import { COMPLETION_EDIT_TYPES, CONTINUOUS_EDIT_TYPES } from "../shared/constants.js";
|
|
18
|
+
import { EDIT_SHAPE_LAYER_ID } from "./constants.js";
|
|
19
|
+
import { cancelEditingFromLayer, editStore, saveEditingFromLayer, updateFeatureFromLayer } from "./store.js";
|
|
20
|
+
import { MapContext } from "../../base-map/provider.js";
|
|
18
21
|
import { getFillColor, getLineColor } from "../shared/utils/style-utils.js";
|
|
19
|
-
import { ShapeFeatureType } from "../shared/types.js";
|
|
20
22
|
import { useShiftZoomDisable } from "../shared/hooks/use-shift-zoom-disable.js";
|
|
21
23
|
import { getDefaultEditableLayerProps } from "../shared/utils/layer-config.js";
|
|
22
|
-
import {
|
|
24
|
+
import { useHotkey } from "../../../hotkey-manager/dist/react/use-hotkey.js";
|
|
23
25
|
import { getEditModeInstance } from "./modes/index.js";
|
|
24
|
-
import {
|
|
25
|
-
import { useContext, useEffect, useRef } from "react";
|
|
26
|
+
import { useCallback, useContext, useEffect, useMemo, useRef } from "react";
|
|
26
27
|
import { jsx } from "react/jsx-runtime";
|
|
28
|
+
import { Keycode, globalBind, registerHotkey } from "@accelint/hotkey-manager";
|
|
27
29
|
|
|
28
30
|
//#region src/deckgl/shapes/edit-shape-layer/index.tsx
|
|
29
31
|
/**
|
|
@@ -115,8 +117,30 @@ function EditShapeLayer({ id = EDIT_SHAPE_LAYER_ID, mapId, unit }) {
|
|
|
115
117
|
const actualMapId = mapId ?? contextId;
|
|
116
118
|
if (!actualMapId) throw new Error("EditShapeLayer requires either a mapId prop or to be used within a MapProvider");
|
|
117
119
|
const { state: editingState } = editStore.use(actualMapId);
|
|
118
|
-
|
|
120
|
+
const isEditing = editingState?.editingShape != null;
|
|
119
121
|
const pendingUpdateRef = useRef(null);
|
|
122
|
+
const editingStateRef = useRef(editingState);
|
|
123
|
+
editingStateRef.current = editingState;
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
globalBind();
|
|
126
|
+
}, []);
|
|
127
|
+
const cancelPendingUpdate = useCallback(() => {
|
|
128
|
+
if (pendingUpdateRef.current) {
|
|
129
|
+
cancelAnimationFrame(pendingUpdateRef.current.rafId);
|
|
130
|
+
pendingUpdateRef.current = null;
|
|
131
|
+
}
|
|
132
|
+
}, []);
|
|
133
|
+
useHotkey(useMemo(() => registerHotkey({
|
|
134
|
+
id: `saveEditHotkey-${actualMapId}`,
|
|
135
|
+
key: { code: Keycode.Enter },
|
|
136
|
+
onKeyUp: () => {
|
|
137
|
+
if (editingStateRef.current?.editingShape) {
|
|
138
|
+
cancelPendingUpdate();
|
|
139
|
+
saveEditingFromLayer(actualMapId);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}), [actualMapId, cancelPendingUpdate]));
|
|
143
|
+
useShiftZoomDisable(actualMapId, isEditing);
|
|
120
144
|
useEffect(() => {
|
|
121
145
|
return () => {
|
|
122
146
|
if (pendingUpdateRef.current) cancelAnimationFrame(pendingUpdateRef.current.rafId);
|
|
@@ -126,12 +150,6 @@ function EditShapeLayer({ id = EDIT_SHAPE_LAYER_ID, mapId, unit }) {
|
|
|
126
150
|
const { editingShape, editMode, featureBeingEdited } = editingState;
|
|
127
151
|
const mode = getEditModeInstance(editMode);
|
|
128
152
|
const data = toFeatureCollection(featureBeingEdited ?? editingShape.feature, editingShape.shape);
|
|
129
|
-
const cancelPendingUpdate = () => {
|
|
130
|
-
if (pendingUpdateRef.current) {
|
|
131
|
-
cancelAnimationFrame(pendingUpdateRef.current.rafId);
|
|
132
|
-
pendingUpdateRef.current = null;
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
153
|
const handleEdit = ({ updatedData, editType }) => {
|
|
136
154
|
const feature = updatedData.features[0];
|
|
137
155
|
if (isContinuousEditType(editType) && feature) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["shapeProperty: string | undefined"],"sources":["../../../../src/deckgl/shapes/edit-shape-layer/index.tsx"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport { useContext, useEffect, useRef } from 'react';\nimport { MapContext } from '../../base-map/provider';\nimport { useShiftZoomDisable } from '../shared/hooks/use-shift-zoom-disable';\nimport { ShapeFeatureType, type ShapeFeatureTypeValues } from '../shared/types';\nimport { getDefaultEditableLayerProps } from '../shared/utils/layer-config';\nimport { getFillColor, getLineColor } from '../shared/utils/style-utils';\nimport {\n COMPLETION_EDIT_TYPES,\n CONTINUOUS_EDIT_TYPES,\n EDIT_SHAPE_LAYER_ID,\n} from './constants';\nimport { getEditModeInstance } from './modes';\nimport {\n cancelEditingFromLayer,\n editStore,\n updateFeatureFromLayer,\n} from './store';\nimport type {\n EditAction,\n FeatureCollection,\n} from '@deck.gl-community/editable-layers';\nimport type { Feature } from 'geojson';\nimport type { EditShapeLayerProps } from './types';\n\n/**\n * Check if an edit type is a continuous event (fires during drag).\n * Continuous events are batched with RAF for smooth performance.\n *\n * @param editType - The edit type string from EditableGeoJsonLayer\n * @returns True if the edit type is continuous (e.g., 'translating', 'scaling', 'rotating')\n */\nfunction isContinuousEditType(editType: string): boolean {\n return CONTINUOUS_EDIT_TYPES.has(editType);\n}\n\n/**\n * Check if an edit type is a completion event (fires at drag end).\n * Completion events update state immediately without RAF batching.\n *\n * @param editType - The edit type string from EditableGeoJsonLayer\n * @returns True if the edit type is a completion event (e.g., 'translated', 'scaled', 'rotated')\n */\nfunction isCompletionEditType(editType: string): boolean {\n return COMPLETION_EDIT_TYPES.has(editType);\n}\n\n/**\n * Convert a GeoJSON Feature to a FeatureCollection for EditableGeoJsonLayer.\n * The editable-layers library accepts standard GeoJSON FeatureCollection at runtime,\n * but has stricter internal types. We use the geojson types which are compatible.\n *\n * For circles, adds the `shape: 'Circle'` property required by ResizeCircleMode.\n * ResizeCircleMode checks `properties.shape.includes('Circle')` to identify circles.\n *\n * For rectangles, adds the `shape: 'Rectangle'` property required by ModifyMode's\n * lockRectangles feature. ModifyMode checks `properties.shape === 'Rectangle'`.\n */\nfunction toFeatureCollection(\n feature: Feature,\n shape: ShapeFeatureTypeValues,\n): import('geojson').FeatureCollection {\n // Add shape property for modes that require it\n // - ResizeCircleMode requires shape: 'Circle'\n // - ModifyMode lockRectangles requires shape: 'Rectangle'\n let shapeProperty: string | undefined;\n if (shape === ShapeFeatureType.Circle) {\n shapeProperty = 'Circle';\n } else if (shape === ShapeFeatureType.Rectangle) {\n shapeProperty = 'Rectangle';\n }\n\n const featureWithShape = shapeProperty\n ? {\n ...feature,\n properties: {\n ...feature.properties,\n shape: shapeProperty,\n },\n }\n : feature;\n\n return {\n type: 'FeatureCollection',\n features: [featureWithShape],\n };\n}\n\n/**\n * EditShapeLayer - A React component for editing existing shapes on the map.\n *\n * This component wraps the EditableGeoJsonLayer from @deck.gl-community/editable-layers\n * and integrates with the map-mode and map-cursor systems for proper coordination.\n *\n * Key features:\n * - Renders only when actively editing (returns null otherwise)\n * - Uses cached mode instances to prevent deck.gl assertion errors\n * - Integrates with the editing store for state management\n * - Neutral mode authorization (lets UI decide how to handle mode conflicts)\n * - Circles use ResizeCircleMode with tooltip, other shapes use ModifyMode\n * - Fill colors rendered at 20% opacity, edit handles are white\n * - Live dimension/area tooltips during editing\n * - requestAnimationFrame() batching for smooth drag performance\n *\n * @example\n * ```tsx\n * // Import the fiber registration for JSX support\n * import '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/fiber';\n * import '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/fiber';\n *\n * function Map({ mapId }) {\n * const { editingShape } = useEditShape(mapId);\n *\n * return (\n * <BaseMap id={mapId}>\n * <displayShapeLayer\n * data={shapes}\n * mapId={mapId}\n * selectedShapeId={editingShape?.id}\n * />\n * <EditShapeLayer mapId={mapId} />\n * </BaseMap>\n * );\n * }\n * ```\n */\nexport function EditShapeLayer({\n id = EDIT_SHAPE_LAYER_ID,\n mapId,\n unit,\n}: EditShapeLayerProps) {\n // Get mapId from context if not provided\n const contextId = useContext(MapContext);\n const actualMapId = mapId ?? contextId;\n\n if (!actualMapId) {\n throw new Error(\n 'EditShapeLayer requires either a mapId prop or to be used within a MapProvider',\n );\n }\n\n // Subscribe to editing state using the v2 store API\n const { state: editingState } = editStore.use(actualMapId);\n\n const isEditing = editingState?.editingShape != null;\n\n // Disable zoom while Shift is held during editing\n // This prevents boxZoom (Shift+drag) from interfering with Shift modifier constraints\n // (e.g., Shift for uniform scaling, Shift for rotation snap)\n useShiftZoomDisable(actualMapId, isEditing);\n\n // RAF batching for movePosition events to reduce React updates during drag\n const pendingUpdateRef = useRef<{\n feature: Feature;\n rafId: number;\n } | null>(null);\n\n // Cleanup RAF on unmount\n useEffect(() => {\n return () => {\n if (pendingUpdateRef.current) {\n cancelAnimationFrame(pendingUpdateRef.current.rafId);\n }\n };\n }, []);\n\n // If not editing, return null (don't render the editable layer)\n if (!editingState?.editingShape) {\n return null;\n }\n\n const { editingShape, editMode, featureBeingEdited } = editingState;\n\n // Get the cached mode instance\n const mode = getEditModeInstance(editMode);\n\n // Use the live feature being edited, or fall back to original shape\n const featureToRender = featureBeingEdited ?? editingShape.feature;\n const data = toFeatureCollection(featureToRender, editingShape.shape);\n\n // Helper to cancel any pending RAF update\n const cancelPendingUpdate = () => {\n if (pendingUpdateRef.current) {\n cancelAnimationFrame(pendingUpdateRef.current.rafId);\n pendingUpdateRef.current = null;\n }\n };\n\n // Handle edit events from EditableGeoJsonLayer\n const handleEdit = ({\n updatedData,\n editType,\n }: EditAction<FeatureCollection>) => {\n const feature = updatedData.features[0];\n\n // Continuous events (during drag): batch with RAF for smooth performance\n if (isContinuousEditType(editType) && feature) {\n cancelPendingUpdate();\n const rafId = requestAnimationFrame(() => {\n updateFeatureFromLayer(actualMapId, feature as Feature);\n pendingUpdateRef.current = null;\n });\n pendingUpdateRef.current = { feature: feature as Feature, rafId };\n return;\n }\n\n // Completion events (drag end): update immediately\n if (isCompletionEditType(editType)) {\n cancelPendingUpdate();\n if (feature) {\n updateFeatureFromLayer(actualMapId, feature as Feature);\n }\n return;\n }\n\n // ESC key cancellation\n if (editType === 'cancelFeature') {\n cancelPendingUpdate();\n cancelEditingFromLayer(actualMapId);\n }\n };\n\n // Get colors from the shape's existing style properties with base opacity applied\n const fillColor = getFillColor(editingShape.feature, true);\n const lineColor = getLineColor(editingShape.feature);\n\n return (\n <editableGeoJsonLayer\n id={id}\n data={data}\n mode={mode}\n selectedFeatureIndexes={[0]}\n onEdit={handleEdit}\n getFillColor={fillColor}\n getLineColor={lineColor}\n getTentativeFillColor={fillColor}\n getTentativeLineColor={lineColor}\n {...getDefaultEditableLayerProps(unit)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAS,qBAAqB,UAA2B;AACvD,QAAO,sBAAsB,IAAI,SAAS;;;;;;;;;AAU5C,SAAS,qBAAqB,UAA2B;AACvD,QAAO,sBAAsB,IAAI,SAAS;;;;;;;;;;;;;AAc5C,SAAS,oBACP,SACA,OACqC;CAIrC,IAAIA;AACJ,KAAI,UAAU,iBAAiB,OAC7B,iBAAgB;UACP,UAAU,iBAAiB,UACpC,iBAAgB;AAalB,QAAO;EACL,MAAM;EACN,UAAU,CAZa,gBACrB;GACE,GAAG;GACH,YAAY;IACV,GAAG,QAAQ;IACX,OAAO;IACR;GACF,GACD,QAI0B;EAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCH,SAAgB,eAAe,EAC7B,KAAK,qBACL,OACA,QACsB;CAEtB,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,cAAc,SAAS;AAE7B,KAAI,CAAC,YACH,OAAM,IAAI,MACR,iFACD;CAIH,MAAM,EAAE,OAAO,iBAAiB,UAAU,IAAI,YAAY;AAO1D,qBAAoB,aALF,cAAc,gBAAgB,KAKL;CAG3C,MAAM,mBAAmB,OAGf,KAAK;AAGf,iBAAgB;AACd,eAAa;AACX,OAAI,iBAAiB,QACnB,sBAAqB,iBAAiB,QAAQ,MAAM;;IAGvD,EAAE,CAAC;AAGN,KAAI,CAAC,cAAc,aACjB,QAAO;CAGT,MAAM,EAAE,cAAc,UAAU,uBAAuB;CAGvD,MAAM,OAAO,oBAAoB,SAAS;CAI1C,MAAM,OAAO,oBADW,sBAAsB,aAAa,SACT,aAAa,MAAM;CAGrE,MAAM,4BAA4B;AAChC,MAAI,iBAAiB,SAAS;AAC5B,wBAAqB,iBAAiB,QAAQ,MAAM;AACpD,oBAAiB,UAAU;;;CAK/B,MAAM,cAAc,EAClB,aACA,eACmC;EACnC,MAAM,UAAU,YAAY,SAAS;AAGrC,MAAI,qBAAqB,SAAS,IAAI,SAAS;AAC7C,wBAAqB;AAKrB,oBAAiB,UAAU;IAAW;IAAoB,OAJ5C,4BAA4B;AACxC,4BAAuB,aAAa,QAAmB;AACvD,sBAAiB,UAAU;MAC3B;IAC+D;AACjE;;AAIF,MAAI,qBAAqB,SAAS,EAAE;AAClC,wBAAqB;AACrB,OAAI,QACF,wBAAuB,aAAa,QAAmB;AAEzD;;AAIF,MAAI,aAAa,iBAAiB;AAChC,wBAAqB;AACrB,0BAAuB,YAAY;;;CAKvC,MAAM,YAAY,aAAa,aAAa,SAAS,KAAK;CAC1D,MAAM,YAAY,aAAa,aAAa,QAAQ;AAEpD,QACE,oBAAC;EACK;EACE;EACA;EACN,wBAAwB,CAAC,EAAE;EAC3B,QAAQ;EACR,cAAc;EACd,cAAc;EACd,uBAAuB;EACvB,uBAAuB;EACvB,GAAI,6BAA6B,KAAK;GACtC"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["shapeProperty: string | undefined"],"sources":["../../../../src/deckgl/shapes/edit-shape-layer/index.tsx"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport { globalBind, Keycode, registerHotkey } from '@accelint/hotkey-manager';\nimport { useHotkey } from '@accelint/hotkey-manager/react';\nimport { useCallback, useContext, useEffect, useMemo, useRef } from 'react';\nimport { MapContext } from '../../base-map/provider';\nimport { useShiftZoomDisable } from '../shared/hooks/use-shift-zoom-disable';\nimport { ShapeFeatureType, type ShapeFeatureTypeValues } from '../shared/types';\nimport { getDefaultEditableLayerProps } from '../shared/utils/layer-config';\nimport { getFillColor, getLineColor } from '../shared/utils/style-utils';\nimport {\n COMPLETION_EDIT_TYPES,\n CONTINUOUS_EDIT_TYPES,\n EDIT_SHAPE_LAYER_ID,\n} from './constants';\nimport { getEditModeInstance } from './modes';\nimport {\n cancelEditingFromLayer,\n editStore,\n saveEditingFromLayer,\n updateFeatureFromLayer,\n} from './store';\nimport type {\n EditAction,\n FeatureCollection,\n} from '@deck.gl-community/editable-layers';\nimport type { Feature } from 'geojson';\nimport type { EditShapeLayerProps } from './types';\n\n/**\n * Check if an edit type is a continuous event (fires during drag).\n * Continuous events are batched with RAF for smooth performance.\n *\n * @param editType - The edit type string from EditableGeoJsonLayer\n * @returns True if the edit type is continuous (e.g., 'translating', 'scaling', 'rotating')\n */\nfunction isContinuousEditType(editType: string): boolean {\n return CONTINUOUS_EDIT_TYPES.has(editType);\n}\n\n/**\n * Check if an edit type is a completion event (fires at drag end).\n * Completion events update state immediately without RAF batching.\n *\n * @param editType - The edit type string from EditableGeoJsonLayer\n * @returns True if the edit type is a completion event (e.g., 'translated', 'scaled', 'rotated')\n */\nfunction isCompletionEditType(editType: string): boolean {\n return COMPLETION_EDIT_TYPES.has(editType);\n}\n\n/**\n * Convert a GeoJSON Feature to a FeatureCollection for EditableGeoJsonLayer.\n * The editable-layers library accepts standard GeoJSON FeatureCollection at runtime,\n * but has stricter internal types. We use the geojson types which are compatible.\n *\n * For circles, adds the `shape: 'Circle'` property required by ResizeCircleMode.\n * ResizeCircleMode checks `properties.shape.includes('Circle')` to identify circles.\n *\n * For rectangles, adds the `shape: 'Rectangle'` property required by ModifyMode's\n * lockRectangles feature. ModifyMode checks `properties.shape === 'Rectangle'`.\n */\nfunction toFeatureCollection(\n feature: Feature,\n shape: ShapeFeatureTypeValues,\n): import('geojson').FeatureCollection {\n // Add shape property for modes that require it\n // - ResizeCircleMode requires shape: 'Circle'\n // - ModifyMode lockRectangles requires shape: 'Rectangle'\n let shapeProperty: string | undefined;\n if (shape === ShapeFeatureType.Circle) {\n shapeProperty = 'Circle';\n } else if (shape === ShapeFeatureType.Rectangle) {\n shapeProperty = 'Rectangle';\n }\n\n const featureWithShape = shapeProperty\n ? {\n ...feature,\n properties: {\n ...feature.properties,\n shape: shapeProperty,\n },\n }\n : feature;\n\n return {\n type: 'FeatureCollection',\n features: [featureWithShape],\n };\n}\n\n/**\n * EditShapeLayer - A React component for editing existing shapes on the map.\n *\n * This component wraps the EditableGeoJsonLayer from @deck.gl-community/editable-layers\n * and integrates with the map-mode and map-cursor systems for proper coordination.\n *\n * Key features:\n * - Renders only when actively editing (returns null otherwise)\n * - Uses cached mode instances to prevent deck.gl assertion errors\n * - Integrates with the editing store for state management\n * - Neutral mode authorization (lets UI decide how to handle mode conflicts)\n * - Circles use ResizeCircleMode with tooltip, other shapes use ModifyMode\n * - Fill colors rendered at 20% opacity, edit handles are white\n * - Live dimension/area tooltips during editing\n * - requestAnimationFrame() batching for smooth drag performance\n *\n * @example\n * ```tsx\n * // Import the fiber registration for JSX support\n * import '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/fiber';\n * import '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/fiber';\n *\n * function Map({ mapId }) {\n * const { editingShape } = useEditShape(mapId);\n *\n * return (\n * <BaseMap id={mapId}>\n * <displayShapeLayer\n * data={shapes}\n * mapId={mapId}\n * selectedShapeId={editingShape?.id}\n * />\n * <EditShapeLayer mapId={mapId} />\n * </BaseMap>\n * );\n * }\n * ```\n */\nexport function EditShapeLayer({\n id = EDIT_SHAPE_LAYER_ID,\n mapId,\n unit,\n}: EditShapeLayerProps) {\n // Get mapId from context if not provided\n const contextId = useContext(MapContext);\n const actualMapId = mapId ?? contextId;\n\n if (!actualMapId) {\n throw new Error(\n 'EditShapeLayer requires either a mapId prop or to be used within a MapProvider',\n );\n }\n\n // Subscribe to editing state using the v2 store API\n const { state: editingState } = editStore.use(actualMapId);\n\n const isEditing = editingState?.editingShape != null;\n\n // RAF batching for movePosition events to reduce React updates during drag\n const pendingUpdateRef = useRef<{\n feature: Feature;\n rafId: number;\n } | null>(null);\n\n // Keep a ref to the latest editing state so the hotkey handler can access it\n const editingStateRef = useRef(editingState);\n editingStateRef.current = editingState;\n\n // Ensure global hotkey listeners are initialized\n // Safe to call multiple times - globalBind() checks if already bound\n useEffect(() => {\n globalBind();\n }, []);\n\n // Helper to cancel any pending RAF update (stable reference with useCallback)\n const cancelPendingUpdate = useCallback(() => {\n if (pendingUpdateRef.current) {\n cancelAnimationFrame(pendingUpdateRef.current.rafId);\n pendingUpdateRef.current = null;\n }\n }, []);\n\n // Register Enter key hotkey scoped to this component and map instance\n // Handler checks if actively editing to prevent conflicts with other Enter key uses\n const saveEditHotkey = useMemo(\n () =>\n registerHotkey({\n id: `saveEditHotkey-${actualMapId}`,\n key: { code: Keycode.Enter },\n onKeyUp: () => {\n // Use ref to get current editing state, avoiding stale closure\n if (editingStateRef.current?.editingShape) {\n cancelPendingUpdate();\n saveEditingFromLayer(actualMapId);\n }\n },\n }),\n [actualMapId, cancelPendingUpdate],\n );\n\n useHotkey(saveEditHotkey);\n\n // Disable zoom while Shift is held during editing\n // This prevents boxZoom (Shift+drag) from interfering with Shift modifier constraints\n // (e.g., Shift for uniform scaling, Shift for rotation snap)\n useShiftZoomDisable(actualMapId, isEditing);\n\n // Cleanup RAF on unmount\n useEffect(() => {\n return () => {\n if (pendingUpdateRef.current) {\n cancelAnimationFrame(pendingUpdateRef.current.rafId);\n }\n };\n }, []);\n\n // If not editing, return null (don't render the editable layer)\n if (!editingState?.editingShape) {\n return null;\n }\n\n const { editingShape, editMode, featureBeingEdited } = editingState;\n\n // Get the cached mode instance\n const mode = getEditModeInstance(editMode);\n\n // Use the live feature being edited, or fall back to original shape\n const featureToRender = featureBeingEdited ?? editingShape.feature;\n const data = toFeatureCollection(featureToRender, editingShape.shape);\n\n // Handle edit events from EditableGeoJsonLayer\n const handleEdit = ({\n updatedData,\n editType,\n }: EditAction<FeatureCollection>) => {\n const feature = updatedData.features[0];\n\n // Continuous events (during drag): batch with RAF for smooth performance\n if (isContinuousEditType(editType) && feature) {\n cancelPendingUpdate();\n const rafId = requestAnimationFrame(() => {\n updateFeatureFromLayer(actualMapId, feature as Feature);\n pendingUpdateRef.current = null;\n });\n pendingUpdateRef.current = { feature: feature as Feature, rafId };\n return;\n }\n\n // Completion events (drag end): update immediately\n if (isCompletionEditType(editType)) {\n cancelPendingUpdate();\n if (feature) {\n updateFeatureFromLayer(actualMapId, feature as Feature);\n }\n return;\n }\n\n // ESC key cancellation\n if (editType === 'cancelFeature') {\n cancelPendingUpdate();\n cancelEditingFromLayer(actualMapId);\n }\n };\n\n // Get colors from the shape's existing style properties with base opacity applied\n const fillColor = getFillColor(editingShape.feature, true);\n const lineColor = getLineColor(editingShape.feature);\n\n return (\n <editableGeoJsonLayer\n id={id}\n data={data}\n mode={mode}\n selectedFeatureIndexes={[0]}\n onEdit={handleEdit}\n getFillColor={fillColor}\n getLineColor={lineColor}\n getTentativeFillColor={fillColor}\n getTentativeLineColor={lineColor}\n {...getDefaultEditableLayerProps(unit)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAS,qBAAqB,UAA2B;AACvD,QAAO,sBAAsB,IAAI,SAAS;;;;;;;;;AAU5C,SAAS,qBAAqB,UAA2B;AACvD,QAAO,sBAAsB,IAAI,SAAS;;;;;;;;;;;;;AAc5C,SAAS,oBACP,SACA,OACqC;CAIrC,IAAIA;AACJ,KAAI,UAAU,iBAAiB,OAC7B,iBAAgB;UACP,UAAU,iBAAiB,UACpC,iBAAgB;AAalB,QAAO;EACL,MAAM;EACN,UAAU,CAZa,gBACrB;GACE,GAAG;GACH,YAAY;IACV,GAAG,QAAQ;IACX,OAAO;IACR;GACF,GACD,QAI0B;EAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCH,SAAgB,eAAe,EAC7B,KAAK,qBACL,OACA,QACsB;CAEtB,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,cAAc,SAAS;AAE7B,KAAI,CAAC,YACH,OAAM,IAAI,MACR,iFACD;CAIH,MAAM,EAAE,OAAO,iBAAiB,UAAU,IAAI,YAAY;CAE1D,MAAM,YAAY,cAAc,gBAAgB;CAGhD,MAAM,mBAAmB,OAGf,KAAK;CAGf,MAAM,kBAAkB,OAAO,aAAa;AAC5C,iBAAgB,UAAU;AAI1B,iBAAgB;AACd,cAAY;IACX,EAAE,CAAC;CAGN,MAAM,sBAAsB,kBAAkB;AAC5C,MAAI,iBAAiB,SAAS;AAC5B,wBAAqB,iBAAiB,QAAQ,MAAM;AACpD,oBAAiB,UAAU;;IAE5B,EAAE,CAAC;AAoBN,WAhBuB,cAEnB,eAAe;EACb,IAAI,kBAAkB;EACtB,KAAK,EAAE,MAAM,QAAQ,OAAO;EAC5B,eAAe;AAEb,OAAI,gBAAgB,SAAS,cAAc;AACzC,yBAAqB;AACrB,yBAAqB,YAAY;;;EAGtC,CAAC,EACJ,CAAC,aAAa,oBAAoB,CACnC,CAEwB;AAKzB,qBAAoB,aAAa,UAAU;AAG3C,iBAAgB;AACd,eAAa;AACX,OAAI,iBAAiB,QACnB,sBAAqB,iBAAiB,QAAQ,MAAM;;IAGvD,EAAE,CAAC;AAGN,KAAI,CAAC,cAAc,aACjB,QAAO;CAGT,MAAM,EAAE,cAAc,UAAU,uBAAuB;CAGvD,MAAM,OAAO,oBAAoB,SAAS;CAI1C,MAAM,OAAO,oBADW,sBAAsB,aAAa,SACT,aAAa,MAAM;CAGrE,MAAM,cAAc,EAClB,aACA,eACmC;EACnC,MAAM,UAAU,YAAY,SAAS;AAGrC,MAAI,qBAAqB,SAAS,IAAI,SAAS;AAC7C,wBAAqB;AAKrB,oBAAiB,UAAU;IAAW;IAAoB,OAJ5C,4BAA4B;AACxC,4BAAuB,aAAa,QAAmB;AACvD,sBAAiB,UAAU;MAC3B;IAC+D;AACjE;;AAIF,MAAI,qBAAqB,SAAS,EAAE;AAClC,wBAAqB;AACrB,OAAI,QACF,wBAAuB,aAAa,QAAmB;AAEzD;;AAIF,MAAI,aAAa,iBAAiB;AAChC,wBAAqB;AACrB,0BAAuB,YAAY;;;CAKvC,MAAM,YAAY,aAAa,aAAa,SAAS,KAAK;CAC1D,MAAM,YAAY,aAAa,aAAa,QAAQ;AAEpD,QACE,oBAAC;EACK;EACE;EACA;EACN,wBAAwB,CAAC,EAAE;EAC3B,QAAQ;EACR,cAAc;EACd,cAAc;EACd,uBAAuB;EACvB,uBAAuB;EACvB,GAAI,6BAA6B,KAAK;GACtC"}
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
import { formatEllipseTooltip, formatRectangleTooltip } from "../../shared/constants.js";
|
|
15
14
|
import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
|
|
15
|
+
import { formatEllipseTooltip, formatRectangleTooltip } from "../../shared/constants.js";
|
|
16
16
|
import { computeEllipseMeasurementsFromPolygon, computeRectangleMeasurementsFromCorners } from "../../shared/utils/geometry-measurements.js";
|
|
17
17
|
import { BaseTransformMode } from "./base-transform-mode.js";
|
|
18
18
|
import { RotateModeWithSnap } from "./rotate-mode-with-snap.js";
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
import { formatCircleTooltip } from "../../shared/constants.js";
|
|
15
14
|
import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
|
|
15
|
+
import { formatCircleTooltip } from "../../shared/constants.js";
|
|
16
16
|
import { computeCircleMeasurements } from "../../shared/utils/geometry-measurements.js";
|
|
17
17
|
import { BaseTransformMode } from "./base-transform-mode.js";
|
|
18
|
-
import { ResizeCircleMode, TranslateMode } from "@deck.gl-community/editable-layers";
|
|
19
18
|
import { centroid } from "@turf/turf";
|
|
19
|
+
import { ResizeCircleMode, TranslateMode } from "@deck.gl-community/editable-layers";
|
|
20
20
|
|
|
21
21
|
//#region src/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.ts
|
|
22
22
|
/**
|
|
@@ -26,7 +26,7 @@ import { centroid } from "@turf/turf";
|
|
|
26
26
|
* This composite mode provides:
|
|
27
27
|
* - **Resize** (ResizeCircleMode): Drag edge to resize from center
|
|
28
28
|
* - **Translation** (TranslateMode): Drag the circle body to move it
|
|
29
|
-
* - **Live tooltip**: Shows
|
|
29
|
+
* - **Live tooltip**: Shows radius and area during resize
|
|
30
30
|
*
|
|
31
31
|
* ## Handle Priority Logic
|
|
32
32
|
* When drag starts, modes are evaluated in this priority order:
|
|
@@ -76,7 +76,7 @@ var CircleTransformMode = class extends BaseTransformMode {
|
|
|
76
76
|
return this.translateMode;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* Update the tooltip with circle
|
|
79
|
+
* Update the tooltip with circle radius and area during resize.
|
|
80
80
|
*/
|
|
81
81
|
onDragging(event, props) {
|
|
82
82
|
if (this.activeDragMode !== this.resizeMode) return;
|
|
@@ -99,10 +99,10 @@ var CircleTransformMode = class extends BaseTransformMode {
|
|
|
99
99
|
}
|
|
100
100
|
const center = centroid(feature).geometry.coordinates;
|
|
101
101
|
const firstPoint = coordinates[0];
|
|
102
|
-
const {
|
|
102
|
+
const { radius, area: area$1 } = computeCircleMeasurements(center, firstPoint, distanceUnits);
|
|
103
103
|
this.tooltip = {
|
|
104
104
|
position: mapCoords,
|
|
105
|
-
text: formatCircleTooltip(
|
|
105
|
+
text: formatCircleTooltip(radius, area$1, getDistanceUnitAbbreviation(distanceUnits))
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"circle-transform-mode.js","names":["area"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type DraggingEvent,\n type FeatureCollection,\n type GeoJsonEditMode,\n type ModeProps,\n ResizeCircleMode,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { centroid } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatCircleTooltip } from '../../shared/constants';\nimport { computeCircleMeasurements } from '../../shared/utils/geometry-measurements';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport type { Feature, Polygon } from 'geojson';\n\n/**\n * Transform mode for circles combining resize and translate.\n *\n * ## Capabilities\n * This composite mode provides:\n * - **Resize** (ResizeCircleMode): Drag edge to resize from center\n * - **Translation** (TranslateMode): Drag the circle body to move it\n * - **Live tooltip**: Shows
|
|
1
|
+
{"version":3,"file":"circle-transform-mode.js","names":["area"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type DraggingEvent,\n type FeatureCollection,\n type GeoJsonEditMode,\n type ModeProps,\n ResizeCircleMode,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { centroid } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatCircleTooltip } from '../../shared/constants';\nimport { computeCircleMeasurements } from '../../shared/utils/geometry-measurements';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport type { Feature, Polygon } from 'geojson';\n\n/**\n * Transform mode for circles combining resize and translate.\n *\n * ## Capabilities\n * This composite mode provides:\n * - **Resize** (ResizeCircleMode): Drag edge to resize from center\n * - **Translation** (TranslateMode): Drag the circle body to move it\n * - **Live tooltip**: Shows radius and area during resize\n *\n * ## Handle Priority Logic\n * When drag starts, modes are evaluated in this priority order:\n * 1. If dragging on the edge/handle → resize takes priority\n * 2. Otherwise → dragging the circle body translates it\n *\n * ## Resize Behavior\n * Unlike scale operations in BoundingTransformMode, circle resize maintains\n * the shape's circular geometry by resizing from the center point. The center\n * remains fixed while the radius changes based on cursor distance.\n *\n * @example\n * ```typescript\n * import { CircleTransformMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * // Used internally by EditShapeLayer for circles\n * const mode = new CircleTransformMode();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: circleFeatureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * modeConfig: { distanceUnits: 'kilometers' },\n * // ... other props\n * });\n * ```\n */\nexport class CircleTransformMode extends BaseTransformMode {\n private resizeMode: ResizeCircleMode;\n private translateMode: TranslateMode;\n\n constructor() {\n const resizeMode = new ResizeCircleMode();\n const translateMode = new TranslateMode();\n\n // Order matters: resize first so edge handles take priority\n super([resizeMode, translateMode]);\n\n this.resizeMode = resizeMode;\n this.translateMode = translateMode;\n }\n\n protected override getHandleMatchers(): HandleMatcher[] {\n return [\n {\n // Resize handle: intermediate point on circle edge\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.guideType === 'editHandle' &&\n pick.object?.properties?.editHandleType === 'intermediate',\n ),\n mode: this.resizeMode,\n // No shift config - resize doesn't have modifiers\n },\n ];\n }\n\n protected override getDefaultMode(): GeoJsonEditMode {\n return this.translateMode;\n }\n\n /**\n * Update the tooltip with circle radius and area during resize.\n */\n protected override onDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n // Only show tooltip when resizing\n if (this.activeDragMode !== this.resizeMode) {\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n // Get the selected feature to calculate radius from its geometry\n const selectedIndexes = props.selectedIndexes;\n const selectedIndex = selectedIndexes?.[0];\n if (selectedIndex === undefined) {\n this.tooltip = null;\n return;\n }\n\n const feature = props.data.features[selectedIndex] as\n | Feature<Polygon>\n | undefined;\n if (!feature || feature.geometry.type !== 'Polygon') {\n this.tooltip = null;\n return;\n }\n\n // Calculate center and radius from the polygon geometry\n const coordinates = feature.geometry.coordinates[0];\n if (!coordinates || coordinates.length < 3) {\n this.tooltip = null;\n return;\n }\n\n const centerFeature = centroid(feature);\n const center = centerFeature.geometry.coordinates as [number, number];\n const firstPoint = coordinates[0] as [number, number];\n const { radius, area } = computeCircleMeasurements(\n center,\n firstPoint,\n distanceUnits,\n );\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n // Position tooltip at cursor - offset is applied via getPixelOffset in sublayer props\n this.tooltip = {\n position: mapCoords,\n text: formatCircleTooltip(radius, area, unitAbbrev),\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,IAAa,sBAAb,cAAyC,kBAAkB;CACzD,AAAQ;CACR,AAAQ;CAER,cAAc;EACZ,MAAM,aAAa,IAAI,kBAAkB;EACzC,MAAM,gBAAgB,IAAI,eAAe;AAGzC,QAAM,CAAC,YAAY,cAAc,CAAC;AAElC,OAAK,aAAa;AAClB,OAAK,gBAAgB;;CAGvB,AAAmB,oBAAqC;AACtD,SAAO,CACL;GAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,cAAc,gBACvC,KAAK,QAAQ,YAAY,mBAAmB,eAC/C;GACH,MAAM,KAAK;GAEZ,CACF;;CAGH,AAAmB,iBAAkC;AACnD,SAAO,KAAK;;;;;CAMd,AAAmB,WACjB,OACA,OACM;AAEN,MAAI,KAAK,mBAAmB,KAAK,WAC/B;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EAIrC,MAAM,gBADkB,MAAM,kBACU;AACxC,MAAI,kBAAkB,QAAW;AAC/B,QAAK,UAAU;AACf;;EAGF,MAAM,UAAU,MAAM,KAAK,SAAS;AAGpC,MAAI,CAAC,WAAW,QAAQ,SAAS,SAAS,WAAW;AACnD,QAAK,UAAU;AACf;;EAIF,MAAM,cAAc,QAAQ,SAAS,YAAY;AACjD,MAAI,CAAC,eAAe,YAAY,SAAS,GAAG;AAC1C,QAAK,UAAU;AACf;;EAIF,MAAM,SADgB,SAAS,QAAQ,CACV,SAAS;EACtC,MAAM,aAAa,YAAY;EAC/B,MAAM,EAAE,QAAQ,iBAAS,0BACvB,QACA,YACA,cACD;AAID,OAAK,UAAU;GACb,UAAU;GACV,MAAM,oBAAoB,QAAQA,QALjB,4BAA4B,cAAc,CAKR;GACpD"}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { BoundingTransformMode } from "./bounding-transform-mode.js";
|
|
15
15
|
import { CircleTransformMode } from "./circle-transform-mode.js";
|
|
16
|
+
import { PointTranslateMode } from "./point-translate-mode.js";
|
|
16
17
|
import { VertexTransformMode } from "./vertex-transform-mode.js";
|
|
17
18
|
import { TranslateMode, ViewMode } from "@deck.gl-community/editable-layers";
|
|
18
19
|
|
|
@@ -35,16 +36,20 @@ import { TranslateMode, ViewMode } from "@deck.gl-community/editable-layers";
|
|
|
35
36
|
*
|
|
36
37
|
* CircleTransformMode combines ResizeCircleMode with TranslateMode
|
|
37
38
|
* for circles, allowing resize from edge plus drag to translate.
|
|
38
|
-
* Shows live
|
|
39
|
+
* Shows live radius/area tooltips during resize.
|
|
39
40
|
*
|
|
40
|
-
*
|
|
41
|
+
* PointTranslateMode allows clicking anywhere on the map to reposition
|
|
42
|
+
* a point, or dragging the point for traditional translation behavior.
|
|
43
|
+
*
|
|
44
|
+
* TranslateMode allows dragging to move the shape (generic translation).
|
|
41
45
|
*/
|
|
42
46
|
const EDIT_MODE_INSTANCES = {
|
|
43
47
|
view: new ViewMode(),
|
|
44
48
|
"bounding-transform": new BoundingTransformMode(),
|
|
45
49
|
"vertex-transform": new VertexTransformMode(),
|
|
46
50
|
"circle-transform": new CircleTransformMode(),
|
|
47
|
-
translate: new TranslateMode()
|
|
51
|
+
translate: new TranslateMode(),
|
|
52
|
+
"point-translate": new PointTranslateMode()
|
|
48
53
|
};
|
|
49
54
|
/**
|
|
50
55
|
* Get the cached mode instance for an edit mode.
|
|
@@ -57,7 +62,8 @@ const EDIT_MODE_INSTANCES = {
|
|
|
57
62
|
* - `'bounding-transform'`: For shapes without vertex editing (rectangles, ellipses)
|
|
58
63
|
* - `'vertex-transform'`: For shapes with vertex editing (polygons, lines)
|
|
59
64
|
* - `'circle-transform'`: For circles (resize from edge + translate)
|
|
60
|
-
* - `'translate'`: For points (drag to move)
|
|
65
|
+
* - `'point-translate'`: For points (click to place + drag to move)
|
|
66
|
+
* - `'translate'`: Generic translation (drag to move)
|
|
61
67
|
*
|
|
62
68
|
* @param mode - The edit mode to get the instance for
|
|
63
69
|
* @returns The cached mode instance
|