diff --git a/electron/index.js b/electron/index.js index f49eeb6aa..8dea1e234 100644 --- a/electron/index.js +++ b/electron/index.js @@ -299,6 +299,32 @@ app.on('before-quit', () => { appState.isQuitting = true; }); +// Get the content of a file as a raw buffer of bytes. +// Useful to convert a file path to a File instance. +// Example: +// const result = await ipcMain.invoke('get-file-from-path', 'path/to/file'); +// const file = new File([result.buffer], result.name); +ipcMain.handle('get-file-from-path', (event, path) => { + return new Promise((resolve, reject) => { + // Encoding null ensures data results in a Buffer. + fs.readFile(path, { encoding: null }, (err, data) => { + if (err) { + reject(err); + return; + } + // Separate folders considering "\" and "/" + // as separators (cross platform) + const folders = path.split(/[\\/]/); + const fileName = folders[folders.length - 1]; + resolve({ + name: fileName, + path: path, + buffer: data, + }); + }); + }); +}); + ipcMain.on('get-disk-space', async (event) => { try { const { data_dir } = await Lbry.settings_get(); diff --git a/ui/component/common/file-selector.jsx b/ui/component/common/file-selector.jsx index bd1729d31..a74b5cb27 100644 --- a/ui/component/common/file-selector.jsx +++ b/ui/component/common/file-selector.jsx @@ -1,6 +1,7 @@ // @flow import * as React from 'react'; import * as remote from '@electron/remote'; +import { ipcRenderer } from 'electron'; import Button from 'component/button'; import { FormField } from 'component/common/form'; @@ -14,6 +15,7 @@ type Props = { error?: string, disabled?: boolean, autoFocus?: boolean, + filters?: Array<{ name: string, extension: string[] }>, }; class FileSelector extends React.PureComponent<Props> { @@ -64,13 +66,26 @@ class FileSelector extends React.PureComponent<Props> { properties = ['openDirectory']; } - remote.dialog.showOpenDialog({ properties, defaultPath }).then((result) => { - const path = result && result.filePaths[0]; - if (path) { - // $FlowFixMe - this.props.onFileChosen({ path }); - } - }); + remote.dialog + .showOpenDialog({ + properties, + defaultPath, + filters: this.props.filters, + }) + .then((result) => { + const path = result && result.filePaths[0]; + if (path) { + return ipcRenderer.invoke('get-file-from-path', path); + } + return undefined; + }) + .then((result) => { + if (!result) { + return; + } + const file = new File([result.buffer], result.name); + this.props.onFileChosen(file); + }); }; fileInputButton = () => { diff --git a/ui/component/selectAsset/view.jsx b/ui/component/selectAsset/view.jsx index 13fe2c324..05b9d92bb 100644 --- a/ui/component/selectAsset/view.jsx +++ b/ui/component/selectAsset/view.jsx @@ -27,6 +27,14 @@ type Props = { // passed to the onUpdate function after the // upload service returns success. buildImagePreview?: boolean, + // File extension filtering. Files can be filtered + // but the "All Files" options always shows up. To + // avoid that, you can use the filters property. + // For example, to only accept images pass the + // following filter: + // { name: 'Images', extensions: ['jpg', 'png', 'gif'] }, + filters?: Array<{ name: string, extension: string[] }>, + type?: string, }; function filePreview(file) { @@ -43,7 +51,8 @@ function filePreview(file) { } function SelectAsset(props: Props) { - const { onUpdate, onDone, assetName, currentValue, recommended, title, inline, buildImagePreview } = props; + const { onUpdate, onDone, assetName, currentValue, recommended, title, inline, buildImagePreview, filters, type } = + props; const [pathSelected, setPathSelected] = React.useState(''); const [fileSelected, setFileSelected] = React.useState<any>(null); const [uploadStatus, setUploadStatus] = React.useState(SPEECH_READY); @@ -121,6 +130,8 @@ function SelectAsset(props: Props) { /> ) : ( <FileSelector + filters={filters} + type={type} autoFocus disabled={uploadStatus === SPEECH_UPLOADING} label={fileSelectorLabel} diff --git a/ui/modal/modalImageUpload/view.jsx b/ui/modal/modalImageUpload/view.jsx index b76fe10db..9bbc0e8ce 100644 --- a/ui/modal/modalImageUpload/view.jsx +++ b/ui/modal/modalImageUpload/view.jsx @@ -14,10 +14,13 @@ type Props = { function ModalImageUpload(props: Props) { const { closeModal, currentValue, title, assetName, helpText, onUpdate } = props; + const filters = React.useMemo(() => [{ name: 'Images', extensions: ['jpg', 'png', 'gif'] }]); return ( <Modal isOpen type="card" onAborted={closeModal} contentLabel={title}> <SelectAsset + filters={filters} + type="openFile" onUpdate={onUpdate} currentValue={currentValue} assetName={assetName}