1 |
|
2 | import * as React from 'react'
|
3 | import isEqual from 'lodash/isEqual'
|
4 | import createFilterOrig from 'mapeo-entity-filter'
|
5 |
|
6 | import ObservationDialog from './ObservationDialog'
|
7 | import getStats from './stats'
|
8 | import { defaultGetPreset } from './utils/helpers'
|
9 |
|
10 | import type { Observation } from 'mapeo-schema'
|
11 | import type {
|
12 | PresetWithFields,
|
13 | PresetWithAdditionalFields,
|
14 | GetMedia,
|
15 | Filter,
|
16 | GetIconUrl,
|
17 | GetMediaUrl
|
18 | } from './types'
|
19 |
|
20 | export type CommonViewProps = {
|
21 |
|
22 | observations?: Array<Observation>,
|
23 |
|
24 | onUpdateObservation: (observation: Observation) => void,
|
25 | onDeleteObservation: (id: string) => void,
|
26 | |
27 |
|
28 | presets?: PresetWithFields[],
|
29 | getMediaUrl: GetMediaUrl,
|
30 | getIconUrl?: GetIconUrl,
|
31 |
|
32 | filter?: Filter
|
33 | }
|
34 |
|
35 | type 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 |
|
45 | const noop = () => {}
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | const 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 |
|
67 | const 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 |
|
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 |
|
163 | export default WrappedMapView
|
164 |
|
165 |
|
166 | function 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 | }
|