From 5bb289a822d28914392ee2bd7130afb8d00f2722 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Fri, 16 Apr 2021 20:42:38 +0800 Subject: [PATCH 1/5] FileExporter: add 'fetch' hook + Web support --- ui/component/common/file-exporter.jsx | 149 ++++++++++++++------------ ui/component/common/icon-custom.jsx | 7 ++ ui/constants/icons.js | 1 + 3 files changed, 90 insertions(+), 67 deletions(-) diff --git a/ui/component/common/file-exporter.jsx b/ui/component/common/file-exporter.jsx index 2969bc90c..5f6e2ae25 100644 --- a/ui/component/common/file-exporter.jsx +++ b/ui/component/common/file-exporter.jsx @@ -3,92 +3,107 @@ import * as ICONS from 'constants/icons'; import React from 'react'; import Button from 'component/button'; +import Spinner from 'component/spinner'; import parseData from 'util/parse-data'; -import { remote } from 'electron'; -import path from 'path'; -// @if TARGET='app' -import fs from 'fs'; -// @endif type Props = { data: Array, - title: string, label: string, - defaultPath?: string, - filters: Array, - onFileCreated?: string => void, - disabled: boolean, + tooltip?: string, + defaultFileName?: string, + filters?: Array, + parseFormat: string, + onFetch?: () => void, + isFetching?: boolean, + onSuccess?: () => void, + onError?: (string) => void, + disabled?: boolean, }; class FileExporter extends React.PureComponent { - static defaultProps = { - filters: [], - }; - constructor() { super(); - (this: any).handleButtonClick = this.handleButtonClick.bind(this); + (this: any).handleDownload = this.handleDownload.bind(this); } - handleFileCreation(filename: string, data: any) { - const { onFileCreated } = this.props; - // @if TARGET='app' - fs.writeFile(filename, data, err => { - if (err) throw err; - // Do something after creation + handleDownload() { + const { data, defaultFileName, filters, parseFormat, onSuccess, onError } = this.props; - if (onFileCreated) { - onFileCreated(filename); - } - }); - // @endif - } + if ((!data || data.length === 0) && onError) { + onError('No data to export'); + return; + } - handleButtonClick() { - const { title, data, defaultPath, filters } = this.props; + const parsed = parseData(data, parseFormat, filters); + if (!parsed && onError) { + onError('Failed to process fetched data.'); + return; + } - const options = { - title, - defaultPath, - filters: [ - { - name: 'CSV', - extensions: ['csv'], - }, - { - name: 'JSON', - extensions: ['json'], - }, - ], - }; + const element = document.createElement('a'); + const file = new Blob([parsed], { type: 'text/plain' }); + element.href = URL.createObjectURL(file); + element.download = defaultFileName || 'file.txt'; + // $FlowFixMe + document.body.appendChild(element); + element.click(); + // $FlowFixMe + document.body.removeChild(element); - remote.dialog.showSaveDialog(remote.getCurrentWindow(), options, filename => { - // User hit cancel so do nothing: - if (!filename) return; - // Get extension and remove initial dot - // @if TARGET='app' - const format = path.extname(filename).replace(/\./g, ''); - // @endif - // Parse data to string with the chosen format - const parsed = parseData(data, format, filters); - // Write file - if (parsed) { - this.handleFileCreation(filename, parsed); - } - }); + if (onSuccess) { + onSuccess(); + } } render() { - const { label, disabled } = this.props; - return ( -