4444 <v-btn size =" x-small" class =" bg-[#FFFFFF22] shadow-1" variant =" flat" @click =" openTutorial" >
4545 Show tutorial
4646 </v-btn >
47- <v-btn
48- v-if =" isElectron()"
49- size =" x-small"
50- class =" bg-[#FFFFFF22] shadow-1"
51- variant =" flat"
52- @click =" openCockpitFolder"
53- >
54- Open Cockpit folder
55- </v-btn >
5647 <v-btn
5748 size =" x-small"
5849 class =" bg-[#FFFFFF22] shadow-1"
7061 {{ interfaceStore.pirateMode ? 'Disable pirate mode' : 'Enable pirate mode' }}
7162 </v-btn >
7263 </div >
64+ <v-divider v-if =" isElectron()" class =" w-full opacity-[0.08]" />
65+ <div v-if =" isElectron()" class =" flex flex-col w-full py-4 gap-1" >
66+ <div class =" flex align-center w-full justify-between pr-2" >
67+ <div class =" flex items-center min-w-0" >
68+ <span class =" mr-2 whitespace-nowrap" >Cockpit folder location:</span >
69+ <v-tooltip :text =" cockpitFolderPath" location =" bottom" open-delay =" 300" >
70+ <template #activator =" { props } " >
71+ <span v-bind =" props" class =" font-semibold text-base truncate" >{{ cockpitFolderPath }}</span >
72+ </template >
73+ </v-tooltip >
74+ </div >
75+ <div class =" flex justify-end" >
76+ <v-btn
77+ size =" small"
78+ append-icon =" mdi-folder-open-outline"
79+ class =" bg-[#FFFFFF22] shadow-2 -mr-2"
80+ variant =" flat"
81+ @click =" openCockpitFolder"
82+ >
83+ Open folder
84+ </v-btn >
85+ </div >
86+ </div >
87+ <div class =" flex items-center gap-1" >
88+ <span class =" text-xs text-slate-400 cursor-pointer hover:text-slate-100" @click =" browseCockpitFolder" >
89+ Change cockpit folder location
90+ </span >
91+ <template v-if =" cockpitFolderPath !== defaultCockpitFolderPath " >
92+ <span class =" text-xs text-slate-400" >or</span >
93+ <span
94+ class =" text-xs text-slate-400 cursor-pointer hover:text-slate-100"
95+ @click =" resetCockpitFolderPath"
96+ >
97+ reset to the default location
98+ </span >
99+ </template >
100+ </div >
101+ </div >
73102 </div >
74103 </template >
75104 </ExpansiblePanel >
@@ -392,6 +421,7 @@ import { defaultGlobalAddress } from '@/assets/defaults'
392421import ManageCockpitSettings from ' @/components/configuration/CockpitSettingsManager.vue'
393422import ExpansiblePanel from ' @/components/ExpansiblePanel.vue'
394423import VehicleDiscoveryDialog from ' @/components/VehicleDiscoveryDialog.vue'
424+ import { useInteractionDialog } from ' @/composables/interactionDialog'
395425import { useSnackbar } from ' @/composables/snackbar'
396426import * as Connection from ' @/libs/connection/connection'
397427import { ConnectionManager } from ' @/libs/connection/connection-manager'
@@ -416,12 +446,76 @@ const mainVehicleStore = useMainVehicleStore()
416446const interfaceStore = useAppInterfaceStore ()
417447const missionStore = useMissionStore ()
418448const { openSnackbar } = useSnackbar ()
449+ const { showDialog, closeDialog } = useInteractionDialog ()
419450
420451const globalAddressForm = ref ()
421452const globalAddressFormValid = ref (false )
422453const newGlobalAddress = ref (mainVehicleStore .globalAddress )
423454const showCockpitSettingsDialog = ref (false )
424455
456+ const cockpitFolderPath = ref (' ' )
457+ const defaultCockpitFolderPath = ref (' ' )
458+
459+ const loadCockpitFolderPath = async (): Promise <void > => {
460+ if (! isElectron () || ! window .electronAPI ) return
461+ cockpitFolderPath .value = await window .electronAPI .getCockpitFolderPath ()
462+ defaultCockpitFolderPath .value = await window .electronAPI .getDefaultCockpitFolderPath ()
463+ }
464+
465+ const applyFolderPath = async (path : string ): Promise <void > => {
466+ if (! window .electronAPI ) return
467+ await window .electronAPI .setCockpitFolderPath (path )
468+ cockpitFolderPath .value = path
469+ }
470+
471+ const browseCockpitFolder = async (): Promise <void > => {
472+ if (! window .electronAPI ) return
473+ const selected = await window .electronAPI .selectCockpitFolder ()
474+ if (! selected ) return
475+
476+ const folderName = selected .split (/ [/\\ ] / ).filter (Boolean ).pop ()
477+ if (folderName === ' Cockpit' ) {
478+ await applyFolderPath (selected )
479+ return
480+ }
481+
482+ const selectedName = selected .split (/ [/\\ ] / ).filter (Boolean ).pop () ?? ' '
483+ showDialog ({
484+ title: ' Cockpit folder location' ,
485+ message:
486+ ` The selected folder is not named "Cockpit". Would you like to use ` +
487+ ` ${selected } directly, or create and use a Cockpit subfolder inside it? ` ,
488+ variant: ' info' ,
489+ persistent: true ,
490+ maxWidth: 700 ,
491+ actions: [
492+ { text: ' Cancel' , size: ' small' , action : () => closeDialog () },
493+ {
494+ text: ` Use ${selectedName } ` ,
495+ size: ' small' ,
496+ action : () => {
497+ closeDialog ()
498+ applyFolderPath (selected )
499+ },
500+ },
501+ {
502+ text: ` Use ${selectedName }/Cockpit ` ,
503+ size: ' small' ,
504+ action : () => {
505+ closeDialog ()
506+ applyFolderPath (` ${selected }/Cockpit ` )
507+ },
508+ },
509+ ],
510+ })
511+ }
512+
513+ const resetCockpitFolderPath = async (): Promise <void > => {
514+ if (! window .electronAPI ) return
515+ await window .electronAPI .setCockpitFolderPath (defaultCockpitFolderPath .value )
516+ cockpitFolderPath .value = defaultCockpitFolderPath .value
517+ }
518+
425519const setGlobalAddress = async (): Promise <void > => {
426520 await globalAddressForm .value .validate ()
427521
@@ -643,6 +737,7 @@ const newGenericWebSocketUrl = ref(exampleGenericWebSocketUrl)
643737let unsubscribeGenericWebSocket: (() => void ) | null = null
644738
645739onMounted (() => {
740+ loadCockpitFolderPath ()
646741 tryToPrettifyRtcConfig ()
647742 unsubscribeGenericWebSocket = listenToGenericWebSocketConnections ((connections ) => {
648743 genericWebSocketConnections .value = connections
0 commit comments