Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Implementation of Slide to Confirm #689

Merged
Merged
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
Binary file modified bun.lockb
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"date-fns": "^2.29.3",
"file-saver": "^2.0.5",
"fix-webm-duration": "^1.0.5",
"floating-vue": "^2.0.0-beta.20",
"floating-vue": "^5.0.3",
"gamepad.js": "^1.0.4",
ericjohnson97 marked this conversation as resolved.
Show resolved Hide resolved
"gsap": "^3.11.3",
"haversine-distance": "^1.2.1",
Expand All @@ -47,6 +47,7 @@
"vue": "^3.3.4",
"vue-draggable-plus": "^0.2.0-beta.2",
"vue-router": "^4.0.14",
"vue-slide-unlock": "^0.4.8",
"vue-virtual-scroller": "^2.0.0-beta.8",
"vuetify": "3.4.7",
"webfontloader": "^1.0.0",
Expand Down
59 changes: 38 additions & 21 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,27 +102,27 @@
</div>
</div>
<Transition name="fade">
<div
v-if="showBottomBarNow"
class="z-[60] w-full h-12 bg-slate-600/50 absolute flex bottom-0 backdrop-blur-[2px] justify-between"
>
<MiniWidgetContainer
:container="widgetStore.currentView.miniWidgetContainers[0]"
:allow-editing="widgetStore.editingMode"
align="start"
/>
<div />
<MiniWidgetContainer
:container="widgetStore.currentView.miniWidgetContainers[1]"
:allow-editing="widgetStore.editingMode"
align="center"
/>
<div />
<MiniWidgetContainer
:container="widgetStore.currentView.miniWidgetContainers[2]"
:allow-editing="widgetStore.editingMode"
align="end"
/>
<div v-if="showBottomBarNow" class="bottom-container">
<SlideToConfirm />
<div class="bottom-bar h-12">
<MiniWidgetContainer
:container="widgetStore.currentView.miniWidgetContainers[0]"
:allow-editing="widgetStore.editingMode"
align="start"
/>
<div />
<MiniWidgetContainer
:container="widgetStore.currentView.miniWidgetContainers[1]"
:allow-editing="widgetStore.editingMode"
align="center"
/>
<div />
<MiniWidgetContainer
:container="widgetStore.currentView.miniWidgetContainers[2]"
:allow-editing="widgetStore.editingMode"
align="end"
/>
</div>
</div>
</Transition>
<router-view />
Expand Down Expand Up @@ -158,6 +158,7 @@ import { useMissionStore } from '@/stores/mission'
import Dialog from './components/Dialog.vue'
import EditMenu from './components/EditMenu.vue'
import MiniWidgetContainer from './components/MiniWidgetContainer.vue'
import SlideToConfirm from './components/SlideToConfirm.vue'
import Alerter from './components/widgets/Alerter.vue'
import { datalogger } from './libs/sensors-logging'
import { useWidgetManagerStore } from './stores/widgetManager'
Expand Down Expand Up @@ -266,4 +267,20 @@ body.hide-cursor {
opacity: 0;
transform: translate(0, 100px);
}
.bottom-container {
position: absolute;
bottom: 0;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
z-index: 60; /* Adjust z-index as needed */
}

.bottom-bar {
width: 100%;
background: rgba(108, 117, 125, 0.5);
display: flex;
justify-content: space-between;
}
</style>
41 changes: 41 additions & 0 deletions src/components/SlideToConfirm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<template>
<div v-if="showSlideToConfirm">
<div class="flex items-center space-x-4 mb-3">
<slide-unlock
ref="vueslideunlock"
:auto-width="false"
:circle="true"
:disabled="false"
:noanimate="false"
:width="400"
:height="50"
text="slide to confirm"
success-text="action confirmed"
name="slideunlock"
class="slide-unlock"
@completed="onSlideConfirmed()"
/>
<button class="w-12 h-12 flex items-center justify-center rounded-full bg-white text-gray" @click="cancelAction">
X
</button>
</div>
</div>
</template>

<script setup lang="ts">
import SlideUnlock from 'vue-slide-unlock'

import { confirmed, showSlideToConfirm } from '@/libs/slide-to-confirm'

const onSlideConfirmed = (): void => {
showSlideToConfirm.value = false
confirmed.value = true
console.log('Slide confirmed!')
}

const cancelAction = (): void => {
showSlideToConfirm.value = false
confirmed.value = false
console.log('Slide canceled!')
}
</script>
35 changes: 35 additions & 0 deletions src/libs/slide-to-confirm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ref, watch } from 'vue' // Adjust this import based on your Vue version

// Reactive variables (if they are not provided from outside)
export const showSlideToConfirm = ref(false)
export const confirmed = ref(false)

/**
* Calls the provided action function if the user confirms through the slide-to-confirm component.
* @param {() => void} actionFunc - A function representing the action to be confirmed.
* @returns {Promise<void>} A Promise that resolves if the action is successfully executed or rejects in case of cancellation or errors.
*/
export function slideToConfirm(actionFunc: () => void): Promise<void> {
console.log('slideToConfirm')
return new Promise((resolve, reject) => {
// Show slide to confirm component
showSlideToConfirm.value = true

// Watch for changes on confirmed and showSlideToConfirm variables
const stopWatching = watch([confirmed, showSlideToConfirm], ([newConfirmed, newShowSlideToConfirm]) => {
if (newConfirmed) {
stopWatching()
confirmed.value = false
try {
actionFunc()
resolve()
} catch (error) {
reject(error)
}
} else if (!newShowSlideToConfirm) {
stopWatching()
reject(new Error('User cancelled the action'))
}
})
})
}
54 changes: 42 additions & 12 deletions src/stores/mainVehicle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
registerActionCallback,
} from '@/libs/joystick/protocols/cockpit-actions'
import { MavlinkManualControlManager } from '@/libs/joystick/protocols/mavlink-manual-control'
import { slideToConfirm } from '@/libs/slide-to-confirm'
import type { ArduPilot } from '@/libs/vehicle/ardupilot/ardupilot'
import type { ArduPilotParameterSetData } from '@/libs/vehicle/ardupilot/types'
import * as Protocol from '@/libs/vehicle/protocol/protocol'
Expand Down Expand Up @@ -125,29 +126,52 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {

/**
* Arm the vehicle
* @returns { void } A Promise that resolves when arming is successful or rejects if an error occurs or the action is cancelled.
*/
function arm(): void {
mainVehicle.value?.arm()
function arm(): Promise<void> {
return slideToConfirm(() => {
if (!mainVehicle.value) {
throw new Error('action rejected or failed')
}
mainVehicle.value.arm()
})
Comment on lines -129 to +137
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this logic is fine and we can proceed with it, I have a suggestion of a slight different one that seems to be less intrusive in the code:

async function arm(): Promise<void> {
  if (!mainVehicle.value) throw new Error('No vehicle available to be armed.')
  
  const slideConfirmed = await slideToConfirm('Slide to arm', 10000)
  if (slideConfirmed) {
    mainVehicle.value.arm()
    return
  }
  console.error('No confirmation for arming action received') 
}

This way we don't have to pass the whole success code (in this case it's only mainVehicle.value.arm(), but on more complex cases could be several lines of code) to the confirmation slider, but just to read its result. This way the code get's cleaner, more readable, with less indentations, and its more in line with what we currently use for SweetAlert2 confirmation dialogs, for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this also makes sense. I will change. Thanks for all the great feedback!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to do this. let me know if you would like me to make that change. If I do, do you think I should reset that commit make the change and recommit, or would it be better to make a separate commit to make that change?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be cool, but at the same time it would be an improvement, so you can add it to a following commit, so you don't have to recommit everything, or we can settle with how it is right now and allow this part to be improved on a future PR, without hurries. I'm fine with both, so let me know what you prefer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After giving it another look, I think it would take me a little bit of time to refactor.

If you are ok with it I think I would like to leave it how it is for now. 🙂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem!
I will test it tomorrow morning so we can merge.

}

/**
* Disarm the vehicle
* @returns { void } A Promise that resolves when disarming is successful or rejects if an error occurs or the action is cancelled.
*/
function disarm(): void {
mainVehicle.value?.disarm()
function disarm(): Promise<void> {
return slideToConfirm(() => {
if (!mainVehicle.value) {
throw new Error('action rejected or failed')
}
mainVehicle.value.disarm()
})
}
/**
* Takeoff the vehicle
* Initiates the takeoff process, requiring user confirmation.
* @returns { void } A Promise that resolves when the takeoff is successful or rejects if an error occurs or the action is cancelled.
*/
function takeoff(): void {
mainVehicle.value?.takeoff()
function takeoff(): Promise<void> {
return slideToConfirm(() => {
if (!mainVehicle.value) {
throw new Error('action rejected or failed')
}
mainVehicle.value.takeoff()
})
}

/**
* Land the vehicle
* @returns { void } A Promise that resolves when landing is successful or rejects if an error occurs or the action is cancelled.
*/
function land(): void {
mainVehicle.value?.land()
function land(): Promise<void> {
return slideToConfirm(() => {
if (!mainVehicle.value) {
throw new Error('action rejected or failed')
}
mainVehicle.value.land()
})
}

/**
Expand All @@ -159,6 +183,7 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
* @param { number } latitude Latitude in degrees
* @param { number } longitude Longitude in degrees
* @param { number } alt Altitude in meters
* @returns { void } A Promise that resolves when the vehicle reaches the waypoint or rejects if an error occurs or the action is cancelled.
*/
function goTo(
hold: number,
Expand All @@ -168,13 +193,18 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => {
latitude: number,
longitude: number,
alt: number
): void {
): Promise<void> {
const waypoint = new Coordinates()
waypoint.latitude = latitude
waypoint.altitude = alt
waypoint.longitude = longitude

mainVehicle.value?.goTo(hold, acceptanceRadius, passRadius, yaw, waypoint)
return slideToConfirm(() => {
if (!mainVehicle.value) {
throw new Error('action rejected or failed')
}
mainVehicle.value.goTo(hold, acceptanceRadius, passRadius, yaw, waypoint)
})
}

/**
Expand Down
Loading