UNPKG

react-mapfilter

Version:

These components are designed for viewing data in Mapeo. They share a common interface:

523 lines (493 loc) 16.1 kB
import "core-js/modules/es.array.iterator"; import "core-js/modules/web.dom-collections.iterator"; import _extends from "@babel/runtime-corejs3/helpers/extends"; import _objectWithoutPropertiesLoose from "@babel/runtime-corejs3/helpers/objectWithoutPropertiesLoose"; import _Array$isArray from "@babel/runtime-corejs3/core-js-stable/array/is-array"; import _mapInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/map"; import _reduceInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/reduce"; // @flow import React, { useState } from 'react'; import { makeStyles, withStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import TextField from './TextField'; import IconButton from '@material-ui/core/IconButton'; import CloseIcon from '@material-ui/icons/Close'; import Collapse from '@material-ui/core/Collapse'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import Fade from '@material-ui/core/Fade'; import DateFnsUtils from '@date-io/date-fns'; import enLocale from 'date-fns/locale/en-US'; import frLocale from 'date-fns/locale/fr'; import ptLocale from 'date-fns/locale/pt-BR'; import esLocale from 'date-fns/locale/es'; import { MuiPickersUtilsProvider } from '@material-ui/pickers'; import MuiAccordion from '@material-ui/core/Accordion'; import MuiAccordionSummary from '@material-ui/core/AccordionSummary'; import MuiAccordionDetails from '@material-ui/core/AccordionDetails'; import DialogActions from '@material-ui/core/DialogActions'; import MuiDialogContent from '@material-ui/core/DialogContent'; import DialogContentText from '@material-ui/core/DialogContentText'; import DialogTitle from '@material-ui/core/DialogTitle'; import MenuIcon from '@material-ui/icons/MoreVert'; import Menu from '@material-ui/core/Menu'; import MenuItem from '@material-ui/core/MenuItem'; import { Typography } from '@material-ui/core'; import { useIntl, FormattedMessage, defineMessages } from 'react-intl'; import clone from 'clone-deep'; import FeatureHeader from '../internal/FeatureHeader'; import MediaCarousel from './MediaCarousel'; import { defaultGetPreset } from '../utils/helpers'; import { get, set } from '../utils/get_set'; import Field from './Field'; /*:: import type { Observation } from 'mapeo-schema'*/ /*:: import type { PresetWithAdditionalFields, GetMedia, Attachment, Key } from '../types'*/ const m = defineMessages({ confirmCloseTitle: { "id": "I3F5WA==", "defaultMessage": 'Close without saving changes?' }, confirmCloseDescription: { "id": "qMB8GQ==", "defaultMessage": 'You have made some changes, closing without saving will loose the changes you make' }, confirmCloseButtonConfirm: { "id": "yIQFKA==", "defaultMessage": 'Discard changes' }, confirmCloseButtonCancel: { "id": "FF0Vyg==", "defaultMessage": 'Cancel' }, confirmDeleteTitle: { "id": "ZjTMNg==", "defaultMessage": 'Delete observation?' }, confirmDeleteButtonConfirm: { "id": "xxS3fw==", "defaultMessage": 'Yes, delete' }, confirmDeleteButtonCancel: { "id": "qdS9PA==", "defaultMessage": 'Cancel' }, // Header for section that includes the additional details for an observation detailsHeader: { "id": "uHtAUQ==", "defaultMessage": 'Details' }, // Header for section with additional fields that are not defined in the preset additionalHeader: { "id": "7EPUxA==", "defaultMessage": 'Additional data' }, // Cancel button once observation has been edited cancelEditButton: { "id": "bjpM6A==", "defaultMessage": 'Cancel' }, // Save edit button saveEditButton: { "id": "3StVcg==", "defaultMessage": 'Save' }, // Menu item to delete an observation deleteObservationMenuItem: { "id": "o+27Wg==", "defaultMessage": 'Delete observation' } }); /*:: type ImageMediaItem = { src: string, type: 'image' }*/ /*:: type Props = { open?: boolean, onRequestClose: () => void, onDelete: (id: string) => void, observation: Observation, // The initial image to show in the media carousel initialImageIndex?: number, onSave: (observation: Observation) => void, getPreset?: Observation => PresetWithAdditionalFields | void, /** * For a given attachment, return `src` and `type` *-/ getMedia: GetMedia }*/ const localeMap = { en: enLocale, fr: frLocale, pt: ptLocale, es: esLocale }; function getLocaleData(locale) { if (!locale) return localeMap.en; return localeMap[locale] || localeMap[locale.split('-')[0]] || localeMap.en; } function defaultGetMedia({ type, id } /*: Attachment*/ ) { if (type && type.split('/')[0] !== 'image') return; return { type: 'image', src: id }; } const ConfirmCloseDialog = ({ open, onCancel, onConfirm }) => /*#__PURE__*/React.createElement(Dialog, { disableBackdropClick: true, open: open, onClose: onCancel, "aria-labelledby": "close-dialog-title", "aria-describedby": "close-dialog-description" }, /*#__PURE__*/React.createElement(DialogTitle, { id: "close-dialog-title" }, /*#__PURE__*/React.createElement(FormattedMessage, m.confirmCloseTitle)), /*#__PURE__*/React.createElement(MuiDialogContent, null, /*#__PURE__*/React.createElement(DialogContentText, { id: "close-dialog-description" }, /*#__PURE__*/React.createElement(FormattedMessage, m.confirmCloseDescription))), /*#__PURE__*/React.createElement(DialogActions, null, /*#__PURE__*/React.createElement(Button, { onClick: onCancel, color: "primary" }, /*#__PURE__*/React.createElement(FormattedMessage, m.confirmCloseButtonCancel)), /*#__PURE__*/React.createElement(Button, { onClick: onConfirm, color: "primary", autoFocus: true }, /*#__PURE__*/React.createElement(FormattedMessage, m.confirmCloseButtonConfirm)))); const ConfirmDeleteDialog = ({ open, onCancel, onConfirm }) => /*#__PURE__*/React.createElement(Dialog, { disableBackdropClick: true, open: open, onClose: onCancel, "aria-labelledby": "delete-dialog-title" }, /*#__PURE__*/React.createElement(DialogTitle, { id: "delete-dialog-title" }, /*#__PURE__*/React.createElement(FormattedMessage, m.confirmDeleteTitle)), /*#__PURE__*/React.createElement(DialogActions, null, /*#__PURE__*/React.createElement(Button, { onClick: onCancel, color: "primary" }, /*#__PURE__*/React.createElement(FormattedMessage, m.confirmDeleteButtonCancel)), /*#__PURE__*/React.createElement(Button, { onClick: onConfirm, color: "primary", autoFocus: true }, /*#__PURE__*/React.createElement(FormattedMessage, m.confirmDeleteButtonConfirm)))); const Accordion = withStyles({ root: { border: '1px solid rgba(0, 0, 0, .125)', boxShadow: 'none', '&:before': { display: 'none' }, '&$expanded': { margin: 'auto' } }, expanded: {} })(MuiAccordion); const AccordionSummary = withStyles({ root: { '&$expanded': { minHeight: 48 } }, content: { '&$expanded': { margin: '12px 0' } }, expanded: {} })(MuiAccordionSummary); const AccordionDetails = withStyles({ root: { display: 'flex', flexDirection: 'column', flexWrap: 'wrap', padding: '0 21px 10px 21px', '& > div:first-child': { marginTop: 10 } } })(MuiAccordionDetails); const ObservationActions = ({ onDeleteClick }) => { const [anchorEl, setAnchorEl] = React.useState(null); const [confirm, setConfirm] = useState(null); const handleOpenClick = event => { setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; const createHandleItemClick = (action /*: () => any*/ = () => {}) => () => { // Can't setState to a function setConfirm(state => didConfirm => { setConfirm(null); if (didConfirm) action(); handleClose(); }); }; return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(IconButton, { "aria-controls": "simple-menu", "aria-haspopup": "true", onClick: handleOpenClick }, /*#__PURE__*/React.createElement(MenuIcon, null)), /*#__PURE__*/React.createElement(Menu, { id: "simple-menu", anchorEl: anchorEl, keepMounted: true, open: Boolean(anchorEl), onClose: handleClose }, /*#__PURE__*/React.createElement(MenuItem, { onClick: createHandleItemClick(onDeleteClick) }, /*#__PURE__*/React.createElement(FormattedMessage, m.deleteObservationMenuItem))), /*#__PURE__*/React.createElement(ConfirmDeleteDialog, { open: !!confirm, onConfirm: () => confirm && confirm(true), onCancel: () => confirm && confirm(false) })); }; const DialogContent = ({ open = false, onRequestClose, onSave, onDelete, observation, initialImageIndex, getPreset = defaultGetPreset, getMedia = defaultGetMedia } /*: { ...$Exact<Props>, onRequestClose: (shouldConfirm: boolean) => void }*/ ) => { var _context, _context2, _context3; const cx = useStyles(); const [values, setValues] = useState(observation.tags ? clone(observation.tags) : {}); const [dirty, setDirty] = useState(false); const { locale } = useIntl(); const handleSave = () => { onSave(set(observation, 'tags', values)); onRequestClose(false); }; const handleRequestClose = () => { // Ask for confirmation if form is dirty onRequestClose(dirty); }; const handleDeleteClick = () => { onRequestClose(false); onDelete(observation.id); }; const handleChange = (key /*: Key*/ , newValue /*: any*/ ) => { setDirty(true); setValues(set(values, key, newValue)); }; const preset = getPreset(observation) || {}; const coords = observation.lat != null && observation.lon != null ? { latitude: observation.lat, longitude: observation.lon } : undefined; const descriptionKey = values.note ? 'note' : 'notes'; const mediaItems /*: ImageMediaItem[]*/ = _reduceInstanceProperty(_context = observation.attachments || []).call(_context, (acc, cur) => { const item = getMedia(cur, { width: 800, height: 600 }); // $FlowFixMe - need to fix type refinement here if (item && item.type === 'image') acc.push(item); return acc; }, []); return /*#__PURE__*/React.createElement(MuiPickersUtilsProvider, { utils: DateFnsUtils, locale: getLocaleData(locale) }, /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(IconButton, { className: cx.closeButton, color: "inherit", onClick: handleRequestClose, "aria-label": "Close" }, /*#__PURE__*/React.createElement(CloseIcon, null)), mediaItems.length > 0 && /*#__PURE__*/React.createElement("div", { className: cx.mediaWrapper }, /*#__PURE__*/React.createElement(MediaCarousel, { items: mediaItems, initialIndex: initialImageIndex, className: cx.media })), /*#__PURE__*/React.createElement(FeatureHeader, { icon: preset.icon, name: preset.name, coords: coords, createdAt: new Date(observation.created_at), action: /*#__PURE__*/React.createElement(ObservationActions, { onDeleteClick: handleDeleteClick }) }), /*#__PURE__*/React.createElement(TextField, { value: values[descriptionKey], onChange: newValue => handleChange(descriptionKey, newValue), multiline: true, margin: "dense", label: "Description", className: cx.descriptionField }), preset.fields.length > 0 && /*#__PURE__*/React.createElement(Accordion, { TransitionProps: { unmountOnExit: true } }, /*#__PURE__*/React.createElement(AccordionSummary, { expandIcon: /*#__PURE__*/React.createElement(ExpandMoreIcon, null), "aria-controls": "panel1a-content", id: "panel1a-header" }, /*#__PURE__*/React.createElement(Typography, { component: "h2", className: cx.sectionHeading }, /*#__PURE__*/React.createElement(FormattedMessage, m.detailsHeader))), /*#__PURE__*/React.createElement(AccordionDetails, null, _mapInstanceProperty(_context2 = preset.fields).call(_context2, field => /*#__PURE__*/React.createElement(Field, { key: field.id, field: field, value: get(values, field.key), onChange: handleChange })))), _Array$isArray(preset.additionalFields) && preset.additionalFields.length > 0 && /*#__PURE__*/React.createElement(Accordion, null, /*#__PURE__*/React.createElement(AccordionSummary, { expandIcon: /*#__PURE__*/React.createElement(ExpandMoreIcon, null), "aria-controls": "panel1a-content", id: "panel1a-header" }, /*#__PURE__*/React.createElement(Typography, { component: "h2", className: cx.sectionHeading }, /*#__PURE__*/React.createElement(FormattedMessage, m.additionalHeader))), /*#__PURE__*/React.createElement(AccordionDetails, null, _mapInstanceProperty(_context3 = preset.additionalFields).call(_context3, field => /*#__PURE__*/React.createElement(Field, { key: field.id, field: field, value: get(values, field.key), onChange: handleChange })))), /*#__PURE__*/React.createElement(Fade, { in: dirty }, /*#__PURE__*/React.createElement(Collapse, { in: dirty, className: cx.actions }, /*#__PURE__*/React.createElement(Button, { color: "default", variant: "contained", className: cx.button, onClick: handleRequestClose }, /*#__PURE__*/React.createElement(FormattedMessage, m.cancelEditButton)), /*#__PURE__*/React.createElement(Button, { color: "primary", variant: "contained", className: cx.button, onClick: handleSave }, /*#__PURE__*/React.createElement(FormattedMessage, m.saveEditButton)))))); }; const ObservationDialog = (_ref) => { let { open, onRequestClose } /*: Props*/ = _ref, otherProps = _objectWithoutPropertiesLoose(_ref, ["open", "onRequestClose"]); const [confirm, setConfirm] = useState(null); const handleRequestClose = shouldConfirm => { if (!shouldConfirm) { setConfirm(null); onRequestClose(); return; } setConfirm(state => didConfirm => { setConfirm(null); if (didConfirm) onRequestClose(); }); }; return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Dialog, { disableBackdropClick: true, open: open, onClose: handleRequestClose, scroll: "body", fullWidth: true, maxWidth: "sm" }, open && /*#__PURE__*/React.createElement(DialogContent, _extends({}, otherProps, { onRequestClose: handleRequestClose }))), /*#__PURE__*/React.createElement(ConfirmCloseDialog, { open: !!confirm, onConfirm: () => confirm && confirm(true), onCancel: () => confirm && confirm(false) })); }; export default ObservationDialog; const useStyles = makeStyles(theme => ({ closeButton: { position: 'absolute', zIndex: 999, left: 0, top: 0, color: 'white', backgroundColor: 'rgba(0,0,0,0.4)', borderRadius: '0 0 4px 0', '&:hover': { backgroundColor: 'rgba(0,0,0,0.5)' } }, descriptionField: { boxSizing: 'border-box', margin: '0 10px 13.5px 10px', width: 'calc(100% - 20px)', '& .MuiInputBase-inputMultiline': theme.typography.body1, '& .MuiInputLabel-root': { opacity: 0, transition: 'opacity 200ms cubic-bezier(0.0, 0, 0.2, 1)' }, '&:hover .MuiInputLabel-root, & .MuiInputLabel-root.Mui-focused': { opacity: 1 }, '& .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(0,0,0,0)' } }, sectionHeading: { fontWeight: 500 }, mediaWrapper: { width: '100%', paddingTop: '75%', position: 'relative', height: 0 }, media: { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }, details: { flexDirection: 'column', flex: 1, minWidth: 320, flexBasis: '33%', backgroundColor: 'white', overflowY: 'scroll' }, actions: { margin: '0 10px', textAlign: 'right' }, button: { marginLeft: theme.spacing(1), marginTop: 14, marginBottom: 14 } })); //# sourceMappingURL=ObservationDialog.js.map