diff --git a/src/App.vue b/src/App.vue
index d74fab7606..8ca7ee5af5 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 354193dbd3..003af027d4 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)
@@ -199,14 +210,14 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.isMainMenuVisible = false
interfaceStore.mainMenuCurrentStep = 1
interfaceStore.componentToHighlight = 'none'
- interfaceStore.currentSubMenuComponentName = null
+ interfaceStore.currentSubMenuComponentName = undefined
interfaceStore.currentSubMenuName = null
break
case 2:
interfaceStore.isMainMenuVisible = false
interfaceStore.mainMenuCurrentStep = 1
interfaceStore.componentToHighlight = 'menu-trigger'
- interfaceStore.currentSubMenuComponentName = null
+ interfaceStore.currentSubMenuComponentName = undefined
interfaceStore.currentSubMenuName = null
interfaceStore.userHasSeenTutorial = false
break
@@ -215,7 +226,7 @@ const handleStepChange = (newStep: number): void => {
interfaceStore.mainMenuCurrentStep = 2
interfaceStore.currentSubMenuName = SubMenuName.settings
interfaceStore.componentToHighlight = 'settings-menu-item'
- interfaceStore.currentSubMenuComponentName = null
+ interfaceStore.currentSubMenuComponentName = SubMenuComponentName.None
interfaceStore.userHasSeenTutorial = false
break
case 4:
@@ -316,6 +327,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',
@@ -348,8 +360,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 c795218567..5ba294defd 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 0000000000..39afdb6c12
--- /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,
+ }
+}
diff --git a/src/stores/appInterface.ts b/src/stores/appInterface.ts
index f330e555eb..cfb2a5bdad 100644
--- a/src/stores/appInterface.ts
+++ b/src/stores/appInterface.ts
@@ -28,8 +28,10 @@ export enum SubMenuComponentName {
SettingsDev = 'settings-dev',
SettingsMission = 'settings-mission',
SettingsActions = 'settings-actions',
+ SettingsDataLake = 'settings-datalake',
ToolsMAVLink = 'tools-mavlink',
ToolsDataLake = 'tools-datalake',
+ None = 'none',
}
export const useAppInterfaceStore = defineStore('responsive', {
@@ -50,7 +52,7 @@ export const useAppInterfaceStore = defineStore('responsive', {
isMainMenuVisible: false,
mainMenuCurrentStep: 1,
currentSubMenuName: ref(null),
- currentSubMenuComponentName: ref(null),
+ currentSubMenuComponentName: ref(undefined),
isGlassModalAlwaysOnTop: false,
isTutorialVisible: false,
userHasSeenTutorial: useBlueOsStorage('cockpit-has-seen-tutorial', false),
diff --git a/src/types/general.ts b/src/types/general.ts
index 90c189aa60..f0216f9674 100644
--- a/src/types/general.ts
+++ b/src/types/general.ts
@@ -32,7 +32,7 @@ export interface DialogActions {
disabled?: boolean
}
-export type SubMenuComponent = DefineComponent, Record, unknown> | null
+export type SubMenuComponent = DefineComponent, Record, unknown> | undefined
export interface StorageDB {
getItem: (key: string) => Promise
@@ -78,3 +78,28 @@ export interface ValidationFunctionReturn {
*/
error?: string
}
+
+/**
+ * Cockpit settings object
+ */
+export interface Settings {
+ [key: string]: any // eslint-disable-line @typescript-eslint/no-explicit-any
+}
+
+/**
+ * Config item for Cockpit settings
+ */
+export interface SettingItem {
+ /**
+ *
+ */
+ setting: string
+ /**
+ *
+ */
+ originalKey: string
+ /**
+ *
+ */
+ changed?: boolean
+}