diff --git a/messages/renderer/en.json b/messages/renderer/en.json index 68c881a69..fb7288fbd 100644 --- a/messages/renderer/en.json +++ b/messages/renderer/en.json @@ -155,62 +155,70 @@ "renderer.components.MapEditor.index.notes": { "message": "Description" }, - "renderer.components.MapFilter.DataExportDialog.cancel": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.cancel": { "description": "cancel button", "message": "Cancel" }, - "renderer.components.MapFilter.DataExportDialog.close": { - "description": "Close button (shown if user has no data to export)", - "message": "Close" - }, - "renderer.components.MapFilter.DataExportDialog.defaultExportFilename": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.defaultExportFilename": { "description": "Default filename for exported data", "message": "mapeo-observation-data" }, - "renderer.components.MapFilter.DataExportDialog.exportAll": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.exportAll": { "message": "All {count} observations" }, - "renderer.components.MapFilter.DataExportDialog.exportFiltered": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.exportFiltered": { "message": "{count} filtered observations" }, - "renderer.components.MapFilter.DataExportDialog.filteredOrAll": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.filteredOrAll": { "description": "Label for select to export all data or only filtered", "message": "Only filtered data or all data?" }, - "renderer.components.MapFilter.DataExportDialog.format": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.format": { "description": "Label for data format selector", "message": "Data format" }, - "renderer.components.MapFilter.DataExportDialog.includePhotos": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.includePhotos": { "description": "Label for select to include photos in export", "message": "Also export photos?" }, - "renderer.components.MapFilter.DataExportDialog.includePhotosHint": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.includePhotosHint": { "description": "Hint shown when user has selected photos to be included in the export", "message": "Export will be a zip file including data and photos" }, - "renderer.components.MapFilter.DataExportDialog.includePhotosNone": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.includePhotosNone": { "description": "Label for select option to include no photos in export", "message": "No Photos" }, - "renderer.components.MapFilter.DataExportDialog.includePhotosOriginal": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.includePhotosOriginal": { "description": "Label for select option to include full size photos in export", "message": "Full size photos" }, - "renderer.components.MapFilter.DataExportDialog.includePhotosPreview": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.includePhotosPreview": { "description": "Label for select option to include preview size photos in export", "message": "Preview size photos" }, - "renderer.components.MapFilter.DataExportDialog.noData": { - "description": "Shown when there is no data to export", - "message": "You don't yet have any data to export." - }, - "renderer.components.MapFilter.DataExportDialog.save": { + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.save": { "description": "Save button", "message": "Save" }, - "renderer.components.MapFilter.DataExportDialog.title": { - "description": "Title for webmaps export dialog", + "renderer.components.MapFilter.DataExportDialog.ExportDetailsForm.title": { + "description": "Title for export dialog", + "message": "Export Observations" + }, + "renderer.components.MapFilter.DataExportDialog.ExportSuccessful.exportSuccessful": { + "message": "Successfully exported observations." + }, + "renderer.components.MapFilter.DataExportDialog.ExportSuccessful.exportSuccessfulButton": { + "message": "OK" + }, + "renderer.components.MapFilter.DataExportDialog.NoData.close": { + "message": "Close" + }, + "renderer.components.MapFilter.DataExportDialog.NoData.noData": { + "message": "You don't yet have any data to export." + }, + "renderer.components.MapFilter.DataExportDialog.Template.title": { + "description": "Title for observations export dialog", "message": "Export Observations" }, "renderer.components.MapFilter.ExportButton.exportButton": { diff --git a/src/renderer/components/MapFilter/DataExportDialog/Content.js b/src/renderer/components/MapFilter/DataExportDialog/Content.js new file mode 100644 index 000000000..694e05dd2 --- /dev/null +++ b/src/renderer/components/MapFilter/DataExportDialog/Content.js @@ -0,0 +1,30 @@ +import React from 'react' + +import { ExportDetailsForm } from './ExportDetailsForm' +import { ExportSuccessful } from './ExportSuccessful' +import { NoData } from './NoData' + +export const Content = ({ + allObservations, + filteredObservations, + getMediaUrl, + onClose +}) => { + const [showExportSuccess, setShowExportSuccess] = React.useState(false) + + const noData = allObservations.length === 0 + + return noData ? ( + + ) : showExportSuccess ? ( + + ) : ( + setShowExportSuccess(true)} + /> + ) +} diff --git a/src/renderer/components/MapFilter/DataExportDialog.js b/src/renderer/components/MapFilter/DataExportDialog/ExportDetailsForm.js similarity index 78% rename from src/renderer/components/MapFilter/DataExportDialog.js rename to src/renderer/components/MapFilter/DataExportDialog/ExportDetailsForm.js index a27f9802d..438f0b078 100644 --- a/src/renderer/components/MapFilter/DataExportDialog.js +++ b/src/renderer/components/MapFilter/DataExportDialog/ExportDetailsForm.js @@ -1,38 +1,31 @@ -import React, { useState } from 'react' -import { makeStyles } from '@material-ui/core/styles' +import * as React from 'react' +import { remote } from 'electron' import Button from '@material-ui/core/Button' -import Dialog from '@material-ui/core/Dialog' -import DialogActions from '@material-ui/core/DialogActions' -import DialogContent from '@material-ui/core/DialogContent' -import DialogTitle from '@material-ui/core/DialogTitle' -import DialogContentText from '@material-ui/core/DialogContentText' -import { defineMessages, useIntl, FormattedMessage } from 'react-intl' import FormControl from '@material-ui/core/FormControl' +import FormHelperText from '@material-ui/core/FormHelperText' import InputLabel from '@material-ui/core/InputLabel' import MenuItem from '@material-ui/core/MenuItem' import Select from '@material-ui/core/Select' -import FormHelperText from '@material-ui/core/FormHelperText' +import { makeStyles } from '@material-ui/core/styles' +import isodate from '@segment/isodate' import { csvFormat } from 'd3-dsv' -import ViewWrapper from './ViewWrapper' import flatten from 'flat' -import isodate from '@segment/isodate' -import { fromLatLon } from 'utm' -import { remote } from 'electron' -import path from 'path' import fs from 'fs' import fsWriteStreamAtomic from 'fs-write-stream-atomic' +import path from 'path' import pump from 'pump' +import { defineMessages, useIntl, FormattedMessage } from 'react-intl' +import { fromLatLon } from 'utm' -import logger from '../../../logger' -import createZip from '../../create-zip' +import logger from '../../../../logger' +import createZip from '../../../create-zip' +import { Template } from './Template' const msgs = defineMessages({ - // Title for webmaps export dialog + // Title for export dialog title: 'Export Observations', // Save button save: 'Save', - // Close button (shown if user has no data to export) - close: 'Close', // cancel button cancel: 'Cancel', // Label for data format selector @@ -41,8 +34,6 @@ const msgs = defineMessages({ filteredOrAll: 'Only filtered data or all data?', exportAll: 'All {count} observations', exportFiltered: '{count} filtered observations', - // Shown when there is no data to export - noData: "You don't yet have any data to export.", // Label for select to include photos in export includePhotos: 'Also export photos?', // Hint shown when user has selected photos to be included in the export @@ -57,17 +48,19 @@ const msgs = defineMessages({ defaultExportFilename: 'mapeo-observation-data' }) -const ExportDialogContent = ({ - filteredObservations = [], - allObservations = [], - getPreset, +export const ExportDetailsForm = ({ + allObservations, + filteredObservations, getMediaUrl, - onClose + onClose, + onSuccess }) => { - const classes = useStyles() - const [saving, setSaving] = useState() const { formatMessage: t } = useIntl() + const classes = useStyles() + const isFiltered = allObservations.length !== filteredObservations.length + + const [saving, setSaving] = React.useState(false) const [values, setValues] = React.useState({ format: 'geojson', include: isFiltered && filteredObservations.length ? 'filtered' : 'all', @@ -83,35 +76,38 @@ const ExportDialogContent = ({ onClose() } - const handleSave = e => { - e.preventDefault() + const handleSave = event => { + event.preventDefault() + setSaving(true) const observationsToSave = values.include === 'all' ? allObservations : filteredObservations - let exportData + let dataToExport let ext = values.format + switch (values.format) { case 'geojson': - exportData = observationsToGeoJson(observationsToSave, { + dataToExport = observationsToGeoJson(observationsToSave, { photos: values.photos !== 'none' }) break case 'csv': - exportData = observationsToCsv(observationsToSave, { + dataToExport = observationsToCsv(observationsToSave, { photos: values.photos !== 'none' }) break case 'smart': ext = 'csv' - exportData = observationsToSmartCsv(observationsToSave, { + dataToExport = observationsToSmartCsv(observationsToSave, { photos: values.photos !== 'none' }) break } const saveExt = values.photos === 'none' ? ext : 'zip' + remote.dialog .showSaveDialog({ title: t(msgs.title), @@ -119,20 +115,22 @@ const ExportDialogContent = ({ filters: [{ name: saveExt + ' files', extensions: [saveExt] }] }) .then(({ canceled, filePath }) => { - if (canceled) return handleClose() + if (canceled) { + handleClose() + return + } + const filepathWithExtension = path.join( path.dirname(filePath), path.basename(filePath, '.' + saveExt) + '.' + saveExt ) - onSelectFile(filepathWithExtension) + + exportData(filepathWithExtension) }) - function onSelectFile (filePath) { + function exportData (filePath) { if (values.photos === 'none') { - fs.writeFile(filePath, exportData, err => { - if (err) logger.error('DataExportDialog: onSelectFile', err) - handleClose() - }) + fs.writeFile(filePath, dataToExport, onFinish('onSelectFile')) return } @@ -145,10 +143,11 @@ const ExportDialogContent = ({ const localFiles = [ { - data: exportData, + data: dataToExport, metadataPath: t(msgs.defaultExportFilename) + '.' + ext } ] + const remoteFiles = photosToSave.map(id => { // If the user is trying to export originals, use preview sized images // as a fallback. TODO: Show a warning to the user that originals are @@ -162,30 +161,44 @@ const ExportDialogContent = ({ metadataPath: 'images/' + id } }) + const output = fsWriteStreamAtomic(filePath) const archive = createZip(localFiles, remoteFiles, { formatMessage: t }) - pump(archive, output, err => { - if (err) logger.error('DataExportDialog: pump create zip', err) - handleClose() - }) + pump(archive, output, onFinish('pump create zip')) + + function onFinish (namespace) { + return function (err) { + if (err) { + logger.error(`ExportDetailsForm: ${namespace}`, err) + handleClose() + } else { + onSuccess() + } + } + } } } - const noData = allObservations.length === 0 - return ( -
- - - - - - {noData ? ( - - - - ) : ( + +