@itwin/saved-views-react 0.2.1 → 0.3.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.
Files changed (31) hide show
  1. package/lib/LayeredDropdownMenu/LayeredDropdownMenu.css +5 -1
  2. package/lib/LayeredDropdownMenu/LayeredDropdownMenu.d.ts +3 -1
  3. package/lib/LayeredDropdownMenu/LayeredDropdownMenu.js +2 -2
  4. package/lib/SavedView.d.ts +2 -0
  5. package/lib/SavedViewTile/SavedViewOptions.d.ts +2 -0
  6. package/lib/SavedViewTile/SavedViewOptions.js +4 -4
  7. package/lib/SavedViewTile/SavedViewTile.css +8 -1
  8. package/lib/SavedViewTile/SavedViewTile.d.ts +5 -4
  9. package/lib/SavedViewTile/SavedViewTile.js +12 -14
  10. package/lib/SavedViewsClient/ITwinSavedViewsClient.js +6 -2
  11. package/lib/StickyExpandableBlock/StickyExpandableBlock.js +9 -1
  12. package/lib/api/clients/ISavedViewsClient.d.ts +5 -5
  13. package/lib/api/utilities/SavedViewTypes.d.ts +1 -1
  14. package/lib/api/utilities/translation/SavedViewTranslation.d.ts +19 -15
  15. package/lib/api/utilities/translation/SavedViewTranslation.js +174 -160
  16. package/lib/api/utilities/translation/displayStyleExtractor.js +55 -5
  17. package/lib/api/utilities/translation/viewExtractorSavedViewToLegacySavedView.d.ts +2 -19
  18. package/lib/api/utilities/translation/viewExtractorSavedViewToLegacySavedView.js +82 -133
  19. package/lib/captureSavedViewData.d.ts +4 -1
  20. package/lib/captureSavedViewData.js +12 -10
  21. package/lib/experimental.d.ts +3 -1
  22. package/lib/experimental.js +2 -1
  23. package/lib/useSavedViews.d.ts +2 -2
  24. package/lib/useSavedViews.js +76 -60
  25. package/package.json +3 -3
  26. package/lib/api/clients/IModelQueryClient.d.ts +0 -10
  27. package/lib/api/clients/IModelQueryClient.js +0 -45
  28. package/lib/api/utilities/translation/ModelsAndCategoriesHelper.d.ts +0 -3
  29. package/lib/api/utilities/translation/ModelsAndCategoriesHelper.js +0 -57
  30. package/lib/api/utilities/translation/urlConverter.d.ts +0 -7
  31. package/lib/api/utilities/translation/urlConverter.js +0 -42
@@ -1,30 +1,73 @@
1
1
  import { SheetViewState } from "@itwin/core-frontend";
2
2
  import { extractClipVectors } from "./clipVectorsExtractor.js";
3
3
  import { extractDisplayStyle, extractDisplayStyle3d } from "./displayStyleExtractor.js";
4
- import { convertAllLegacyUrlsToUrls, urlToLegacyUrl } from "./urlConverter.js";
5
- const UNGROUPED_ID = "-1";
6
- /**
7
- * Extracts id from href
8
- * @param href
9
- */
10
- export const extractIdFromHref = (href) => {
11
- return href.split("/").pop();
12
- };
13
- /**
14
- * Extract all the tags
15
- * @param creator href for the creator
16
- * @param tags the list of tags in the saved view
17
- * @returns
18
- */
19
- const extractTags = (creator, tags) => {
20
- return tags?.map((tag) => {
21
- const legacyTag = {
22
- name: tag.displayName,
23
- createdByUserId: extractIdFromHref(creator) ?? "",
24
- };
25
- return legacyTag;
26
- });
27
- };
4
+ export function savedViewITwin3dToLegacy3dSavedView(savedViewRsp, seedSpatialViewState) {
5
+ const modelSelector = seedSpatialViewState.modelSelector;
6
+ const itwin3dView = savedViewRsp.savedViewData.itwin3dView;
7
+ const legacyView = {
8
+ id: savedViewRsp.id,
9
+ is2d: false,
10
+ groupId: savedViewRsp._links.group ? extractIdFromHref(savedViewRsp._links.group.href) : undefined,
11
+ tags: extractTags(savedViewRsp._links.creator?.href ?? "", savedViewRsp.tags),
12
+ name: savedViewRsp.displayName,
13
+ userId: extractIdFromHref(savedViewRsp._links.creator?.href ?? "") ?? "",
14
+ shared: savedViewRsp.shared,
15
+ thumbnailId: savedViewRsp.id ?? "",
16
+ viewDefinitionProps: {
17
+ origin: itwin3dView.origin,
18
+ extents: itwin3dView.extents,
19
+ angles: itwin3dView.angles ?? {},
20
+ camera: itwin3dView.camera,
21
+ jsonProperties: {
22
+ viewDetails: extractClipVectors(itwin3dView) ?? {},
23
+ },
24
+ classFullName: seedSpatialViewState.classFullName,
25
+ code: seedSpatialViewState.code,
26
+ model: seedSpatialViewState.model,
27
+ categorySelectorId: seedSpatialViewState.categorySelector.id,
28
+ displayStyleId: seedSpatialViewState.displayStyle.id,
29
+ cameraOn: itwin3dView.camera !== undefined,
30
+ modelSelectorId: seedSpatialViewState.modelSelector.id,
31
+ },
32
+ modelSelectorProps: {
33
+ classFullName: modelSelector.classFullName,
34
+ code: {
35
+ spec: modelSelector.code.spec,
36
+ scope: modelSelector.code.scope,
37
+ value: modelSelector.code.value,
38
+ },
39
+ model: modelSelector.model,
40
+ models: itwin3dView.models?.enabled ?? [],
41
+ },
42
+ categorySelectorProps: {
43
+ classFullName: seedSpatialViewState.categorySelector.classFullName,
44
+ categories: itwin3dView.categories?.enabled ?? [],
45
+ code: {
46
+ scope: seedSpatialViewState.categorySelector.code.scope,
47
+ spec: seedSpatialViewState.categorySelector.code.spec,
48
+ value: seedSpatialViewState.categorySelector.code.value,
49
+ },
50
+ model: seedSpatialViewState.categorySelector.model,
51
+ },
52
+ displayStyleProps: {
53
+ id: seedSpatialViewState.displayStyle.id,
54
+ classFullName: seedSpatialViewState.displayStyle.classFullName,
55
+ code: seedSpatialViewState.displayStyle.code,
56
+ model: seedSpatialViewState.displayStyle.model,
57
+ jsonProperties: {
58
+ styles: extractDisplayStyle3d(savedViewRsp.savedViewData.itwin3dView),
59
+ },
60
+ },
61
+ };
62
+ appendHiddenCategoriesToLegacyView(itwin3dView, legacyView);
63
+ appendHiddenModelsTo3dLegacySavedView(itwin3dView, legacyView);
64
+ return legacyView;
65
+ }
66
+ function appendHiddenModelsTo3dLegacySavedView(view, legacyView) {
67
+ if (view.models?.disabled) {
68
+ legacyView.hiddenModels = view.models?.disabled;
69
+ }
70
+ }
28
71
  /**
29
72
  * Transform a ViewDataITwinDrawing into a legacy SavedView if possible
30
73
  * @param savedViewRsp
@@ -32,15 +75,12 @@ const extractTags = (creator, tags) => {
32
75
  * @returns SavedView2d
33
76
  */
34
77
  export function savedViewItwinDrawingToLegacyDrawingView(savedViewRsp, seedDrawingViewState) {
35
- convertAllLegacyUrlsToUrls(savedViewRsp.savedViewData, urlToLegacyUrl);
36
78
  const iTwinDrawingView = savedViewRsp.savedViewData.itwinDrawingView;
37
79
  seedDrawingViewState.displayStyle;
38
80
  const legacyView = {
39
81
  id: savedViewRsp.id,
40
82
  is2d: true,
41
- groupId: savedViewRsp._links.group
42
- ? extractIdFromHref(savedViewRsp._links.group.href)
43
- : UNGROUPED_ID ?? "",
83
+ groupId: savedViewRsp._links.group ? extractIdFromHref(savedViewRsp._links.group.href) : undefined,
44
84
  tags: extractTags(savedViewRsp._links.creator?.href ?? "", savedViewRsp.tags),
45
85
  name: savedViewRsp.displayName,
46
86
  userId: extractIdFromHref(savedViewRsp._links.creator?.href ?? "") ?? "",
@@ -48,7 +88,7 @@ export function savedViewItwinDrawingToLegacyDrawingView(savedViewRsp, seedDrawi
48
88
  thumbnailId: savedViewRsp.id ?? "",
49
89
  categorySelectorProps: {
50
90
  classFullName: seedDrawingViewState.categorySelector.classFullName,
51
- categories: (iTwinDrawingView.categories?.enabled ?? []),
91
+ categories: iTwinDrawingView.categories?.enabled ?? [],
52
92
  code: {
53
93
  scope: seedDrawingViewState.categorySelector.code.scope,
54
94
  spec: seedDrawingViewState.categorySelector.code.spec,
@@ -63,7 +103,7 @@ export function savedViewItwinDrawingToLegacyDrawingView(savedViewRsp, seedDrawi
63
103
  id: seedDrawingViewState.id,
64
104
  jsonProperties: {
65
105
  viewDetails: {
66
- gridOrient: seedDrawingViewState.getGridOrientation() ?? undefined,
106
+ gridOrient: seedDrawingViewState.getGridOrientation(),
67
107
  },
68
108
  },
69
109
  code: {
@@ -107,14 +147,11 @@ export function savedViewItwinDrawingToLegacyDrawingView(savedViewRsp, seedDrawi
107
147
  * @returns SavedView2d
108
148
  */
109
149
  export function savedViewItwinSheetToLegacySheetSavedView(savedViewRsp, seedSheetViewState) {
110
- convertAllLegacyUrlsToUrls(savedViewRsp.savedViewData, urlToLegacyUrl);
111
150
  const itwinSheetView = savedViewRsp.savedViewData.itwinSheetView;
112
151
  const legacyView = {
113
152
  id: savedViewRsp.id,
114
153
  is2d: true,
115
- groupId: savedViewRsp._links.group
116
- ? extractIdFromHref(savedViewRsp._links.group.href)
117
- : UNGROUPED_ID,
154
+ groupId: savedViewRsp._links.group ? extractIdFromHref(savedViewRsp._links.group.href) : undefined,
118
155
  tags: extractTags(savedViewRsp._links.creator?.href ?? "", savedViewRsp.tags),
119
156
  name: savedViewRsp.displayName,
120
157
  userId: extractIdFromHref(savedViewRsp._links.creator?.href ?? "") ?? "",
@@ -122,7 +159,7 @@ export function savedViewItwinSheetToLegacySheetSavedView(savedViewRsp, seedShee
122
159
  thumbnailId: savedViewRsp.id ?? "",
123
160
  categorySelectorProps: {
124
161
  classFullName: seedSheetViewState.categorySelector.classFullName,
125
- categories: (itwinSheetView.categories?.enabled ?? []),
162
+ categories: itwinSheetView.categories?.enabled ?? [],
126
163
  code: {
127
164
  scope: seedSheetViewState.categorySelector.code.scope,
128
165
  spec: seedSheetViewState.categorySelector.code.spec,
@@ -187,107 +224,19 @@ export function savedViewItwinSheetToLegacySheetSavedView(savedViewRsp, seedShee
187
224
  return legacyView;
188
225
  }
189
226
  /**
190
- * Transform a ViewDataItwin3d into a legacy SavedView if possible
191
- * @param savedViewRsp
192
- * @param seedSpatialViewState
193
- * @returns SavedView
227
+ * Extract all the tags.
228
+ * @param creator href for the creator
229
+ * @param tags the list of tags in the saved view
194
230
  */
195
- export function savedViewITwin3dToLegacy3dSavedView(savedViewRsp, seedSpatialViewState) {
196
- convertAllLegacyUrlsToUrls(savedViewRsp.savedViewData, urlToLegacyUrl);
197
- const modelSelector = seedSpatialViewState.modelSelector;
198
- const itwin3dView = savedViewRsp.savedViewData.itwin3dView;
199
- const legacyView = {
200
- id: savedViewRsp.id,
201
- is2d: false,
202
- groupId: savedViewRsp._links.group
203
- ? extractIdFromHref(savedViewRsp._links.group.href)
204
- : UNGROUPED_ID,
205
- tags: extractTags(savedViewRsp._links.creator?.href ?? "", savedViewRsp.tags),
206
- name: savedViewRsp.displayName,
207
- userId: extractIdFromHref(savedViewRsp._links.creator?.href ?? "") ?? "",
208
- shared: savedViewRsp.shared,
209
- thumbnailId: savedViewRsp.id ?? "",
210
- viewDefinitionProps: {
211
- origin: itwin3dView.origin,
212
- extents: itwin3dView.extents,
213
- angles: itwin3dView.angles ?? {},
214
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
215
- camera: itwin3dView.camera,
216
- jsonProperties: {
217
- viewDetails: extractClipVectors(itwin3dView) ?? {},
218
- },
219
- classFullName: seedSpatialViewState.classFullName,
220
- code: seedSpatialViewState.code,
221
- model: seedSpatialViewState.model,
222
- categorySelectorId: seedSpatialViewState.categorySelector.id,
223
- displayStyleId: seedSpatialViewState.displayStyle.id,
224
- cameraOn: itwin3dView.camera !== undefined,
225
- modelSelectorId: seedSpatialViewState.modelSelector.id,
226
- },
227
- modelSelectorProps: {
228
- classFullName: modelSelector.classFullName,
229
- code: {
230
- spec: modelSelector.code.spec,
231
- scope: modelSelector.code.scope,
232
- value: modelSelector.code.value,
233
- },
234
- model: modelSelector.model,
235
- models: (itwin3dView.models?.enabled ?? []),
236
- },
237
- categorySelectorProps: {
238
- classFullName: seedSpatialViewState.categorySelector.classFullName,
239
- categories: (itwin3dView.categories?.enabled ?? []),
240
- code: {
241
- scope: seedSpatialViewState.categorySelector.code.scope,
242
- spec: seedSpatialViewState.categorySelector.code.spec,
243
- value: seedSpatialViewState.categorySelector.code.value,
244
- },
245
- model: seedSpatialViewState.categorySelector.model,
246
- },
247
- displayStyleProps: {
248
- id: seedSpatialViewState.displayStyle.id,
249
- classFullName: seedSpatialViewState.displayStyle.classFullName,
250
- code: seedSpatialViewState.displayStyle.code,
251
- model: seedSpatialViewState.displayStyle.model,
252
- jsonProperties: {
253
- styles: extractDisplayStyle3d(savedViewRsp.savedViewData.itwin3dView),
254
- },
255
- },
256
- };
257
- appendHiddenCategoriesToLegacyView(itwin3dView, legacyView);
258
- appendHiddenModelsTo3dLegacySavedView(itwin3dView, legacyView);
259
- return legacyView;
231
+ function extractTags(creator, tags) {
232
+ const createdByUserId = extractIdFromHref(creator) ?? "";
233
+ return tags?.map((tag) => ({ name: tag.displayName, createdByUserId }));
234
+ }
235
+ function extractIdFromHref(href) {
236
+ return href.split("/").pop();
260
237
  }
261
- /** Append Hidden Categories Or Models To Legacy Saved View. */
262
238
  function appendHiddenCategoriesToLegacyView(iTwinView, legacyView) {
263
- if (iTwinView.categories && iTwinView.categories.disabled) {
239
+ if (iTwinView.categories?.disabled) {
264
240
  legacyView.hiddenCategories = iTwinView.categories.disabled;
265
241
  }
266
242
  }
267
- /**
268
- * append Hidden Categories Or Models To Legacy Saved View
269
- * @param view new schema
270
- * @param legacyView
271
- * @returns iModelViewData
272
- */
273
- function appendHiddenModelsTo3dLegacySavedView(view, legacyView) {
274
- if (view.models && view.models.disabled) {
275
- legacyView.hiddenModels = view.models?.disabled;
276
- }
277
- }
278
- /**
279
- * removes null and undefined from legacy view model selectors props models
280
- * @param savedView
281
- * @returns SavedViewWithData
282
- */
283
- export const cleanLegacyViewModelSelectorPropsModels = (savedView) => {
284
- if (savedView.savedViewData.legacyView?.modelSelectorProps) {
285
- const savedViewCopy = structuredClone(savedView);
286
- const legacyView = savedViewCopy.savedViewData.legacyView;
287
- legacyView.modelSelectorProps.models =
288
- legacyView.modelSelectorProps.models.filter((model) => !!model);
289
- savedViewCopy.savedViewData.legacyView = legacyView;
290
- return savedViewCopy;
291
- }
292
- return savedView;
293
- };
@@ -1,4 +1,5 @@
1
- import { type Viewport } from "@itwin/core-frontend";
1
+ import { Id64Array } from "@itwin/core-bentley";
2
+ import { type IModelConnection, type Viewport } from "@itwin/core-frontend";
2
3
  import { type ViewData } from "@itwin/saved-views-client";
3
4
  interface CaptureSavedViewDataArgs {
4
5
  /** Viewport to capture the view from. */
@@ -10,4 +11,6 @@ interface CaptureSavedViewDataArgs {
10
11
  captureHiddenModelsAndCategories?: boolean | undefined;
11
12
  }
12
13
  export declare function captureSavedViewData(args: CaptureSavedViewDataArgs): Promise<ViewData>;
14
+ export declare function getMissingModels(iModel: IModelConnection, knownModels: Set<string>): Promise<string[]>;
15
+ export declare function getMissingCategories(iModel: IModelConnection, knownCategories: Set<string>): Promise<Id64Array>;
13
16
  export {};
@@ -3,11 +3,15 @@ import { extractClipVectorsFromLegacy } from "./api/utilities/translation/clipVe
3
3
  import { extractDisplayStyle2dFromLegacy, extractDisplayStyle3dFromLegacy, } from "./api/utilities/translation/displayStyleExtractor.js";
4
4
  export async function captureSavedViewData(args) {
5
5
  const { captureHiddenModelsAndCategories = true } = args;
6
- const hiddenCategoriesPromise = captureHiddenModelsAndCategories ? getHiddenCategories(args.viewport) : undefined;
6
+ const hiddenCategoriesPromise = captureHiddenModelsAndCategories
7
+ ? getMissingCategories(args.viewport.iModel, new Set(args.viewport.view.categorySelector.toJSON().categories))
8
+ : undefined;
7
9
  if (args.viewport.view.isSpatialView()) {
8
10
  const [hiddenCategories, hiddenModels] = await Promise.all([
9
11
  hiddenCategoriesPromise,
10
- captureHiddenModelsAndCategories ? getHiddenModels(args.viewport) : undefined,
12
+ captureHiddenModelsAndCategories
13
+ ? getMissingModels(args.viewport.iModel, new Set(args.viewport.view.modelSelector.toJSON().models))
14
+ : undefined,
11
15
  ]);
12
16
  return createSpatialSavedViewObject(args.viewport, hiddenCategories, hiddenModels);
13
17
  }
@@ -120,10 +124,9 @@ function toDegrees(angle) {
120
124
  }
121
125
  return undefined;
122
126
  }
123
- async function getHiddenModels(vp) {
124
- const allModels = await getAllModels(vp.iModel);
125
- const visibleModels = new Set(vp.view.modelSelector.toJSON().models);
126
- return allModels.map(({ id }) => id).filter((model) => !visibleModels.has(model));
127
+ export async function getMissingModels(iModel, knownModels) {
128
+ const allModels = await getAllModels(iModel);
129
+ return allModels.map(({ id }) => id).filter((model) => !knownModels.has(model));
127
130
  }
128
131
  async function getAllModels(iModel) {
129
132
  // Note: IsNotSpatiallyLocated was introduced in a later version of the BisCore ECSchema. If the iModel has an earlier
@@ -136,10 +139,9 @@ async function getAllModels(iModel) {
136
139
  return executeQuery(iModel, "SELECT ECInstanceId FROM Bis.GeometricModel3D WHERE IsPrivate = false AND IsTemplate = false");
137
140
  }
138
141
  }
139
- async function getHiddenCategories(vp) {
140
- const visibleCategories = new Set(vp.view.categorySelector.toJSON().categories);
141
- const allCategories = await getAllCategories(vp.iModel);
142
- return allCategories.map(({ id }) => id).filter((category) => !visibleCategories.has(category));
142
+ export async function getMissingCategories(iModel, knownCategories) {
143
+ const allCategories = await getAllCategories(iModel);
144
+ return allCategories.map(({ id }) => id).filter((category) => !knownCategories.has(category));
143
145
  }
144
146
  async function getAllCategories(iModel) {
145
147
  return executeQuery(iModel, "SELECT DISTINCT Category.Id AS id FROM BisCore.GeometricElement3d WHERE Category.Id IN (SELECT ECInstanceId FROM BisCore.SpatialCategory)");
@@ -1,4 +1,6 @@
1
1
  export { createSavedViewOptions, type CreateSavedViewOptionsParams } from "./SavedViewTile/SavedViewOptions.js";
2
2
  export { SavedViewsExpandableBlockWidget } from "./SavedViewsWidget/SavedViewsExpandableBlockWidget.js";
3
3
  export { SavedViewsFolderWidget } from "./SavedViewsWidget/SavedViewsFolderWidget.js";
4
- export { applyExtensionsToViewport, translateLegacySavedViewToITwinJsViewState, translateSavedViewResponseToLegacySavedViewResponse, } from "./api/utilities/translation/SavedViewTranslation.js";
4
+ export type { LegacySavedViewBase } from "./api/utilities/SavedViewTypes.js";
5
+ export { applyExtensionsToViewport, augmentWithScheduleScript, translateLegacySavedViewToITwinJsViewState, translateSavedViewToLegacySavedView, } from "./api/utilities/translation/SavedViewTranslation.js";
6
+ export { ModelCategoryOverrideProvider } from "./ui/viewlist/ModelCategoryOverrideProvider.js";
@@ -5,4 +5,5 @@
5
5
  export { createSavedViewOptions } from "./SavedViewTile/SavedViewOptions.js";
6
6
  export { SavedViewsExpandableBlockWidget } from "./SavedViewsWidget/SavedViewsExpandableBlockWidget.js";
7
7
  export { SavedViewsFolderWidget } from "./SavedViewsWidget/SavedViewsFolderWidget.js";
8
- export { applyExtensionsToViewport, translateLegacySavedViewToITwinJsViewState, translateSavedViewResponseToLegacySavedViewResponse, } from "./api/utilities/translation/SavedViewTranslation.js";
8
+ export { applyExtensionsToViewport, augmentWithScheduleScript, translateLegacySavedViewToITwinJsViewState, translateSavedViewToLegacySavedView, } from "./api/utilities/translation/SavedViewTranslation.js";
9
+ export { ModelCategoryOverrideProvider } from "./ui/viewlist/ModelCategoryOverrideProvider.js";
@@ -25,8 +25,8 @@ interface UseSavedViewsResult {
25
25
  actions: SavedViewActions;
26
26
  }
27
27
  export interface SavedViewActions {
28
- createSavedView: (savedViewName: string, savedViewData: ViewData) => Promise<string>;
29
- renameSavedView: (savedViewId: string, newName: string) => void;
28
+ submitSavedView: (savedView: string | Partial<SavedView> & Pick<SavedView, "displayName">, savedViewData: ViewData) => Promise<string>;
29
+ renameSavedView: (savedViewId: string, newName: string | undefined) => void;
30
30
  shareSavedView: (savedViewId: string, share: boolean) => void;
31
31
  deleteSavedView: (savedViewId: string) => void;
32
32
  createGroup: (groupName: string) => void;
@@ -75,12 +75,14 @@ export function useSavedViews(args) {
75
75
  return;
76
76
  }
77
77
  setState({
78
- savedViews: new Map(result.savedViews.map((savedView) => {
79
- if (savedView.thumbnail === undefined) {
80
- savedView.thumbnail = _jsx(ThumbnailPlaceholder, { savedViewId: savedView.id, observer: observer });
81
- }
82
- return [savedView.id, savedView];
83
- })),
78
+ savedViews: new Map(result.savedViews.map((savedView) => [
79
+ savedView.id,
80
+ {
81
+ ...savedView,
82
+ thumbnail: savedView.thumbnail
83
+ ?? _jsx(ThumbnailPlaceholder, { savedViewId: savedView.id, observer: observer }),
84
+ },
85
+ ])),
84
86
  groups: new Map(result.groups.map((group) => [group.id, group])),
85
87
  tags: new Map(result.tags.map((tag) => [tag.id, tag])),
86
88
  thumbnails: new Map(),
@@ -88,7 +90,7 @@ export function useSavedViews(args) {
88
90
  setProvider(createSavedViewActions(args.iTwinId, args.iModelId, args.client, setState, providerRef, onUpdateInProgress, onUpdateComplete, onUpdateError));
89
91
  }
90
92
  catch (error) {
91
- if (error.name !== "AbortError") {
93
+ if (!isAbortError(error)) {
92
94
  throw error;
93
95
  }
94
96
  }
@@ -119,31 +121,51 @@ function useEvent(handleEvent) {
119
121
  function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpdateInProgress, onUpdateComplete, onUpdateError) {
120
122
  const signal = ref.current.abortController.signal;
121
123
  return {
122
- createSavedView: actionWrapper(async (savedViewName, savedViewData) => {
123
- const savedView = await client.createSavedView({
124
- iTwinId: iTwinId,
125
- iModelId: iModelId,
126
- savedView: {
127
- displayName: savedViewName,
128
- },
129
- savedViewData,
130
- signal,
124
+ submitSavedView: actionWrapper(async (savedView, savedViewData) => {
125
+ let newSavedView;
126
+ if (typeof savedView !== "string" && savedView.id) {
127
+ newSavedView = await client.updateSavedView({
128
+ // TypeScript cannot tell that `savedView` object contains `id` string without a little help
129
+ savedView: { id: savedView.id, ...savedView },
130
+ savedViewData,
131
+ signal,
132
+ });
133
+ }
134
+ else {
135
+ newSavedView = await client.createSavedView({
136
+ iTwinId: iTwinId,
137
+ iModelId: iModelId,
138
+ savedView: typeof savedView === "string" ? { displayName: savedView } : savedView,
139
+ savedViewData,
140
+ signal,
141
+ });
142
+ }
143
+ updateSavedViews((savedViews) => {
144
+ const entries = Array.from(savedViews.values());
145
+ entries.push(newSavedView);
146
+ entries.sort((a, b) => a.displayName.localeCompare(b.displayName));
147
+ return new Map(entries.map((savedView) => [savedView.id, savedView]));
131
148
  });
132
- updateSavedViews((savedViews) => savedViews.set(savedView.id, savedView));
133
- return savedView.id;
149
+ return newSavedView.id;
134
150
  }),
135
151
  renameSavedView: actionWrapper(async (savedViewId, newName) => {
152
+ if (!newName) {
153
+ return;
154
+ }
155
+ const savedView = ref.current.mostRecentState.savedViews.get(savedViewId);
156
+ if (!savedView || newName === savedView.displayName) {
157
+ return;
158
+ }
136
159
  let prevName;
137
160
  updateSavedView(savedViewId, (savedView) => {
138
161
  prevName = savedView.displayName;
139
162
  savedView.displayName = newName;
140
163
  });
141
164
  try {
142
- const savedView = await client.updateSavedView({
165
+ await client.updateSavedView({
143
166
  savedView: { id: savedViewId, displayName: newName },
144
167
  signal,
145
168
  });
146
- updateSavedView(savedViewId, () => savedView);
147
169
  }
148
170
  catch (error) {
149
171
  if (prevName !== undefined) {
@@ -164,8 +186,7 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
164
186
  savedView.shared = share;
165
187
  });
166
188
  try {
167
- const savedView = await client.updateSavedView({ savedView: { id: savedViewId, shared: share }, signal });
168
- updateSavedView(savedView.id, () => savedView);
189
+ await client.updateSavedView({ savedView: { id: savedViewId, shared: share }, signal });
169
190
  }
170
191
  catch (error) {
171
192
  updateSavedView(savedViewId, (savedView) => {
@@ -175,22 +196,19 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
175
196
  }
176
197
  }),
177
198
  deleteSavedView: actionWrapper(async (savedViewId) => {
178
- let prevSavedView;
199
+ let prevSavedViews;
179
200
  updateSavedViews((savedViews) => {
180
- prevSavedView = savedViews.get(savedViewId);
201
+ prevSavedViews = new Map(savedViews);
181
202
  savedViews.delete(savedViewId);
182
203
  });
183
204
  try {
184
205
  await client.deleteSavedView({ savedViewId, signal });
185
206
  }
186
- catch {
187
- if (prevSavedView !== undefined) {
188
- const restoredSavedView = prevSavedView;
189
- updateSavedViews((savedViews) => {
190
- // The deleted view will return at the last position in the enumeration
191
- savedViews.set(savedViewId, restoredSavedView);
192
- });
207
+ catch (error) {
208
+ if (prevSavedViews !== undefined) {
209
+ updateSavedViews(() => prevSavedViews);
193
210
  }
211
+ throw error;
194
212
  }
195
213
  }),
196
214
  createGroup: actionWrapper(async (groupName) => {
@@ -200,7 +218,12 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
200
218
  group: { displayName: groupName },
201
219
  signal,
202
220
  });
203
- updateGroups((groups) => groups.set(group.id, group));
221
+ updateGroups((groups) => {
222
+ const entries = Array.from(groups.values());
223
+ entries.push(group);
224
+ entries.sort((a, b) => a.displayName.localeCompare(b.displayName));
225
+ return new Map(entries.map((group) => [group.id, group]));
226
+ });
204
227
  }),
205
228
  renameGroup: actionWrapper(async (groupId, newName) => {
206
229
  let prevName;
@@ -248,8 +271,7 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
248
271
  savedView.groupId = groupId;
249
272
  });
250
273
  try {
251
- const savedView = await client.updateSavedView({ savedView: { id: savedViewId, groupId }, signal });
252
- updateSavedView(savedView.id, () => savedView);
274
+ await client.updateSavedView({ savedView: { id: savedViewId, groupId }, signal });
253
275
  }
254
276
  catch (error) {
255
277
  const restoredGroupId = prevGroupId;
@@ -276,8 +298,7 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
276
298
  savedView.groupId = group.id;
277
299
  });
278
300
  try {
279
- const savedView = await client.updateSavedView({ savedView: { id: savedViewId, groupId: group.id }, signal });
280
- updateSavedView(savedView.id, () => savedView);
301
+ await client.updateSavedView({ savedView: { id: savedViewId, groupId: group.id }, signal });
281
302
  }
282
303
  catch (error) {
283
304
  updateSavedView(savedViewId, (savedView) => {
@@ -287,27 +308,23 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
287
308
  }
288
309
  }),
289
310
  deleteGroup: actionWrapper(async (groupId) => {
290
- let prevGroup;
311
+ let prevGroups;
291
312
  updateGroups((groups) => {
292
- prevGroup = groups.get(groupId);
313
+ prevGroups = new Map(groups);
293
314
  groups.delete(groupId);
294
315
  });
295
316
  try {
296
317
  await client.deleteGroup({ groupId, signal });
297
318
  }
298
319
  catch (error) {
299
- if (prevGroup) {
300
- const restoredGroup = prevGroup;
301
- updateGroups((groups) => {
302
- // The deleted group will return at the last position in the enumeration
303
- groups.set(groupId, restoredGroup);
304
- });
320
+ if (prevGroups) {
321
+ updateGroups(() => prevGroups);
305
322
  }
306
323
  throw error;
307
324
  }
308
325
  }),
309
326
  addTag: actionWrapper(async (savedViewId, tagId) => {
310
- let savedView = ref.current.mostRecentState.savedViews.get(savedViewId);
327
+ const savedView = ref.current.mostRecentState.savedViews.get(savedViewId);
311
328
  if (!savedView) {
312
329
  return;
313
330
  }
@@ -324,8 +341,7 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
324
341
  savedView.tagIds = tagIds;
325
342
  });
326
343
  try {
327
- savedView = await client.updateSavedView({ savedView: { id: savedViewId, tagIds }, signal });
328
- updateSavedView(savedView.id, () => savedView);
344
+ await client.updateSavedView({ savedView: { id: savedViewId, tagIds }, signal });
329
345
  }
330
346
  catch (error) {
331
347
  updateSavedView(savedViewId, (savedView) => {
@@ -335,7 +351,7 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
335
351
  }
336
352
  }),
337
353
  addNewTag: actionWrapper(async (savedViewId, tagName) => {
338
- let savedView = ref.current.mostRecentState.savedViews.get(savedViewId);
354
+ const savedView = ref.current.mostRecentState.savedViews.get(savedViewId);
339
355
  if (!savedView) {
340
356
  return;
341
357
  }
@@ -361,8 +377,7 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
361
377
  savedView.tagIds = tagIds;
362
378
  });
363
379
  try {
364
- savedView = await client.updateSavedView({ savedView: { id: savedViewId, tagIds }, signal });
365
- updateSavedView(savedView.id, () => savedView);
380
+ await client.updateSavedView({ savedView: { id: savedViewId, tagIds }, signal });
366
381
  }
367
382
  catch (error) {
368
383
  updateSavedView(savedViewId, (savedView) => {
@@ -372,7 +387,7 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
372
387
  }
373
388
  }),
374
389
  removeTag: actionWrapper(async (savedViewId, tagId) => {
375
- let savedView = ref.current.mostRecentState.savedViews.get(savedViewId);
390
+ const savedView = ref.current.mostRecentState.savedViews.get(savedViewId);
376
391
  if (!savedView) {
377
392
  return;
378
393
  }
@@ -386,8 +401,7 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
386
401
  savedView.tagIds = tagIds;
387
402
  });
388
403
  try {
389
- savedView = await client.updateSavedView({ savedView: { id: savedViewId, tagIds }, signal });
390
- updateSavedView(savedView.id, () => savedView);
404
+ await client.updateSavedView({ savedView: { id: savedViewId, tagIds }, signal });
391
405
  }
392
406
  catch (error) {
393
407
  updateSavedView(savedViewId, (savedView) => {
@@ -407,9 +421,8 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
407
421
  }
408
422
  catch (error) {
409
423
  if (prevThumnbnail !== undefined) {
410
- const restoredDisplayName = prevThumnbnail;
411
424
  updateSavedView(savedViewId, (savedView) => {
412
- savedView.thumbnail = restoredDisplayName;
425
+ savedView.thumbnail = prevThumnbnail;
413
426
  });
414
427
  }
415
428
  throw error;
@@ -434,7 +447,7 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
434
447
  }
435
448
  catch (error) {
436
449
  reject(error);
437
- if (error && typeof error === "object" && "name" in error && error.name === "AbortError") {
450
+ if (isAbortError(error)) {
438
451
  // It's a cancellation error, no need to report it
439
452
  }
440
453
  else {
@@ -464,8 +477,8 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
464
477
  }
465
478
  setState((prev) => {
466
479
  const store = { ...prev };
467
- store.savedViews = new Map(prev.savedViews);
468
- callback(store.savedViews);
480
+ const savedViews = new Map(prev.savedViews);
481
+ store.savedViews = callback(savedViews) ?? savedViews;
469
482
  return store;
470
483
  });
471
484
  }
@@ -491,8 +504,8 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
491
504
  }
492
505
  setState((prev) => {
493
506
  const store = { ...prev };
494
- store.groups = new Map(prev.groups);
495
- callback(store.groups);
507
+ const groups = new Map(prev.groups);
508
+ store.groups = callback(groups) ?? groups;
496
509
  return store;
497
510
  });
498
511
  }
@@ -524,6 +537,9 @@ function createSavedViewActions(iTwinId, iModelId, client, setState, ref, onUpda
524
537
  });
525
538
  }
526
539
  }
540
+ function isAbortError(error) {
541
+ return error instanceof DOMException && error.name === "AbortError";
542
+ }
527
543
  function ThumbnailPlaceholder(props) {
528
544
  const divRef = useRef(null);
529
545
  useEffect(() => {