Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
JoaoMario109 committed Apr 3, 2024
1 parent 2dcd642 commit d527590
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 119 deletions.
19 changes: 18 additions & 1 deletion src/components/mini-widgets/ArmerButton.vue
@@ -1,7 +1,7 @@
<template>
<button
class="relative flex items-center justify-center w-32 p-1 rounded-md shadow-inner h-9 bg-slate-800/60"
@click="vehicleStore.isArmed ? vehicleStore.disarm() : vehicleStore.arm()"
@click="vehicleStore.isArmed ? arm() : disarm()"
>
<div
class="absolute top-auto flex items-center px-1 rounded-[4px] shadow transition-all w-[70%] h-[80%]"
Expand All @@ -21,7 +21,24 @@
</template>

<script setup lang="ts">
import { EventCategory, slideToConfirm } from '@/libs/slide-to-confirm';
import { useMainVehicleStore } from '@/stores/mainVehicle'
const vehicleStore = useMainVehicleStore()
const arm = (): void => {
slideToConfirm(vehicleStore.arm, {
text: 'Confirm Arm',
confirmationText: 'Arm command confirmed',
category: EventCategory.ARM,
})
}
const disarm = (): void => {
slideToConfirm(vehicleStore.disarm, {
text: 'Confirm Disarm',
confirmationText: 'Disarm command confirmed',
category: EventCategory.DISARM,
})
}
</script>
20 changes: 19 additions & 1 deletion src/components/mini-widgets/ChangeAltitudeCommander.vue
Expand Up @@ -5,14 +5,32 @@
vehicleStore.flying ? 'bg-slate-800/60 hover:bg-slate-400/60' : 'bg-slate-400/60 cursor-not-allowed',
]"
:disabled="!vehicleStore.flying"
@click="vehicleStore.changeAlt()"
@click="changeAlt()"
>
<span class="inline-block font-extrabold align-middle text-white"> Change Alt </span>
</button>
</template>

<script setup lang="ts">
import { showAltitudeSlider } from '@/libs/altitude-slider'
import { EventCategory, slideToConfirm } from '@/libs/slide-to-confirm'
import { useMainVehicleStore } from '@/stores/mainVehicle'
const vehicleStore = useMainVehicleStore()
const changeAlt = (): void => {
showAltitudeSlider.value = true
slideToConfirm(
() => {
showAltitudeSlider.value = false
vehicleStore.changeAlt()
},
{
text: 'Confirm Altitude Change',
confirmationText: 'Alt Change Cmd Confirmed',
category: EventCategory.ALT_CHANGE,
}
)
}
</script>
28 changes: 27 additions & 1 deletion src/components/mini-widgets/TakeoffLandCommander.vue
@@ -1,7 +1,7 @@
<template>
<button
class="relative flex items-center justify-center w-32 p-1 rounded-md shadow-inner h-9 bg-slate-800/60 hover:bg-slate-400/60"
@click="vehicleStore.flying ? vehicleStore.land() : vehicleStore.takeoff()"
@click="vehicleStore.flying ? land() : takeoff()"
>
<span class="inline-block font-extrabold align-middle text-white">
{{ vehicleStore.flying === undefined ? '...' : vehicleStore.flying ? 'Land' : 'Takeoff' }}
Expand All @@ -10,7 +10,33 @@
</template>

<script setup lang="ts">
import { showAltitudeSlider } from '@/libs/altitude-slider'
import { EventCategory, slideToConfirm } from '@/libs/slide-to-confirm'
import { useMainVehicleStore } from '@/stores/mainVehicle'
const vehicleStore = useMainVehicleStore()
const takeoff = (): void => {
showAltitudeSlider.value = true
slideToConfirm(
() => {
showAltitudeSlider.value = false
vehicleStore.takeoff()
},
{
text: 'Confirm Takeoff',
confirmationText: 'Takeoff command confirmed',
category: EventCategory.TAKEOFF,
}
)
}
const land = (): void => {
slideToConfirm(vehicleStore.land, {
text: 'Confirm Land',
confirmationText: 'Land command confirmed',
category: EventCategory.LAND,
})
}
</script>
12 changes: 11 additions & 1 deletion src/components/widgets/Map.vue
Expand Up @@ -162,6 +162,7 @@ import {
} from 'vue'
import { datalogger, DatalogVariable } from '@/libs/sensors-logging'
import { EventCategory, slideToConfirm } from '@/libs/slide-to-confirm'
import { degrees } from '@/libs/utils'
import { TargetFollower, WhoToFollow } from '@/libs/utils-map'
import { useMainVehicleStore } from '@/stores/mainVehicle'
Expand Down Expand Up @@ -383,7 +384,16 @@ const onMenuOptionSelect = (option: string): void => {
const latitude = clickedLocation.value[0]
const longitude = clickedLocation.value[1]
vehicleStore.goTo(hold, acceptanceRadius, passRadius, yaw, latitude, longitude, altitude)
slideToConfirm(
() => {
vehicleStore.goTo(hold, acceptanceRadius, passRadius, yaw, latitude, longitude, altitude)
},
{
text: 'Confirm GoTo',
confirmationText: 'GoTo command confirmed',
category: EventCategory.GOTO,
}
)
}
break
Expand Down
19 changes: 17 additions & 2 deletions src/libs/joystick/protocols/cockpit-actions.ts
Expand Up @@ -3,6 +3,7 @@
/* eslint-disable max-len */
import { v4 as uuid4 } from 'uuid'

import { slideToConfirm } from '@/libs/slide-to-confirm'
import { type JoystickProtocolActionsMapping,type JoystickState, type ProtocolAction, JoystickProtocol } from '@/types/joystick'

/**
Expand Down Expand Up @@ -83,11 +84,18 @@ export class CockpitActionsManager {
joystickState: JoystickState
currentActionsMapping: JoystickProtocolActionsMapping
activeButtonsActions: ProtocolAction[]
actionsJoystickConfirmRequired: Record<string, boolean>

updateControllerData = (state: JoystickState, protocolActionsMapping: JoystickProtocolActionsMapping, activeButtonsActions: ProtocolAction[]): void => {
updateControllerData = (
state: JoystickState,
protocolActionsMapping: JoystickProtocolActionsMapping,
activeButtonsActions: ProtocolAction[],
actionsJoystickConfirmRequired: Record<string, boolean>
): void => {
this.joystickState = state
this.currentActionsMapping = protocolActionsMapping
this.activeButtonsActions = activeButtonsActions
this.actionsJoystickConfirmRequired = actionsJoystickConfirmRequired
}

sendCockpitActions = (): void => {
Expand All @@ -97,7 +105,14 @@ export class CockpitActionsManager {
Object.values(actionsCallbacks).forEach((entry) => {
if (actionsToCallback.map((a) => a.id).includes(entry.action.id)) {
console.log(entry.action.name)
entry.callback()
slideToConfirm(
entry.callback,
{
text: `Confirm ${entry.action.name}`,
confirmationText: 'Confirmed',
},
!this.actionsJoystickConfirmRequired[entry.action.id]
)
}
})
}
Expand Down
1 change: 0 additions & 1 deletion src/libs/joystick/protocols/mavlink-manual-control.ts
Expand Up @@ -611,4 +611,3 @@ export class MavlinkManualControlManager {
})
}
}

135 changes: 87 additions & 48 deletions src/libs/slide-to-confirm.ts
Expand Up @@ -14,6 +14,13 @@ export const sliderPercentage = ref(0)
export const confirmationSliderText = ref('Action Confirm')
export const confirmed = ref(false)

/**
* Callback to confirm the action
* @callback ConfirmCallback
* @returns {void}
*/
export type ConfirmCallback = () => void | Promise<void>

/**
* Different categories of events that requires confirmation from the user.
* @enum {string}
Expand All @@ -27,6 +34,30 @@ export enum EventCategory {
GOTO = 'Goto',
}

/**
* The content of the confirmation slider
* @interface ConfirmContent
*/
export interface ConfirmContent {
/**
* The category of the event that requires confirmation
* @type {EventCategory}
*/
category?: EventCategory

/**
* The text to display in the slider
* @type {string}
*/
text: string

/**
* The text to display in the confirmation slider
* @type {string}
*/
confirmationText: string
}

/**
* Specify if by default event categories need to be confirmed by the user.
* @type {boolean}
Expand Down Expand Up @@ -59,63 +90,71 @@ const maxRepeatIntervalMs = 50
const holdActionTimeMs = 1000

/**
* Waits for user confirmation through the slide-to-confirm component.
* @param {EventCategory} category - The category of the event that requires confirmation.
* @param {string} text - The custom text to display on the slider.
* @param {string} confirmationText - The custom text to display on the confirmation slider after the user slides to confirm.
* @returns {Promise<boolean>} - A promise that resolves with true when the action is confirmed and false when the user cancels the action.
* Wraps a callback and wait for the user confirmation by a popup to call it
* @param {ConfirmCallback} callback The callback to call after the user confirms the action
* @param {ConfirmContent} content The content of the confirmation slider
* @param {boolean} byPass If true, the action is confirmed without waiting for the user to slide
* @returns {void}
*/
export function slideToConfirm(category: EventCategory, text: string, confirmationText: string): Promise<boolean> {
console.log(`slideToConfirm from category ${category} with text: ${text}`)

const missionStore = useMissionStore()
export function slideToConfirm(callback: ConfirmCallback, content: ConfirmContent, byPass = false): void {
console.log(`slideToConfirm from category ${content.category} with text: ${content.text}`)

return new Promise((resolve) => {
if (!missionStore.slideEventsEnabled || !missionStore.slideEventsCategoriesRequired[category]) {
return resolve(true)
}
// Early return if the action is already confirmed or doesn't require confirmation
if (byPass) {
callback()
return
}

let holdToConfirmCallbackId: string | undefined
const missionStore = useMissionStore()

if (missionStore.holdToConfirmEnabled) {
let lastConfirmCall = 0
let totalPressedTime = 0
if (
content.category &&
(!missionStore.slideEventsEnabled || !missionStore.slideEventsCategoriesRequired[content.category])
) {
callback()
return
}

let holdToConfirmCallbackId: string | undefined

if (missionStore.holdToConfirmEnabled) {
let lastConfirmCall = 0
let totalPressedTime = 0

const onHoldConfirmCallback = (): void => {
const dt = Date.now() - lastConfirmCall

// Reset total pressed time if user releases the button
if (dt > maxRepeatIntervalMs) {
totalPressedTime = 0
sliderPercentage.value = 0
} else {
totalPressedTime += dt
sliderPercentage.value = Math.min((totalPressedTime / holdActionTimeMs) * 100, 100)
}

const onHoldConfirmCallback = (): void => {
const dt = Date.now() - lastConfirmCall
lastConfirmCall = Date.now()
}

// Reset total pressed time if user releases the button
if (dt > maxRepeatIntervalMs) {
totalPressedTime = 0
sliderPercentage.value = 0
} else {
totalPressedTime += dt
sliderPercentage.value = Math.min((totalPressedTime / holdActionTimeMs) * 100, 100)
}
holdToConfirmCallbackId = registerActionCallback(availableCockpitActions.hold_to_confirm, onHoldConfirmCallback)
}

lastConfirmCall = Date.now()
}
sliderText.value = content.text
confirmationSliderText.value = content.confirmationText
showSlideToConfirm.value = true

holdToConfirmCallbackId = registerActionCallback(availableCockpitActions.hold_to_confirm, onHoldConfirmCallback)
const stopWatching = watch([confirmed, showSlideToConfirm], ([newConfirmed, newShowSlideToConfirm]) => {
if (holdToConfirmCallbackId) {
unregisterActionCallback(holdToConfirmCallbackId)
}

sliderText.value = text
confirmationSliderText.value = confirmationText
showSlideToConfirm.value = true

const stopWatching = watch([confirmed, showSlideToConfirm], ([newConfirmed, newShowSlideToConfirm]) => {
if (newConfirmed) {
stopWatching()
confirmed.value = false
resolve(true)
} else if (!newShowSlideToConfirm) {
stopWatching()
resolve(false)
}

if (holdToConfirmCallbackId) {
unregisterActionCallback(holdToConfirmCallbackId)
}
})
if (newConfirmed) {
stopWatching()
confirmed.value = false
callback()
return
} else if (!newShowSlideToConfirm) {
stopWatching()
}
})
}
16 changes: 14 additions & 2 deletions src/stores/controller.ts
Expand Up @@ -24,7 +24,8 @@ import {
export type controllerUpdateCallback = (
state: JoystickState,
protocolActionsMapping: JoystickProtocolActionsMapping,
activeButtonActions: ProtocolAction[]
activeButtonActions: ProtocolAction[],
actionsJoystickConfirmRequired: Record<string, boolean>
) => void

const protocolMappingsKey = 'cockpit-protocol-mappings-v1'
Expand All @@ -41,6 +42,11 @@ export const useControllerStore = defineStore('controller', () => {
const availableButtonActions = allAvailableButtons
const enableForwarding = ref(true)
const holdLastInputWhenWindowHidden = useStorage('cockpit-hold-last-joystick-input-when-window-hidden', false)
// Confirmation required currently is only available for cokpit actions
const actionsJoystickConfirmRequired = useStorage(
'cockpit-actions-joystick-confirm-required',
{} as Record<string, boolean>
)

const protocolMapping = computed<JoystickProtocolActionsMapping>({
get() {
Expand Down Expand Up @@ -122,7 +128,12 @@ export const useControllerStore = defineStore('controller', () => {
joystick.gamepadToCockpitMap = cockpitStdMappings.value[joystickModel]

for (const callback of updateCallbacks.value) {
callback(joystick.state, protocolMapping.value, activeButtonActions(joystick.state, protocolMapping.value))
callback(
joystick.state,
protocolMapping.value,
activeButtonActions(joystick.state, protocolMapping.value),
actionsJoystickConfirmRequired.value
)
}
}

Expand Down Expand Up @@ -332,6 +343,7 @@ export const useControllerStore = defineStore('controller', () => {
cockpitStdMappings,
availableAxesActions,
availableButtonActions,
actionsJoystickConfirmRequired,
loadProtocolMapping,
exportJoystickMapping,
importJoystickMapping,
Expand Down

0 comments on commit d527590

Please sign in to comment.