@comapeo/core-react 6.0.0 → 6.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.
@@ -626,7 +626,13 @@ export declare function useExportGeoJSON({ projectId }: {
626
626
  mutate: import("@tanstack/react-query").UseMutateFunction<string, Error, {
627
627
  path: string;
628
628
  exportOptions: {
629
- observations?: boolean;
629
+ observations
630
+ /**
631
+ * Update the settings of a project.
632
+ *
633
+ * @param opts.projectId Public ID of the project to apply changes to.
634
+ */
635
+ ?: boolean;
630
636
  tracks?: boolean;
631
637
  lang?: string;
632
638
  };
@@ -634,7 +640,13 @@ export declare function useExportGeoJSON({ projectId }: {
634
640
  mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<string, Error, {
635
641
  path: string;
636
642
  exportOptions: {
637
- observations?: boolean;
643
+ observations
644
+ /**
645
+ * Update the settings of a project.
646
+ *
647
+ * @param opts.projectId Public ID of the project to apply changes to.
648
+ */
649
+ ?: boolean;
638
650
  tracks?: boolean;
639
651
  lang?: string;
640
652
  };
@@ -646,7 +658,13 @@ export declare function useExportGeoJSON({ projectId }: {
646
658
  mutate: import("@tanstack/react-query").UseMutateFunction<string, Error, {
647
659
  path: string;
648
660
  exportOptions: {
649
- observations?: boolean;
661
+ observations
662
+ /**
663
+ * Update the settings of a project.
664
+ *
665
+ * @param opts.projectId Public ID of the project to apply changes to.
666
+ */
667
+ ?: boolean;
650
668
  tracks?: boolean;
651
669
  lang?: string;
652
670
  };
@@ -654,7 +672,13 @@ export declare function useExportGeoJSON({ projectId }: {
654
672
  mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<string, Error, {
655
673
  path: string;
656
674
  exportOptions: {
657
- observations?: boolean;
675
+ observations
676
+ /**
677
+ * Update the settings of a project.
678
+ *
679
+ * @param opts.projectId Public ID of the project to apply changes to.
680
+ */
681
+ ?: boolean;
658
682
  tracks?: boolean;
659
683
  lang?: string;
660
684
  };
@@ -29,6 +29,7 @@ const react_query_1 = require("@tanstack/react-query");
29
29
  const react_1 = require("react");
30
30
  const projects_js_1 = require("../lib/react-query/projects.js");
31
31
  const sync_js_1 = require("../lib/sync.js");
32
+ const urls_js_1 = require("../lib/urls.js");
32
33
  const client_js_1 = require("./client.js");
33
34
  /**
34
35
  * Retrieve the project settings for a project.
@@ -185,13 +186,10 @@ function useManyMembers({ projectId }) {
185
186
  */
186
187
  function useIconUrl({ projectId, iconId, ...mimeBasedOpts }) {
187
188
  const { data: projectApi } = useSingleProject({ projectId });
188
- const { data, error, isRefetching } = (0, react_query_1.useSuspenseQuery)((0, projects_js_1.iconUrlQueryOptions)({
189
- ...mimeBasedOpts,
190
- projectApi,
191
- projectId,
192
- iconId,
193
- }));
194
- return { data, error, isRefetching };
189
+ const { data: port, error, isRefetching } = useMediaServerPort({ projectApi });
190
+ const baseUrl = `http://127.0.0.1:${port}`;
191
+ const iconUrl = (0, urls_js_1.getIconUrl)(baseUrl, iconId, mimeBasedOpts);
192
+ return { data: iconUrl, error, isRefetching };
195
193
  }
196
194
  /**
197
195
  * Retrieve a URL that points to a desired blob resource.
@@ -246,10 +244,18 @@ function useIconUrl({ projectId, iconId, ...mimeBasedOpts }) {
246
244
  */
247
245
  function useAttachmentUrl({ projectId, blobId, }) {
248
246
  const { data: projectApi } = useSingleProject({ projectId });
249
- const { data, error, isRefetching } = (0, react_query_1.useSuspenseQuery)((0, projects_js_1.attachmentUrlQueryOptions)({
247
+ const { data: port, error, isRefetching } = useMediaServerPort({ projectApi });
248
+ const baseUrl = `http://127.0.0.1:${port}`;
249
+ const blobUrl = (0, urls_js_1.getBlobUrl)(baseUrl, blobId);
250
+ return { data: blobUrl, error, isRefetching };
251
+ }
252
+ /**
253
+ * @internal
254
+ * Hack to retrieve the media server port.
255
+ */
256
+ function useMediaServerPort({ projectApi }) {
257
+ const { data, error, isRefetching } = (0, react_query_1.useSuspenseQuery)((0, projects_js_1.mediaServerPortQueryOptions)({
250
258
  projectApi,
251
- projectId,
252
- blobId,
253
259
  }));
254
260
  return { data, error, isRefetching };
255
261
  }
@@ -1,4 +1,4 @@
1
- import type { BlobApi, IconApi } from '@comapeo/core' with { 'resolution-mode': 'import' };
1
+ import type { BlobApi } from '@comapeo/core' with { 'resolution-mode': 'import' };
2
2
  import type { MapeoClientApi, MapeoProjectApi } from '@comapeo/ipc' with { 'resolution-mode': 'import' };
3
3
  import type { ProjectSettings } from '@comapeo/schema' with { 'resolution-mode': 'import' };
4
4
  import { type QueryClient, type UnusedSkipTokenOptions } from '@tanstack/react-query';
@@ -19,27 +19,16 @@ export declare function getMemberByIdQueryKey({ projectId, deviceId, }: {
19
19
  projectId: string;
20
20
  deviceId: string;
21
21
  }): readonly ["@comapeo/core-react", "projects", string, "members", string];
22
- export declare function getIconUrlQueryKey({ projectId, iconId, ...mimeBasedOpts }: {
23
- projectId: string;
24
- iconId: string;
25
- } & (IconApi.BitmapOpts | IconApi.SvgOpts)): readonly ["@comapeo/core-react", "projects", string, "icons", string, {
26
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant["mimeType"], "image/png">;
27
- pixelDensity: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant, {
28
- mimeType: "image/png";
29
- }>["pixelDensity"];
30
- size: import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).ValidSizes;
31
- } | {
32
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant["mimeType"], "image/svg+xml">;
33
- size: import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).ValidSizes;
34
- }];
35
22
  export declare function getDocumentCreatedByQueryKey({ projectId, originalVersionId, }: {
36
23
  projectId: string;
37
24
  originalVersionId: string;
38
25
  }): readonly ["@comapeo/core-react", "projects", string, "document_created_by", string];
39
- export declare function getAttachmentUrlQueryKey({ projectId, blobId, }: {
40
- projectId: string;
41
- blobId: BlobApi.BlobId;
42
- }): readonly ["@comapeo/core-react", "projects", string, "attachments", import("@comapeo/core/dist/types.js", { with: { "resolution-mode": "import" } }).BlobId];
26
+ /**
27
+ * We call this within a project hook, because that's the only place the API is
28
+ * exposed right now, but it is the same for all projects, so no need for
29
+ * scoping the query key to the project
30
+ */
31
+ export declare function getMediaServerPortQueryKey(): readonly ["@comapeo/core-react", "media_server_port"];
43
32
  export declare function projectsQueryOptions({ clientApi, }: {
44
33
  clientApi: MapeoClientApi;
45
34
  }): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<import("@comapeo/core/dist/mapeo-manager.js", { with: { "resolution-mode": "import" } }).ListedProject[], Error, import("@comapeo/core/dist/mapeo-manager.js", { with: { "resolution-mode": "import" } }).ListedProject[], readonly ["@comapeo/core-react", "projects"]>, "queryFn"> & {
@@ -99,45 +88,6 @@ export declare function projectOwnRoleQueryOptions({ projectApi, projectId, }: {
99
88
  [dataTagErrorSymbol]: Error;
100
89
  };
101
90
  };
102
- export declare function iconUrlQueryOptions({ projectApi, projectId, iconId, ...mimeBasedOpts }: {
103
- projectApi: MapeoProjectApi;
104
- projectId: string;
105
- iconId: Parameters<MapeoProjectApi['$icons']['getIconUrl']>[0];
106
- } & (IconApi.BitmapOpts | IconApi.SvgOpts)): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<string, Error, string, readonly ["@comapeo/core-react", "projects", string, "icons", string, {
107
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant["mimeType"], "image/png">;
108
- pixelDensity: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant, {
109
- mimeType: "image/png";
110
- }>["pixelDensity"];
111
- size: import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).ValidSizes;
112
- } | {
113
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant["mimeType"], "image/svg+xml">;
114
- size: import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).ValidSizes;
115
- }]>, "queryFn"> & {
116
- queryFn?: import("@tanstack/react-query").QueryFunction<string, readonly ["@comapeo/core-react", "projects", string, "icons", string, {
117
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant["mimeType"], "image/png">;
118
- pixelDensity: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant, {
119
- mimeType: "image/png";
120
- }>["pixelDensity"];
121
- size: import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).ValidSizes;
122
- } | {
123
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant["mimeType"], "image/svg+xml">;
124
- size: import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).ValidSizes;
125
- }], never> | undefined;
126
- } & {
127
- queryKey: readonly ["@comapeo/core-react", "projects", string, "icons", string, {
128
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant["mimeType"], "image/png">;
129
- pixelDensity: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant, {
130
- mimeType: "image/png";
131
- }>["pixelDensity"];
132
- size: import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).ValidSizes;
133
- } | {
134
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).IconVariant["mimeType"], "image/svg+xml">;
135
- size: import("@comapeo/core/dist/icon-api.js", { with: { "resolution-mode": "import" } }).ValidSizes;
136
- }] & {
137
- [dataTagSymbol]: string;
138
- [dataTagErrorSymbol]: Error;
139
- };
140
- };
141
91
  export declare function documentCreatedByQueryOptions({ projectApi, projectId, originalVersionId, }: {
142
92
  projectApi: MapeoProjectApi;
143
93
  projectId: string;
@@ -150,14 +100,12 @@ export declare function documentCreatedByQueryOptions({ projectApi, projectId, o
150
100
  [dataTagErrorSymbol]: Error;
151
101
  };
152
102
  };
153
- export declare function attachmentUrlQueryOptions({ projectApi, projectId, blobId, }: {
103
+ export declare function mediaServerPortQueryOptions({ projectApi, }: {
154
104
  projectApi: MapeoProjectApi;
155
- projectId: string;
156
- blobId: BlobApi.BlobId;
157
- }): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<string, Error, string, readonly ["@comapeo/core-react", "projects", string, "attachments", import("@comapeo/core/dist/types.js", { with: { "resolution-mode": "import" } }).BlobId]>, "queryFn"> & {
158
- queryFn?: import("@tanstack/react-query").QueryFunction<string, readonly ["@comapeo/core-react", "projects", string, "attachments", import("@comapeo/core/dist/types.js", { with: { "resolution-mode": "import" } }).BlobId], never> | undefined;
105
+ }): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<string, Error, string, readonly ["@comapeo/core-react", "media_server_port"]>, "queryFn"> & {
106
+ queryFn?: import("@tanstack/react-query").QueryFunction<string, readonly ["@comapeo/core-react", "media_server_port"], never> | undefined;
159
107
  } & {
160
- queryKey: readonly ["@comapeo/core-react", "projects", string, "attachments", import("@comapeo/core/dist/types.js", { with: { "resolution-mode": "import" } }).BlobId] & {
108
+ queryKey: readonly ["@comapeo/core-react", "media_server_port"] & {
161
109
  [dataTagSymbol]: string;
162
110
  [dataTagErrorSymbol]: Error;
163
111
  };
@@ -6,18 +6,16 @@ exports.getProjectSettingsQueryKey = getProjectSettingsQueryKey;
6
6
  exports.getProjectRoleQueryKey = getProjectRoleQueryKey;
7
7
  exports.getMembersQueryKey = getMembersQueryKey;
8
8
  exports.getMemberByIdQueryKey = getMemberByIdQueryKey;
9
- exports.getIconUrlQueryKey = getIconUrlQueryKey;
10
9
  exports.getDocumentCreatedByQueryKey = getDocumentCreatedByQueryKey;
11
- exports.getAttachmentUrlQueryKey = getAttachmentUrlQueryKey;
10
+ exports.getMediaServerPortQueryKey = getMediaServerPortQueryKey;
12
11
  exports.projectsQueryOptions = projectsQueryOptions;
13
12
  exports.projectByIdQueryOptions = projectByIdQueryOptions;
14
13
  exports.projectSettingsQueryOptions = projectSettingsQueryOptions;
15
14
  exports.projectMembersQueryOptions = projectMembersQueryOptions;
16
15
  exports.projectMemberByIdQueryOptions = projectMemberByIdQueryOptions;
17
16
  exports.projectOwnRoleQueryOptions = projectOwnRoleQueryOptions;
18
- exports.iconUrlQueryOptions = iconUrlQueryOptions;
19
17
  exports.documentCreatedByQueryOptions = documentCreatedByQueryOptions;
20
- exports.attachmentUrlQueryOptions = attachmentUrlQueryOptions;
18
+ exports.mediaServerPortQueryOptions = mediaServerPortQueryOptions;
21
19
  exports.addServerPeerMutationOptions = addServerPeerMutationOptions;
22
20
  exports.removeServerPeerMutationOptions = removeServerPeerMutationOptions;
23
21
  exports.createProjectMutationOptions = createProjectMutationOptions;
@@ -52,16 +50,6 @@ function getMembersQueryKey({ projectId }) {
52
50
  function getMemberByIdQueryKey({ projectId, deviceId, }) {
53
51
  return [shared_js_1.ROOT_QUERY_KEY, 'projects', projectId, 'members', deviceId];
54
52
  }
55
- function getIconUrlQueryKey({ projectId, iconId, ...mimeBasedOpts }) {
56
- return [
57
- shared_js_1.ROOT_QUERY_KEY,
58
- 'projects',
59
- projectId,
60
- 'icons',
61
- iconId,
62
- mimeBasedOpts,
63
- ];
64
- }
65
53
  function getDocumentCreatedByQueryKey({ projectId, originalVersionId, }) {
66
54
  return [
67
55
  shared_js_1.ROOT_QUERY_KEY,
@@ -71,8 +59,13 @@ function getDocumentCreatedByQueryKey({ projectId, originalVersionId, }) {
71
59
  originalVersionId,
72
60
  ];
73
61
  }
74
- function getAttachmentUrlQueryKey({ projectId, blobId, }) {
75
- return [shared_js_1.ROOT_QUERY_KEY, 'projects', projectId, 'attachments', blobId];
62
+ /**
63
+ * We call this within a project hook, because that's the only place the API is
64
+ * exposed right now, but it is the same for all projects, so no need for
65
+ * scoping the query key to the project
66
+ */
67
+ function getMediaServerPortQueryKey() {
68
+ return [shared_js_1.ROOT_QUERY_KEY, 'media_server_port'];
76
69
  }
77
70
  function projectsQueryOptions({ clientApi, }) {
78
71
  return (0, react_query_1.queryOptions)({
@@ -128,15 +121,6 @@ function projectOwnRoleQueryOptions({ projectApi, projectId, }) {
128
121
  },
129
122
  });
130
123
  }
131
- function iconUrlQueryOptions({ projectApi, projectId, iconId, ...mimeBasedOpts }) {
132
- return (0, react_query_1.queryOptions)({
133
- ...(0, shared_js_1.baseQueryOptions)(),
134
- queryKey: getIconUrlQueryKey({ ...mimeBasedOpts, projectId, iconId }),
135
- queryFn: async () => {
136
- return projectApi.$icons.getIconUrl(iconId, mimeBasedOpts);
137
- },
138
- });
139
- }
140
124
  function documentCreatedByQueryOptions({ projectApi, projectId, originalVersionId, }) {
141
125
  return (0, react_query_1.queryOptions)({
142
126
  ...(0, shared_js_1.baseQueryOptions)(),
@@ -147,16 +131,31 @@ function documentCreatedByQueryOptions({ projectApi, projectId, originalVersionI
147
131
  queryFn: async () => {
148
132
  return projectApi.$originalVersionIdToDeviceId(originalVersionId);
149
133
  },
134
+ staleTime: 'static',
135
+ gcTime: Infinity,
150
136
  });
151
137
  }
152
- function attachmentUrlQueryOptions({ projectApi, projectId, blobId, }) {
138
+ // Used as a placeholder so that we can read the server port from the $blobs.getUrl() method
139
+ const FAKE_BLOB_ID = {
140
+ type: 'photo',
141
+ variant: 'original',
142
+ name: 'name',
143
+ driveId: 'drive-id',
144
+ };
145
+ function mediaServerPortQueryOptions({ projectApi, }) {
153
146
  return (0, react_query_1.queryOptions)({
154
147
  ...(0, shared_js_1.baseQueryOptions)(),
155
- queryKey: getAttachmentUrlQueryKey({ projectId, blobId }),
148
+ // HACK: The server doesn't yet expose a method to get its port, so we use
149
+ // the existing $blobs.getUrl() to get the port with a fake BlobId. The port
150
+ // is the same regardless of the blobId, so it's not necessary to include it
151
+ // as a dep for the query key.
152
+ queryKey: getMediaServerPortQueryKey(),
156
153
  queryFn: async () => {
157
- // TODO: Might need a refresh token? (similar to map style url)
158
- return projectApi.$blobs.getUrl(blobId);
154
+ const url = await projectApi.$blobs.getUrl(FAKE_BLOB_ID);
155
+ return new URL(url).port;
159
156
  },
157
+ staleTime: 'static',
158
+ gcTime: Infinity,
160
159
  });
161
160
  }
162
161
  function addServerPeerMutationOptions({ projectApi, projectId, queryClient, }) {
@@ -0,0 +1,12 @@
1
+ import type { BlobApi, IconApi } from '@comapeo/core';
2
+ /**
3
+ * Get a url for a blob based on its BlobId
4
+ */
5
+ export declare function getBlobUrl(baseUrl: string, blobId: BlobApi.BlobId): string;
6
+ /**
7
+ * @param {string} iconId
8
+ * @param {BitmapOpts | SvgOpts} opts
9
+ *
10
+ * @returns {Promise<string>}
11
+ */
12
+ export declare function getIconUrl(baseUrl: string, iconId: string, opts: IconApi.BitmapOpts | IconApi.SvgOpts): string;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ // TODO: Move these into a separate "@comapeo/asset-server" module which can
3
+ // export them to be imported directly in a client.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.getBlobUrl = getBlobUrl;
6
+ exports.getIconUrl = getIconUrl;
7
+ const MIME_TO_EXTENSION = {
8
+ 'image/png': '.png',
9
+ 'image/svg+xml': '.svg',
10
+ };
11
+ /**
12
+ * Get a url for a blob based on its BlobId
13
+ */
14
+ function getBlobUrl(baseUrl, blobId) {
15
+ const { driveId, type, variant, name } = blobId;
16
+ if (!baseUrl.endsWith('/')) {
17
+ baseUrl += '/';
18
+ }
19
+ return baseUrl + `${driveId}/${type}/${variant}/${name}`;
20
+ }
21
+ /**
22
+ * @param {string} iconId
23
+ * @param {BitmapOpts | SvgOpts} opts
24
+ *
25
+ * @returns {Promise<string>}
26
+ */
27
+ function getIconUrl(baseUrl, iconId, opts) {
28
+ if (!baseUrl.endsWith('/')) {
29
+ baseUrl += '/';
30
+ }
31
+ const mimeExtension = MIME_TO_EXTENSION[opts.mimeType];
32
+ const pixelDensity = opts.mimeType === 'image/svg+xml' ||
33
+ // if the pixel density is 1, we can omit the density suffix in the resulting url
34
+ // and assume the pixel density is 1 for applicable mime types when using the url
35
+ opts.pixelDensity === 1
36
+ ? undefined
37
+ : opts.pixelDensity;
38
+ return (baseUrl +
39
+ constructIconPath({
40
+ pixelDensity,
41
+ size: opts.size,
42
+ extension: mimeExtension,
43
+ iconId,
44
+ }));
45
+ }
46
+ /**
47
+ * General purpose path builder for an icon
48
+ */
49
+ function constructIconPath({ size, pixelDensity, iconId, extension, }) {
50
+ if (iconId.length === 0 || size.length === 0 || extension.length === 0) {
51
+ throw new Error('iconId, size, and extension cannot be empty strings');
52
+ }
53
+ let result = `${iconId}/${size}`;
54
+ if (typeof pixelDensity === 'number') {
55
+ if (pixelDensity < 1) {
56
+ throw new Error('pixelDensity must be a positive number');
57
+ }
58
+ result += `@${pixelDensity}x`;
59
+ }
60
+ result += extension.startsWith('.') ? extension : '.' + extension;
61
+ return result;
62
+ }
@@ -626,7 +626,13 @@ export declare function useExportGeoJSON({ projectId }: {
626
626
  mutate: import("@tanstack/react-query").UseMutateFunction<string, Error, {
627
627
  path: string;
628
628
  exportOptions: {
629
- observations?: boolean;
629
+ observations
630
+ /**
631
+ * Update the settings of a project.
632
+ *
633
+ * @param opts.projectId Public ID of the project to apply changes to.
634
+ */
635
+ ?: boolean;
630
636
  tracks?: boolean;
631
637
  lang?: string;
632
638
  };
@@ -634,7 +640,13 @@ export declare function useExportGeoJSON({ projectId }: {
634
640
  mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<string, Error, {
635
641
  path: string;
636
642
  exportOptions: {
637
- observations?: boolean;
643
+ observations
644
+ /**
645
+ * Update the settings of a project.
646
+ *
647
+ * @param opts.projectId Public ID of the project to apply changes to.
648
+ */
649
+ ?: boolean;
638
650
  tracks?: boolean;
639
651
  lang?: string;
640
652
  };
@@ -646,7 +658,13 @@ export declare function useExportGeoJSON({ projectId }: {
646
658
  mutate: import("@tanstack/react-query").UseMutateFunction<string, Error, {
647
659
  path: string;
648
660
  exportOptions: {
649
- observations?: boolean;
661
+ observations
662
+ /**
663
+ * Update the settings of a project.
664
+ *
665
+ * @param opts.projectId Public ID of the project to apply changes to.
666
+ */
667
+ ?: boolean;
650
668
  tracks?: boolean;
651
669
  lang?: string;
652
670
  };
@@ -654,7 +672,13 @@ export declare function useExportGeoJSON({ projectId }: {
654
672
  mutateAsync: import("@tanstack/react-query").UseMutateAsyncFunction<string, Error, {
655
673
  path: string;
656
674
  exportOptions: {
657
- observations?: boolean;
675
+ observations
676
+ /**
677
+ * Update the settings of a project.
678
+ *
679
+ * @param opts.projectId Public ID of the project to apply changes to.
680
+ */
681
+ ?: boolean;
658
682
  tracks?: boolean;
659
683
  lang?: string;
660
684
  };
@@ -1,7 +1,8 @@
1
1
  import { useMutation, useQueryClient, useSuspenseQuery, } from '@tanstack/react-query';
2
2
  import { useSyncExternalStore } from 'react';
3
- import { addServerPeerMutationOptions, attachmentUrlQueryOptions, connectSyncServersMutationOptions, createBlobMutationOptions, createProjectMutationOptions, disconnectSyncServersMutationOptions, documentCreatedByQueryOptions, exportGeoJSONMutationOptions, exportZipFileMutationOptions, iconUrlQueryOptions, importProjectConfigMutationOptions, leaveProjectMutationOptions, projectByIdQueryOptions, projectMemberByIdQueryOptions, projectMembersQueryOptions, projectOwnRoleQueryOptions, projectSettingsQueryOptions, projectsQueryOptions, removeServerPeerMutationOptions, setAutostopDataSyncTimeoutMutationOptions, startSyncMutationOptions, stopSyncMutationOptions, updateProjectSettingsMutationOptions, } from '../lib/react-query/projects.js';
3
+ import { addServerPeerMutationOptions, connectSyncServersMutationOptions, createBlobMutationOptions, createProjectMutationOptions, disconnectSyncServersMutationOptions, documentCreatedByQueryOptions, exportGeoJSONMutationOptions, exportZipFileMutationOptions, importProjectConfigMutationOptions, leaveProjectMutationOptions, mediaServerPortQueryOptions, projectByIdQueryOptions, projectMemberByIdQueryOptions, projectMembersQueryOptions, projectOwnRoleQueryOptions, projectSettingsQueryOptions, projectsQueryOptions, removeServerPeerMutationOptions, setAutostopDataSyncTimeoutMutationOptions, startSyncMutationOptions, stopSyncMutationOptions, updateProjectSettingsMutationOptions, } from '../lib/react-query/projects.js';
4
4
  import { SyncStore } from '../lib/sync.js';
5
+ import { getBlobUrl, getIconUrl } from '../lib/urls.js';
5
6
  import { useClientApi } from './client.js';
6
7
  /**
7
8
  * Retrieve the project settings for a project.
@@ -158,13 +159,10 @@ export function useManyMembers({ projectId }) {
158
159
  */
159
160
  export function useIconUrl({ projectId, iconId, ...mimeBasedOpts }) {
160
161
  const { data: projectApi } = useSingleProject({ projectId });
161
- const { data, error, isRefetching } = useSuspenseQuery(iconUrlQueryOptions({
162
- ...mimeBasedOpts,
163
- projectApi,
164
- projectId,
165
- iconId,
166
- }));
167
- return { data, error, isRefetching };
162
+ const { data: port, error, isRefetching } = useMediaServerPort({ projectApi });
163
+ const baseUrl = `http://127.0.0.1:${port}`;
164
+ const iconUrl = getIconUrl(baseUrl, iconId, mimeBasedOpts);
165
+ return { data: iconUrl, error, isRefetching };
168
166
  }
169
167
  /**
170
168
  * Retrieve a URL that points to a desired blob resource.
@@ -219,10 +217,18 @@ export function useIconUrl({ projectId, iconId, ...mimeBasedOpts }) {
219
217
  */
220
218
  export function useAttachmentUrl({ projectId, blobId, }) {
221
219
  const { data: projectApi } = useSingleProject({ projectId });
222
- const { data, error, isRefetching } = useSuspenseQuery(attachmentUrlQueryOptions({
220
+ const { data: port, error, isRefetching } = useMediaServerPort({ projectApi });
221
+ const baseUrl = `http://127.0.0.1:${port}`;
222
+ const blobUrl = getBlobUrl(baseUrl, blobId);
223
+ return { data: blobUrl, error, isRefetching };
224
+ }
225
+ /**
226
+ * @internal
227
+ * Hack to retrieve the media server port.
228
+ */
229
+ function useMediaServerPort({ projectApi }) {
230
+ const { data, error, isRefetching } = useSuspenseQuery(mediaServerPortQueryOptions({
223
231
  projectApi,
224
- projectId,
225
- blobId,
226
232
  }));
227
233
  return { data, error, isRefetching };
228
234
  }
@@ -1,4 +1,4 @@
1
- import type { BlobApi, IconApi } from '@comapeo/core' with { 'resolution-mode': 'import' };
1
+ import type { BlobApi } from '@comapeo/core' with { 'resolution-mode': 'import' };
2
2
  import type { MapeoClientApi, MapeoProjectApi } from '@comapeo/ipc' with { 'resolution-mode': 'import' };
3
3
  import type { ProjectSettings } from '@comapeo/schema' with { 'resolution-mode': 'import' };
4
4
  import { type QueryClient, type UnusedSkipTokenOptions } from '@tanstack/react-query';
@@ -19,27 +19,16 @@ export declare function getMemberByIdQueryKey({ projectId, deviceId, }: {
19
19
  projectId: string;
20
20
  deviceId: string;
21
21
  }): readonly ["@comapeo/core-react", "projects", string, "members", string];
22
- export declare function getIconUrlQueryKey({ projectId, iconId, ...mimeBasedOpts }: {
23
- projectId: string;
24
- iconId: string;
25
- } & (IconApi.BitmapOpts | IconApi.SvgOpts)): readonly ["@comapeo/core-react", "projects", string, "icons", string, {
26
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant["mimeType"], "image/png">;
27
- pixelDensity: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant, {
28
- mimeType: "image/png";
29
- }>["pixelDensity"];
30
- size: import("@comapeo/core/dist/icon-api.js").ValidSizes;
31
- } | {
32
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant["mimeType"], "image/svg+xml">;
33
- size: import("@comapeo/core/dist/icon-api.js").ValidSizes;
34
- }];
35
22
  export declare function getDocumentCreatedByQueryKey({ projectId, originalVersionId, }: {
36
23
  projectId: string;
37
24
  originalVersionId: string;
38
25
  }): readonly ["@comapeo/core-react", "projects", string, "document_created_by", string];
39
- export declare function getAttachmentUrlQueryKey({ projectId, blobId, }: {
40
- projectId: string;
41
- blobId: BlobApi.BlobId;
42
- }): readonly ["@comapeo/core-react", "projects", string, "attachments", import("@comapeo/core/dist/types.js").BlobId];
26
+ /**
27
+ * We call this within a project hook, because that's the only place the API is
28
+ * exposed right now, but it is the same for all projects, so no need for
29
+ * scoping the query key to the project
30
+ */
31
+ export declare function getMediaServerPortQueryKey(): readonly ["@comapeo/core-react", "media_server_port"];
43
32
  export declare function projectsQueryOptions({ clientApi, }: {
44
33
  clientApi: MapeoClientApi;
45
34
  }): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<import("@comapeo/core/dist/mapeo-manager.js").ListedProject[], Error, import("@comapeo/core/dist/mapeo-manager.js").ListedProject[], readonly ["@comapeo/core-react", "projects"]>, "queryFn"> & {
@@ -99,45 +88,6 @@ export declare function projectOwnRoleQueryOptions({ projectApi, projectId, }: {
99
88
  [dataTagErrorSymbol]: Error;
100
89
  };
101
90
  };
102
- export declare function iconUrlQueryOptions({ projectApi, projectId, iconId, ...mimeBasedOpts }: {
103
- projectApi: MapeoProjectApi;
104
- projectId: string;
105
- iconId: Parameters<MapeoProjectApi['$icons']['getIconUrl']>[0];
106
- } & (IconApi.BitmapOpts | IconApi.SvgOpts)): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<string, Error, string, readonly ["@comapeo/core-react", "projects", string, "icons", string, {
107
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant["mimeType"], "image/png">;
108
- pixelDensity: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant, {
109
- mimeType: "image/png";
110
- }>["pixelDensity"];
111
- size: import("@comapeo/core/dist/icon-api.js").ValidSizes;
112
- } | {
113
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant["mimeType"], "image/svg+xml">;
114
- size: import("@comapeo/core/dist/icon-api.js").ValidSizes;
115
- }]>, "queryFn"> & {
116
- queryFn?: import("@tanstack/react-query").QueryFunction<string, readonly ["@comapeo/core-react", "projects", string, "icons", string, {
117
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant["mimeType"], "image/png">;
118
- pixelDensity: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant, {
119
- mimeType: "image/png";
120
- }>["pixelDensity"];
121
- size: import("@comapeo/core/dist/icon-api.js").ValidSizes;
122
- } | {
123
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant["mimeType"], "image/svg+xml">;
124
- size: import("@comapeo/core/dist/icon-api.js").ValidSizes;
125
- }], never> | undefined;
126
- } & {
127
- queryKey: readonly ["@comapeo/core-react", "projects", string, "icons", string, {
128
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant["mimeType"], "image/png">;
129
- pixelDensity: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant, {
130
- mimeType: "image/png";
131
- }>["pixelDensity"];
132
- size: import("@comapeo/core/dist/icon-api.js").ValidSizes;
133
- } | {
134
- mimeType: Extract<import("@comapeo/core/dist/icon-api.js").IconVariant["mimeType"], "image/svg+xml">;
135
- size: import("@comapeo/core/dist/icon-api.js").ValidSizes;
136
- }] & {
137
- [dataTagSymbol]: string;
138
- [dataTagErrorSymbol]: Error;
139
- };
140
- };
141
91
  export declare function documentCreatedByQueryOptions({ projectApi, projectId, originalVersionId, }: {
142
92
  projectApi: MapeoProjectApi;
143
93
  projectId: string;
@@ -150,14 +100,12 @@ export declare function documentCreatedByQueryOptions({ projectApi, projectId, o
150
100
  [dataTagErrorSymbol]: Error;
151
101
  };
152
102
  };
153
- export declare function attachmentUrlQueryOptions({ projectApi, projectId, blobId, }: {
103
+ export declare function mediaServerPortQueryOptions({ projectApi, }: {
154
104
  projectApi: MapeoProjectApi;
155
- projectId: string;
156
- blobId: BlobApi.BlobId;
157
- }): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<string, Error, string, readonly ["@comapeo/core-react", "projects", string, "attachments", import("@comapeo/core/dist/types.js").BlobId]>, "queryFn"> & {
158
- queryFn?: import("@tanstack/react-query").QueryFunction<string, readonly ["@comapeo/core-react", "projects", string, "attachments", import("@comapeo/core/dist/types.js").BlobId], never> | undefined;
105
+ }): import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<string, Error, string, readonly ["@comapeo/core-react", "media_server_port"]>, "queryFn"> & {
106
+ queryFn?: import("@tanstack/react-query").QueryFunction<string, readonly ["@comapeo/core-react", "media_server_port"], never> | undefined;
159
107
  } & {
160
- queryKey: readonly ["@comapeo/core-react", "projects", string, "attachments", import("@comapeo/core/dist/types.js").BlobId] & {
108
+ queryKey: readonly ["@comapeo/core-react", "media_server_port"] & {
161
109
  [dataTagSymbol]: string;
162
110
  [dataTagErrorSymbol]: Error;
163
111
  };
@@ -18,16 +18,6 @@ export function getMembersQueryKey({ projectId }) {
18
18
  export function getMemberByIdQueryKey({ projectId, deviceId, }) {
19
19
  return [ROOT_QUERY_KEY, 'projects', projectId, 'members', deviceId];
20
20
  }
21
- export function getIconUrlQueryKey({ projectId, iconId, ...mimeBasedOpts }) {
22
- return [
23
- ROOT_QUERY_KEY,
24
- 'projects',
25
- projectId,
26
- 'icons',
27
- iconId,
28
- mimeBasedOpts,
29
- ];
30
- }
31
21
  export function getDocumentCreatedByQueryKey({ projectId, originalVersionId, }) {
32
22
  return [
33
23
  ROOT_QUERY_KEY,
@@ -37,8 +27,13 @@ export function getDocumentCreatedByQueryKey({ projectId, originalVersionId, })
37
27
  originalVersionId,
38
28
  ];
39
29
  }
40
- export function getAttachmentUrlQueryKey({ projectId, blobId, }) {
41
- return [ROOT_QUERY_KEY, 'projects', projectId, 'attachments', blobId];
30
+ /**
31
+ * We call this within a project hook, because that's the only place the API is
32
+ * exposed right now, but it is the same for all projects, so no need for
33
+ * scoping the query key to the project
34
+ */
35
+ export function getMediaServerPortQueryKey() {
36
+ return [ROOT_QUERY_KEY, 'media_server_port'];
42
37
  }
43
38
  export function projectsQueryOptions({ clientApi, }) {
44
39
  return queryOptions({
@@ -94,15 +89,6 @@ export function projectOwnRoleQueryOptions({ projectApi, projectId, }) {
94
89
  },
95
90
  });
96
91
  }
97
- export function iconUrlQueryOptions({ projectApi, projectId, iconId, ...mimeBasedOpts }) {
98
- return queryOptions({
99
- ...baseQueryOptions(),
100
- queryKey: getIconUrlQueryKey({ ...mimeBasedOpts, projectId, iconId }),
101
- queryFn: async () => {
102
- return projectApi.$icons.getIconUrl(iconId, mimeBasedOpts);
103
- },
104
- });
105
- }
106
92
  export function documentCreatedByQueryOptions({ projectApi, projectId, originalVersionId, }) {
107
93
  return queryOptions({
108
94
  ...baseQueryOptions(),
@@ -113,16 +99,31 @@ export function documentCreatedByQueryOptions({ projectApi, projectId, originalV
113
99
  queryFn: async () => {
114
100
  return projectApi.$originalVersionIdToDeviceId(originalVersionId);
115
101
  },
102
+ staleTime: 'static',
103
+ gcTime: Infinity,
116
104
  });
117
105
  }
118
- export function attachmentUrlQueryOptions({ projectApi, projectId, blobId, }) {
106
+ // Used as a placeholder so that we can read the server port from the $blobs.getUrl() method
107
+ const FAKE_BLOB_ID = {
108
+ type: 'photo',
109
+ variant: 'original',
110
+ name: 'name',
111
+ driveId: 'drive-id',
112
+ };
113
+ export function mediaServerPortQueryOptions({ projectApi, }) {
119
114
  return queryOptions({
120
115
  ...baseQueryOptions(),
121
- queryKey: getAttachmentUrlQueryKey({ projectId, blobId }),
116
+ // HACK: The server doesn't yet expose a method to get its port, so we use
117
+ // the existing $blobs.getUrl() to get the port with a fake BlobId. The port
118
+ // is the same regardless of the blobId, so it's not necessary to include it
119
+ // as a dep for the query key.
120
+ queryKey: getMediaServerPortQueryKey(),
122
121
  queryFn: async () => {
123
- // TODO: Might need a refresh token? (similar to map style url)
124
- return projectApi.$blobs.getUrl(blobId);
122
+ const url = await projectApi.$blobs.getUrl(FAKE_BLOB_ID);
123
+ return new URL(url).port;
125
124
  },
125
+ staleTime: 'static',
126
+ gcTime: Infinity,
126
127
  });
127
128
  }
128
129
  export function addServerPeerMutationOptions({ projectApi, projectId, queryClient, }) {
@@ -0,0 +1,12 @@
1
+ import type { BlobApi, IconApi } from '@comapeo/core';
2
+ /**
3
+ * Get a url for a blob based on its BlobId
4
+ */
5
+ export declare function getBlobUrl(baseUrl: string, blobId: BlobApi.BlobId): string;
6
+ /**
7
+ * @param {string} iconId
8
+ * @param {BitmapOpts | SvgOpts} opts
9
+ *
10
+ * @returns {Promise<string>}
11
+ */
12
+ export declare function getIconUrl(baseUrl: string, iconId: string, opts: IconApi.BitmapOpts | IconApi.SvgOpts): string;
@@ -0,0 +1,58 @@
1
+ // TODO: Move these into a separate "@comapeo/asset-server" module which can
2
+ // export them to be imported directly in a client.
3
+ const MIME_TO_EXTENSION = {
4
+ 'image/png': '.png',
5
+ 'image/svg+xml': '.svg',
6
+ };
7
+ /**
8
+ * Get a url for a blob based on its BlobId
9
+ */
10
+ export function getBlobUrl(baseUrl, blobId) {
11
+ const { driveId, type, variant, name } = blobId;
12
+ if (!baseUrl.endsWith('/')) {
13
+ baseUrl += '/';
14
+ }
15
+ return baseUrl + `${driveId}/${type}/${variant}/${name}`;
16
+ }
17
+ /**
18
+ * @param {string} iconId
19
+ * @param {BitmapOpts | SvgOpts} opts
20
+ *
21
+ * @returns {Promise<string>}
22
+ */
23
+ export function getIconUrl(baseUrl, iconId, opts) {
24
+ if (!baseUrl.endsWith('/')) {
25
+ baseUrl += '/';
26
+ }
27
+ const mimeExtension = MIME_TO_EXTENSION[opts.mimeType];
28
+ const pixelDensity = opts.mimeType === 'image/svg+xml' ||
29
+ // if the pixel density is 1, we can omit the density suffix in the resulting url
30
+ // and assume the pixel density is 1 for applicable mime types when using the url
31
+ opts.pixelDensity === 1
32
+ ? undefined
33
+ : opts.pixelDensity;
34
+ return (baseUrl +
35
+ constructIconPath({
36
+ pixelDensity,
37
+ size: opts.size,
38
+ extension: mimeExtension,
39
+ iconId,
40
+ }));
41
+ }
42
+ /**
43
+ * General purpose path builder for an icon
44
+ */
45
+ function constructIconPath({ size, pixelDensity, iconId, extension, }) {
46
+ if (iconId.length === 0 || size.length === 0 || extension.length === 0) {
47
+ throw new Error('iconId, size, and extension cannot be empty strings');
48
+ }
49
+ let result = `${iconId}/${size}`;
50
+ if (typeof pixelDensity === 'number') {
51
+ if (pixelDensity < 1) {
52
+ throw new Error('pixelDensity must be a positive number');
53
+ }
54
+ result += `@${pixelDensity}x`;
55
+ }
56
+ result += extension.startsWith('.') ? extension : '.' + extension;
57
+ return result;
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comapeo/core-react",
3
- "version": "6.0.0",
3
+ "version": "6.1.0",
4
4
  "description": "React wrapper for working with @comapeo/core",
5
5
  "repository": {
6
6
  "type": "git",