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 (
-
)
}
-export default function DataExportDialog ({
- observations,
- presets,
- filter,
- getMediaUrl,
- onClose,
- open,
- ...otherProps
-}) {
- return (
-
- )
-}
+const useStyles = makeStyles(theme => ({
+ formControl: {
+ marginBottom: theme.spacing(2),
+ '&:not(:last-child)': {
+ marginBottom: theme.spacing(4)
+ }
+ },
+ select: {
+ fontFamily: theme.typography.body1.fontFamily
+ }
+}))
function observationsToGeoJson (obs, { photos } = {}) {
const features = obs.map(o => {
@@ -509,27 +466,3 @@ function leftPad (num, len, char = 0) {
}
return pad + str
}
-
-const useStyles = makeStyles(theme => ({
- appBar: {
- position: 'relative'
- },
- title: {
- marginLeft: theme.spacing(2),
- flex: 1
- },
- content: {
- display: 'flex',
- flexDirection: 'column',
- paddingTop: 0
- },
- formControl: {
- marginBottom: theme.spacing(2),
- '&:not(:last-child)': {
- marginBottom: theme.spacing(4)
- }
- },
- select: {
- fontFamily: theme.typography.body1.fontFamily
- }
-}))
diff --git a/src/renderer/components/MapFilter/DataExportDialog/ExportSuccessful.js b/src/renderer/components/MapFilter/DataExportDialog/ExportSuccessful.js
new file mode 100644
index 000000000..020065260
--- /dev/null
+++ b/src/renderer/components/MapFilter/DataExportDialog/ExportSuccessful.js
@@ -0,0 +1,26 @@
+import React from 'react'
+import Button from '@material-ui/core/Button'
+import DialogContentText from '@material-ui/core/DialogContentText'
+import { defineMessages, FormattedMessage } from 'react-intl'
+
+import { Template } from './Template'
+
+const msgs = defineMessages({
+ exportSuccessfulButton: 'OK',
+ exportSuccessful: 'Successfully exported observations.'
+})
+
+export const ExportSuccessful = ({ onClose }) => (
+
+
+
+ }
+ content={
+
+
+
+ }
+ />
+)
diff --git a/src/renderer/components/MapFilter/DataExportDialog/NoData.js b/src/renderer/components/MapFilter/DataExportDialog/NoData.js
new file mode 100644
index 000000000..8787d928b
--- /dev/null
+++ b/src/renderer/components/MapFilter/DataExportDialog/NoData.js
@@ -0,0 +1,26 @@
+import React from 'react'
+import Button from '@material-ui/core/Button'
+import DialogContentText from '@material-ui/core/DialogContentText'
+import { defineMessages, FormattedMessage } from 'react-intl'
+
+import { Template } from './Template'
+
+const msgs = defineMessages({
+ close: 'Close',
+ noData: "You don't yet have any data to export."
+})
+
+export const NoData = ({ onClose }) => (
+
+
+
+ }
+ content={
+
+
+
+ }
+ />
+)
diff --git a/src/renderer/components/MapFilter/DataExportDialog/Template.js b/src/renderer/components/MapFilter/DataExportDialog/Template.js
new file mode 100644
index 000000000..fef7b8516
--- /dev/null
+++ b/src/renderer/components/MapFilter/DataExportDialog/Template.js
@@ -0,0 +1,36 @@
+import React from 'react'
+import DialogActions from '@material-ui/core/DialogActions'
+import DialogContent from '@material-ui/core/DialogContent'
+import DialogTitle from '@material-ui/core/DialogTitle'
+import { makeStyles } from '@material-ui/core/styles'
+import { defineMessages, FormattedMessage } from 'react-intl'
+
+const msgs = defineMessages({
+ // Title for observations export dialog
+ title: 'Export Observations'
+})
+
+export const Template = ({ actions, content }) => {
+ const classes = useStyles()
+ return (
+ <>
+
+
+
+ {content}
+ {actions}
+ >
+ )
+}
+
+const useStyles = makeStyles(theme => ({
+ title: {
+ flex: 1,
+ paddingBottom: 8
+ },
+ content: {
+ display: 'flex',
+ flexDirection: 'column',
+ paddingTop: 0
+ }
+}))
diff --git a/src/renderer/components/MapFilter/DataExportDialog/index.js b/src/renderer/components/MapFilter/DataExportDialog/index.js
new file mode 100644
index 000000000..85c894b46
--- /dev/null
+++ b/src/renderer/components/MapFilter/DataExportDialog/index.js
@@ -0,0 +1,40 @@
+import React from 'react'
+import Dialog from '@material-ui/core/Dialog'
+
+import ViewWrapper from '../ViewWrapper'
+import { Content } from './Content'
+
+export const DataExportDialog = ({
+ filter,
+ getMediaUrl,
+ observations,
+ onClose,
+ open,
+ presets
+}) => (
+
+)
diff --git a/src/renderer/components/MapFilter/MapFilter.js b/src/renderer/components/MapFilter/MapFilter.js
index e68d8008e..530c184c7 100644
--- a/src/renderer/components/MapFilter/MapFilter.js
+++ b/src/renderer/components/MapFilter/MapFilter.js
@@ -14,7 +14,7 @@ import Loading from '../Loading'
import MapStyleProvider from './MapStyleProvider'
import api from '../../new-api'
import MapExportDialog from './MapExportDialog'
-import DataExportDialog from './DataExportDialog'
+import { DataExportDialog } from './DataExportDialog'
import ExportButton from './ExportButton'
import MapView from './MapView'
import ReportView from './ReportView'