diff --git a/electron/preload.ts b/electron/preload.ts index a26e42dce..b3929e0a1 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -31,4 +31,6 @@ contextBridge.exposeInMainWorld('electronAPI', { keys: async (subFolders?: string[]) => { return await ipcRenderer.invoke('keys', { subFolders }) }, + openCockpitFolder: () => ipcRenderer.invoke('open-cockpit-folder'), + openVideoFolder: () => ipcRenderer.invoke('open-video-folder'), }) diff --git a/electron/services/storage.ts b/electron/services/storage.ts index f005494df..f6349f72e 100644 --- a/electron/services/storage.ts +++ b/electron/services/storage.ts @@ -1,10 +1,10 @@ -import { ipcMain } from 'electron' +import { ipcMain, shell } from 'electron' import { app } from 'electron' import * as fs from 'fs/promises' import { dirname, join } from 'path' // Create a new storage interface for filesystem -const cockpitFolderPath = join(app.getPath('home'), 'Cockpit') +export const cockpitFolderPath = join(app.getPath('home'), 'Cockpit') fs.mkdir(cockpitFolderPath, { recursive: true }) export const filesystemStorage = { @@ -58,4 +58,13 @@ export const setupFilesystemStorage = (): void => { ipcMain.handle('keys', async (_, data) => { return await filesystemStorage.keys(data.subFolders) }) + ipcMain.handle('open-cockpit-folder', async () => { + await fs.mkdir(cockpitFolderPath, { recursive: true }) + await shell.openPath(cockpitFolderPath) + }) + ipcMain.handle('open-video-folder', async () => { + const videoFolderPath = join(cockpitFolderPath, 'videos') + await fs.mkdir(videoFolderPath, { recursive: true }) + await shell.openPath(videoFolderPath) + }) } diff --git a/src/components/VideoLibraryModal.vue b/src/components/VideoLibraryModal.vue index 2ea3d5379..a44575a30 100644 --- a/src/components/VideoLibraryModal.vue +++ b/src/components/VideoLibraryModal.vue @@ -484,6 +484,7 @@ import { ref, watch } from 'vue' import { useInteractionDialog } from '@/composables/interactionDialog' import { useSnackbar } from '@/composables/snackbar' +import { isElectron } from '@/libs/utils' import { useAppInterfaceStore } from '@/stores/appInterface' import { useVideoStore } from '@/stores/video' import { DialogActions } from '@/types/general' @@ -580,8 +581,31 @@ const fileActionButtons = computed(() => [ disabled: showOnScreenProgress.value === true || isPreparingDownload.value === true, action: () => downloadVideoAndTelemetryFiles(), }, + { + name: 'Open Folder', + icon: 'mdi-folder-outline', + size: 28, + tooltip: 'Open videos folder', + confirmAction: false, + show: isElectron(), + disabled: false, + action: () => openVideoFolder(), + }, ]) +const openVideoFolder = (): void => { + if (isElectron() && window.electronAPI) { + window.electronAPI?.openVideoFolder() + } else { + showSnackbar({ + message: 'This feature is only available in the desktop version of Cockpit.', + duration: 3000, + variant: 'error', + closeButton: true, + }) + } +} + const closeModal = (): void => { isVisible.value = false emits('update:openModal', false) diff --git a/src/libs/cosmos.ts b/src/libs/cosmos.ts index b74b2f9e8..ecd0c0a97 100644 --- a/src/libs/cosmos.ts +++ b/src/libs/cosmos.ts @@ -224,6 +224,14 @@ declare global { * Register callback for download progress event */ onDownloadProgress: (callback: (info: any) => void) => void + /** + * Open cockpit folder + */ + openCockpitFolder: () => void + /** + * Open video folder + */ + openVideoFolder: () => void } } } diff --git a/src/views/ConfigurationGeneralView.vue b/src/views/ConfigurationGeneralView.vue index 029378016..1548bf100 100644 --- a/src/views/ConfigurationGeneralView.vue +++ b/src/views/ConfigurationGeneralView.vue @@ -32,9 +32,20 @@ @click="missionStore.changeUsername" /> - - Show tutorial - +
+ + Show tutorial + + + Open Cockpit folder + +
@@ -292,6 +303,7 @@ import { onMounted, ref, watch } from 'vue' import { defaultGlobalAddress } from '@/assets/defaults' import ExpansiblePanel from '@/components/ExpansiblePanel.vue' import VehicleDiscoveryDialog from '@/components/VehicleDiscoveryDialog.vue' +import { useSnackbar } from '@/composables/snackbar' import * as Connection from '@/libs/connection/connection' import { ConnectionManager } from '@/libs/connection/connection-manager' import { isValidNetworkAddress, reloadCockpit } from '@/libs/utils' @@ -306,6 +318,7 @@ import BaseConfigurationView from './BaseConfigurationView.vue' const mainVehicleStore = useMainVehicleStore() const interfaceStore = useAppInterfaceStore() const missionStore = useMissionStore() +const { showSnackbar } = useSnackbar() const globalAddressForm = ref() const globalAddressFormValid = ref(false) @@ -517,6 +530,19 @@ onMounted(() => { }) const showDiscoveryDialog = ref(false) + +const openCockpitFolder = (): void => { + if (isElectron() && window.electronAPI) { + window.electronAPI?.openCockpitFolder() + } else { + showSnackbar({ + message: 'This feature is only available in the desktop version of Cockpit.', + duration: 3000, + variant: 'error', + closeButton: true, + }) + } +}