UNPKG

react-mapfilter

Version:

A React Component for viewing and filtering GeoJSON

247 lines (232 loc) 7.63 kB
import React from 'react' import PropTypes from 'prop-types' import { createSelector } from 'reselect' import Typography from '@material-ui/core/Typography' import { withStyles } from '@material-ui/core/styles' import AutoSizer from 'react-virtualized/dist/commonjs/AutoSizer' import Table from '@material-ui/core/Table' import TableBody from '@material-ui/core/TableBody' import TableCell from '@material-ui/core/TableCell' import TableRow from '@material-ui/core/TableRow' import classNames from 'classnames' import FormattedValue from '../Shared/FormattedValue' import FormattedFieldname from '../Shared/FormattedFieldname' import TextField from '@material-ui/core/TextField' import Input from '@material-ui/core/Input' import MenuItem from '@material-ui/core/MenuItem' import MUISelect from '@material-ui/core/Select' import Select from '../Shared/Select' import MultiSelect from '../Shared/MultiSelect' import makePure from 'recompose/pure' import { FIELD_TYPE_LOCATION, FIELD_TYPE_STRING, FIELD_TYPE_ARRAY, FIELD_TYPE_BOOLEAN, FIELD_TYPE_UUID, FIELD_TYPE_MIXED, FIELD_TYPE_IMAGE, FIELD_TYPE_VIDEO, FIELD_TYPE_MEDIA, FIELD_TYPE_AUDIO, UNDEFINED_KEY } from '../../constants' const styles = { root: { overflow: 'auto' }, row: { verticalAlign: 'top', position: 'relative' }, col1: { padding: '14px 12px 14px 24px' }, col1Text: { fontWeight: 500, textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap', direction: 'rtl' }, col2: { padding: '14px 24px 14px 12px !important', width: '100%', maxWidth: 0, whiteSpace: 'initial', fontSize: '0.875rem' }, col2Edit: { paddingTop: '9px !important', paddingBottom: '9px !important' }, col2TextEdit: { paddingTop: 5, paddingBottom: 5 }, col2TextNoWrap: { overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }, input: { fontSize: 'inherit' } } const shouldNotWrap = { [FIELD_TYPE_UUID]: true, [FIELD_TYPE_MIXED]: true, [FIELD_TYPE_IMAGE]: true, [FIELD_TYPE_VIDEO]: true, [FIELD_TYPE_MEDIA]: true, [FIELD_TYPE_AUDIO]: true } const ValueCell = ({value, type, coordFormat, editMode, classes}) => ( <Typography className={classNames({ [classes.col2TextEdit]: editMode, [classes.col2TextNoWrap]: shouldNotWrap[type] })}> <FormattedValue value={value} type={type} coordFormat={coordFormat} /> </Typography> ) const ValueCellEdit = makePure(({value, type, rowKey, coordFormat, fieldMetadata = {}, onChange, classes}) => { const suggestions = Array.isArray(fieldMetadata.values) && fieldMetadata.values.map(d => d.value) const isDiscreteField = type === FIELD_TYPE_STRING && fieldMetadata.values && fieldMetadata.values.length / fieldMetadata.count < 0.8 if (isDiscreteField) { return <Select value={value} onChange={(e, {newValue, type}) => onChange(rowKey, newValue)} suggestions={suggestions} style={styles.selectField} /> } if (type === FIELD_TYPE_BOOLEAN) { return <MUISelect MenuProps={{MenuListProps: {dense: true}}} fullWidth autoWidth value={value + ''} onChange={(e) => { const newValue = e.target.value === 'true' ? true : e.target.value === 'false' ? false : undefined onChange(rowKey, newValue) }} input={<Input className={classes.input} />} style={styles.muiSelect}> <MenuItem value='undefined' /> <MenuItem value='true'>Yes</MenuItem> <MenuItem value='false'>No</MenuItem> </MUISelect> } if (type === FIELD_TYPE_STRING || type === FIELD_TYPE_MIXED) { return <TextField InputClassName={classes.input} fullWidth multiline value={value} onChange={(e) => onChange(rowKey, e.target.value)} style={styles.textField} /> } if (type === FIELD_TYPE_ARRAY) { return <MultiSelect value={value} onChange={(e, {newValue, type}) => onChange(rowKey, newValue)} suggestions={suggestions} style={styles.selectField} /> } return <ValueCell value={value} type={type} coordFormat={coordFormat} editMode classes={classes} /> }) const FeatureTable = (props) => { const {editMode, classes, coordFormat, fieldAnalysis, onValueChange} = props const rows = getRows(props) return ( <AutoSizer disableHeight> {({ width }) => ( <Table className={classes.root} style={{width: width}}> <TableBody> {rows.map((row, i) => ( <TableRow key={i} className={classes.row} style={{zIndex: rows.length - i}}> <TableCell padding='dense' className={classes.col1} style={{maxWidth: width / 3 - 36}}> <Typography className={classes.col1Text}> <FormattedFieldname fieldname={row.key} /> </Typography> </TableCell> <TableCell padding='dense' className={classNames(classes.col2, {[classes.col2Edit]: editMode})}> {editMode ? <ValueCellEdit value={row.value} type={row.type} rowKey={row.key} coordFormat={coordFormat} classes={classes} onChange={onValueChange} fieldMetadata={fieldAnalysis.properties[row.key]} /> : <ValueCell value={row.value} type={row.type} coordFormat={coordFormat} classes={classes} />} </TableCell> </TableRow> ))} </TableBody> </Table> )} </AutoSizer> ) } FeatureTable.propTypes = { hiddenFields: PropTypes.object.isRequired, fieldOrder: PropTypes.object.isRequired } FeatureTable.defaultProps = { hiddenFields: {}, fieldOrder: {} } // TODO: Does not actually work and memoize anything because props.feature // changes every edit const getRows = createSelector( props => props.feature, props => props.fieldAnalysis, props => props.hiddenFields, props => props.fieldOrder, props => props.editMode, (feature, fieldAnalysis, hiddenFields, fieldOrder, editMode) => { const rows = Object.keys(fieldAnalysis.properties) .map(key => ({ key: key, value: feature.properties[key], type: fieldAnalysis.properties[key].type, visible: !hiddenFields[key] })) .filter(row => editMode || (!hiddenFields[row.key] && (typeof row.value !== 'string' || row.value.length) && typeof row.value !== 'undefined' && row.value !== UNDEFINED_KEY)) if (feature.geometry) { rows.unshift({ key: 'location', value: feature.geometry && feature.geometry.coordinates, type: FIELD_TYPE_LOCATION, visible: !hiddenFields.location }) } // Sort rows by `fieldOrder` from state, if an order is set, if not then sort lexically. return rows.sort((a, b) => { var orderA = typeof fieldOrder[a.key] !== 'undefined' ? fieldOrder[a.key] : Infinity var orderB = typeof fieldOrder[b.key] !== 'undefined' ? fieldOrder[b.key] : Infinity if (orderA === Infinity && orderB === Infinity) { return lexicalSort(a, b) } else { return orderA - orderB } }) } ) function lexicalSort (a, b) { var nameA = a.key.toUpperCase() // ignore upper and lowercase var nameB = b.key.toUpperCase() // ignore upper and lowercase if (nameA < nameB) { return -1 } if (nameA > nameB) { return 1 } // names must be equal return 0 } export default withStyles(styles)(FeatureTable)