-
Notifications
You must be signed in to change notification settings - Fork 16
/
DraftObservationContext.js
135 lines (117 loc) · 3.96 KB
/
DraftObservationContext.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// @flow
import * as React from "react";
import debug from "debug";
import AsyncStorage from "@react-native-community/async-storage";
import type { UseState } from "../types";
import type { ObservationValue } from "./ObservationsContext";
// WARNING: This needs to change if we change the draft data structure
const STORE_KEY = "@MapeoDraft@2";
const log = debug("mapeo:DraftObservationContext");
// save = debounce(this.save, 500, { leading: true });
// Photo from an existing observation that are already saved
export type SavedPhoto = {|
// id of the photo in the Mapeo database
id: string,
type?: "image/jpeg",
// If an image is to be deleted
deleted?: boolean
|};
// Photo added to a draft observation, that has not yet been saved
// It is added to the draft observation as soon as capturing starts, when it
// does not yet have any image associated with it
export type DraftPhoto = {|
// If the photo is still being captured
capturing: boolean,
// uri to a local thumbnail image (this is uploaded to Mapeo server)
thumbnailUri?: string,
// uri to a local preview image
previewUri?: string,
// uri to a local full-resolution image (this is uploaded to Mapeo server)
originalUri?: string,
// If an image is to be deleted
deleted?: boolean,
// If there was any kind of error on image capture
error?: boolean
|};
/**
* A Photo does not become an observation attachment until it is actually saved.
* Only then are deleted attachments removed from disk, so that we can support
* cancellation of any edits. During photo capture a preview is available to
* show in the UI while the full-res photo is saved.
*/
export type Photo = SavedPhoto | DraftPhoto;
export type DraftObservationContextState = {|
photos: Array<SavedPhoto | DraftPhoto>,
value: ObservationValue | null,
photoPromises: Array<
Promise<DraftPhoto> & { signal?: { didCancel: boolean } }
>,
loading: boolean,
observationId?: string
|};
export type DraftObservationContextType = UseState<DraftObservationContextState>;
const defaultContext: DraftObservationContextType = [
{
photos: [],
value: null,
photoPromises: [],
loading: true
},
() => {}
];
const DraftObservationContext = React.createContext<DraftObservationContextType>(
defaultContext
);
type Props = {
children: React.Node
};
export const DraftObservationProvider = ({ children }: Props) => {
const [state, setState] = React.useState(defaultContext[0]);
const contextValue = React.useMemo(() => [state, setState], [state]);
// When the app first mounts, load draft from storage
React.useEffect(() => {
let didCancel = false;
AsyncStorage.getItem(STORE_KEY)
.then(savedDraft => {
if (savedDraft == null || didCancel) return;
const { photos, value, observationId } = JSON.parse(savedDraft);
setState(state => ({
...state,
photos: photos.filter(filterCapturedPhotos),
value,
observationId,
loading: false
}));
})
.catch(e => {
log("Error reading draft from storage", e);
setState(state => ({ ...state, loading: false }));
});
return () => {
didCancel = true;
};
}, []);
// Save draft to local storage on every update
React.useEffect(() => {
const { photos, value, observationId } = state;
AsyncStorage.setItem(
STORE_KEY,
JSON.stringify({ photos, value, observationId })
).catch(e => {
log("Error writing to storage", e);
});
});
return (
<DraftObservationContext.Provider value={contextValue}>
{children}
</DraftObservationContext.Provider>
);
};
export default DraftObservationContext;
// If we crash during photo capture and restore state from async storage, then,
// unfortunately, any photos that did not finish capturing are lost :(
function filterCapturedPhotos(photo: Photo): boolean {
if (typeof photo.id === "string") return true;
if (typeof photo.originalUri === "string") return true;
return false;
}