feat: save value to file

This commit is contained in:
Björn Dalfors
2024-05-27 14:38:17 +02:00
parent 1ba0d07757
commit f17640c9db
5 changed files with 121 additions and 10 deletions

View File

@@ -17,31 +17,36 @@ export const setTopic = (topic?: string): Action => {
export const openFile = () => async (dispatch: Dispatch<any>, getState: () => AppState) => { export const openFile = () => async (dispatch: Dispatch<any>, getState: () => AppState) => {
try { try {
const file = await getFileContent() const file = await getFileContent()
if (file) {
dispatch( dispatch(
setPayload(Base64Message.fromBuffer(file.data).toUnicodeString() setPayload(Base64Message.fromBuffer(file.data).toUnicodeString()
)) ))
}
} catch (error) { } catch (error) {
dispatch(showError(error)) dispatch(showError(error))
} }
} }
type FileParameters = { type FileParameters = {
name: string, name: string,
data: Buffer data: Buffer
} }
async function getFileContent(): Promise<FileParameters> { async function getFileContent(): Promise<FileParameters | undefined> {
const rejectReasons = { const rejectReasons = {
noFileSelected: 'No file selected', noFileSelected: 'No file selected',
errorReadingFile: 'Error reading file' errorReadingFile: 'Error reading file'
} }
const openDialogReturnValue = await rendererRpc.call(makeOpenDialogRpc(), { const { canceled, filePaths } = await rendererRpc.call(makeOpenDialogRpc(), {
properties: ['openFile'], properties: ['openFile'],
securityScopedBookmarks: true, securityScopedBookmarks: true,
}) })
const selectedFile = openDialogReturnValue.filePaths && openDialogReturnValue.filePaths[0] if (canceled) {
return
}
const selectedFile = filePaths[0]
if (!selectedFile) { if (!selectedFile) {
throw rejectReasons.noFileSelected throw rejectReasons.noFileSelected
} }

View File

@@ -1,6 +1,7 @@
import * as q from '../../../../../backend/src/Model' import * as q from '../../../../../backend/src/Model'
import ActionButtons from './ActionButtons' import ActionButtons from './ActionButtons'
import Copy from '../../helper/Copy' import Copy from '../../helper/Copy'
import Save from '../../helper/Save'
import DateFormatter from '../../helper/DateFormatter' import DateFormatter from '../../helper/DateFormatter'
import MessageHistory from './MessageHistory' import MessageHistory from './MessageHistory'
import Panel from '../Panel' import Panel from '../Panel'
@@ -59,6 +60,12 @@ function ValuePanel(props: Props) {
return node?.message && decodeMessage(node.message)?.message?.toUnicodeString() return node?.message && decodeMessage(node.message)?.message?.toUnicodeString()
}, [node, decodeMessage]) }, [node, decodeMessage])
const getBuffer = () => {
if (node?.message && node.message.payload) {
return node.message.payload.toBuffer()
}
}
function messageMetaInfo() { function messageMetaInfo() {
if (!props.node || !props.node.message) { if (!props.node || !props.node.message) {
return null return null
@@ -93,10 +100,13 @@ function ValuePanel(props: Props) {
const [value] = const [value] =
node && node.message && node.message.payload ? node.message.payload?.format(node.type) : [null, undefined] node && node.message && node.message.payload ? node.message.payload?.format(node.type) : [null, undefined]
const copyValue = value ? <Copy getValue={getDecodedValue} /> : null const copyValue = value ? <Copy getValue={getDecodedValue} /> : null
const saveValue = value ? <Save getBuffer={getBuffer} /> : null
return ( return (
<Panel> <Panel>
<span>Value {copyValue}</span> <span>
Value {copyValue} {saveValue}
</span>
<span style={{ width: '100%' }}> <span style={{ width: '100%' }}>
{renderViewOptions()} {renderViewOptions()}
<div style={{ marginBottom: '-8px', marginTop: '8px' }}> <div style={{ marginBottom: '-8px', marginTop: '8px' }}>

View File

@@ -0,0 +1,85 @@
import * as React from 'react'
import { connect } from 'react-redux'
import Check from '@material-ui/icons/Check'
import CustomIconButton from './CustomIconButton'
import { promises as fsPromise } from 'fs'
import { SaveAlt } from '@material-ui/icons'
import { bindActionCreators } from 'redux'
import { rendererRpc } from '../../../../events'
import { makeSaveDialogRpc } from '../../../../events/OpenDialogRequest'
import { globalActions } from '../../actions'
export async function saveToFile(buffer: Buffer | string): Promise<string | undefined> {
const rejectReasons = {
errorWritingFile: 'Error writing file',
}
const { canceled, filePath } = await rendererRpc.call(makeSaveDialogRpc(), {
securityScopedBookmarks: true,
})
if (!canceled && filePath !== undefined) {
try {
await fsPromise.writeFile(filePath, buffer)
return filePath
} catch (error) {
throw rejectReasons.errorWritingFile
}
}
}
interface Props {
getBuffer: () => Buffer | undefined
actions: {
global: typeof globalActions
}
}
interface State {
didSave: boolean
}
class Save extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props)
this.state = { didSave: false }
}
private handleClick = async (event: React.MouseEvent) => {
event.stopPropagation()
const buffer = this.props.getBuffer()
if (buffer !== undefined) {
const filename = await saveToFile(buffer)
this.props.actions.global.showNotification(`Saved to ${filename}`)
this.setState({ didSave: true })
setTimeout(() => {
this.setState({ didSave: false })
}, 1500)
}
}
public render() {
const icon = !this.state.didSave ? (
<SaveAlt fontSize="inherit" />
) : (
<Check fontSize="inherit" style={{ cursor: 'default' }} />
)
return (
<CustomIconButton onClick={this.handleClick} tooltip="Save to file">
<div style={{ marginTop: '2px' }}>{icon}</div>
</CustomIconButton>
)
}
}
const mapDispatchToProps = (dispatch: any) => {
return {
actions: {
global: bindActionCreators(globalActions, dispatch),
},
}
}
export default connect(undefined, mapDispatchToProps)(Save)

View File

@@ -1,4 +1,4 @@
import { OpenDialogOptions, OpenDialogReturnValue } from 'electron' import { OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'electron'
import { RpcEvent } from './EventSystem/Rpc' import { RpcEvent } from './EventSystem/Rpc'
export function makeOpenDialogRpc(): RpcEvent<OpenDialogOptions, OpenDialogReturnValue> { export function makeOpenDialogRpc(): RpcEvent<OpenDialogOptions, OpenDialogReturnValue> {
@@ -6,3 +6,9 @@ export function makeOpenDialogRpc(): RpcEvent<OpenDialogOptions, OpenDialogRetur
topic: 'openDialog', topic: 'openDialog',
} }
} }
export function makeSaveDialogRpc(): RpcEvent<SaveDialogOptions, SaveDialogReturnValue> {
return {
topic: 'saveDialog',
}
}

View File

@@ -10,7 +10,7 @@ import buildOptions from './buildOptions'
import { waitForDevServer, isDev, runningUiTestOnCi, loadDevTools } from './development' import { waitForDevServer, isDev, runningUiTestOnCi, loadDevTools } from './development'
import { shouldAutoUpdate, handleAutoUpdate } from './autoUpdater' import { shouldAutoUpdate, handleAutoUpdate } from './autoUpdater'
import { registerCrashReporter } from './registerCrashReporter' import { registerCrashReporter } from './registerCrashReporter'
import { makeOpenDialogRpc } from '../events/OpenDialogRequest' import { makeOpenDialogRpc, makeSaveDialogRpc } from '../events/OpenDialogRequest'
import { backendRpc, getAppVersion } from '../events' import { backendRpc, getAppVersion } from '../events'
registerCrashReporter() registerCrashReporter()
@@ -25,6 +25,11 @@ app.whenReady().then(() => {
backendRpc.on(makeOpenDialogRpc(), async request => { backendRpc.on(makeOpenDialogRpc(), async request => {
return dialog.showOpenDialog(BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], request) return dialog.showOpenDialog(BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], request)
}) })
backendRpc.on(makeSaveDialogRpc(), async request => {
return dialog.showSaveDialog(BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], request)
})
backendRpc.on(getAppVersion, async () => app.getVersion()) backendRpc.on(getAppVersion, async () => app.getVersion())
}) })