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 @@ + 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, + } +}