@comapeo/core-react 7.2.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/commonjs/contexts/ClientApi.d.ts +8 -6
- package/dist/commonjs/contexts/ClientApi.js +15 -0
- package/dist/commonjs/contexts/ComapeoCore.d.ts +8 -0
- package/dist/commonjs/contexts/ComapeoCore.js +9 -0
- package/dist/commonjs/contexts/MapServer.d.ts +69 -0
- package/dist/commonjs/contexts/MapServer.js +92 -0
- package/dist/commonjs/contexts/MapShares.d.ts +52 -0
- package/dist/commonjs/contexts/MapShares.js +74 -0
- package/dist/commonjs/hooks/client.d.ts +3 -1
- package/dist/commonjs/hooks/documents.d.ts +3 -1
- package/dist/commonjs/hooks/invites.d.ts +0 -16
- package/dist/commonjs/hooks/invites.js +0 -32
- package/dist/commonjs/hooks/maps.d.ts +460 -3
- package/dist/commonjs/hooks/maps.js +261 -4
- package/dist/commonjs/hooks/projects.d.ts +17 -3
- package/dist/commonjs/index.d.ts +6 -3
- package/dist/commonjs/index.js +20 -6
- package/dist/commonjs/lib/http.d.ts +45 -0
- package/dist/commonjs/lib/http.js +103 -0
- package/dist/commonjs/lib/map-shares-stores.d.ts +80 -0
- package/dist/commonjs/lib/map-shares-stores.js +299 -0
- package/dist/commonjs/lib/presets.d.ts +3 -1
- package/dist/commonjs/lib/react-query/client.d.ts +6 -2
- package/dist/commonjs/lib/react-query/documents.d.ts +7 -3
- package/dist/commonjs/lib/react-query/invites.d.ts +6 -2
- package/dist/commonjs/lib/react-query/maps.d.ts +67 -19
- package/dist/commonjs/lib/react-query/maps.js +113 -11
- package/dist/commonjs/lib/react-query/mutation-result.d.ts +8 -0
- package/dist/commonjs/lib/react-query/mutation-result.js +22 -0
- package/dist/commonjs/lib/react-query/projects.d.ts +5 -1
- package/dist/commonjs/lib/react-query/projects.js +1 -6
- package/dist/commonjs/lib/sync.d.ts +3 -1
- package/dist/commonjs/lib/types.d.ts +3 -1
- package/dist/esm/contexts/ClientApi.d.ts +8 -6
- package/dist/esm/contexts/ClientApi.js +16 -1
- package/dist/esm/contexts/ComapeoCore.d.ts +8 -0
- package/dist/esm/contexts/ComapeoCore.js +6 -0
- package/dist/esm/contexts/MapServer.d.ts +69 -0
- package/dist/esm/contexts/MapServer.js +86 -0
- package/dist/esm/contexts/MapShares.d.ts +52 -0
- package/dist/esm/contexts/MapShares.js +65 -0
- package/dist/esm/hooks/client.d.ts +3 -1
- package/dist/esm/hooks/documents.d.ts +3 -1
- package/dist/esm/hooks/invites.d.ts +0 -16
- package/dist/esm/hooks/invites.js +1 -32
- package/dist/esm/hooks/maps.d.ts +460 -3
- package/dist/esm/hooks/maps.js +252 -6
- package/dist/esm/hooks/projects.d.ts +17 -3
- package/dist/esm/index.d.ts +6 -3
- package/dist/esm/index.js +5 -3
- package/dist/esm/lib/http.d.ts +45 -0
- package/dist/esm/lib/http.js +98 -0
- package/dist/esm/lib/map-shares-stores.d.ts +80 -0
- package/dist/esm/lib/map-shares-stores.js +291 -0
- package/dist/esm/lib/presets.d.ts +3 -1
- package/dist/esm/lib/react-query/client.d.ts +6 -2
- package/dist/esm/lib/react-query/documents.d.ts +7 -3
- package/dist/esm/lib/react-query/invites.d.ts +6 -2
- package/dist/esm/lib/react-query/maps.d.ts +67 -19
- package/dist/esm/lib/react-query/maps.js +109 -12
- package/dist/esm/lib/react-query/mutation-result.d.ts +8 -0
- package/dist/esm/lib/react-query/mutation-result.js +19 -0
- package/dist/esm/lib/react-query/projects.d.ts +5 -1
- package/dist/esm/lib/react-query/projects.js +1 -6
- package/dist/esm/lib/sync.d.ts +3 -1
- package/dist/esm/lib/types.d.ts +3 -1
- package/docs/API.md +567 -60
- package/package.json +40 -29
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { CUSTOM_MAP_ID } from '@comapeo/map-server/constants.js';
|
|
2
|
+
import { errors } from '@comapeo/map-server/errors.js';
|
|
3
|
+
import ensureError from 'ensure-error';
|
|
4
|
+
import { invalidateMapQueries } from './react-query/maps.js';
|
|
5
|
+
// ============================================
|
|
6
|
+
// ACTION OPTIONS TYPES
|
|
7
|
+
// These are defined here so that VSCode tooltips work for the mutation
|
|
8
|
+
// functions - if the documentation comments are added inline for the store
|
|
9
|
+
// actions, they do not show for the mutate() function in hooks.
|
|
10
|
+
// ============================================
|
|
11
|
+
/** Known reasons for declining a map share */
|
|
12
|
+
export const DeclineReason = {
|
|
13
|
+
/** User explicitly rejected the map share */
|
|
14
|
+
user_rejected: 'user_rejected',
|
|
15
|
+
/** Device storage is full */
|
|
16
|
+
storage_full: 'storage_full',
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* This is like a mini zustand store. Keeping the map shares in an external
|
|
20
|
+
* store avoids unnecessary re-renders of the entire app when map shares are
|
|
21
|
+
* updated (e.g. if we kept the state in the context), and it avoids potential
|
|
22
|
+
* tearing issues with concurrent rendering.
|
|
23
|
+
*
|
|
24
|
+
* This is the base store for both sent and received map shares, since they
|
|
25
|
+
* share a lot of logic around managing the map shares and monitoring their
|
|
26
|
+
* status.
|
|
27
|
+
*/
|
|
28
|
+
function createMapSharesStore({ mapServerApi }) {
|
|
29
|
+
let mapShares = [];
|
|
30
|
+
const listeners = new Set();
|
|
31
|
+
function update(shareId, stateUpdate) {
|
|
32
|
+
const index = mapShares.findIndex((s) => s.shareId === shareId);
|
|
33
|
+
const existing = mapShares[index];
|
|
34
|
+
if (!existing) {
|
|
35
|
+
throw new errors.MAP_SHARE_NOT_FOUND(`Map share with id ${shareId} not found`);
|
|
36
|
+
}
|
|
37
|
+
assertValidStatusTransition(existing.status, stateUpdate.status);
|
|
38
|
+
mapShares[index] = { ...existing, ...stateUpdate };
|
|
39
|
+
const isDownloadProgressUpdate = stateUpdate.status === 'downloading' && existing.status === 'downloading';
|
|
40
|
+
// IMPORTANT: For download progress updates, the store state is mutated, so
|
|
41
|
+
// maintains Object.is equality. This means that components listening to the
|
|
42
|
+
// store state without a selector _will not update_ when download progress
|
|
43
|
+
// updates. However, all other updates will result in a re-render, and using
|
|
44
|
+
// a selector to listen to an individual map share will also update during
|
|
45
|
+
// download progress.
|
|
46
|
+
if (!isDownloadProgressUpdate) {
|
|
47
|
+
mapShares = [...mapShares];
|
|
48
|
+
}
|
|
49
|
+
emit();
|
|
50
|
+
}
|
|
51
|
+
function add(mapShare) {
|
|
52
|
+
mapShares = [...mapShares, mapShare];
|
|
53
|
+
emit();
|
|
54
|
+
}
|
|
55
|
+
function get(shareId) {
|
|
56
|
+
const mapShare = mapShares.find((share) => share.shareId === shareId);
|
|
57
|
+
if (!mapShare) {
|
|
58
|
+
throw new errors.MAP_SHARE_NOT_FOUND(`Map share with id ${shareId} not found`);
|
|
59
|
+
}
|
|
60
|
+
return mapShare;
|
|
61
|
+
}
|
|
62
|
+
function handleError(shareId, cause) {
|
|
63
|
+
const error = ensureError(cause);
|
|
64
|
+
const errorCode = 'code' in error ? String(error.code) : 'UNKNOWN_ERROR';
|
|
65
|
+
try {
|
|
66
|
+
update(shareId, {
|
|
67
|
+
status: 'error',
|
|
68
|
+
error: {
|
|
69
|
+
message: error.message,
|
|
70
|
+
code: errorCode,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
// TODO: log errors with Sentry
|
|
76
|
+
console.error(`Failed to update map share ${shareId} with error ${errorCode}:`, e);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function monitor(mapShareId, path) {
|
|
80
|
+
// TODO: add a timeout in case the download stalls and never completes
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
const es = mapServerApi.createEventSource({
|
|
83
|
+
url: path,
|
|
84
|
+
onMessage({ data }) {
|
|
85
|
+
try {
|
|
86
|
+
const stateUpdate = JSON.parse(data);
|
|
87
|
+
update(mapShareId, stateUpdate);
|
|
88
|
+
if (isFinalStatus(stateUpdate.status)) {
|
|
89
|
+
es.close();
|
|
90
|
+
resolve(stateUpdate);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
// NB: Don't handleError here - because we optimistically update the
|
|
95
|
+
// status, some of the updates from the event source will throw for
|
|
96
|
+
// being an invalid status transition, but we can just ignore those
|
|
97
|
+
// errors.
|
|
98
|
+
// TODO: Custom errors for status transitions, and only ignore those
|
|
99
|
+
es.close();
|
|
100
|
+
reject(e);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function emit() {
|
|
107
|
+
listeners.forEach((l) => l());
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
subscribe(listener) {
|
|
111
|
+
listeners.add(listener);
|
|
112
|
+
return () => listeners.delete(listener);
|
|
113
|
+
},
|
|
114
|
+
getSnapshot() {
|
|
115
|
+
return mapShares;
|
|
116
|
+
},
|
|
117
|
+
update,
|
|
118
|
+
add,
|
|
119
|
+
get,
|
|
120
|
+
handleError,
|
|
121
|
+
monitor,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Store and actions for received map shares.
|
|
126
|
+
*/
|
|
127
|
+
export function createReceivedMapSharesStore({ clientApi, mapServerApi, queryClient, }) {
|
|
128
|
+
const { subscribe, getSnapshot, update, add, get, handleError, monitor } = createMapSharesStore({ mapServerApi });
|
|
129
|
+
// Tracks downloads in progress so they can be aborted. Currently there is no
|
|
130
|
+
// cleanup, but this is unlikely to be an issue in practice.
|
|
131
|
+
const downloads = new Map();
|
|
132
|
+
clientApi.on('map-share', (mapShare) => {
|
|
133
|
+
add({ ...mapShare, status: 'pending' });
|
|
134
|
+
});
|
|
135
|
+
const actions = {
|
|
136
|
+
async download({ shareId }) {
|
|
137
|
+
const mapShare = get(shareId);
|
|
138
|
+
update(shareId, { status: 'downloading', bytesDownloaded: 0 });
|
|
139
|
+
try {
|
|
140
|
+
const downloadIdPromise = mapServerApi
|
|
141
|
+
.post(`downloads`, {
|
|
142
|
+
json: {
|
|
143
|
+
senderDeviceId: mapShare.senderDeviceId,
|
|
144
|
+
shareId: mapShare.shareId,
|
|
145
|
+
mapShareUrls: mapShare.mapShareUrls,
|
|
146
|
+
estimatedSizeBytes: mapShare.estimatedSizeBytes,
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
.json()
|
|
150
|
+
.then(({ downloadId }) => downloadId);
|
|
151
|
+
// Not strictly necessary, because the `await downloadIdPromise` in the
|
|
152
|
+
// same tick below ensures that this is handled, but this protects
|
|
153
|
+
// against a refactor which could add another async function between
|
|
154
|
+
// setting the promise on the map and awaiting the downloadIdPromise,
|
|
155
|
+
// which would result in an unhandled rejection without this.
|
|
156
|
+
downloadIdPromise.catch(noop);
|
|
157
|
+
downloads.set(shareId, downloadIdPromise);
|
|
158
|
+
const downloadId = await downloadIdPromise;
|
|
159
|
+
monitor(shareId, `downloads/${downloadId}/events`)
|
|
160
|
+
.then((stateUpdate) => {
|
|
161
|
+
downloads.delete(shareId);
|
|
162
|
+
// Invalidate map queries when download completes to trigger reload of map
|
|
163
|
+
if (stateUpdate.status === 'completed') {
|
|
164
|
+
return invalidateMapQueries(queryClient, {
|
|
165
|
+
mapId: mapShare.mapId,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
.catch(noop);
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
downloads.delete(shareId);
|
|
173
|
+
handleError(shareId, e);
|
|
174
|
+
throw e;
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
async decline({ shareId, reason }) {
|
|
178
|
+
const mapShare = get(shareId);
|
|
179
|
+
update(shareId, { status: 'declined', reason });
|
|
180
|
+
try {
|
|
181
|
+
await mapServerApi.post(`mapShares/${shareId}/decline`, {
|
|
182
|
+
json: {
|
|
183
|
+
senderDeviceId: mapShare.senderDeviceId,
|
|
184
|
+
mapShareUrls: mapShare.mapShareUrls,
|
|
185
|
+
reason,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
handleError(shareId, e);
|
|
191
|
+
throw e;
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
async abort({ shareId }) {
|
|
195
|
+
update(shareId, { status: 'aborted' });
|
|
196
|
+
try {
|
|
197
|
+
const downloadId = await downloads.get(shareId);
|
|
198
|
+
if (!downloadId) {
|
|
199
|
+
throw new errors.DOWNLOAD_NOT_FOUND(`No download in progress for map share with id ${shareId}`);
|
|
200
|
+
}
|
|
201
|
+
await mapServerApi.post(`downloads/${downloadId}/abort`);
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
handleError(shareId, e);
|
|
205
|
+
throw e;
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
downloads.delete(shareId);
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
subscribe,
|
|
214
|
+
getSnapshot,
|
|
215
|
+
actions,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Store and actions for sent map share.
|
|
220
|
+
*/
|
|
221
|
+
export function createSentMapSharesStore({ clientApi, mapServerApi, }) {
|
|
222
|
+
const { subscribe, getSnapshot, update, add, handleError, monitor } = createMapSharesStore({ mapServerApi });
|
|
223
|
+
const actions = {
|
|
224
|
+
async createAndSend({ projectId, receiverDeviceId, mapId = CUSTOM_MAP_ID, }) {
|
|
225
|
+
const mapShare = await mapServerApi
|
|
226
|
+
.post('mapShares', {
|
|
227
|
+
json: { receiverDeviceId, mapId },
|
|
228
|
+
})
|
|
229
|
+
.json();
|
|
230
|
+
try {
|
|
231
|
+
const project = await clientApi.getProject(projectId);
|
|
232
|
+
await project.$sendMapShare(mapShare);
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
await mapServerApi.post(`mapShares/${mapShare.shareId}/cancel`);
|
|
236
|
+
throw e;
|
|
237
|
+
}
|
|
238
|
+
add(mapShare);
|
|
239
|
+
monitor(mapShare.shareId, `mapShares/${mapShare.shareId}/events`).catch(noop);
|
|
240
|
+
return mapShare;
|
|
241
|
+
},
|
|
242
|
+
async cancel({ shareId }) {
|
|
243
|
+
update(shareId, { status: 'canceled' });
|
|
244
|
+
try {
|
|
245
|
+
await mapServerApi.post(`mapShares/${shareId}/cancel`);
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
handleError(shareId, e);
|
|
249
|
+
throw e;
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
return {
|
|
254
|
+
subscribe,
|
|
255
|
+
getSnapshot,
|
|
256
|
+
actions,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const allowedStatusTransitions = {
|
|
260
|
+
pending: ['pending', 'downloading', 'declined', 'canceled', 'error'],
|
|
261
|
+
downloading: ['downloading', 'aborted', 'completed', 'canceled', 'error'],
|
|
262
|
+
completed: ['error'],
|
|
263
|
+
canceled: ['error'],
|
|
264
|
+
aborted: ['error'],
|
|
265
|
+
declined: ['error'],
|
|
266
|
+
error: ['error'],
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* Asserts that the transition from current to next status is valid. Throws if
|
|
270
|
+
* the transition is invalid.
|
|
271
|
+
*/
|
|
272
|
+
function assertValidStatusTransition(current, next) {
|
|
273
|
+
if (!allowedStatusTransitions[current].includes(next)) {
|
|
274
|
+
throw new Error(`Invalid status transition from ${current} to ${next}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const finalStatuses = [
|
|
278
|
+
'declined',
|
|
279
|
+
'canceled',
|
|
280
|
+
'aborted',
|
|
281
|
+
'completed',
|
|
282
|
+
'error',
|
|
283
|
+
];
|
|
284
|
+
/**
|
|
285
|
+
* Returns true if the status is a final status, meaning that no further updates
|
|
286
|
+
* should be expected for the map share.
|
|
287
|
+
*/
|
|
288
|
+
function isFinalStatus(status) {
|
|
289
|
+
return finalStatuses.includes(status);
|
|
290
|
+
}
|
|
291
|
+
function noop() { }
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import type { Preset } from '@comapeo/schema' with {
|
|
1
|
+
import type { Preset } from '@comapeo/schema' with {
|
|
2
|
+
'resolution-mode': 'import'
|
|
3
|
+
};
|
|
2
4
|
export declare function getPresetsSelection(presets: Array<Preset>, orderedPresetIds?: Array<string>): Array<Preset>;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import type { MapeoClientApi } from '@comapeo/ipc' with {
|
|
2
|
-
|
|
1
|
+
import type { MapeoClientApi } from '@comapeo/ipc' with {
|
|
2
|
+
'resolution-mode': 'import'
|
|
3
|
+
};
|
|
4
|
+
import type { DeviceInfo } from '@comapeo/schema' with {
|
|
5
|
+
'resolution-mode': 'import'
|
|
6
|
+
};
|
|
3
7
|
import { type QueryClient } from '@tanstack/react-query';
|
|
4
8
|
export declare function getClientQueryKey(): readonly ["@comapeo/core-react", "client"];
|
|
5
9
|
export declare function getDeviceInfoQueryKey(): readonly ["@comapeo/core-react", "client", "device_info"];
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import type { DerivedDocFields } from '@comapeo/core/dist/datatype/index.js' with {
|
|
2
|
-
|
|
1
|
+
import type { DerivedDocFields } from '@comapeo/core/dist/datatype/index.js' with {
|
|
2
|
+
'resolution-mode': 'import'
|
|
3
|
+
};
|
|
4
|
+
import type { MapeoProjectApi } from '@comapeo/ipc' with {
|
|
5
|
+
'resolution-mode': 'import'
|
|
6
|
+
};
|
|
3
7
|
import { type QueryClient } from '@tanstack/react-query';
|
|
4
|
-
import { WriteableDocument, WriteableDocumentType, WriteableValue } from '../types.js';
|
|
8
|
+
import type { WriteableDocument, WriteableDocumentType, WriteableValue } from '../types.js';
|
|
5
9
|
export declare function getDocumentsQueryKey<D extends WriteableDocumentType>({ projectId, docType, }: {
|
|
6
10
|
projectId: string;
|
|
7
11
|
docType: D;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import type { MemberApi } from '@comapeo/core' with {
|
|
2
|
-
|
|
1
|
+
import type { MemberApi } from '@comapeo/core' with {
|
|
2
|
+
'resolution-mode': 'import'
|
|
3
|
+
};
|
|
4
|
+
import type { MapeoClientApi, MapeoProjectApi } from '@comapeo/ipc' with {
|
|
5
|
+
'resolution-mode': 'import'
|
|
6
|
+
};
|
|
3
7
|
import { type QueryClient } from '@tanstack/react-query';
|
|
4
8
|
export declare function getInvitesQueryKey(): readonly ["@comapeo/core-react", "invites"];
|
|
5
9
|
export declare function getInvitesByIdQueryKey({ inviteId }: {
|
|
@@ -1,24 +1,72 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
export declare function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { type QueryClient, type UseMutationOptions } from '@tanstack/react-query';
|
|
2
|
+
import type { MapServerApi } from '../../contexts/MapServer.js';
|
|
3
|
+
import type { ReceivedMapSharesStore, SentMapSharesStore } from '../map-shares-stores.js';
|
|
4
|
+
type CompatFile = Omit<File, 'lastModified' | 'webkitRelativePath'>;
|
|
5
|
+
type ExpoFileDuckType = CompatFile & {
|
|
6
|
+
exists: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function getMapQueryKey({ mapId }: {
|
|
9
|
+
mapId: string;
|
|
10
|
+
}): readonly ["@comapeo/core-react", "maps", string];
|
|
11
|
+
export declare function getStyleJsonUrlQueryKey({ mapId }: {
|
|
12
|
+
mapId: string;
|
|
13
|
+
}): readonly ["@comapeo/core-react", "maps", string, "stylejson_url"];
|
|
14
|
+
/**
|
|
15
|
+
* Invalidate queries for this map and the default map (which internally
|
|
16
|
+
* redirects to custom) so that they will be refetched with the new map data.
|
|
17
|
+
*/
|
|
18
|
+
export declare function invalidateMapQueries(queryClient: QueryClient, { mapId }: {
|
|
19
|
+
mapId: string;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
export declare function mapStyleJsonUrlQueryOptions({ mapServerApi, mapId, }: {
|
|
22
|
+
mapServerApi: MapServerApi;
|
|
23
|
+
mapId?: string;
|
|
24
|
+
}): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<string, Error, string, readonly ["@comapeo/core-react", "maps", string, "stylejson_url"]>, "queryFn"> & {
|
|
25
|
+
queryFn?: import("@tanstack/react-query").QueryFunction<string, readonly ["@comapeo/core-react", "maps", string, "stylejson_url"], never> | undefined;
|
|
17
26
|
} & {
|
|
18
|
-
queryKey: readonly ["@comapeo/core-react", "maps", "stylejson_url"
|
|
19
|
-
readonly refreshToken: string | undefined;
|
|
20
|
-
}] & {
|
|
27
|
+
queryKey: readonly ["@comapeo/core-react", "maps", string, "stylejson_url"] & {
|
|
21
28
|
[dataTagSymbol]: string;
|
|
22
29
|
[dataTagErrorSymbol]: Error;
|
|
23
30
|
};
|
|
24
31
|
};
|
|
32
|
+
export declare function mapInfoQueryOptions({ mapServerApi, mapId, }: {
|
|
33
|
+
mapServerApi: MapServerApi;
|
|
34
|
+
mapId?: string;
|
|
35
|
+
}): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<unknown, Error, unknown, readonly ["@comapeo/core-react", "maps", string, "info"]>, "queryFn"> & {
|
|
36
|
+
queryFn?: import("@tanstack/react-query").QueryFunction<unknown, readonly ["@comapeo/core-react", "maps", string, "info"], never> | undefined;
|
|
37
|
+
} & {
|
|
38
|
+
queryKey: readonly ["@comapeo/core-react", "maps", string, "info"] & {
|
|
39
|
+
[dataTagSymbol]: unknown;
|
|
40
|
+
[dataTagErrorSymbol]: Error;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export declare function mapImportMutationOptions({ mapServerApi, queryClient, }: {
|
|
44
|
+
mapServerApi: MapServerApi;
|
|
45
|
+
queryClient: QueryClient;
|
|
46
|
+
}): {
|
|
47
|
+
mutationFn: ({ file }: {
|
|
48
|
+
file: File | ExpoFileDuckType;
|
|
49
|
+
}) => Promise<Response>;
|
|
50
|
+
onSuccess: () => Promise<void>;
|
|
51
|
+
networkMode: "always";
|
|
52
|
+
retry: false;
|
|
53
|
+
};
|
|
54
|
+
export declare function mapRemoveMutationOptions({ mapServerApi, queryClient, }: {
|
|
55
|
+
mapServerApi: MapServerApi;
|
|
56
|
+
queryClient: QueryClient;
|
|
57
|
+
}): {
|
|
58
|
+
mutationFn: () => Promise<Response>;
|
|
59
|
+
onSuccess: () => Promise<void>;
|
|
60
|
+
networkMode: "always";
|
|
61
|
+
retry: false;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Mutation options for actions on sent map shares
|
|
65
|
+
*/
|
|
66
|
+
export declare function mapSharesMutationOptions<TAction extends SentMapSharesStore['actions'][keyof SentMapSharesStore['actions']] | keyof ReceivedMapSharesStore['actions'][keyof ReceivedMapSharesStore['actions']]>(options: {
|
|
67
|
+
action: Exclude<TAction, SentMapSharesStore['actions']['createAndSend']>;
|
|
68
|
+
} | {
|
|
69
|
+
action: SentMapSharesStore['actions']['createAndSend'];
|
|
70
|
+
projectId: string;
|
|
71
|
+
}): UseMutationOptions<ReturnType<TAction>, Error, TAction extends SentMapSharesStore['actions']['createAndSend'] ? Parameters<TAction>[0] : Omit<Parameters<TAction>[0], 'projectId'>>;
|
|
72
|
+
export {};
|
|
@@ -1,22 +1,119 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { CUSTOM_MAP_ID, DEFAULT_MAP_ID } from '@comapeo/map-server/constants.js';
|
|
2
|
+
import { queryOptions, } from '@tanstack/react-query';
|
|
3
|
+
import { baseMutationOptions, baseQueryOptions, ROOT_QUERY_KEY, } from './shared.js';
|
|
4
|
+
// ============================================
|
|
5
|
+
// QUERY KEYS
|
|
6
|
+
// ============================================
|
|
7
|
+
const MAPS_ROOT_QUERY_KEY = [ROOT_QUERY_KEY, 'maps'];
|
|
8
|
+
export function getMapQueryKey({ mapId }) {
|
|
9
|
+
return [...MAPS_ROOT_QUERY_KEY, mapId];
|
|
5
10
|
}
|
|
6
|
-
export function getStyleJsonUrlQueryKey({
|
|
7
|
-
return [
|
|
11
|
+
export function getStyleJsonUrlQueryKey({ mapId }) {
|
|
12
|
+
return [...getMapQueryKey({ mapId }), 'stylejson_url'];
|
|
8
13
|
}
|
|
9
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Invalidate queries for this map and the default map (which internally
|
|
16
|
+
* redirects to custom) so that they will be refetched with the new map data.
|
|
17
|
+
*/
|
|
18
|
+
export async function invalidateMapQueries(queryClient, { mapId }) {
|
|
19
|
+
await Promise.all([
|
|
20
|
+
queryClient.invalidateQueries({
|
|
21
|
+
queryKey: getMapQueryKey({ mapId }),
|
|
22
|
+
}),
|
|
23
|
+
queryClient.invalidateQueries({
|
|
24
|
+
queryKey: getMapQueryKey({ mapId: DEFAULT_MAP_ID }),
|
|
25
|
+
}),
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
// ============================================
|
|
29
|
+
// QUERY OPTIONS
|
|
30
|
+
// ============================================
|
|
31
|
+
export function mapStyleJsonUrlQueryOptions({ mapServerApi, mapId = DEFAULT_MAP_ID, }) {
|
|
32
|
+
if (mapId !== DEFAULT_MAP_ID) {
|
|
33
|
+
throw new Error('Custom map IDs are not supported yet');
|
|
34
|
+
}
|
|
10
35
|
return queryOptions({
|
|
11
36
|
...baseQueryOptions(),
|
|
12
|
-
queryKey: getStyleJsonUrlQueryKey({
|
|
37
|
+
queryKey: getStyleJsonUrlQueryKey({ mapId }),
|
|
13
38
|
queryFn: async () => {
|
|
14
|
-
const result = await
|
|
15
|
-
if (!refreshToken)
|
|
16
|
-
return result;
|
|
39
|
+
const result = await mapServerApi.getMapStyleJsonUrl(mapId);
|
|
17
40
|
const u = new URL(result);
|
|
18
|
-
|
|
41
|
+
// This ensures that every time this query is refetched, it will have a different search param, forcing the map to reload.
|
|
42
|
+
u.searchParams.set('refresh_token', Date.now().toString());
|
|
19
43
|
return u.href;
|
|
20
44
|
},
|
|
45
|
+
// Keep this cached until the cache is manually invalidated by a map upload
|
|
46
|
+
staleTime: Infinity,
|
|
47
|
+
gcTime: Infinity,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export function mapInfoQueryOptions({ mapServerApi, mapId = DEFAULT_MAP_ID, }) {
|
|
51
|
+
if (mapId !== CUSTOM_MAP_ID) {
|
|
52
|
+
throw new Error('Only custom map ID is currently supported');
|
|
53
|
+
}
|
|
54
|
+
return queryOptions({
|
|
55
|
+
...baseQueryOptions(),
|
|
56
|
+
queryKey: [...getMapQueryKey({ mapId }), 'info'],
|
|
57
|
+
queryFn: async () => {
|
|
58
|
+
return mapServerApi.get(`maps/${mapId}/info`).json();
|
|
59
|
+
},
|
|
60
|
+
// Keep this cached until the cache is manually invalidated by a map upload
|
|
61
|
+
staleTime: Infinity,
|
|
62
|
+
gcTime: Infinity,
|
|
21
63
|
});
|
|
22
64
|
}
|
|
65
|
+
// ============================================
|
|
66
|
+
// MUTATION OPTIONS
|
|
67
|
+
// ============================================
|
|
68
|
+
export function mapImportMutationOptions({ mapServerApi, queryClient, }) {
|
|
69
|
+
// TODO: Support importing to custom map IDs, to support multiple maps.
|
|
70
|
+
const mapId = CUSTOM_MAP_ID;
|
|
71
|
+
return {
|
|
72
|
+
...baseMutationOptions(),
|
|
73
|
+
mutationFn: async ({ file }) => {
|
|
74
|
+
if ('exists' in file && !file.exists) {
|
|
75
|
+
throw new Error('File does not exist or is not accessible');
|
|
76
|
+
}
|
|
77
|
+
return mapServerApi.put(`maps/${mapId}`, {
|
|
78
|
+
body: file,
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/octet-stream',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
onSuccess: async () => {
|
|
85
|
+
await invalidateMapQueries(queryClient, { mapId });
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function mapRemoveMutationOptions({ mapServerApi, queryClient, }) {
|
|
90
|
+
// TODO: Support removing from custom map IDs, to support multiple maps.
|
|
91
|
+
const mapId = CUSTOM_MAP_ID;
|
|
92
|
+
return {
|
|
93
|
+
...baseMutationOptions(),
|
|
94
|
+
mutationFn: async () => {
|
|
95
|
+
return mapServerApi.delete(`maps/${mapId}`);
|
|
96
|
+
},
|
|
97
|
+
onSuccess: async () => {
|
|
98
|
+
await invalidateMapQueries(queryClient, { mapId });
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Mutation options for actions on sent map shares
|
|
104
|
+
*/
|
|
105
|
+
export function mapSharesMutationOptions(options) {
|
|
106
|
+
return {
|
|
107
|
+
...baseMutationOptions(),
|
|
108
|
+
mutationFn: async (variables) => {
|
|
109
|
+
// For consistency with other hooks, we use `projectId` as a parameter of
|
|
110
|
+
// the hook, rather than a parameter of the mutate function.
|
|
111
|
+
const actionOptions = 'projectId' in options
|
|
112
|
+
? { ...variables, projectId: options.projectId }
|
|
113
|
+
: variables;
|
|
114
|
+
return options.action(
|
|
115
|
+
// @ts-expect-error - TS can't help us here
|
|
116
|
+
actionOptions);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { UseMutationResult } from '@tanstack/react-query';
|
|
2
|
+
import type { DistributedPick } from 'type-fest';
|
|
3
|
+
/**
|
|
4
|
+
* Filters a `UseMutationResult` to only include a subset of its keys, and uses
|
|
5
|
+
* `DistributedPick` to preserve the discriminated union types of the mutation
|
|
6
|
+
* result based on the `status` property.
|
|
7
|
+
*/
|
|
8
|
+
export declare function filterMutationResult<TResult extends UseMutationResult<any, any, any, any>>(mutationResult: TResult): DistributedPick<TResult, "error" | "status" | "mutate" | "reset" | "mutateAsync">;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const PICKED_MUTATION_RESULT_KEYS = [
|
|
2
|
+
'error',
|
|
3
|
+
'mutate',
|
|
4
|
+
'mutateAsync',
|
|
5
|
+
'reset',
|
|
6
|
+
'status',
|
|
7
|
+
];
|
|
8
|
+
/**
|
|
9
|
+
* Filters a `UseMutationResult` to only include a subset of its keys, and uses
|
|
10
|
+
* `DistributedPick` to preserve the discriminated union types of the mutation
|
|
11
|
+
* result based on the `status` property.
|
|
12
|
+
*/
|
|
13
|
+
export function filterMutationResult(mutationResult) {
|
|
14
|
+
const filteredResult = {};
|
|
15
|
+
for (const key of PICKED_MUTATION_RESULT_KEYS) {
|
|
16
|
+
filteredResult[key] = mutationResult[key];
|
|
17
|
+
}
|
|
18
|
+
return filteredResult;
|
|
19
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { BlobApi, MemberApi } from '@comapeo/core' with { 'resolution-mode': 'import' };
|
|
2
|
-
import type { MapeoClientApi, MapeoProjectApi } from '@comapeo/ipc' with {
|
|
2
|
+
import type { MapeoClientApi, MapeoProjectApi } from '@comapeo/ipc' with {
|
|
3
|
+
'resolution-mode': 'import'
|
|
4
|
+
};
|
|
3
5
|
import { type QueryClient, type UnusedSkipTokenOptions } from '@tanstack/react-query';
|
|
4
6
|
export declare function getProjectsQueryKey(): readonly ["@comapeo/core-react", "projects"];
|
|
5
7
|
export declare function getProjectByIdQueryKey({ projectId }: {
|
|
@@ -150,6 +152,8 @@ export declare function createProjectMutationOptions({ clientApi, queryClient, }
|
|
|
150
152
|
mutationFn: (opts: {
|
|
151
153
|
name?: string;
|
|
152
154
|
configPath?: string;
|
|
155
|
+
projectColor?: string;
|
|
156
|
+
projectDescription?: string;
|
|
153
157
|
} | undefined) => Promise<string>;
|
|
154
158
|
onSuccess: () => void;
|
|
155
159
|
networkMode: "always";
|
|
@@ -162,12 +162,7 @@ export function createProjectMutationOptions({ clientApi, queryClient, }) {
|
|
|
162
162
|
mutationFn: async (opts) => {
|
|
163
163
|
// Have to avoid passing `undefined` explicitly
|
|
164
164
|
// See https://github.com/digidem/rpc-reflector/issues/21
|
|
165
|
-
return opts
|
|
166
|
-
? clientApi.createProject({
|
|
167
|
-
configPath: opts.configPath,
|
|
168
|
-
name: opts.name,
|
|
169
|
-
})
|
|
170
|
-
: clientApi.createProject();
|
|
165
|
+
return opts ? clientApi.createProject(opts) : clientApi.createProject();
|
|
171
166
|
},
|
|
172
167
|
onSuccess: () => {
|
|
173
168
|
queryClient.invalidateQueries({
|
package/dist/esm/lib/sync.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { MapeoProjectApi } from '@comapeo/ipc' with {
|
|
1
|
+
import type { MapeoProjectApi } from '@comapeo/ipc' with {
|
|
2
|
+
'resolution-mode': 'import'
|
|
3
|
+
};
|
|
2
4
|
export type SyncState = Awaited<ReturnType<MapeoProjectApi['$sync']['getState']>>;
|
|
3
5
|
export declare function getDataSyncCountForDevice(syncStateForDevice: SyncState['remoteDeviceSyncState'][string]): number;
|
|
4
6
|
export declare class SyncStore {
|
package/dist/esm/lib/types.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { MapeoDoc, MapeoValue } from '@comapeo/schema' with {
|
|
1
|
+
import type { MapeoDoc, MapeoValue } from '@comapeo/schema' with {
|
|
2
|
+
'resolution-mode': 'import'
|
|
3
|
+
};
|
|
2
4
|
export type WriteableDocumentType = Extract<MapeoDoc['schemaName'], 'field' | 'observation' | 'preset' | 'track' | 'remoteDetectionAlert'>;
|
|
3
5
|
export type WriteableValue<D extends WriteableDocumentType> = Extract<MapeoValue, {
|
|
4
6
|
schemaName: D;
|