feat: save value to file
This commit is contained in:
@@ -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()
|
||||||
dispatch(
|
if (file) {
|
||||||
setPayload(Base64Message.fromBuffer(file.data).toUnicodeString()
|
dispatch(
|
||||||
))
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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' }}>
|
||||||
|
|||||||
85
app/src/components/helper/Save.tsx
Normal file
85
app/src/components/helper/Save.tsx
Normal 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)
|
||||||
@@ -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',
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user