react-mapfilter
Version:
These components are designed for viewing data in Mapeo. They share a common interface:
261 lines (243 loc) • 7.01 kB
JavaScript
// @flow
import React, { useState, useMemo } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import getScrollBarWidth from 'get-scrollbar-width'
import {
AutoSizer,
List,
CellMeasurer,
CellMeasurerCache
} from 'react-virtualized'
import type { Observation } from 'mapeo-schema'
// import ReportFeature from './ReportFeature'
import ReportPageContent from './ReportPageContent'
import ReportPaper from './ReportPaper'
import MapView from '../MapView/MapViewContent'
import { cm, inch } from '../utils/dom'
import { getLastImage, defaultGetPreset } from '../utils/helpers'
import type { PaperSize, CameraOptions, CommonViewContentProps } from '../types'
export type ReportViewContentProps = {
/** Called with
* [CameraOptions](https://docs.mapbox.com/mapbox-gl-js/api/#cameraoptions)
* with properties `center`, `zoom`, `bearing`, `pitch` */
onMapMove?: CameraOptions => any,
/** Initial position of the map - an object with properties `center`, `zoom`,
* `bearing`, `pitch`. If this is not set then the map will by default zoom to
* the bounds of the observations. If you are going to unmount and re-mount
* this component (e.g. within tabs) then you will want to use onMove to store
* the position in state, and pass it as initialPosition for when the map
* re-mounts. */
initialMapPosition?: $Shape<CameraOptions>,
/** Mapbox access token */
mapboxAccessToken: string,
/** Mapbox style url */
mapStyle?: any
}
type Props = {
...$Exact<ReportViewContentProps>,
...$Exact<CommonViewContentProps>,
/** Paper size for report */
paperSize?: PaperSize,
/** Render for printing (for screen display only visible observations are
* rendered, for performance reasons) */
print?: boolean
}
const BORDER_SIZE = 0.5 * inch()
const noop = () => {}
const ReportViewContent = ({
observations,
onClick = () => {},
onMapMove,
initialMapPosition,
getPreset = defaultGetPreset,
getMedia,
paperSize = 'a4',
print = false,
mapboxAccessToken,
mapStyle
}: Props) => {
const classes = useStyles()
const [mapPosition, setMapPosition] = useState()
const scrollbarWidth = useMemo(() => getScrollBarWidth(), [])
const cacheRef = React.useRef(
new CellMeasurerCache({
fixedWidth: true,
minHeight: 200
})
)
const cache = cacheRef.current
const paperWidthPx = paperSize === 'a4' ? 21 * cm() : 8.5 * inch()
function getLastImageUrl(observation: Observation): string | void {
const lastImageAttachment = getLastImage(observation)
if (!lastImageAttachment) return
const media = getMedia(lastImageAttachment, {
width: paperWidthPx - 2 * BORDER_SIZE,
height: paperWidthPx - 2 * BORDER_SIZE
})
if (media) return media.src
}
function renderPage({ index, key, style, parent }) {
return (
<CellMeasurer
cache={cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}>
<div style={style}>
{index === 0
? renderMapPage({ index, key })
: renderFeaturePage({ index: index - 1, key })}
</div>
</CellMeasurer>
)
}
function renderMapPage({ key }: { key?: string } = {}) {
return (
<ReportPaper
key={key}
paperSize={paperSize}
classes={{ content: classes.paperContentMap }}>
<MapView
mapStyle={mapStyle}
onClick={noop}
getPreset={getPreset}
observations={observations}
getMedia={getMedia}
initialMapPosition={initialMapPosition || mapPosition}
onMapMove={onMapMove || setMapPosition}
mapboxAccessToken={mapboxAccessToken}
print
/>
</ReportPaper>
)
}
function renderFeaturePage({ index, key }: { index: number, key?: string }) {
const observation = observations[index]
const coords =
typeof observation.lon === 'number' && typeof observation.lat === 'number'
? {
longitude: observation.lon,
latitude: observation.lat
}
: undefined
const createdAt =
typeof observation.created_at === 'string'
? new Date(observation.created_at)
: undefined
const preset = getPreset(observation) || {}
const fields = preset.fields.concat(preset.additionalFields)
return (
<ReportPaper
key={key}
paperSize={paperSize}
onClick={() => onClick(observation.id)}>
<ReportPageContent
name={typeof preset.name === 'string' ? preset.name : undefined}
createdAt={createdAt}
coords={coords}
fields={fields}
imageSrc={getLastImageUrl(observation)}
tags={observation.tags}
paperSize={paperSize}
/>
</ReportPaper>
)
}
function renderVirtualList() {
return (
<AutoSizer>
{({ height, width }) => (
<List
className={classes.reportWrapper + ' ' + classes[paperSize]}
containerStyle={{ overflowX: 'scroll' }}
height={height}
width={width}
rowCount={observations.length + 1 /* for additional map page */}
rowRenderer={renderPage}
deferredMeasurementCache={cache}
rowHeight={cache.rowHeight}
overscanRowCount={3}
estimatedRowSize={
200 /* paperSize === 'a4' ? 29.7 * cm() : 11 * inch()} */
}
/>
)}
</AutoSizer>
)
}
function renderPrintList() {
return (
<React.Fragment>
{renderMapPage()}
{observations.map((_, index) =>
renderFeaturePage({ index, key: index + '' })
)}
</React.Fragment>
)
}
return (
<div className={classes.root}>
<div className={classes.scrollWrapper}>
{print ? renderPrintList() : renderVirtualList()}
</div>
</div>
)
}
export default ReportViewContent
const useStyles = makeStyles({
root: {
flex: 1,
display: 'flex',
flexDirection: 'column',
backgroundColor: 'rgba(236, 236, 236, 1)',
'@media only print': {
width: 'auto',
height: 'auto',
position: 'static',
backgroundColor: 'inherit',
display: 'block'
}
},
reportWrapper: {
'@media only print': {
padding: 0,
minWidth: 'auto'
}
},
paperContentMap: {
display: 'flex'
},
letter: {
'&$reportWrapper': {
// minWidth: 8.5 * inch()
}
},
a4: {
'&$reportWrapper': {
// minWidth: 21 * cm()
}
},
scrollWrapper: {
flex: '1 1 auto',
overflow: 'hidden',
'@media only print': {
overflow: 'auto',
flex: 'initial',
position: 'static'
}
},
'@global': {
'@media only print': {
tr: {
pageBreakInside: 'avoid'
},
'.d-print-none': {
display: 'none'
},
'.mapboxgl-ctrl-group, .mapboxgl-ctrl-attrib': {
display: 'none'
}
}
}
})