-
Notifications
You must be signed in to change notification settings - Fork 19
/
slide-to-confirm.ts
189 lines (161 loc) · 5.2 KB
/
slide-to-confirm.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import { ref } from 'vue'
import { useMissionStore } from '@/stores/mission'
import {
availableCockpitActions,
registerActionCallback,
unregisterActionCallback,
} from './joystick/protocols/cockpit-actions'
/**
* Callback to confirm the action
* @callback ConfirmCallback
* @returns {void}
*/
export type ConfirmCallback = () => void | Promise<void>
/** Refs */
export const showSlideToConfirm = ref(false)
export const sliderText = ref('Slide to Confirm')
export const confirmationSliderText = ref('Action Confirm')
export const deniedText = ref('Action Denied')
export const expiredText = ref('Action Expired')
export const sliderPercentage = ref(0)
export const onAction = ref<(confirmed: boolean) => void>()
/**
* Different categories of events that requires confirmation from the user.
* @enum {string}
*/
export enum EventCategory {
ARM = 'Arm',
DISARM = 'Disarm',
TAKEOFF = 'Takeoff',
ALT_CHANGE = 'Altitude Change',
LAND = 'Land',
GOTO = 'Goto',
}
/**
* The content of the confirmation slider
* @interface ConfirmContent
*/
export interface ConfirmContent {
/**
* The text to display in the slider
* @type {string}
*/
command: string
/**
* The text to display in the confirmation slider, if not provided, will be used
* `$Confirm {command}`
*/
text?: string
/**
* The text to display if confirmed, if not provided, will be used
* `${command} confirmed`
* @type {string}
*/
confirmedText?: string
/**
* The text to display if denied, if not provided, will be used
* `${command} denied`
* @type {string}
*/
deniedText?: string
/**
* The text to display if the confirmation is expired, if not provided, will be used
* `${command} expired`
* @type {string}
*/
expiredText?: string
}
/**
* Specify if by default event categories need to be confirmed by the user.
* @type {boolean}
*/
const defaultCategoriesRequired = true
/**
* The default mapping of event categories to the confirmation requirement.
* @type {Record<string, boolean>}
*/
export const eventCategoriesDefaultMapping: Record<string, boolean> = Object.values(EventCategory).reduce(
(acc: Record<string, boolean>, v) => {
acc[v] = defaultCategoriesRequired
return acc
},
{}
)
/**
* The maximum interval in milliseconds between the last call from a joystick action
* to be considered as a repeat action
* @type {number}
*/
const maxRepeatIntervalMs = 150
/**
* Time interval in ms needed to keep a repeat action pressed to be considered as a hold action
* @type {number}
*/
const holdActionTimeMs = 1000
/**
* Check if slide can be bypassed for the given category
* @param {EventCategory} category The category of the event
* @returns {boolean} True if the slide can be bypassed, false otherwise
*/
export const canByPassCategory = (category: EventCategory): boolean => {
const missionStore = useMissionStore()
return !(missionStore.slideEventsEnabled && eventCategoriesDefaultMapping[category])
}
/**
* Register the hold to confirm action
* @returns {string} The id of the registered action callback
*/
const registerHoldToConfirm = (): string => {
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)
}
lastConfirmCall = Date.now()
}
return registerActionCallback(availableCockpitActions.hold_to_confirm, onHoldConfirmCallback)
}
/**
* 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 | Promise<void>}
*/
export function slideToConfirm(
callback: ConfirmCallback,
content: ConfirmContent,
byPass = false
): void | Promise<void> {
// Early return if the action is already confirmed or doesn't require confirmation
if (byPass) {
return callback()
}
/** If there is already some confirmation step, deny the action */
if (showSlideToConfirm.value) {
return
}
console.log(`slideToConfirm with text: ${content.text}`)
// Register the hold to confirm action for joystick listening
const holdToConfirmCallbackId = registerHoldToConfirm()
// Register the callback to call the action
onAction.value = (confirmed: boolean) => {
// Call the callback
confirmed && callback()
// Unregister the hold to confirm action callback
unregisterActionCallback(holdToConfirmCallbackId)
}
sliderText.value = content.text ?? `Confirm ${content.command}`
confirmationSliderText.value = content.confirmedText ?? `${content.command} confirmed`
deniedText.value = content.deniedText ?? `${content.command} denied`
expiredText.value = content.expiredText ?? `${content.command} expired`
// Show the slide to confirm
showSlideToConfirm.value = true
}