Skip to content

Commit e5611e4

Browse files
committed
Mission-planning: Implement undo for last generated survey
Signed-off-by: Arturo Manzoli <[email protected]>
1 parent 75aa4fd commit e5611e4

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

src/components/mission-planning/ContextMenu.vue

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,24 @@
5151
</template>
5252
</v-tooltip>
5353
</div>
54+
<div v-if="enableUndo" id="button-3" class="orbit-button orbit-button-3">
55+
<v-tooltip text="Edit survey's polygon">
56+
<template #activator="{ props: tooltipProps2 }">
57+
<v-btn
58+
v-bind="tooltipProps2"
59+
variant="elevated"
60+
icon="mdi-pencil"
61+
:style="{ backgroundColor: '#333333EE' }"
62+
rounded="full"
63+
:disabled="undoIsInProgress"
64+
size="x-small"
65+
color="#FFFFFF22"
66+
class="text-[13px] rotate-[220deg]"
67+
@click="handleUndoGenerateWaypoints"
68+
></v-btn>
69+
</template>
70+
</v-tooltip>
71+
</div>
5472
<v-tooltip text="Delete survey">
5573
<template #activator="{ props: tooltipProps3 }">
5674
<div
@@ -118,6 +136,8 @@ const props = defineProps<{
118136
selectedSurveyId: string | null
119137
isCreatingSurvey: boolean
120138
isCreatingSimpleMission: boolean
139+
undoIsInProgress: boolean
140+
enableUndo: boolean
121141
}>()
122142
/* eslint-enable jsdoc/require-jsdoc */
123143
@@ -126,6 +146,7 @@ const emit = defineEmits<{
126146
(event: 'toggleSurvey'): void
127147
(event: 'toggleSimpleMission'): void
128148
(event: 'deleteSelectedSurvey'): void
149+
(event: 'undoGeneratedWaypoints'): void
129150
(event: 'surveyLinesAngle', angle: number): void
130151
(event: 'regenerateSurveyWaypoints', angle: number): void
131152
}>()
@@ -168,6 +189,10 @@ const handleToggleSimpleMission = (): void => {
168189
emit('close')
169190
}
170191
192+
const handleUndoGenerateWaypoints = (): void => {
193+
emit('undoGeneratedWaypoints')
194+
}
195+
171196
const handleDeleteSelectedSurvey = (): void => {
172197
emit('deleteSelectedSurvey')
173198
}

src/views/MissionPlanningView.vue

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,13 @@
303303
:is-creating-simple-mission="isCreatingSimpleMission"
304304
:surveys="surveys"
305305
:selected-survey-id="selectedSurveyId"
306+
:undo-is-in-progress="undoIsInProgress"
307+
:enable-undo="enableUndoForCurrentSurvey"
306308
@close="hideContextMenu"
307309
@delete-selected-survey="deleteSelectedSurvey"
308310
@toggle-survey="toggleSurvey"
309311
@toggle-simple-mission="toggleSimpleMission"
312+
@undo-generated-waypoints="undoGenerateWaypoints"
310313
@regenerate-survey-waypoints="regenerateSurveyWaypoints"
311314
@survey-lines-angle="onSurveyLinesAngleChange"
312315
/>
@@ -402,11 +405,22 @@ const selectedSurveyId = ref<string>('')
402405
const surveyPolygonLayers = ref<{ [key: string]: Polygon }>({})
403406
const lastSelectedSurveyId = ref('')
404407
const surveys = ref<Survey[]>([])
408+
const canUndo = ref<Record<string, boolean>>({})
409+
const undoIsInProgress = ref(false)
410+
const lastSurveyState = ref<Record<string, SurveyPolygon>>({})
405411
const isDragging = ref(false)
406412
let dragStartLatLng: L.LatLng | null = null
407413
let polygonLatLngsAtDragStart: L.LatLng[] = []
408414
let ignoreNextClick = false
409415
416+
const enableUndoForCurrentSurvey = computed(() => {
417+
return (
418+
surveys.value.length > 0 &&
419+
selectedSurveyId.value === surveys.value[surveys.value.length - 1].id &&
420+
canUndo.value[selectedSurveyId.value]
421+
)
422+
})
423+
410424
const selectedSurvey = computed(() => {
411425
return surveys.value.find((survey) => survey.id === selectedSurveyId.value)
412426
})
@@ -547,6 +561,8 @@ const toggleSurvey = (): void => {
547561
}
548562
if (isCreatingSurvey.value) {
549563
isCreatingSurvey.value = false
564+
lastSurveyState.value = {}
565+
canUndo.value = {}
550566
return
551567
}
552568
isCreatingSurvey.value = true
@@ -650,11 +666,18 @@ const handleKeyDown = (event: KeyboardEvent): void => {
650666
if (event.key === 'Delete' && selectedSurveyId.value) {
651667
deleteSelectedSurvey()
652668
}
669+
if (event.ctrlKey && event.key.toLowerCase() === 'z' && enableUndoForCurrentSurvey.value && !undoIsInProgress.value) {
670+
console.log('Undo In Progress')
671+
undoGenerateWaypoints()
672+
event.preventDefault()
673+
}
653674
}
654675
655676
const clearSurveyCreation = (): void => {
656677
clearSurveyPath()
657678
isCreatingSurvey.value = false
679+
lastSurveyState.value = {}
680+
canUndo.value = {}
658681
}
659682
660683
const deleteSelectedSurvey = (): void => {
@@ -699,6 +722,12 @@ const deleteSelectedSurvey = (): void => {
699722
if (selectedSurveyId.value === surveyId) {
700723
selectedSurveyId.value = surveys.value.length > 0 ? surveys.value[0].id : ''
701724
}
725+
if (lastSurveyState.value[surveyId]) {
726+
delete lastSurveyState.value[surveyId]
727+
}
728+
if (canUndo.value[surveyId]) {
729+
delete canUndo.value[surveyId]
730+
}
702731
703732
showSnackbar({ variant: 'success', message: 'Survey deleted.', duration: 2000 })
704733
hideContextMenu()
@@ -1089,12 +1118,17 @@ const generateWaypointsFromSurvey = (): void => {
10891118
}
10901119
10911120
const newSurveyId = uuid()
1121+
canUndo.value[newSurveyId] = true
10921122
10931123
const polygonCoordinates: WaypointCoordinates[] = surveyPolygonVertexesPositions.value.map((latLng) => [
10941124
latLng.lat,
10951125
latLng.lng,
10961126
])
10971127
1128+
lastSurveyState.value[newSurveyId] = {
1129+
polygonPositions: polygonCoordinates,
1130+
}
1131+
10981132
const adjustedAngle = 90 - surveyLinesAngle.value
10991133
const continuousPath = generateSurveyPath(
11001134
surveyPolygonVertexesPositions.value,
@@ -1269,6 +1303,108 @@ const createSurveyVertexMarker = (
12691303
})
12701304
}
12711305
1306+
const undoGenerateWaypoints = (): void => {
1307+
if (undoIsInProgress.value) return
1308+
contextMenuVisible.value = false
1309+
undoIsInProgress.value = true
1310+
const surveyId = selectedSurveyId.value
1311+
1312+
if (!surveyId || !canUndo.value[surveyId] || !lastSurveyState.value[surveyId]) {
1313+
showSnackbar({ variant: 'error', message: 'Nothing to undo.', duration: 2000 })
1314+
undoIsInProgress.value = false
1315+
return
1316+
}
1317+
1318+
if (selectedSurvey.value) {
1319+
selectedSurvey.value.waypoints.forEach((waypoint) => {
1320+
const index = missionStore.currentPlanningWaypoints.findIndex((wp) => wp.id === waypoint.id)
1321+
if (index !== -1) {
1322+
missionStore.currentPlanningWaypoints.splice(index, 1)
1323+
}
1324+
const marker = waypointMarkers.value[waypoint.id]
1325+
if (marker) {
1326+
planningMap.value?.removeLayer(marker)
1327+
delete waypointMarkers.value[waypoint.id]
1328+
}
1329+
})
1330+
}
1331+
1332+
planningMap.value?.eachLayer((layer) => {
1333+
if (layer instanceof L.Polyline && layer.options.className === 'waypoint-connection') {
1334+
planningMap.value?.removeLayer(layer)
1335+
}
1336+
})
1337+
1338+
const index = surveys.value.findIndex((survey) => survey.id === surveyId)
1339+
if (index !== -1) {
1340+
surveys.value.splice(index, 1)
1341+
}
1342+
selectedSurveyId.value = ''
1343+
1344+
const surveyState = lastSurveyState.value[surveyId]
1345+
surveyPolygonVertexesPositions.value = surveyState.polygonPositions.map(([lat, lng]) => L.latLng(lat, lng))
1346+
1347+
surveyPolygonVertexesMarkers.value.forEach((marker) => marker.remove())
1348+
surveyPolygonVertexesMarkers.value = []
1349+
1350+
surveyEdgeAddMarkers.forEach((marker) => marker.remove())
1351+
surveyEdgeAddMarkers.length = 0
1352+
1353+
if (surveyPolygonLayer.value) {
1354+
planningMap.value?.removeLayer(surveyPolygonLayer.value as unknown as L.Layer)
1355+
surveyPolygonLayer.value = null
1356+
}
1357+
if (surveyPathLayer.value) {
1358+
planningMap.value?.removeLayer(surveyPathLayer.value as unknown as L.Layer)
1359+
surveyPathLayer.value = null
1360+
}
1361+
1362+
surveyPolygonVertexesPositions.value.forEach((latLng) => {
1363+
const newMarker = createSurveyVertexMarker(
1364+
latLng,
1365+
// onClick callback
1366+
(marker) => {
1367+
const targetIndex = surveyPolygonVertexesMarkers.value.indexOf(marker)
1368+
if (targetIndex !== -1) {
1369+
surveyPolygonVertexesPositions.value.splice(targetIndex, 1)
1370+
surveyPolygonVertexesMarkers.value.splice(targetIndex, 1)
1371+
marker.remove()
1372+
updatePolygon()
1373+
updateSurveyEdgeAddMarkers()
1374+
createSurveyPath()
1375+
}
1376+
},
1377+
// onDrag callback
1378+
() => {
1379+
updatePolygon()
1380+
createSurveyPath()
1381+
}
1382+
).addTo(planningMap.value!)
1383+
1384+
surveyPolygonVertexesMarkers.value.push(newMarker)
1385+
})
1386+
1387+
updateSurveyEdgeAddMarkers()
1388+
1389+
surveyPolygonLayer.value = L.polygon(surveyPolygonVertexesPositions.value, {
1390+
color: '#3B82F6',
1391+
fillColor: '#60A5FA',
1392+
fillOpacity: 0.2,
1393+
weight: 3,
1394+
className: 'survey-polygon',
1395+
}).addTo(planningMap.value!)
1396+
1397+
enablePolygonDragging()
1398+
1399+
delete lastSurveyState.value[surveyId]
1400+
delete canUndo.value[surveyId]
1401+
isCreatingSurvey.value = true
1402+
1403+
createSurveyPath()
1404+
showSnackbar({ variant: 'success', message: 'Undo successful.', duration: 1000 })
1405+
undoIsInProgress.value = false
1406+
}
1407+
12721408
const addWaypointMarker = (waypoint: Waypoint): void => {
12731409
if (!planningMap.value) return
12741410

0 commit comments

Comments
 (0)