UNPKG

5.25 kBJavaScriptView Raw
1// @flow
2import * as React from 'react'
3import isEqual from 'lodash/isEqual'
4import createFilterOrig from 'mapeo-entity-filter'
5
6import ObservationDialog from './ObservationDialog'
7import getStats from './stats'
8import { defaultGetPreset } from './utils/helpers'
9
10import type { Observation } from 'mapeo-schema'
11import type {
12 PresetWithFields,
13 PresetWithAdditionalFields,
14 GetMedia,
15 Filter,
16 GetIconUrl,
17 GetMediaUrl
18} from './types'
19
20export type CommonViewProps = {
21 /** Array of observations to render */
22 observations?: Array<Observation>,
23 /** Called when an observation is editing/updated */
24 onUpdateObservation: (observation: Observation) => void,
25 onDeleteObservation: (id: string) => void,
26 /** A function called with an observation that should return a matching preset
27 * with field definitions */
28 presets?: PresetWithFields[],
29 getMediaUrl: GetMediaUrl,
30 getIconUrl?: GetIconUrl,
31 /** Filter to apply to observations */
32 filter?: Filter
33}
34
35type Props = {
36 ...$Exact<CommonViewProps>,
37 children: ({
38 filteredObservations: Array<Observation>,
39 onClickObservation: (observationId: string, imageIndex?: number) => void,
40 getPreset: Observation => PresetWithAdditionalFields,
41 getMedia: GetMedia
42 }) => React.Node
43}
44
45const noop = () => {}
46
47// This is a temporary wrapper to compile a filter that defines $preset into a
48// filter that will work with our dataset, which currently uses categoryId to
49// define which preset applies. We will need to improve how this works in the
50// future once we start matching presets like we do with iD
51const createFilter = (filter: Filter | void) => {
52 if (!Array.isArray(filter) || filter[0] !== 'all' || filter.length < 2) {
53 return () => true
54 }
55 const presetFilter = filter.map(subFilter => {
56 if (
57 !Array.isArray(subFilter) ||
58 (subFilter[1] !== '$preset' && !isEqual(subFilter[1], ['$preset']))
59 ) {
60 return subFilter
61 }
62 return [subFilter[0], 'categoryId', ...subFilter.slice(2)]
63 })
64 return createFilterOrig(presetFilter)
65}
66
67const WrappedMapView = ({
68 observations = [],
69 onUpdateObservation = noop,
70 onDeleteObservation = noop,
71 presets = [],
72 getMediaUrl,
73 filter,
74 children,
75 ...otherProps
76}: Props) => {
77 const stats = React.useMemo(() => getStats(observations), [observations])
78 const [editingObservation, setEditingObservation] = React.useState(null)
79 const [
80 editingInitialImageIndex,
81 setEditingInitialImageIndex
82 ] = React.useState()
83
84 const getPresetWithFallback = React.useCallback(
85 (observation: Observation): PresetWithAdditionalFields => {
86 const preset = getPreset(observation, presets)
87 const defaultPreset = defaultGetPreset(observation, stats)
88 if (!preset) return defaultPreset
89 return {
90 ...preset,
91 additionalFields: defaultPreset.additionalFields.filter(
92 // Any fields that are not defined in the preset we show as 'additionalFields'
93 additionalField => {
94 return !preset.fields.find(field => {
95 const fieldKey = Array.isArray(field.key)
96 ? field.key
97 : [field.key]
98 const additionalFieldKey = Array.isArray(additionalField.key)
99 ? additionalField.key
100 : [additionalField.key]
101 return isEqual(fieldKey, additionalFieldKey)
102 })
103 }
104 )
105 }
106 },
107 [presets, stats]
108 )
109
110 const handleObservationClick = React.useCallback(
111 (observationId, imageIndex) => {
112 setEditingInitialImageIndex(imageIndex)
113 setEditingObservation(observations.find(obs => obs.id === observationId))
114 },
115 [observations]
116 )
117
118 const getMedia = React.useCallback(
119 (attachment, { width = 800 } = {}) => {
120 const dpr = window.devicePixelRatio
121 const size =
122 width < 300 * dpr
123 ? 'thumbnail'
124 : width < 1200 * dpr
125 ? 'preview'
126 : 'original'
127 return {
128 src: getMediaUrl(attachment.id, size),
129 type: 'image'
130 }
131 },
132 [getMediaUrl]
133 )
134
135 const filterFunction = React.useMemo(() => createFilter(filter), [filter])
136 const filteredObservations = React.useMemo(
137 () => (filter ? observations.filter(filterFunction) : observations),
138 [observations, filterFunction, filter]
139 )
140
141 return (
142 <>
143 {children({
144 onClickObservation: handleObservationClick,
145 filteredObservations,
146 getPreset: getPresetWithFallback,
147 getMedia
148 })}
149 <ObservationDialog
150 open={!!editingObservation}
151 observation={editingObservation}
152 initialImageIndex={editingInitialImageIndex}
153 getPreset={getPresetWithFallback}
154 getMedia={getMedia}
155 onRequestClose={() => setEditingObservation(false)}
156 onSave={onUpdateObservation}
157 onDelete={onDeleteObservation}
158 />
159 </>
160 )
161}
162
163export default WrappedMapView
164
165// TODO: Update this function to match presets like ID Editor
166function getPreset(
167 observation: Observation,
168 presets: PresetWithFields[]
169): PresetWithFields | void {
170 const tags = observation.tags
171 if (!tags || !tags.categoryId) return
172 const preset = presets.find(preset => preset.id === tags.categoryId)
173 return preset
174}