diff --git a/src/App.vue b/src/App.vue
index d74fab760..8ca7ee5af 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -310,10 +310,11 @@
-
-
-
+
+
+
+
@@ -336,6 +337,7 @@ import Tutorial from '@/components/Tutorial.vue'
import UpdateNotification from '@/components/UpdateNotification.vue'
import VehicleDiscoveryDialog from '@/components/VehicleDiscoveryDialog.vue'
import VideoLibraryModal from '@/components/VideoLibraryModal.vue'
+import { useDialogQueue } from '@/composables/dialogQueue'
import { useInteractionDialog } from '@/composables/interactionDialog'
import {
availableCockpitActions,
@@ -366,6 +368,8 @@ import ConfigurationUIView from './views/ConfigurationUIView.vue'
import ConfigurationVideoView from './views/ConfigurationVideoView.vue'
import ToolsDataLakeView from './views/ToolsDataLakeView.vue'
import ToolsMAVLinkView from './views/ToolsMAVLinkView.vue'
+
+const { enqueueDialog, WrappedDialog } = useDialogQueue()
const { showDialog, closeDialog } = useInteractionDialog()
const { openSnackbar } = useSnackbar()
@@ -760,23 +764,27 @@ onBeforeUnmount(() => {
const currentTopBarHeightPixels = computed(() => `${widgetStore.currentTopBarHeightPixels}px`)
const currentBottomBarHeightPixels = computed(() => `${widgetStore.currentBottomBarHeightPixels}px`)
-const showDiscoveryDialog = ref(false)
const preventAutoSearch = useStorage('cockpit-prevent-auto-vehicle-discovery-dialog', false)
onMounted(() => {
- if (isElectron() && !preventAutoSearch.value) {
+ if (!interfaceStore.userHasSeenTutorial) {
+ enqueueDialog({
+ component: Tutorial,
+ })
+ }
+ if (isElectron()) {
+ enqueueDialog({
+ component: UpdateNotification,
+ })
// Wait 5 seconds to check if we're connected to a vehicle
setTimeout(() => {
- if (vehicleStore.isVehicleOnline) return
- showDiscoveryDialog.value = true
+ if (vehicleStore.isVehicleOnline || preventAutoSearch.value) return
+ enqueueDialog({
+ component: VehicleDiscoveryDialog,
+ props: { showAutoSearchOption: true },
+ })
}, 5000)
}
-
- if (!interfaceStore.userHasSeenTutorial) {
- setTimeout(() => {
- interfaceStore.isTutorialVisible = true
- }, 6000)
- }
})
diff --git a/src/components/Tutorial.vue b/src/components/Tutorial.vue
index 354193dbd..ef2fa6e2c 100644
--- a/src/components/Tutorial.vue
+++ b/src/components/Tutorial.vue
@@ -98,7 +98,18 @@ const { openSnackbar } = useSnackbar()
const interfaceStore = useAppInterfaceStore()
const vehicleStore = useMainVehicleStore()
-const showTutorial = ref(true)
+const props = defineProps<{
+ /**
+ * Parent-controlled trigger for showing the dialog.
+ */
+ modelValue: boolean
+}>()
+
+const emit = defineEmits<{
+ (e: 'update:modelValue', value: boolean): void
+}>()
+
+const showTutorial = ref(false || props.modelValue)
const currentTutorialStep = useStorage('cockpit-last-tutorial-step', 1)
const isVehicleConnectedVisible = ref(false)
const tallContent = ref(false)
@@ -208,7 +219,6 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.componentToHighlight = 'menu-trigger'
interfaceStore.currentSubMenuComponentName = null
interfaceStore.currentSubMenuName = null
- interfaceStore.userHasSeenTutorial = false
break
case 3:
interfaceStore.isMainMenuVisible = true
@@ -216,7 +226,6 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.currentSubMenuName = SubMenuName.settings
interfaceStore.componentToHighlight = 'settings-menu-item'
interfaceStore.currentSubMenuComponentName = null
- interfaceStore.userHasSeenTutorial = false
break
case 4:
interfaceStore.isMainMenuVisible = true
@@ -224,7 +233,6 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.currentSubMenuName = SubMenuName.settings
interfaceStore.currentSubMenuComponentName = SubMenuComponentName.SettingsGeneral
tallContent.value = true
- interfaceStore.userHasSeenTutorial = false
interfaceStore.componentToHighlight = 'General'
break
case 5:
@@ -232,7 +240,6 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.mainMenuCurrentStep = 2
interfaceStore.currentSubMenuName = SubMenuName.settings
interfaceStore.currentSubMenuComponentName = SubMenuComponentName.SettingsGeneral
- interfaceStore.userHasSeenTutorial = false
tallContent.value = false
interfaceStore.componentToHighlight = 'vehicle-address'
setVehicleConnectedVisible()
@@ -243,7 +250,6 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.currentSubMenuName = SubMenuName.settings
interfaceStore.currentSubMenuComponentName = SubMenuComponentName.SettingsInterface
tallContent.value = false
- interfaceStore.userHasSeenTutorial = false
interfaceStore.componentToHighlight = 'Interface'
break
case 7:
@@ -251,7 +257,6 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.mainMenuCurrentStep = 2
interfaceStore.currentSubMenuName = SubMenuName.settings
interfaceStore.currentSubMenuComponentName = SubMenuComponentName.SettingsJoystick
- interfaceStore.userHasSeenTutorial = false
tallContent.value = true
interfaceStore.componentToHighlight = 'Joystick'
break
@@ -260,7 +265,6 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.mainMenuCurrentStep = 2
interfaceStore.currentSubMenuName = SubMenuName.settings
interfaceStore.currentSubMenuComponentName = SubMenuComponentName.SettingsVideo
- interfaceStore.userHasSeenTutorial = false
tallContent.value = true
interfaceStore.componentToHighlight = 'Video'
break
@@ -269,7 +273,6 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.mainMenuCurrentStep = 2
interfaceStore.currentSubMenuName = SubMenuName.settings
interfaceStore.currentSubMenuComponentName = SubMenuComponentName.SettingsTelemetry
- interfaceStore.userHasSeenTutorial = false
tallContent.value = false
interfaceStore.isGlassModalAlwaysOnTop = true
interfaceStore.componentToHighlight = 'Telemetry'
@@ -279,7 +282,6 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.mainMenuCurrentStep = 2
interfaceStore.currentSubMenuName = SubMenuName.settings
interfaceStore.currentSubMenuComponentName = SubMenuComponentName.SettingsAlerts
- interfaceStore.userHasSeenTutorial = false
tallContent.value = false
interfaceStore.isGlassModalAlwaysOnTop = false
interfaceStore.componentToHighlight = 'Alerts'
@@ -289,7 +291,6 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.mainMenuCurrentStep = 2
interfaceStore.currentSubMenuName = SubMenuName.settings
interfaceStore.currentSubMenuComponentName = SubMenuComponentName.SettingsDev
- interfaceStore.userHasSeenTutorial = false
tallContent.value = true
interfaceStore.isGlassModalAlwaysOnTop = false
interfaceStore.componentToHighlight = 'Dev'
@@ -299,7 +300,6 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.mainMenuCurrentStep = 2
interfaceStore.currentSubMenuName = SubMenuName.settings
interfaceStore.currentSubMenuComponentName = SubMenuComponentName.SettingsMission
- interfaceStore.userHasSeenTutorial = false
tallContent.value = false
interfaceStore.isGlassModalAlwaysOnTop = false
interfaceStore.componentToHighlight = 'Mission'
@@ -316,6 +316,7 @@ const handleStepChange = (newStep: number): void => {
const dontShowTutorialAgain = (): void => {
interfaceStore.userHasSeenTutorial = true
showTutorial.value = false
+ emit('update:modelValue', false)
currentTutorialStep.value = 1
openSnackbar({
message: 'This guide can be reopened via the Settings > General menu',
@@ -332,7 +333,6 @@ const alwaysShowTutorialOnStartup = (): void => {
const nextTutorialStep = (): void => {
if (currentTutorialStep.value === steps.length) {
- dontShowTutorialAgain()
closeTutorial()
return
}
@@ -348,8 +348,8 @@ const backTutorialStep = (): void => {
const closeTutorial = (): void => {
showTutorial.value = false
interfaceStore.componentToHighlight = 'none'
- interfaceStore.userHasSeenTutorial = true
interfaceStore.isTutorialVisible = false
+ emit('update:modelValue', false)
}
const setVehicleConnectedVisible = (): void => {
diff --git a/src/components/UpdateNotification.vue b/src/components/UpdateNotification.vue
index c79521856..5ba294def 100644
--- a/src/components/UpdateNotification.vue
+++ b/src/components/UpdateNotification.vue
@@ -1,6 +1,6 @@
+
+ Close
+
diff --git a/src/composables/dialogQueue.ts b/src/composables/dialogQueue.ts
new file mode 100644
index 000000000..39afdb6c1
--- /dev/null
+++ b/src/composables/dialogQueue.ts
@@ -0,0 +1,96 @@
+import { Component, computed, defineComponent, h, Ref, ref } from 'vue'
+
+/**
+ * Queued component to be displayed. Usually a dialog.
+ */
+export interface QueuedDialog {
+ /**
+ * The component reference (e.g. Tutorial or VehicleDiscoveryDialog)
+ */
+ component: Component
+ /**
+ * Optional props to pass into the component
+ */
+ props?: Record
+}
+
+/**
+ * Component queue to manage the order of components to be displayed.
+ */
+export interface DialogQueue {
+ /**
+ * Enqueue a dialog to be displayed.
+ */
+ enqueueDialog: (item: QueuedDialog) => void
+ /**
+ * Clear the queue.
+ */
+ clear: () => void
+ /**
+ * The queue itself.
+ */
+ queue: Ref
+ /**
+ * The current dialog to be displayed.
+ */
+ currentDialog: Ref
+ /**
+ * Whether the dialog is visible or not.
+ */
+ isDialogVisible: Ref
+ /**
+ * The wrapped dialog component.
+ */
+ WrappedDialog: Ref
+}
+
+export const useDialogQueue = (): DialogQueue => {
+ const queue: Ref = ref([])
+ const isDialogVisible: Ref = ref(true)
+
+ const enqueueDialog = (item: QueuedDialog): void => {
+ queue.value.push(item)
+ }
+
+ const clear = (): void => {
+ queue.value = []
+ }
+
+ const currentDialog = computed(() => queue.value[0] || null)
+
+ const closeCurrentDialog = (): void => {
+ queue.value.shift()
+ isDialogVisible.value = true
+ }
+
+ const WrappedDialog = computed(() => {
+ if (!currentDialog.value) return null
+
+ return defineComponent({
+ name: 'WrappedDialog',
+ setup() {
+ const handleUpdate = (value: boolean): void => {
+ if (!value) {
+ closeCurrentDialog()
+ }
+ }
+
+ return () =>
+ h(currentDialog.value!.component, {
+ ...currentDialog.value!.props,
+ 'modelValue': isDialogVisible.value,
+ 'onUpdate:modelValue': handleUpdate,
+ })
+ },
+ })
+ })
+
+ return {
+ enqueueDialog,
+ clear,
+ queue,
+ currentDialog,
+ isDialogVisible,
+ WrappedDialog,
+ }
+}