diff --git a/src/ipc/index.ts b/src/ipc/index.ts index 895564826..d47adbfef 100644 --- a/src/ipc/index.ts +++ b/src/ipc/index.ts @@ -3,6 +3,7 @@ export enum IpcRequest { OpenLogsFolder = 'OPEN_LOGS_FOLDER', UpdateBuildStatus = 'UPDATE_BUILD_STATUS', ChooseFolder = 'CHOOSE_FOLDER', + SaveFile = 'SAVE_FILE', } export interface OpenFileLocationRequestBody { @@ -17,3 +18,13 @@ export interface ChooseFolderResponseBody { success: boolean; directoryPath: string; } + +export interface SaveFileRequestBody { + defaultPath?: string; + data: string | Uint8Array; +} + +export interface SaveFileResponseBody { + success: boolean; + path: string; +} diff --git a/src/main.dev.ts b/src/main.dev.ts index 530415cfb..d3a732967 100644 --- a/src/main.dev.ts +++ b/src/main.dev.ts @@ -16,12 +16,15 @@ import { autoUpdater } from 'electron-updater'; import log from 'electron-log'; import mkdirp from 'mkdirp'; import winston from 'winston'; +import fs from 'fs'; import MenuBuilder from './menu'; import ApiServer from './api'; import { ChooseFolderResponseBody, IpcRequest, OpenFileLocationRequestBody, + SaveFileRequestBody, + SaveFileResponseBody, UpdateBuildStatusRequestBody, } from './ipc'; import WinstonLoggerService from './api/src/logger/WinstonLogger'; @@ -500,3 +503,24 @@ ipcMain.on( }); } ); + +ipcMain.handle( + IpcRequest.SaveFile, + async (_, arg: SaveFileRequestBody): Promise => { + const result = await dialog.showSaveDialog({ + title: 'Save File', + defaultPath: arg.defaultPath, + }); + if (result.canceled || !result.filePath || result.filePath.length === 0) { + return { + success: false, + path: '', + }; + } + await fs.promises.writeFile(result.filePath, arg.data); + return { + success: true, + path: result.filePath, + }; + } +); diff --git a/src/ui/views/ConfiguratorView/index.tsx b/src/ui/views/ConfiguratorView/index.tsx index 20537c773..9f53068fd 100644 --- a/src/ui/views/ConfiguratorView/index.tsx +++ b/src/ui/views/ConfiguratorView/index.tsx @@ -24,7 +24,7 @@ import { } from '@mui/material'; import SettingsIcon from '@mui/icons-material/Settings'; import { ipcRenderer } from 'electron'; -import { ContentCopy, NetworkWifi } from '@mui/icons-material'; +import { ContentCopy, NetworkWifi, Save } from '@mui/icons-material'; import FirmwareVersionForm from '../../components/FirmwareVersionForm'; import DeviceTargetForm from '../../components/DeviceTargetForm'; import DeviceOptionsForm, { @@ -64,6 +64,8 @@ import BuildResponse from '../../components/BuildResponse'; import { IpcRequest, OpenFileLocationRequestBody, + SaveFileRequestBody, + SaveFileResponseBody, UpdateBuildStatusRequestBody, } from '../../../ipc'; import UserDefinesValidator from './UserDefinesValidator'; @@ -773,6 +775,30 @@ const ConfiguratorView: FunctionComponent = (props) => { setDeviceSelectErrorDialogOpen(false); }, []); + const saveBuildLogToFile = useCallback(async () => { + const saveFileRequestBody: SaveFileRequestBody = { + data: logs, + defaultPath: `ExpressLRSBuildLog_${new Date() + .toISOString() + .replace(/[^0-9]/gi, '')}.txt`, + }; + + const result: SaveFileResponseBody = await ipcRenderer.invoke( + IpcRequest.SaveFile, + saveFileRequestBody + ); + + if (result.success) { + const openFileLocationRequestBody: OpenFileLocationRequestBody = { + path: result.path, + }; + ipcRenderer.send( + IpcRequest.OpenFileLocation, + openFileLocationRequestBody + ); + } + }, [logs]); + return ( {viewState === ViewState.Configuration && ( @@ -1012,13 +1038,21 @@ const ConfiguratorView: FunctionComponent = (props) => { Logs { await navigator.clipboard.writeText(logs); }} > + + + }