@comapeo/core-react 2.0.1 → 3.1.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/dist/commonjs/contexts/ClientApi.d.ts +5 -5
- package/dist/commonjs/contexts/ClientApi.js +3 -3
- package/dist/commonjs/hooks/documents.d.ts +6 -6
- package/dist/commonjs/hooks/projects.d.ts +72 -0
- package/dist/commonjs/hooks/projects.js +83 -0
- package/dist/commonjs/index.d.ts +3 -7
- package/dist/commonjs/index.js +6 -29
- package/dist/commonjs/lib/react-query/documents.d.ts +1 -8
- package/dist/commonjs/lib/react-query/projects.d.ts +16 -0
- package/dist/commonjs/lib/react-query/projects.js +20 -0
- package/dist/commonjs/lib/sync.d.ts +10 -0
- package/dist/commonjs/lib/sync.js +119 -0
- package/dist/commonjs/lib/types.d.ts +9 -0
- package/dist/commonjs/lib/types.js +2 -0
- package/dist/esm/contexts/ClientApi.d.ts +5 -5
- package/dist/esm/contexts/ClientApi.js +3 -3
- package/dist/esm/hooks/documents.d.ts +6 -6
- package/dist/esm/hooks/projects.d.ts +72 -0
- package/dist/esm/hooks/projects.js +79 -1
- package/dist/esm/index.d.ts +3 -7
- package/dist/esm/index.js +1 -7
- package/dist/esm/lib/react-query/documents.d.ts +1 -8
- package/dist/esm/lib/react-query/projects.d.ts +16 -0
- package/dist/esm/lib/react-query/projects.js +18 -0
- package/dist/esm/lib/sync.d.ts +10 -0
- package/dist/esm/lib/sync.js +114 -0
- package/dist/esm/lib/types.d.ts +9 -0
- package/dist/esm/lib/types.js +1 -0
- package/docs/API.md +109 -2
- package/package.json +25 -29
- package/CHANGELOG.md +0 -23
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BitmapOpts, SvgOpts } from '@comapeo/core/dist/icon-api.js' with { 'resolution-mode': 'import' };
|
|
2
2
|
import type { BlobId } from '@comapeo/core/dist/types.js' with { 'resolution-mode': 'import' };
|
|
3
|
+
import { type SyncState } from '../lib/sync.js';
|
|
3
4
|
/**
|
|
4
5
|
* Retrieve the project settings for a project.
|
|
5
6
|
*
|
|
@@ -249,6 +250,28 @@ export declare function useDocumentCreatedBy({ projectId, originalVersionId, }:
|
|
|
249
250
|
error: Error | null;
|
|
250
251
|
isRefetching: boolean;
|
|
251
252
|
};
|
|
253
|
+
/**
|
|
254
|
+
* Get the role for the current device in a specified project.
|
|
255
|
+
* This is a more convenient alternative to using the `useOwnDeviceInfo` and `useManyMembers` hooks.
|
|
256
|
+
*
|
|
257
|
+
* @param opts.projectId Project public ID
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```tsx
|
|
261
|
+
* function BasicExample() {
|
|
262
|
+
* const { data } = useOwnRoleInProject({
|
|
263
|
+
* projectId: '...',
|
|
264
|
+
* })
|
|
265
|
+
* }
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
export declare function useOwnRoleInProject({ projectId }: {
|
|
269
|
+
projectId: string;
|
|
270
|
+
}): {
|
|
271
|
+
data: import("@comapeo/core/dist/roles.js").Role<"a12a6702b93bd7ff" | "f7c150f5a3a9a855" | "012fd2d431c0bf60" | "9e6d29263cba36c9" | "8ced989b1904606b" | "08e4251e36f6e7ed">;
|
|
272
|
+
error: Error | null;
|
|
273
|
+
isRefetching: boolean;
|
|
274
|
+
};
|
|
252
275
|
export declare function useAddServerPeer({ projectId }: {
|
|
253
276
|
projectId: string;
|
|
254
277
|
}): {
|
|
@@ -332,3 +355,52 @@ export declare function useCreateBlob({ projectId }: {
|
|
|
332
355
|
reset: () => void;
|
|
333
356
|
status: "pending" | "error" | "success" | "idle";
|
|
334
357
|
};
|
|
358
|
+
/**
|
|
359
|
+
* Hook to subscribe to the current sync state.
|
|
360
|
+
*
|
|
361
|
+
* Creates a global singleton for each project, to minimize traffic over IPC -
|
|
362
|
+
* this hook can safely be used in more than one place without attaching
|
|
363
|
+
* additional listeners across the IPC channel.
|
|
364
|
+
*
|
|
365
|
+
* @example
|
|
366
|
+
* ```ts
|
|
367
|
+
* function Example() {
|
|
368
|
+
* const syncState = useSyncState({ projectId });
|
|
369
|
+
*
|
|
370
|
+
* if (!syncState) {
|
|
371
|
+
* // Sync information hasn't been loaded yet
|
|
372
|
+
* }
|
|
373
|
+
*
|
|
374
|
+
* // Actual info about sync state is available...
|
|
375
|
+
* }
|
|
376
|
+
* ```
|
|
377
|
+
*
|
|
378
|
+
* @param opts.projectId Project public ID
|
|
379
|
+
*/
|
|
380
|
+
export declare function useSyncState({ projectId, }: {
|
|
381
|
+
projectId: string;
|
|
382
|
+
}): SyncState | null;
|
|
383
|
+
/**
|
|
384
|
+
* Provides the progress of data sync for sync-enabled connected peers
|
|
385
|
+
*
|
|
386
|
+
* @returns `null` if no sync state events have been received. Otherwise returns a value between 0 and 1 (inclusive)
|
|
387
|
+
*/
|
|
388
|
+
export declare function useDataSyncProgress({ projectId, }: {
|
|
389
|
+
projectId: string;
|
|
390
|
+
}): number | null;
|
|
391
|
+
export declare function useStartSync({ projectId }: {
|
|
392
|
+
projectId: string;
|
|
393
|
+
}): {
|
|
394
|
+
mutate: import("@tanstack/react-query").UseMutateFunction<void, Error, {
|
|
395
|
+
autostopDataSyncAfter: number | null;
|
|
396
|
+
} | undefined, unknown>;
|
|
397
|
+
reset: () => void;
|
|
398
|
+
status: "pending" | "error" | "success" | "idle";
|
|
399
|
+
};
|
|
400
|
+
export declare function useStopSync({ projectId }: {
|
|
401
|
+
projectId: string;
|
|
402
|
+
}): {
|
|
403
|
+
mutate: import("@tanstack/react-query").UseMutateFunction<void, Error, void, unknown>;
|
|
404
|
+
reset: () => void;
|
|
405
|
+
status: "pending" | "error" | "success" | "idle";
|
|
406
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useMutation, useQueryClient, useSuspenseQuery, } from '@tanstack/react-query';
|
|
2
|
-
import {
|
|
2
|
+
import { useSyncExternalStore } from 'react';
|
|
3
|
+
import { addServerPeerMutationOptions, attachmentUrlQueryOptions, createBlobMutationOptions, createProjectMutationOptions, documentCreatedByQueryOptions, iconUrlQueryOptions, importProjectConfigMutationOptions, leaveProjectMutationOptions, projectByIdQueryOptions, projectMemberByIdQueryOptions, projectMembersQueryOptions, projectOwnRoleQueryOptions, projectSettingsQueryOptions, projectsQueryOptions, startSyncMutationOptions, stopSyncMutationOptions, updateProjectSettingsMutationOptions, } from '../lib/react-query/projects.js';
|
|
4
|
+
import { SyncStore } from '../lib/sync.js';
|
|
3
5
|
import { useClientApi } from './client.js';
|
|
4
6
|
/**
|
|
5
7
|
* Retrieve the project settings for a project.
|
|
@@ -246,6 +248,26 @@ export function useDocumentCreatedBy({ projectId, originalVersionId, }) {
|
|
|
246
248
|
const { data, error, isRefetching } = useSuspenseQuery(documentCreatedByQueryOptions({ projectApi, projectId, originalVersionId }));
|
|
247
249
|
return { data, error, isRefetching };
|
|
248
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Get the role for the current device in a specified project.
|
|
253
|
+
* This is a more convenient alternative to using the `useOwnDeviceInfo` and `useManyMembers` hooks.
|
|
254
|
+
*
|
|
255
|
+
* @param opts.projectId Project public ID
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* ```tsx
|
|
259
|
+
* function BasicExample() {
|
|
260
|
+
* const { data } = useOwnRoleInProject({
|
|
261
|
+
* projectId: '...',
|
|
262
|
+
* })
|
|
263
|
+
* }
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export function useOwnRoleInProject({ projectId }) {
|
|
267
|
+
const { data: projectApi } = useSingleProject({ projectId });
|
|
268
|
+
const { data, error, isRefetching } = useSuspenseQuery(projectOwnRoleQueryOptions({ projectApi, projectId }));
|
|
269
|
+
return { data, error, isRefetching };
|
|
270
|
+
}
|
|
249
271
|
export function useAddServerPeer({ projectId }) {
|
|
250
272
|
const queryClient = useQueryClient();
|
|
251
273
|
const { data: projectApi } = useSingleProject({ projectId });
|
|
@@ -302,3 +324,59 @@ export function useCreateBlob({ projectId }) {
|
|
|
302
324
|
const { mutate, reset, status } = useMutation(createBlobMutationOptions({ projectApi }));
|
|
303
325
|
return { mutate, reset, status };
|
|
304
326
|
}
|
|
327
|
+
const PROJECT_SYNC_STORE_MAP = new WeakMap();
|
|
328
|
+
function useSyncStore({ projectId }) {
|
|
329
|
+
const { data: projectApi } = useSingleProject({ projectId });
|
|
330
|
+
let syncStore = PROJECT_SYNC_STORE_MAP.get(projectApi);
|
|
331
|
+
if (!syncStore) {
|
|
332
|
+
syncStore = new SyncStore(projectApi);
|
|
333
|
+
PROJECT_SYNC_STORE_MAP.set(projectApi, syncStore);
|
|
334
|
+
}
|
|
335
|
+
return syncStore;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Hook to subscribe to the current sync state.
|
|
339
|
+
*
|
|
340
|
+
* Creates a global singleton for each project, to minimize traffic over IPC -
|
|
341
|
+
* this hook can safely be used in more than one place without attaching
|
|
342
|
+
* additional listeners across the IPC channel.
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```ts
|
|
346
|
+
* function Example() {
|
|
347
|
+
* const syncState = useSyncState({ projectId });
|
|
348
|
+
*
|
|
349
|
+
* if (!syncState) {
|
|
350
|
+
* // Sync information hasn't been loaded yet
|
|
351
|
+
* }
|
|
352
|
+
*
|
|
353
|
+
* // Actual info about sync state is available...
|
|
354
|
+
* }
|
|
355
|
+
* ```
|
|
356
|
+
*
|
|
357
|
+
* @param opts.projectId Project public ID
|
|
358
|
+
*/
|
|
359
|
+
export function useSyncState({ projectId, }) {
|
|
360
|
+
const syncStore = useSyncStore({ projectId });
|
|
361
|
+
const { subscribe, getStateSnapshot } = syncStore;
|
|
362
|
+
return useSyncExternalStore(subscribe, getStateSnapshot);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Provides the progress of data sync for sync-enabled connected peers
|
|
366
|
+
*
|
|
367
|
+
* @returns `null` if no sync state events have been received. Otherwise returns a value between 0 and 1 (inclusive)
|
|
368
|
+
*/
|
|
369
|
+
export function useDataSyncProgress({ projectId, }) {
|
|
370
|
+
const { subscribe, getDataProgressSnapshot } = useSyncStore({ projectId });
|
|
371
|
+
return useSyncExternalStore(subscribe, getDataProgressSnapshot);
|
|
372
|
+
}
|
|
373
|
+
export function useStartSync({ projectId }) {
|
|
374
|
+
const { data: projectApi } = useSingleProject({ projectId });
|
|
375
|
+
const { mutate, reset, status } = useMutation(startSyncMutationOptions({ projectApi }));
|
|
376
|
+
return { mutate, reset, status };
|
|
377
|
+
}
|
|
378
|
+
export function useStopSync({ projectId }) {
|
|
379
|
+
const { data: projectApi } = useSingleProject({ projectId });
|
|
380
|
+
const { mutate, reset, status } = useMutation(stopSyncMutationOptions({ projectApi }));
|
|
381
|
+
return { mutate, reset, status };
|
|
382
|
+
}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -3,10 +3,6 @@ export { useClientApi, useIsArchiveDevice, useOwnDeviceInfo, useSetIsArchiveDevi
|
|
|
3
3
|
export { useCreateDocument, useDeleteDocument, useManyDocs, useSingleDocByDocId, useSingleDocByVersionId, useUpdateDocument, } from './hooks/documents.js';
|
|
4
4
|
export { useAcceptInvite, useRejectInvite, useRequestCancelInvite, useSendInvite, } from './hooks/invites.js';
|
|
5
5
|
export { useMapStyleUrl } from './hooks/maps.js';
|
|
6
|
-
export { useAddServerPeer, useAttachmentUrl, useCreateBlob, useCreateProject, useDocumentCreatedBy, useIconUrl, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useProjectSettings, useSingleMember, useSingleProject, useUpdateProjectSettings, } from './hooks/projects.js';
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export { getInvitesQueryKey, getPendingInvitesQueryKey, } from './lib/react-query/invites.js';
|
|
10
|
-
export { getMapsQueryKey, getStyleJsonUrlQueryKey, } from './lib/react-query/maps.js';
|
|
11
|
-
export { getAttachmentUrlQueryKey, getDocumentCreatedByQueryKey, getIconUrlQueryKey, getMemberByIdQueryKey, getMembersQueryKey, getProjectByIdQueryKey, getProjectRoleQueryKey, getProjectSettingsQueryKey, getProjectsQueryKey, } from './lib/react-query/projects.js';
|
|
12
|
-
export { ROOT_QUERY_KEY } from './lib/react-query/shared.js';
|
|
6
|
+
export { useAddServerPeer, useAttachmentUrl, useCreateBlob, useCreateProject, useDataSyncProgress, useDocumentCreatedBy, useIconUrl, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useOwnRoleInProject, useProjectSettings, useSingleMember, useSingleProject, useStartSync, useStopSync, useSyncState, useUpdateProjectSettings, } from './hooks/projects.js';
|
|
7
|
+
export { type SyncState } from './lib/sync.js';
|
|
8
|
+
export { type WriteableDocument, type WriteableDocumentType, type WriteableValue, } from './lib/types.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -3,10 +3,4 @@ export { useClientApi, useIsArchiveDevice, useOwnDeviceInfo, useSetIsArchiveDevi
|
|
|
3
3
|
export { useCreateDocument, useDeleteDocument, useManyDocs, useSingleDocByDocId, useSingleDocByVersionId, useUpdateDocument, } from './hooks/documents.js';
|
|
4
4
|
export { useAcceptInvite, useRejectInvite, useRequestCancelInvite, useSendInvite, } from './hooks/invites.js';
|
|
5
5
|
export { useMapStyleUrl } from './hooks/maps.js';
|
|
6
|
-
export { useAddServerPeer, useAttachmentUrl, useCreateBlob, useCreateProject, useDocumentCreatedBy, useIconUrl, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useProjectSettings, useSingleMember, useSingleProject, useUpdateProjectSettings, } from './hooks/projects.js';
|
|
7
|
-
export { getClientQueryKey, getDeviceInfoQueryKey, getIsArchiveDeviceQueryKey, } from './lib/react-query/client.js';
|
|
8
|
-
export { getDocumentByDocIdQueryKey, getDocumentByVersionIdQueryKey, getDocumentsQueryKey, getManyDocumentsQueryKey, } from './lib/react-query/documents.js';
|
|
9
|
-
export { getInvitesQueryKey, getPendingInvitesQueryKey, } from './lib/react-query/invites.js';
|
|
10
|
-
export { getMapsQueryKey, getStyleJsonUrlQueryKey, } from './lib/react-query/maps.js';
|
|
11
|
-
export { getAttachmentUrlQueryKey, getDocumentCreatedByQueryKey, getIconUrlQueryKey, getMemberByIdQueryKey, getMembersQueryKey, getProjectByIdQueryKey, getProjectRoleQueryKey, getProjectSettingsQueryKey, getProjectsQueryKey, } from './lib/react-query/projects.js';
|
|
12
|
-
export { ROOT_QUERY_KEY } from './lib/react-query/shared.js';
|
|
6
|
+
export { useAddServerPeer, useAttachmentUrl, useCreateBlob, useCreateProject, useDataSyncProgress, useDocumentCreatedBy, useIconUrl, useImportProjectConfig, useLeaveProject, useManyMembers, useManyProjects, useOwnRoleInProject, useProjectSettings, useSingleMember, useSingleProject, useStartSync, useStopSync, useSyncState, useUpdateProjectSettings, } from './hooks/projects.js';
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import type { MapeoProjectApi } from '@comapeo/ipc' with { 'resolution-mode': 'import' };
|
|
2
|
-
import type { MapeoDoc, MapeoValue } from '@comapeo/schema' with { 'resolution-mode': 'import' };
|
|
3
2
|
import { type QueryClient } from '@tanstack/react-query';
|
|
4
|
-
|
|
5
|
-
export type WriteableValue<D extends WriteableDocumentType> = Extract<MapeoValue, {
|
|
6
|
-
schemaName: D;
|
|
7
|
-
}>;
|
|
8
|
-
export type WriteableDocument<D extends WriteableDocumentType> = Extract<MapeoDoc, {
|
|
9
|
-
schemaName: D;
|
|
10
|
-
}>;
|
|
3
|
+
import { WriteableDocument, WriteableDocumentType, WriteableValue } from '../types.js';
|
|
11
4
|
export declare function getDocumentsQueryKey<D extends WriteableDocumentType>({ projectId, docType, }: {
|
|
12
5
|
projectId: string;
|
|
13
6
|
docType: D;
|
|
@@ -330,3 +330,19 @@ export declare function createBlobMutationOptions({ projectApi, }: {
|
|
|
330
330
|
networkMode: "always";
|
|
331
331
|
retry: false;
|
|
332
332
|
};
|
|
333
|
+
export declare function startSyncMutationOptions({ projectApi, }: {
|
|
334
|
+
projectApi: MapeoProjectApi;
|
|
335
|
+
}): {
|
|
336
|
+
mutationFn: (opts: {
|
|
337
|
+
autostopDataSyncAfter: number | null;
|
|
338
|
+
} | undefined) => Promise<void>;
|
|
339
|
+
networkMode: "always";
|
|
340
|
+
retry: false;
|
|
341
|
+
};
|
|
342
|
+
export declare function stopSyncMutationOptions({ projectApi, }: {
|
|
343
|
+
projectApi: MapeoProjectApi;
|
|
344
|
+
}): {
|
|
345
|
+
mutationFn: () => Promise<void>;
|
|
346
|
+
networkMode: "always";
|
|
347
|
+
retry: false;
|
|
348
|
+
};
|
|
@@ -207,3 +207,21 @@ export function createBlobMutationOptions({ projectApi, }) {
|
|
|
207
207
|
},
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
|
+
export function startSyncMutationOptions({ projectApi, }) {
|
|
211
|
+
return {
|
|
212
|
+
...baseMutationOptions(),
|
|
213
|
+
mutationFn: async (opts) => {
|
|
214
|
+
// Have to avoid passing `undefined` explicitly
|
|
215
|
+
// See https://github.com/digidem/rpc-reflector/issues/21
|
|
216
|
+
return opts ? projectApi.$sync.start(opts) : projectApi.$sync.start();
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
export function stopSyncMutationOptions({ projectApi, }) {
|
|
221
|
+
return {
|
|
222
|
+
...baseMutationOptions(),
|
|
223
|
+
mutationFn: async () => {
|
|
224
|
+
return projectApi.$sync.stop();
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MapeoProjectApi } from '@comapeo/ipc' with { 'resolution-mode': 'import' };
|
|
2
|
+
export type SyncState = Awaited<ReturnType<MapeoProjectApi['$sync']['getState']>>;
|
|
3
|
+
export declare function getDataSyncCountForDevice(syncStateForDevice: SyncState['remoteDeviceSyncState'][string]): number;
|
|
4
|
+
export declare class SyncStore {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(project: MapeoProjectApi);
|
|
7
|
+
subscribe: (listener: () => void) => () => void;
|
|
8
|
+
getStateSnapshot: () => import("@comapeo/core/dist/sync/sync-api.js").State | null;
|
|
9
|
+
getDataProgressSnapshot: () => number | null;
|
|
10
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export function getDataSyncCountForDevice(syncStateForDevice) {
|
|
2
|
+
const { data } = syncStateForDevice;
|
|
3
|
+
return data.want + data.wanted;
|
|
4
|
+
}
|
|
5
|
+
export class SyncStore {
|
|
6
|
+
#project;
|
|
7
|
+
#listeners = new Set();
|
|
8
|
+
#isSubscribedInternal = false;
|
|
9
|
+
#error = null;
|
|
10
|
+
#state = null;
|
|
11
|
+
// Used for calculating sync progress
|
|
12
|
+
#perDeviceMaxSyncCount = new Map();
|
|
13
|
+
constructor(project) {
|
|
14
|
+
this.#project = project;
|
|
15
|
+
}
|
|
16
|
+
subscribe = (listener) => {
|
|
17
|
+
this.#listeners.add(listener);
|
|
18
|
+
if (!this.#isSubscribedInternal)
|
|
19
|
+
this.#startSubscription();
|
|
20
|
+
return () => {
|
|
21
|
+
this.#listeners.delete(listener);
|
|
22
|
+
if (this.#listeners.size === 0)
|
|
23
|
+
this.#stopSubscription();
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
getStateSnapshot = () => {
|
|
27
|
+
if (this.#error)
|
|
28
|
+
throw this.#error;
|
|
29
|
+
return this.#state;
|
|
30
|
+
};
|
|
31
|
+
getDataProgressSnapshot = () => {
|
|
32
|
+
if (this.#state === null) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
let currentSyncCount = 0;
|
|
36
|
+
let totalMaxSyncCount = 0;
|
|
37
|
+
let otherEnabledDevicesExist = false;
|
|
38
|
+
for (const [deviceId, deviceSyncState] of Object.entries(this.#state.remoteDeviceSyncState)) {
|
|
39
|
+
if (deviceSyncState.data.isSyncEnabled) {
|
|
40
|
+
otherEnabledDevicesExist = true;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const existingMaxCount = this.#perDeviceMaxSyncCount.get(deviceId);
|
|
46
|
+
if (typeof existingMaxCount === 'number' && existingMaxCount > 0) {
|
|
47
|
+
currentSyncCount = getDataSyncCountForDevice(deviceSyncState);
|
|
48
|
+
totalMaxSyncCount += existingMaxCount;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!otherEnabledDevicesExist) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
if (totalMaxSyncCount === 0) {
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
const ratio = (totalMaxSyncCount - currentSyncCount) / totalMaxSyncCount;
|
|
58
|
+
if (ratio <= 0)
|
|
59
|
+
return 0;
|
|
60
|
+
if (ratio >= 1)
|
|
61
|
+
return 1;
|
|
62
|
+
return clamp(ratio, 0.01, 0.99);
|
|
63
|
+
};
|
|
64
|
+
#notifyListeners() {
|
|
65
|
+
for (const listener of this.#listeners) {
|
|
66
|
+
listener();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
#onSyncState = (state) => {
|
|
70
|
+
const dataSyncWasEnabled = this.#state
|
|
71
|
+
? this.#state.data.isSyncEnabled
|
|
72
|
+
: false;
|
|
73
|
+
// Reset map keeping track of counts used for progress if data sync is toggled
|
|
74
|
+
if (dataSyncWasEnabled !== state.data.isSyncEnabled) {
|
|
75
|
+
this.#perDeviceMaxSyncCount.clear();
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Remove devices from #perDeviceMaxSyncCount that are no longer found in the new sync state
|
|
79
|
+
for (const deviceId of this.#perDeviceMaxSyncCount.keys()) {
|
|
80
|
+
if (!Object.hasOwn(state.remoteDeviceSyncState, deviceId)) {
|
|
81
|
+
this.#perDeviceMaxSyncCount.delete(deviceId);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const [deviceId, stateForDevice] of Object.entries(state.remoteDeviceSyncState)) {
|
|
86
|
+
const existingCount = this.#perDeviceMaxSyncCount.get(deviceId);
|
|
87
|
+
const newCount = getDataSyncCountForDevice(stateForDevice);
|
|
88
|
+
if (existingCount === undefined || existingCount < newCount) {
|
|
89
|
+
this.#perDeviceMaxSyncCount.set(deviceId, newCount);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
this.#state = state;
|
|
93
|
+
this.#error = null;
|
|
94
|
+
this.#notifyListeners();
|
|
95
|
+
};
|
|
96
|
+
#startSubscription = () => {
|
|
97
|
+
this.#project.$sync.on('sync-state', this.#onSyncState);
|
|
98
|
+
this.#isSubscribedInternal = true;
|
|
99
|
+
this.#project.$sync
|
|
100
|
+
.getState()
|
|
101
|
+
.then(this.#onSyncState)
|
|
102
|
+
.catch((e) => {
|
|
103
|
+
this.#error = e;
|
|
104
|
+
this.#notifyListeners();
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
#stopSubscription = () => {
|
|
108
|
+
this.#isSubscribedInternal = false;
|
|
109
|
+
this.#project.$sync.off('sync-state', this.#onSyncState);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function clamp(value, min, max) {
|
|
113
|
+
return Math.max(min, Math.min(value, max));
|
|
114
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MapeoDoc, MapeoValue } from '@comapeo/schema' with { 'resolution-mode': 'import' };
|
|
2
|
+
export type WriteableDocumentType = Extract<MapeoDoc['schemaName'], 'field' | 'observation' | 'preset' | 'track' | 'remoteDetectionAlert'>;
|
|
3
|
+
export type WriteableValue<D extends WriteableDocumentType> = Extract<MapeoValue, {
|
|
4
|
+
schemaName: D;
|
|
5
|
+
}>;
|
|
6
|
+
export type WriteableDocument<D extends WriteableDocumentType> = Extract<MapeoDoc, {
|
|
7
|
+
schemaName: D;
|
|
8
|
+
}>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/docs/API.md
CHANGED
|
@@ -14,12 +14,17 @@
|
|
|
14
14
|
- [useIconUrl](#useiconurl)
|
|
15
15
|
- [useAttachmentUrl](#useattachmenturl)
|
|
16
16
|
- [useDocumentCreatedBy](#usedocumentcreatedby)
|
|
17
|
+
- [useOwnRoleInProject](#useownroleinproject)
|
|
17
18
|
- [useAddServerPeer](#useaddserverpeer)
|
|
18
19
|
- [useCreateProject](#usecreateproject)
|
|
19
20
|
- [useLeaveProject](#useleaveproject)
|
|
20
21
|
- [useImportProjectConfig](#useimportprojectconfig)
|
|
21
22
|
- [useUpdateProjectSettings](#useupdateprojectsettings)
|
|
22
23
|
- [useCreateBlob](#usecreateblob)
|
|
24
|
+
- [useSyncState](#usesyncstate)
|
|
25
|
+
- [useDataSyncProgress](#usedatasyncprogress)
|
|
26
|
+
- [useStartSync](#usestartsync)
|
|
27
|
+
- [useStopSync](#usestopsync)
|
|
23
28
|
- [useSingleDocByDocId](#usesingledocbydocid)
|
|
24
29
|
- [useSingleDocByVersionId](#usesingledocbyversionid)
|
|
25
30
|
- [useManyDocs](#usemanydocs)
|
|
@@ -38,7 +43,7 @@ Create a context provider that holds a CoMapeo API client instance.
|
|
|
38
43
|
|
|
39
44
|
| Function | Type |
|
|
40
45
|
| ---------- | ---------- |
|
|
41
|
-
| `ClientApiProvider` | `({ children, clientApi, }: { children: ReactNode; clientApi: MapeoClientApi; }) =>
|
|
46
|
+
| `ClientApiProvider` | `({ children, clientApi, }: { children: ReactNode; clientApi: MapeoClientApi; }) => Element` |
|
|
42
47
|
|
|
43
48
|
Parameters:
|
|
44
49
|
|
|
@@ -373,6 +378,31 @@ function BasicExample() {
|
|
|
373
378
|
```
|
|
374
379
|
|
|
375
380
|
|
|
381
|
+
### useOwnRoleInProject
|
|
382
|
+
|
|
383
|
+
Get the role for the current device in a specified project.
|
|
384
|
+
This is a more convenient alternative to using the `useOwnDeviceInfo` and `useManyMembers` hooks.
|
|
385
|
+
|
|
386
|
+
| Function | Type |
|
|
387
|
+
| ---------- | ---------- |
|
|
388
|
+
| `useOwnRoleInProject` | `({ projectId }: { projectId: string; }) => { data: Role<"a12a6702b93bd7ff" or "f7c150f5a3a9a855" or "012fd2d431c0bf60" or "9e6d29263cba36c9" or "8ced989b1904606b" or "08e4251e36f6e7ed">; error: Error or null; isRefetching: boolean; }` |
|
|
389
|
+
|
|
390
|
+
Parameters:
|
|
391
|
+
|
|
392
|
+
* `opts.projectId`: Project public ID
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
Examples:
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
function BasicExample() {
|
|
399
|
+
const { data } = useOwnRoleInProject({
|
|
400
|
+
projectId: '...',
|
|
401
|
+
})
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
|
|
376
406
|
### useAddServerPeer
|
|
377
407
|
|
|
378
408
|
| Function | Type |
|
|
@@ -427,13 +457,65 @@ Create a blob for a project.
|
|
|
427
457
|
|
|
428
458
|
| Function | Type |
|
|
429
459
|
| ---------- | ---------- |
|
|
430
|
-
| `useCreateBlob` | `({ projectId }: { projectId: string; }) => { mutate: UseMutateFunction<{ driveId: string; name: string; type: "
|
|
460
|
+
| `useCreateBlob` | `({ projectId }: { projectId: string; }) => { mutate: UseMutateFunction<{ driveId: string; name: string; type: "photo" or "audio" or "video"; hash: string; }, Error, { original: string; preview?: string or undefined; thumbnail?: string or undefined; metadata: Metadata; }, unknown>; reset: () => void; status: "pending" or ...` |
|
|
431
461
|
|
|
432
462
|
Parameters:
|
|
433
463
|
|
|
434
464
|
* `opts.projectId`: Public project ID of project to apply to changes to.
|
|
435
465
|
|
|
436
466
|
|
|
467
|
+
### useSyncState
|
|
468
|
+
|
|
469
|
+
Hook to subscribe to the current sync state.
|
|
470
|
+
|
|
471
|
+
Creates a global singleton for each project, to minimize traffic over IPC -
|
|
472
|
+
this hook can safely be used in more than one place without attaching
|
|
473
|
+
additional listeners across the IPC channel.
|
|
474
|
+
|
|
475
|
+
| Function | Type |
|
|
476
|
+
| ---------- | ---------- |
|
|
477
|
+
| `useSyncState` | `({ projectId, }: { projectId: string; }) => State or null` |
|
|
478
|
+
|
|
479
|
+
Parameters:
|
|
480
|
+
|
|
481
|
+
* `opts.projectId`: Project public ID
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
Examples:
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
function Example() {
|
|
488
|
+
const syncState = useSyncState({ projectId });
|
|
489
|
+
|
|
490
|
+
if (!syncState) {
|
|
491
|
+
// Sync information hasn't been loaded yet
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Actual info about sync state is available...
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
### useDataSyncProgress
|
|
500
|
+
|
|
501
|
+
Provides the progress of data sync for sync-enabled connected peers
|
|
502
|
+
|
|
503
|
+
| Function | Type |
|
|
504
|
+
| ---------- | ---------- |
|
|
505
|
+
| `useDataSyncProgress` | `({ projectId, }: { projectId: string; }) => number or null` |
|
|
506
|
+
|
|
507
|
+
### useStartSync
|
|
508
|
+
|
|
509
|
+
| Function | Type |
|
|
510
|
+
| ---------- | ---------- |
|
|
511
|
+
| `useStartSync` | `({ projectId }: { projectId: string; }) => { mutate: UseMutateFunction<void, Error, { autostopDataSyncAfter: number or null; } or undefined, unknown>; reset: () => void; status: "pending" or ... 2 more ... or "idle"; }` |
|
|
512
|
+
|
|
513
|
+
### useStopSync
|
|
514
|
+
|
|
515
|
+
| Function | Type |
|
|
516
|
+
| ---------- | ---------- |
|
|
517
|
+
| `useStopSync` | `({ projectId }: { projectId: string; }) => { mutate: UseMutateFunction<void, Error, void, unknown>; reset: () => void; status: "pending" or "error" or "success" or "idle"; }` |
|
|
518
|
+
|
|
437
519
|
### useSingleDocByDocId
|
|
438
520
|
|
|
439
521
|
Retrieve a single document from the database based on the document's document ID.
|
|
@@ -678,3 +760,28 @@ function ExampleWithRefreshToken() {
|
|
|
678
760
|
| `ClientApiContext` | `Context<MapeoClientApi or null>` |
|
|
679
761
|
|
|
680
762
|
|
|
763
|
+
|
|
764
|
+
## Types
|
|
765
|
+
|
|
766
|
+
- [WriteableDocumentType](#writeabledocumenttype)
|
|
767
|
+
- [WriteableValue](#writeablevalue)
|
|
768
|
+
- [WriteableDocument](#writeabledocument)
|
|
769
|
+
|
|
770
|
+
### WriteableDocumentType
|
|
771
|
+
|
|
772
|
+
| Type | Type |
|
|
773
|
+
| ---------- | ---------- |
|
|
774
|
+
| `WriteableDocumentType` | `Extract< MapeoDoc['schemaName'], 'field' or 'observation' or 'preset' or 'track' or 'remoteDetectionAlert' >` |
|
|
775
|
+
|
|
776
|
+
### WriteableValue
|
|
777
|
+
|
|
778
|
+
| Type | Type |
|
|
779
|
+
| ---------- | ---------- |
|
|
780
|
+
| `WriteableValue` | `Extract< MapeoValue, { schemaName: D } >` |
|
|
781
|
+
|
|
782
|
+
### WriteableDocument
|
|
783
|
+
|
|
784
|
+
| Type | Type |
|
|
785
|
+
| ---------- | ---------- |
|
|
786
|
+
| `WriteableDocument` | `Extract< MapeoDoc, { schemaName: D } >` |
|
|
787
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comapeo/core-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "React wrapper for working with @comapeo/core",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
"types": "./dist/commonjs/index.d.ts",
|
|
36
36
|
"module": "./dist/esm/index.js",
|
|
37
37
|
"files": [
|
|
38
|
-
"CHANGELOG.md",
|
|
39
38
|
"dist/",
|
|
40
39
|
"docs/API.md"
|
|
41
40
|
],
|
|
@@ -48,16 +47,14 @@
|
|
|
48
47
|
}
|
|
49
48
|
},
|
|
50
49
|
"scripts": {
|
|
51
|
-
"
|
|
52
|
-
"prepare:tshy": "tshy",
|
|
53
|
-
"prepare": "npm-run-all --parallel prepare:*",
|
|
50
|
+
"docs:generate": "tsdoc --src=src/contexts/*,src/hooks/*,src/lib/types.ts --dest=docs/API.md --noemoji --types",
|
|
54
51
|
"lint:eslint": "eslint --cache .",
|
|
55
52
|
"lint:format": "prettier --cache --check .",
|
|
56
53
|
"lint": "npm-run-all --parallel --continue-on-error --print-label --aggregate-output lint:*",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"test": "
|
|
60
|
-
"
|
|
54
|
+
"prepack": "tshy",
|
|
55
|
+
"prepare": "husky",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"types": "tsc"
|
|
61
58
|
},
|
|
62
59
|
"peerDependencies": {
|
|
63
60
|
"@comapeo/core": "*",
|
|
@@ -67,34 +64,33 @@
|
|
|
67
64
|
"react": "^18 || ^19"
|
|
68
65
|
},
|
|
69
66
|
"devDependencies": {
|
|
70
|
-
"@comapeo/core": "2.3.
|
|
71
|
-
"@comapeo/
|
|
72
|
-
"@
|
|
73
|
-
"@eslint/
|
|
67
|
+
"@comapeo/core": "2.3.1",
|
|
68
|
+
"@comapeo/ipc": "2.1.0",
|
|
69
|
+
"@comapeo/schema": "1.4.1",
|
|
70
|
+
"@eslint/compat": "1.2.7",
|
|
71
|
+
"@eslint/js": "9.23.0",
|
|
74
72
|
"@ianvs/prettier-plugin-sort-imports": "4.4.1",
|
|
75
73
|
"@mapeo/crypto": "1.0.0-alpha.10",
|
|
76
|
-
"@tanstack/eslint-plugin-query": "5.
|
|
77
|
-
"@tanstack/react-query": "5.
|
|
74
|
+
"@tanstack/eslint-plugin-query": "5.68.0",
|
|
75
|
+
"@tanstack/react-query": "5.69.0",
|
|
78
76
|
"@testing-library/dom": "10.4.0",
|
|
79
|
-
"@testing-library/react": "16.
|
|
80
|
-
"@types/
|
|
81
|
-
"@types/
|
|
82
|
-
"@types/react": "19.0.
|
|
83
|
-
"
|
|
84
|
-
"commit-and-tag-version": "12.5.0",
|
|
85
|
-
"eslint": "9.18.0",
|
|
77
|
+
"@testing-library/react": "16.2.0",
|
|
78
|
+
"@types/node": "22.13.13",
|
|
79
|
+
"@types/react": "19.0.12",
|
|
80
|
+
"@types/react-dom": "19.0.4",
|
|
81
|
+
"eslint": "9.23.0",
|
|
86
82
|
"fastify": "4.29.0",
|
|
87
|
-
"globals": "
|
|
83
|
+
"globals": "16.0.0",
|
|
88
84
|
"husky": "9.1.7",
|
|
89
|
-
"lint-staged": "15.
|
|
85
|
+
"lint-staged": "15.5.0",
|
|
90
86
|
"npm-run-all2": "7.0.2",
|
|
91
|
-
"prettier": "3.
|
|
87
|
+
"prettier": "3.5.3",
|
|
92
88
|
"random-access-memory": "6.2.1",
|
|
93
89
|
"react": "19.0.0",
|
|
94
|
-
"tsdoc-markdown": "1.
|
|
90
|
+
"tsdoc-markdown": "1.2.0",
|
|
95
91
|
"tshy": "3.0.2",
|
|
96
|
-
"typescript": "5.
|
|
97
|
-
"typescript-eslint": "8.
|
|
98
|
-
"vitest": "
|
|
92
|
+
"typescript": "5.8.2",
|
|
93
|
+
"typescript-eslint": "8.27.0",
|
|
94
|
+
"vitest": "3.0.9"
|
|
99
95
|
}
|
|
100
96
|
}
|