feat(flightlab): add seatbelt phases to stepper and escape shortcuts

This commit is contained in:
itsrubberduck
2026-02-18 22:52:23 +01:00
parent 390c38d8bd
commit f384e30b9b
3 changed files with 35 additions and 6 deletions

View File

@@ -383,12 +383,33 @@ const pageRoot = ref<HTMLElement | null>(null)
const isFullscreen = ref(false)
const canFullscreen = ref(false)
const fullscreenEvents = ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange']
const ESC_DOUBLE_PRESS_WINDOW_MS = 450
let lastEscapeAt = 0
function onFullscreenChange() {
if (typeof document === 'undefined') return
isFullscreen.value = Boolean(document.fullscreenElement)
}
function onGlobalKeydown(event: KeyboardEvent) {
if (event.key !== 'Escape') return
const now = Date.now()
const isQuickDoubleEscape = now - lastEscapeAt <= ESC_DOUBLE_PRESS_WINDOW_MS
lastEscapeAt = now
if (initialSpeechTimeout) {
clearTimeout(initialSpeechTimeout)
initialSpeechTimeout = null
}
audio.skipSpeech()
if (isQuickDoubleEscape && engine.hasSimConditions.value) {
engine.toggleAutoAdvance()
lastEscapeAt = 0
}
}
async function toggleFullscreen() {
if (typeof document === 'undefined') return
try {
@@ -443,7 +464,7 @@ watch(() => engine.currentPhase.value, (phase) => {
const currentPhase = computed(() => engine.currentPhase.value)
// Main phase IDs for sidebar stepper
const mainPhaseIds = ['welcome', 'briefing', 'runway', 'engines_pre', 'engines_spool', 'takeoff_roll', 'rotation', 'gear_retract', 'climb', 'climb_high', 'leveloff', 'debrief', 'end']
const mainPhaseIds = ['welcome', 'seatbelt_on', 'briefing', 'runway', 'engines_pre', 'engines_spool', 'takeoff_roll', 'rotation', 'gear_retract', 'climb', 'climb_high', 'leveloff', 'seatbelt_off', 'debrief', 'end']
// Collect all unique sound IDs for preloading
const allSoundIds = computed(() => {
@@ -461,6 +482,7 @@ const allSoundIds = computed(() => {
function getPhaseLabel(id: string): string {
const labels: Record<string, string> = {
welcome: 'Willkommen',
seatbelt_on: 'Seatbelts EIN',
briefing: 'Briefing',
runway: 'Startbahn',
engines_pre: 'Triebwerke (Vorb.)',
@@ -471,6 +493,7 @@ function getPhaseLabel(id: string): string {
climb: 'Steigflug',
climb_high: 'Steigflug (hoch)',
leveloff: 'Level-off',
seatbelt_off: 'Seatbelts AUS',
debrief: 'Nachbesprechung',
end: 'Ende',
}
@@ -676,6 +699,9 @@ onMounted(async () => {
})
onFullscreenChange()
}
if (typeof window !== 'undefined') {
window.addEventListener('keydown', onGlobalKeydown)
}
// Speak initial welcome
const phase = engine.currentPhase.value
@@ -688,6 +714,9 @@ onMounted(async () => {
})
onBeforeUnmount(() => {
if (typeof window !== 'undefined') {
window.removeEventListener('keydown', onGlobalKeydown)
}
if (typeof document !== 'undefined') {
fullscreenEvents.forEach((eventName) => {
document.removeEventListener(eventName, onFullscreenChange)

View File

@@ -33,7 +33,7 @@ export function useFlightLabEngine(scenario: FlightLabScenario) {
const currentPhase = computed(() => phasesMap.value.get(currentPhaseId.value) ?? null)
// Count only main phases (not comfort/info branches) for progress
const mainPhaseIds = ['welcome', 'briefing', 'runway', 'engines_pre', 'engines_spool', 'takeoff_roll', 'rotation', 'gear_retract', 'climb', 'climb_high', 'leveloff', 'debrief', 'end']
const mainPhaseIds = ['welcome', 'seatbelt_on', 'briefing', 'runway', 'engines_pre', 'engines_spool', 'takeoff_roll', 'rotation', 'gear_retract', 'climb', 'climb_high', 'leveloff', 'seatbelt_off', 'debrief', 'end']
const progress = computed(() => {
const idx = mainPhaseIds.indexOf(currentPhaseId.value)

View File

@@ -126,9 +126,9 @@ export const takeoffEddf: FlightLabScenario = {
buttons: [
{
id: 'seatbelt_on_done',
label: 'Seat Belt Signs sind EIN',
label: 'Seat Belt Signs sind EIN (warte auf Bestätigung)',
icon: 'mdi-seatbelt',
next: 'briefing',
next: 'seatbelt_on',
type: 'primary'
},
],
@@ -748,9 +748,9 @@ export const takeoffEddf: FlightLabScenario = {
buttons: [
{
id: 'seatbelt_off_done',
label: 'Seat Belt Signs sind AUS',
label: 'Seat Belt Signs sind AUS (warte auf Bestätigung)',
icon: 'mdi-seatbelt',
next: 'debrief',
next: 'seatbelt_off',
type: 'primary'
},
],