Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions demo/js/planning.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ interactiveMap.on('app:ready', function (e) {
tablet: { slot: 'inset', width: '260px', initiallyOpen: false, exclusive: true },
desktop: { slot: 'inset', width: '280px', initiallyOpen: false, exclusive: true }
})
interactiveMap.addPanel('banner', {
label: 'Banner',
showLabel: false,
html: '<p>Test banner</p>',
mobile: { slot: 'banner' },
tablet: { slot: 'banner' },
desktop: { slot: 'banner' }
})
})

interactiveMap.on('map:exit', function (e) {
Expand Down
5 changes: 5 additions & 0 deletions plugins/beta/draw-ml/src/api/newLine.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getSnapInstance } from '../utils/snapHelpers.js'
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
import { DEFAULTS } from '../defaults.js'

/**
* Programmatically create a new line
Expand Down Expand Up @@ -43,6 +44,10 @@ export const newLine = ({ appState, appConfig, pluginConfig, pluginState, mapPro
// Update state so UI can react to snap layer availability
dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })

// Resolve editAfterCreate from per-call options or pluginConfig (default: true)
const editAfterCreate = options.editAfterCreate ?? pluginConfig.editAfterCreate ?? DEFAULTS.editAfterCreate
dispatch({ type: 'SET_EDIT_AFTER_CREATE', payload: editAfterCreate })

// Extract style props and flatten variants into properties
const { stroke, fill, strokeWidth, properties: customProperties, ...modeOptions } = options
const properties = {
Expand Down
5 changes: 5 additions & 0 deletions plugins/beta/draw-ml/src/api/newPolygon.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getSnapInstance } from '../utils/snapHelpers.js'
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
import { DEFAULTS } from '../defaults.js'

/**
* Programmatically create a new polygon
Expand Down Expand Up @@ -43,6 +44,10 @@ export const newPolygon = ({ appState, appConfig, pluginConfig, pluginState, map
// Update state so UI can react to snap layer availability
dispatch({ type: 'SET_HAS_SNAP_LAYERS', payload: snapLayers?.length > 0 })

// Resolve editAfterCreate from per-call options or pluginConfig (default: true)
const editAfterCreate = options.editAfterCreate ?? pluginConfig.editAfterCreate ?? DEFAULTS.editAfterCreate
dispatch({ type: 'SET_EDIT_AFTER_CREATE', payload: editAfterCreate })

// Extract style props and flatten variants into properties
const { stroke, fill, strokeWidth, properties: customProperties, ...modeOptions } = options
const properties = {
Expand Down
3 changes: 2 additions & 1 deletion plugins/beta/draw-ml/src/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export const DEFAULTS = {
midpoint: 'rgba(40,161,151,1)',
edge: 'rgba(29,112,184,1)'
},
snapRadius: 10
snapRadius: 10,
editAfterCreate: false
}
42 changes: 27 additions & 15 deletions plugins/beta/draw-ml/src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,21 +168,33 @@ export function attachEvents ({ appState, appConfig, mapState, pluginState, mapP
// Clear draw mode undo stack - editing starts fresh
mapProvider.undoStack?.clear()

// Switch straight to edit vertex mode
dispatch({ type: 'SET_MODE', payload: 'edit_vertex'})

setTimeout(() => {
draw.changeMode('edit_vertex', {
container: appState.layoutRefs.viewportRef.current,
deleteVertexButtonId: `${appConfig.id}-draw-delete-point`,
undoButtonId: `${appConfig.id}-draw-undo`,
isPanEnabled: appState.interfaceType !== 'keyboard',
interfaceType: appState.interfaceType,
scale: { small: 1, medium: 1.5, large: 2 }[mapState.mapSize],
featureId: newFeature.id,
getSnapEnabled: () => mapProvider.snapEnabled === true
})
}, 0)
if (pluginState.editAfterCreate) {
// Switch straight to edit vertex mode
dispatch({ type: 'SET_MODE', payload: 'edit_vertex'})

setTimeout(() => {
draw.changeMode('edit_vertex', {
container: appState.layoutRefs.viewportRef.current,
deleteVertexButtonId: `${appConfig.id}-draw-delete-point`,
undoButtonId: `${appConfig.id}-draw-undo`,
isPanEnabled: appState.interfaceType !== 'keyboard',
interfaceType: appState.interfaceType,
scale: { small: 1, medium: 1.5, large: 2 }[mapState.mapSize],
featureId: newFeature.id,
getSnapEnabled: () => mapProvider.snapEnabled === true
})
}, 0)
} else {
// Switch to disabled mode - feature stays on map but not editable
dispatch({ type: 'SET_MODE', payload: null })
dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null }})

setTimeout(() => {
draw.changeMode('disabled')
}, 0)

eventBus.emit('draw:done', { newFeature })
}
}
map.on('draw.create', onCreate)

Expand Down
28 changes: 17 additions & 11 deletions plugins/beta/draw-ml/src/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,17 @@ export const manifest = {
label: ({ pluginState }) => pluginState.action ? pluginState.action.charAt(0).toUpperCase() + pluginState.action.slice(1) : 'Done',
hiddenWhen: ({ appState, pluginState }) => !pluginState.mode || appState.interfaceType !== 'mouse' && pluginState.mode !== 'edit_vertex',
enableWhen: ({ pluginState }) => pluginState.action ? pluginState.actionValid : !!pluginState.tempFeature,
...createButtonSlots(true),
excludeWhen: () => true,
...createButtonSlots(false),
variant: 'primary',
},{
id: 'drawFinish',
label: 'Finish shape',
iconId: 'check',
variant: 'primary',
hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line', 'edit_vertex'].includes(pluginState.mode),
enableWhen: ({ pluginState }) => pluginState.numVertecies >= (pluginState.mode === 'draw_polygon' ? 3 : 2),
...createButtonSlots(false)
},{
id: 'drawAddPoint',
label: 'Add point',
Expand All @@ -44,18 +53,10 @@ export const manifest = {
hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line', 'edit_vertex'].includes(pluginState.mode),
enableWhen: ({ pluginState }) => pluginState.undoStackLength > 0,
...createButtonSlots(false)
},{
id: 'drawFinish',
label: 'Finish shape',
iconId: 'check',
variant: 'tertiary',
hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line'].includes(pluginState.mode),
enableWhen: ({ pluginState }) => pluginState.numVertecies >= (pluginState.mode === 'draw_polygon' ? 3 : 2),
...createButtonSlots(false)
},{
id: 'drawDeletePoint',
label: 'Delete point',
iconId: 'close',
iconId: 'delete-vertex',
variant: 'tertiary',
enableWhen: ({ pluginState }) => pluginState.selectedVertexIndex >= 0 && pluginState.numVertecies > (pluginState.tempFeature?.geometry?.type === 'Polygon' ? 3 : 2),
hiddenWhen: ({ pluginState }) => !(['simple_select', 'edit_vertex'].includes(pluginState.mode)),
Expand All @@ -71,9 +72,11 @@ export const manifest = {
},{
id: 'drawCancel',
label: 'Cancel',
iconId: 'close',
variant: 'tertiary',
hiddenWhen: ({ pluginState }) => !pluginState.mode,
...createButtonSlots(true)
// excludeWhen: () => true,
...createButtonSlots(false)
}],

keyboardShortcuts: [{
Expand All @@ -92,6 +95,9 @@ export const manifest = {
},{
id: 'magnet',
svgContent: '<path d="m12 15 4 4"/><path d="M2.352 10.648a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.029-6.029a1 1 0 1 1 3 3l-6.029 6.029a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.365-6.367A1 1 0 0 0 8.716 4.282z"/><path d="m5 8 4 4"/>'
},{
id: 'delete-vertex',
svgContent: '<path d="M18 6L6 18"/><path d="M6,6l12,12"/><circle cx="12" cy="12" r="5"/>'
}],

api: {
Expand Down
13 changes: 11 additions & 2 deletions plugins/beta/draw-ml/src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const initialState = {
numVertecies: null,
snap: false,
hasSnapLayers: false,
undoStackLength: 0
undoStackLength: 0,
editAfterCreate: true
}

const setMode = (state, payload) => {
Expand Down Expand Up @@ -70,6 +71,13 @@ const setUndoStackLength = (state, payload) => {
}
}

const setEditAfterCreate = (state, payload) => {
return {
...state,
editAfterCreate: !!payload
}
}

const actions = {
SET_MODE: setMode,
SET_ACTION: setAction,
Expand All @@ -78,7 +86,8 @@ const actions = {
TOGGLE_SNAP: toggleSnap,
SET_SNAP: setSnap,
SET_HAS_SNAP_LAYERS: setHasSnapLayers,
SET_UNDO_STACK_LENGTH: setUndoStackLength
SET_UNDO_STACK_LENGTH: setUndoStackLength,
SET_EDIT_AFTER_CREATE: setEditAfterCreate
}

export {
Expand Down
35 changes: 35 additions & 0 deletions plugins/interact/src/events.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,39 @@ describe('attachEvents', () => {
cleanup()
Object.values(params.buttonConfig).forEach(btn => expect(btn.onClick).toBeNull())
})

it('selectDone handles emission when no marker/coords exist', () => {
const params = createParams()
cleanup = attachEvents(params)

// Ensure marker returns null (no coords)
params.mapState.markers.getMarker.mockReturnValue(null)

// Set up features and bounds
params.pluginState.selectedFeatures = [{ id: 'f1' }]
params.pluginState.selectionBounds = { sw: [0, 0], ne: [1, 1] }

params.buttonConfig.selectDone.onClick()

expect(params.eventBus.emit).toHaveBeenCalledWith('interact:done', {
selectedFeatures: [{ id: 'f1' }],
selectionBounds: { sw: [0, 0], ne: [1, 1] }
})
})

it('respects default closeOnAction when value is undefined (fallback to true)', () => {
const params = createParams()
// Explicitly set to undefined to trigger the ?? fallback
params.pluginState.closeOnAction = undefined
cleanup = attachEvents(params)

// Test for selectDone
params.buttonConfig.selectDone.onClick()
expect(params.closeApp).toHaveBeenCalledTimes(1)

// Test for selectCancel
params.closeApp.mockClear()
params.buttonConfig.selectCancel.onClick()
expect(params.closeApp).toHaveBeenCalledTimes(1)
})
})
50 changes: 50 additions & 0 deletions plugins/interact/src/hooks/useInteractionHandlers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,53 @@ it('emits selectionchange once when bounds exist', () => {
})
)
})

it('skips emission when selection remains empty after being cleared', () => {
const eventBus = { emit: jest.fn() }

// 1. First render with a feature (prev is null, emission happens)
const { rerender } = renderHook(
({ features }) => useInteractionHandlers({
mapState: { markers: {} },
pluginState: { selectedFeatures: features, selectionBounds: { b: 1 } },
services: { eventBus },
mapProvider: {}
}),
{ initialProps: { features: [{ id: 'f1' }] } }
)

expect(eventBus.emit).toHaveBeenCalledTimes(1)
eventBus.emit.mockClear()

// 2. Rerender with empty selection (prev is now [{id: 'f1'}], emission happens)
rerender({ features: [] })
expect(eventBus.emit).toHaveBeenCalledTimes(1)
eventBus.emit.mockClear()

// 3. Rerender with empty selection AGAIN
// This triggers: prev !== null AND prev.length === 0
rerender({ features: [] })

// Should skip emission because wasEmpty is true (via prev.length === 0)
// and current features.length is 0
expect(eventBus.emit).not.toHaveBeenCalled()
})

/* ------------------------------------------------------------------ */
/* Debug mode */
/* ------------------------------------------------------------------ */

it('logs features when debug mode is enabled', () => {
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {})

const { result } = setup({ debug: true })

click(result)

expect(logSpy).toHaveBeenCalledWith(
expect.stringContaining('--- Features at'),
expect.any(Array)
)

logSpy.mockRestore()
})
Loading