@itwin/saved-views-react 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,10 +2,59 @@
2
2
 
3
3
  ## About
4
4
 
5
- A collection of React components for building applications that deal with [Saved Views](https://developer.bentley.com/apis/savedviews/overview/).
5
+ A collection of utilities and React components for building iTwin applications that deal with [Saved Views](https://developer.bentley.com/apis/savedviews/overview/).
6
6
 
7
7
  ## Documentation
8
8
 
9
+ ### [captureSavedViewData](./src/captureSavedViewData.ts)
10
+
11
+ Captures current viewport state into serializable format. The returned data can later be used to restore viewport's view.
12
+
13
+ ```ts
14
+ const { viewData, extensions = [] } = await captureSavedViewData({ viewport });
15
+ extensions.push(myCustomExtension(viewport));
16
+ console.log({ viewData, extensions }); /*
17
+ {
18
+ viewData: { itwin3dView: {...} },
19
+ extensions: {
20
+ { extensionName: "EmphasizeElements", data: "{...}" },
21
+ { extensionName: "MyCustomExtension", data: "my_custom_extension_data" },
22
+ }
23
+ } */
24
+ ```
25
+
26
+ ### [captureSavedViewThumbnail](./src/captureSavedViewThumbnail.ts)
27
+
28
+ Generates Saved View thumbnail based on what is currently displayed on the viewport.
29
+
30
+ ```ts
31
+ const thumbnail = captureSavedViewThumbnail(viewport);
32
+ console.log(thumbnail); // "data:image/png;base64,iVBORw0KGoAAAANSUhEUg..."
33
+ ```
34
+
35
+ ### [applySavedView](./src/applySavedView.ts)
36
+
37
+ Updates viewport state to match captured Saved View.
38
+
39
+ ```ts
40
+ // Capture viewport state
41
+ const savedViewData = await captureSavedViewData({ viewport });
42
+ // Restore viewport state
43
+ await applySavedView(iModel, viewport, savedViewData);
44
+ ```
45
+
46
+ ### [createViewState](./src/createViewState.ts)
47
+
48
+ Creates ViewState object out of Saved View data. It provides a lower-level access to view data for advanced use.
49
+
50
+ ```ts
51
+ const viewState = await createViewState(iModel, savedViewData.viewData);
52
+ await applySavedView(iModel, viewport, savedViewData, { viewState });
53
+
54
+ // The two lines above are equivalent to
55
+ await applySavedView(iModel, viewport, savedViewData);
56
+ ```
57
+
9
58
  ### React components
10
59
 
11
60
  * [SavedViewTile](./src/SavedViewTile/SavedViewTile.tsx)
@@ -42,7 +91,7 @@ export function SavedViewsWidget(props) {
42
91
  }
43
92
  ```
44
93
 
45
- ### useSavedViews
94
+ ### [useSavedViews](./src/useSavedViews.tsx)
46
95
 
47
96
  [useSavedViews](./src/useSavedViews.tsx) React hook provides basic functionality to jump-start your Saved Views widget. It accepts [`ITwinSavedViewsClient`](./src/SavedViewsClient/ITwinSavedViewsClient.ts) which is used to pull Saved View data and synchronize it back to the [Saved Views service](https://developer.bentley.com/apis/savedviews/overview/).
48
97
 
@@ -1,18 +1,39 @@
1
+ import type { ViewData } from "@itwin/saved-views-client";
1
2
  import type { ReactNode } from "react";
2
3
  export interface SavedView {
3
4
  id: string;
4
5
  displayName: string;
6
+ viewData?: ViewData | undefined;
5
7
  groupId?: string | undefined;
6
8
  creatorId?: string | undefined;
7
9
  tagIds?: string[] | undefined;
8
10
  shared?: boolean | undefined;
9
11
  thumbnail?: ReactNode | string | undefined;
10
- /** `extensionName` and `data` pairs. */
11
- extensions?: Map<string, string> | undefined;
12
+ extensions?: SavedViewExtension[] | undefined;
12
13
  /** Time the saved view was created as an ISO8601 string, `"YYYY-MM-DDTHH:mm:ss.sssZ"` */
13
- creationTime?: string;
14
+ creationTime?: string | undefined;
14
15
  /** Time the saved view was last modified as an ISO8601 string, `"YYYY-MM-DDTHH:mm:ss.sssZ"` */
15
- lastModified?: string;
16
+ lastModified?: string | undefined;
17
+ }
18
+ export interface SavedViewExtension {
19
+ /** Extension identifier. Saved View cannot contain multiple extensions that share the same `extensionName`. */
20
+ extensionName: string;
21
+ /**
22
+ * Serialized extension data.
23
+ *
24
+ * @example
25
+ * const extension = {
26
+ * // Unique identifier makes extension data format portable between applications because it avoids collision
27
+ * // with different implementations
28
+ * extensionName: "CustomHighlight_$5be36494-ae03-4400-bb80-24ffd9db2a87",
29
+ * data: JSON.stringify({
30
+ * description: "For illustrative purposes only. We do not provide implementation for this extensionName."
31
+ * highlightColor: "#f05599",
32
+ * models: ["0x20000000006"],
33
+ * }),
34
+ * };
35
+ */
36
+ data: string;
16
37
  }
17
38
  export interface SavedViewTag {
18
39
  id: string;
@@ -24,3 +45,4 @@ export interface SavedViewGroup {
24
45
  creatorId?: string | undefined;
25
46
  shared?: boolean | undefined;
26
47
  }
48
+ export type WriteableSavedViewProperties = Omit<SavedView, "id" | "creatorId" | "creationTime" | "lastModified">;
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
4
  * See LICENSE.md in the project root for license terms and full copyright notice.
5
5
  *--------------------------------------------------------------------------------------------*/
6
- import { SvgEdit, SvgImageFrame, SvgMore, SvgShare, SvgTag } from "@itwin/itwinui-icons-react";
6
+ import { SvgEdit, SvgMore, SvgSavedView, SvgShare, SvgTag } from "@itwin/itwinui-icons-react";
7
7
  import { Button, IconButton, Input, Text, Tile } from "@itwin/itwinui-react";
8
8
  import { useLayoutEffect, useMemo, useRef, useState, } from "react";
9
9
  import { LayeredDropdownMenu } from "../LayeredDropdownMenu/LayeredDropdownMenu.js";
@@ -67,7 +67,7 @@ export function SavedViewTile(props) {
67
67
  ? _jsx(Tile.ThumbnailPicture, { url: props.savedView.thumbnail })
68
68
  : props.savedView.thumbnail
69
69
  ? props.savedView.thumbnail
70
- : _jsx(Tile.ThumbnailPicture, { children: _jsx(SvgImageFrame, {}) }), _jsx(TileIconContainer, { style: { placeSelf: "start" }, icons: props.leftIcons }), _jsx(TileIconContainer, { style: { placeSelf: "start end" }, icons: rightIcons })] }), _jsxs(Tile.ContentArea, { children: [_jsx(Tile.Metadata, { children: metadata }), (typeof props.options === "function" || (props.options && props.options.length > 0)) &&
70
+ : _jsx(Tile.ThumbnailPicture, { children: _jsx(SvgSavedView, {}) }), _jsx(TileIconContainer, { style: { placeSelf: "start" }, icons: props.leftIcons }), _jsx(TileIconContainer, { style: { placeSelf: "start end" }, icons: rightIcons })] }), _jsxs(Tile.ContentArea, { children: [_jsx(Tile.Metadata, { children: metadata }), (typeof props.options === "function" || (props.options && props.options.length > 0)) &&
71
71
  _jsx("div", { className: "svr-tile--more-options", onClick: (ev) => ev.stopPropagation(), children: _jsx(LayeredDropdownMenu, { menuItems: props.options, children: _jsx(IconButton, { size: "small", styleType: "borderless", children: _jsx(SvgMore, {}) }) }) })] })] }) }));
72
72
  }
73
73
  function isOverflowing(element) {
@@ -1,23 +1,23 @@
1
- import { type SavedViewRepresentation } from "@itwin/saved-views-client";
2
1
  import type { SavedView, SavedViewGroup, SavedViewTag } from "../SavedView.js";
3
- import type { CreateGroupParams, CreateSavedViewParams, CreateTagParams, DeleteGroupParams, DeleteSavedViewParams, DeleteTagParams, GetSavedViewInfoParams, GetSingularSavedViewParams, GetThumbnailUrlParams, SavedViewInfo, SavedViewsClient, UpdateGroupParams, UpdateSavedViewParams, UpdateTagParams, UploadThumbnailParams } from "./SavedViewsClient.js";
2
+ import type { CreateGroupParams, CreateSavedViewParams, CreateTagParams, DeleteGroupParams, DeleteSavedViewParams, DeleteTagParams, GetAllGroupsParams, GetAllSavedViewsParams, GetAllTagsParams, GetSavedViewParams, GetThumbnailUrlParams, SavedViewsClient, UpdateGroupParams, UpdateSavedViewParams, UpdateTagParams, UploadThumbnailParams } from "./SavedViewsClient.js";
4
3
  interface ITwinSavedViewsClientParams {
5
- /** @default "https://api.bentley.com/savedviews" */
6
- getAccessToken: () => Promise<string>;
7
4
  /**
8
5
  * Authorization token that grants access to iTwin Saved Views API. The token should be valid for `savedviews:read`
9
6
  * and `savedviews:modify` OIDC scopes.
10
- */
7
+ */
8
+ getAccessToken: () => Promise<string>;
9
+ /** @default "https://api.bentley.com/savedviews" */
11
10
  baseUrl?: string | undefined;
12
11
  }
13
- /** */
14
12
  export declare class ITwinSavedViewsClient implements SavedViewsClient {
15
- private client;
13
+ #private;
16
14
  constructor(args: ITwinSavedViewsClientParams);
17
- getSavedViewInfo(args: GetSavedViewInfoParams): Promise<SavedViewInfo>;
18
- getSingularSavedView(args: GetSingularSavedViewParams): Promise<SavedViewRepresentation>;
15
+ getAllSavedViews(args: GetAllSavedViewsParams): AsyncIterableIterator<SavedView[]>;
16
+ getAllGroups(args: GetAllGroupsParams): Promise<SavedViewGroup[]>;
17
+ getAllTags(args: GetAllTagsParams): Promise<SavedViewTag[]>;
19
18
  getThumbnailUrl(args: GetThumbnailUrlParams): Promise<string | undefined>;
20
19
  uploadThumbnail(args: UploadThumbnailParams): Promise<void>;
20
+ getSavedView(args: GetSavedViewParams): Promise<SavedView>;
21
21
  createSavedView(args: CreateSavedViewParams): Promise<SavedView>;
22
22
  updateSavedView(args: UpdateSavedViewParams): Promise<SavedView>;
23
23
  deleteSavedView(args: DeleteSavedViewParams): Promise<void>;
@@ -3,33 +3,39 @@
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import { ITwinSavedViewsClient as Client, } from "@itwin/saved-views-client";
6
- /** */
7
6
  export class ITwinSavedViewsClient {
8
- client;
7
+ #client;
9
8
  constructor(args) {
10
- this.client = new Client(args);
11
- }
12
- async getSavedViewInfo(args) {
13
- const [{ savedViews }, { groups }, { tags }] = await Promise.all([
14
- this.client.getAllSavedViewsMinimal(args),
15
- this.client.getAllGroups(args),
16
- this.client.getAllTags(args),
17
- ]);
18
- return {
19
- savedViews: savedViews.map(savedViewResponseToSavedView),
20
- groups: groups.map(groupResponseToSavedViewGroup),
21
- tags: tags.map(tagResponseToSavedViewTag),
22
- };
23
- }
24
- async getSingularSavedView(args) {
25
- const response = await this.client.getSavedViewRepresentation({
26
- savedViewId: args.savedViewId,
9
+ this.#client = new Client(args);
10
+ }
11
+ async *getAllSavedViews(args) {
12
+ const iterable = this.#client.getAllSavedViewsRepresentation({
13
+ iTwinId: args.iTwinId,
14
+ iModelId: args.iModelId,
15
+ signal: args.signal,
16
+ });
17
+ for await (const page of iterable) {
18
+ yield page.savedViews.map(savedViewResponseToSavedView);
19
+ }
20
+ }
21
+ async getAllGroups(args) {
22
+ const response = await this.#client.getAllGroups({
23
+ iTwinId: args.iTwinId,
24
+ iModelId: args.iModelId,
27
25
  signal: args.signal,
28
26
  });
29
- return response.savedView;
27
+ return response.groups.map(groupResponseToSavedViewGroup);
28
+ }
29
+ async getAllTags(args) {
30
+ const response = await this.#client.getAllTags({
31
+ iTwinId: args.iTwinId,
32
+ iModelId: args.iModelId,
33
+ signal: args.signal,
34
+ });
35
+ return response.tags.map(tagResponseToSavedViewTag);
30
36
  }
31
37
  async getThumbnailUrl(args) {
32
- const response = await this.client.getImage({
38
+ const response = await this.#client.getImage({
33
39
  savedViewId: args.savedViewId,
34
40
  size: "thumbnail",
35
41
  signal: args.signal,
@@ -37,43 +43,55 @@ export class ITwinSavedViewsClient {
37
43
  return response.href;
38
44
  }
39
45
  async uploadThumbnail(args) {
40
- await this.client.updateImage({
46
+ await this.#client.updateImage({
41
47
  savedViewId: args.savedViewId,
42
48
  image: args.image,
43
49
  signal: args.signal,
44
50
  });
45
51
  }
52
+ async getSavedView(args) {
53
+ const response = await this.#client.getSavedViewRepresentation({
54
+ savedViewId: args.savedViewId,
55
+ signal: args.signal,
56
+ });
57
+ return savedViewResponseToSavedView(response.savedView);
58
+ }
46
59
  async createSavedView(args) {
47
- const { savedView } = await this.client.createSavedView({
60
+ const { savedView } = await this.#client.createSavedView({
48
61
  iTwinId: args.iTwinId,
49
62
  iModelId: args.iModelId,
50
63
  displayName: args.savedView.displayName,
51
64
  tagIds: args.savedView.tagIds,
52
65
  groupId: args.savedView.groupId,
53
66
  shared: args.savedView.shared,
54
- savedViewData: args.savedViewData,
67
+ savedViewData: args.savedView.viewData,
68
+ extensions: args.savedView.extensions,
55
69
  signal: args.signal,
56
70
  });
57
71
  return savedViewResponseToSavedView(savedView);
58
72
  }
59
73
  async updateSavedView(args) {
60
- const { savedView } = await this.client.updateSavedView({
74
+ const { savedView } = await this.#client.updateSavedView({
61
75
  savedViewId: args.savedView.id,
62
76
  displayName: args.savedView.displayName,
63
77
  tagIds: args.savedView.tagIds,
64
78
  groupId: args.savedView.groupId,
65
79
  shared: args.savedView.shared,
66
- savedViewData: args.savedViewData,
67
- extensions: args.extensions,
80
+ savedViewData: args.savedView.viewData,
68
81
  signal: args.signal,
69
82
  });
83
+ await Promise.all((args.savedView.extensions ?? []).map(({ extensionName, data }) => this.#client.createExtension({
84
+ savedViewId: args.savedView.id,
85
+ extensionName,
86
+ data,
87
+ })));
70
88
  return savedViewResponseToSavedView(savedView);
71
89
  }
72
90
  async deleteSavedView(args) {
73
- await this.client.deleteSavedView({ savedViewId: args.savedViewId, signal: args.signal });
91
+ await this.#client.deleteSavedView({ savedViewId: args.savedViewId, signal: args.signal });
74
92
  }
75
93
  async createGroup(args) {
76
- const { group } = await this.client.createGroup({
94
+ const { group } = await this.#client.createGroup({
77
95
  iTwinId: args.iTwinId,
78
96
  iModelId: args.iModelId,
79
97
  displayName: args.group.displayName,
@@ -82,7 +100,7 @@ export class ITwinSavedViewsClient {
82
100
  return groupResponseToSavedViewGroup(group);
83
101
  }
84
102
  async updateGroup(args) {
85
- const { group } = await this.client.updateGroup({
103
+ const { group } = await this.#client.updateGroup({
86
104
  groupId: args.group.id,
87
105
  displayName: args.group.displayName,
88
106
  shared: args.group.shared,
@@ -91,12 +109,14 @@ export class ITwinSavedViewsClient {
91
109
  return groupResponseToSavedViewGroup(group);
92
110
  }
93
111
  async deleteGroup(args) {
94
- const savedViews = await this.client.getAllSavedViewsMinimal({ groupId: args.groupId, signal: args.signal });
95
- await Promise.all(savedViews.savedViews.map(({ id }) => this.client.deleteSavedView({ savedViewId: id, signal: args.signal })));
96
- await this.client.deleteGroup({ groupId: args.groupId, signal: args.signal });
112
+ const savedViewPages = this.#client.getAllSavedViewsMinimal({ groupId: args.groupId, signal: args.signal });
113
+ for await (const savedViews of savedViewPages) {
114
+ await Promise.all(savedViews.savedViews.map(({ id }) => this.#client.deleteSavedView({ savedViewId: id, signal: args.signal })));
115
+ }
116
+ await this.#client.deleteGroup({ groupId: args.groupId, signal: args.signal });
97
117
  }
98
118
  async createTag(args) {
99
- const { tag } = await this.client.createTag({
119
+ const { tag } = await this.#client.createTag({
100
120
  iTwinId: args.iTwinId,
101
121
  iModelId: args.iModelId,
102
122
  displayName: args.displayName,
@@ -105,7 +125,7 @@ export class ITwinSavedViewsClient {
105
125
  return tagResponseToSavedViewTag(tag);
106
126
  }
107
127
  async updateTag(args) {
108
- const { tag } = await this.client.updateTag({
128
+ const { tag } = await this.#client.updateTag({
109
129
  tagId: args.tag.id,
110
130
  displayName: args.tag.displayName,
111
131
  signal: args.signal,
@@ -113,18 +133,20 @@ export class ITwinSavedViewsClient {
113
133
  return tagResponseToSavedViewTag(tag);
114
134
  }
115
135
  async deleteTag(args) {
116
- await this.client.deleteTag({ tagId: args.tagId, signal: args.signal });
136
+ await this.#client.deleteTag({ tagId: args.tagId, signal: args.signal });
117
137
  }
118
138
  }
119
139
  function savedViewResponseToSavedView(response) {
120
140
  return {
121
141
  id: response.id,
122
142
  displayName: response.displayName,
143
+ viewData: response.savedViewData,
123
144
  tagIds: response.tags?.map((tag) => tag.id),
124
145
  groupId: response._links.group?.href.split("/").at(-1),
125
146
  creatorId: response._links.creator?.href.split("/").at(-1),
126
147
  shared: response.shared,
127
148
  thumbnail: undefined,
149
+ extensions: response.extensions,
128
150
  creationTime: response.creationTime,
129
151
  lastModified: response.lastModified,
130
152
  };
@@ -1,15 +1,12 @@
1
- import type { ExtensionMin, ExtensionSavedViewCreate, SavedViewRepresentation, ViewData } from "@itwin/saved-views-client";
2
- import type { SavedView, SavedViewGroup, SavedViewTag } from "../SavedView.js";
3
- export interface SavedViewInfo {
4
- savedViews: SavedView[];
5
- groups: SavedViewGroup[];
6
- tags: SavedViewTag[];
7
- }
1
+ import type { SavedView, SavedViewGroup, SavedViewTag, WriteableSavedViewProperties } from "../SavedView.js";
2
+ import type { PartialExcept } from "../utils.js";
8
3
  export interface SavedViewsClient {
9
- getSavedViewInfo: (args: GetSavedViewInfoParams) => Promise<SavedViewInfo>;
10
- getSingularSavedView: (args: GetSingularSavedViewParams) => Promise<SavedViewRepresentation>;
4
+ getAllSavedViews: (args: GetAllSavedViewsParams) => AsyncIterableIterator<SavedView[]>;
5
+ getAllGroups: (args: GetAllGroupsParams) => Promise<SavedViewGroup[]>;
6
+ getAllTags: (args: GetAllTagsParams) => Promise<SavedViewTag[]>;
11
7
  getThumbnailUrl: (args: GetThumbnailUrlParams) => Promise<string | undefined>;
12
8
  uploadThumbnail: (args: UploadThumbnailParams) => Promise<void>;
9
+ getSavedView: (args: GetSavedViewParams) => Promise<SavedView>;
13
10
  createSavedView: (args: CreateSavedViewParams) => Promise<SavedView>;
14
11
  updateSavedView: (args: UpdateSavedViewParams) => Promise<SavedView>;
15
12
  deleteSavedView: (args: DeleteSavedViewParams) => Promise<void>;
@@ -20,32 +17,43 @@ export interface SavedViewsClient {
20
17
  updateTag: (args: UpdateTagParams) => Promise<SavedViewTag>;
21
18
  deleteTag: (args: DeleteTagParams) => Promise<void>;
22
19
  }
23
- export interface GetSavedViewInfoParams extends CommonParams {
20
+ export interface GetAllSavedViewsParams extends CommonParams {
24
21
  iTwinId: string;
25
22
  iModelId?: string | undefined;
26
23
  }
27
- export interface GetSingularSavedViewParams extends CommonParams {
28
- savedViewId: string;
24
+ export interface GetAllGroupsParams extends CommonParams {
25
+ iTwinId: string;
26
+ iModelId?: string | undefined;
27
+ }
28
+ export interface GetAllTagsParams extends CommonParams {
29
+ iTwinId: string;
30
+ iModelId?: string | undefined;
29
31
  }
30
32
  export interface GetThumbnailUrlParams extends CommonParams {
31
33
  savedViewId: string;
32
34
  }
33
35
  export interface UploadThumbnailParams extends CommonParams {
34
36
  savedViewId: string;
35
- /** Image data encoded as base64 data URL. */
37
+ /**
38
+ * Image data encoded as base64 data URL.
39
+ *
40
+ * @example
41
+ * "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII="
42
+ */
36
43
  image: string;
37
44
  }
45
+ export interface GetSavedViewParams extends CommonParams {
46
+ savedViewId: string;
47
+ }
38
48
  export interface CreateSavedViewParams extends CommonParams {
39
49
  iTwinId: string;
40
50
  iModelId?: string | undefined;
41
- savedView: Pick<SavedView, "displayName" | "tagIds" | "groupId" | "shared">;
42
- savedViewData: ViewData;
43
- extensions?: ExtensionSavedViewCreate[] | undefined;
51
+ savedView: PartialExcept<WriteableSavedViewProperties, "displayName" | "viewData">;
44
52
  }
45
53
  export interface UpdateSavedViewParams extends CommonParams {
46
- savedView: Pick<SavedView, "id"> & Partial<SavedView>;
47
- savedViewData?: ViewData | undefined;
48
- extensions?: ExtensionMin[] | undefined;
54
+ savedView: Partial<WriteableSavedViewProperties> & {
55
+ id: string;
56
+ };
49
57
  }
50
58
  export interface DeleteSavedViewParams extends CommonParams {
51
59
  savedViewId: string;
@@ -0,0 +1,67 @@
1
+ import { ViewState, type IModelConnection, type Viewport } from "@itwin/core-frontend";
2
+ import type { SavedView } from "./SavedView.js";
3
+ export interface ApplySavedViewSettings {
4
+ /**
5
+ * Strategy to use when other setting does not specify one. By default, viewport is reset to default state and then
6
+ * all captured Saved View data is applied on top.
7
+ * @default "apply"
8
+ *
9
+ * @example
10
+ * await applySavedView(iModel, viewport, savedView, { all: "keep", viewState: "apply" });
11
+ */
12
+ all?: ApplyStrategy | undefined;
13
+ /**
14
+ * How to handle captured {@link ViewState} data. The default behavior is to generate a new `ViewState` object out of
15
+ * {@linkcode SavedView.viewData} and apply it to viewport.
16
+ *
17
+ * You can optionally provide a pre-made `ViewState` instance to conserve resources. It is usually obtained from
18
+ * {@link createViewState} result.
19
+ *
20
+ * @example
21
+ * import { applySavedView, createViewState } from "@itwin/saved-views-react";
22
+ *
23
+ * const viewState = await createViewState(iModel, savedView);
24
+ * await Promise.all([
25
+ * applySavedView(iModel, viewport1, savedView, { viewState }),
26
+ * applySavedView(iModel, viewport2, savedView, { viewState }),
27
+ * ]);
28
+ *
29
+ * @remarks
30
+ * When neither `SavedView.viewData` nor `ViewState` is provided, current {@linkcode Viewport.view} is preserved.
31
+ */
32
+ viewState?: ApplyStrategy | ViewState | undefined;
33
+ /**
34
+ * How to handle visibility of models and categories that exist in iModel but are not captured in Saved View data. Has
35
+ * effect only when `modelAndCategoryVisibility` strategy is set to `"apply"`.
36
+ * @default "hidden"
37
+ */
38
+ modelAndCategoryVisibilityFallback?: "visible" | "hidden" | undefined;
39
+ /** How to handle captured element emphasis data. In default state emphasis is turned off. */
40
+ emphasis?: ApplyStrategy | undefined;
41
+ /**
42
+ * How to handle captured {@link Viewport.perModelCategoryVisibility} data. In default state no overrides are present.
43
+ */
44
+ perModelCategoryVisibility?: ApplyStrategy | undefined;
45
+ }
46
+ /**
47
+ * Controls how viewport state is going to be altered.
48
+ *
49
+ * * `"apply"` – Apply captured Saved View state. Falls back to `"reset"` on failure (e.g. missing Saved View data).
50
+ * * `"reset"` – Reset to the default viewport state
51
+ * * `"keep"` – Keep the current viewport state
52
+ */
53
+ type ApplyStrategy = "apply" | "reset" | "keep";
54
+ /**
55
+ * Updates {@linkcode viewport} state to match captured Saved View.
56
+ *
57
+ * @example
58
+ * // Optionally, you can create and manage ViewState object yourself to avoid redundant work,
59
+ * // e.g. when applying the same Saved View to multiple viewports
60
+ * const viewState = await createViewState(iModel, savedView);
61
+ * await Promise.all([
62
+ * applySavedView(iModel, viewport1, savedView, { viewState }),
63
+ * applySavedView(iModel, viewport2, savedView, { viewState }),
64
+ * ]);
65
+ */
66
+ export declare function applySavedView(iModel: IModelConnection, viewport: Viewport, savedViewData: Pick<SavedView, "viewData" | "extensions">, settings?: ApplySavedViewSettings | undefined): Promise<void>;
67
+ export {};
@@ -0,0 +1,47 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ import { ViewState } from "@itwin/core-frontend";
6
+ import { createViewState } from "./createViewState.js";
7
+ import { extensionHandlers } from "./translation/SavedViewsExtensionHandlers.js";
8
+ /**
9
+ * Updates {@linkcode viewport} state to match captured Saved View.
10
+ *
11
+ * @example
12
+ * // Optionally, you can create and manage ViewState object yourself to avoid redundant work,
13
+ * // e.g. when applying the same Saved View to multiple viewports
14
+ * const viewState = await createViewState(iModel, savedView);
15
+ * await Promise.all([
16
+ * applySavedView(iModel, viewport1, savedView, { viewState }),
17
+ * applySavedView(iModel, viewport2, savedView, { viewState }),
18
+ * ]);
19
+ */
20
+ export async function applySavedView(iModel, viewport, savedViewData, settings = {}) {
21
+ const defaultStrategy = settings.all ?? "apply";
22
+ if ((settings.viewState ?? defaultStrategy) !== "keep") {
23
+ if (settings.viewState instanceof ViewState) {
24
+ viewport.changeView(settings.viewState);
25
+ }
26
+ else if (savedViewData.viewData) {
27
+ const { modelAndCategoryVisibilityFallback } = settings;
28
+ const viewState = await createViewState(iModel, savedViewData.viewData, { modelAndCategoryVisibilityFallback });
29
+ viewport.changeView(viewState);
30
+ }
31
+ }
32
+ const extensions = new Map(savedViewData.extensions?.map(({ extensionName, data }) => [extensionName, data]));
33
+ const processExtension = (extensionHandler, strategy = defaultStrategy) => {
34
+ if (strategy === "keep") {
35
+ return;
36
+ }
37
+ extensionHandler.reset(viewport);
38
+ if (strategy === "apply") {
39
+ const extensionData = extensions.get(extensionHandler.extensionName);
40
+ if (extensionData) {
41
+ extensionHandler.apply(extensionData, viewport);
42
+ }
43
+ }
44
+ };
45
+ processExtension(extensionHandlers.emphasizeElements, settings.emphasis);
46
+ processExtension(extensionHandlers.perModelCategoryVisibility, settings.perModelCategoryVisibility);
47
+ }
@@ -1,16 +1,41 @@
1
1
  import { Id64Array } from "@itwin/core-bentley";
2
2
  import { type IModelConnection, type Viewport } from "@itwin/core-frontend";
3
3
  import { type ViewData } from "@itwin/saved-views-client";
4
+ import type { SavedViewExtension } from "./SavedView.js";
4
5
  interface CaptureSavedViewDataArgs {
5
- /** Viewport to capture the view from. */
6
+ /** Viewport to capture. */
6
7
  viewport: Viewport;
7
8
  /**
8
- * Collect a list of models and categories that are currently not enabled in the {@linkcode viewport}.
9
- * @default true
9
+ * Whether to skip capturing data for `"EmphasizeElements"` extension.
10
+ * @default false
10
11
  */
11
- captureHiddenModelsAndCategories?: boolean | undefined;
12
+ omitEmphasis?: boolean | undefined;
13
+ /**
14
+ * Whether to skip capturing data for `"PerModelCategoryVisibility"` extension.
15
+ * @default false
16
+ */
17
+ omitPerModelCategoryVisibility?: boolean | undefined;
18
+ }
19
+ interface CaptureSavedViewDataResult {
20
+ viewData: ViewData;
21
+ /** Value is `undefined` when no extension data is captured. */
22
+ extensions: SavedViewExtension[] | undefined;
12
23
  }
13
- export declare function captureSavedViewData(args: CaptureSavedViewDataArgs): Promise<ViewData>;
24
+ /**
25
+ * Captures current {@link Viewport} state into serializable format. The returned data can later be used to restore
26
+ * viewport's view.
27
+ *
28
+ * @example
29
+ * import { captureSavedViewData, captureSavedViewThumbnail } from "@itwin/saved-views-react";
30
+ *
31
+ * async function saveViewport(viewport) {
32
+ * const { viewData, extensions = [] } = await captureSavedViewData({ viewport });
33
+ * const myExtensions = captureMyCustomState(viewport);
34
+ * const thumbnail = captureSavedViewThumbnail(viewport);
35
+ * return { thumbnail, viewData, extensions: extensions.concat(myExtensions) };
36
+ * }
37
+ */
38
+ export declare function captureSavedViewData(args: CaptureSavedViewDataArgs): Promise<CaptureSavedViewDataResult>;
14
39
  export declare function getMissingModels(iModel: IModelConnection, knownModels: Set<string>): Promise<string[]>;
15
40
  export declare function getMissingCategories(iModel: IModelConnection, knownCategories: Set<string>): Promise<Id64Array>;
16
41
  export {};
@@ -1,24 +1,45 @@
1
1
  import { QueryRowFormat } from "@itwin/core-common";
2
+ import { extensionHandlers } from "./translation/SavedViewsExtensionHandlers.js";
2
3
  import { extractClipVectorsFromLegacy } from "./translation/clipVectorsLegacyExtractor.js";
3
4
  import { extractDisplayStyle2dFromLegacy, extractDisplayStyle3dFromLegacy, } from "./translation/displayStyleExtractor.js";
5
+ /**
6
+ * Captures current {@link Viewport} state into serializable format. The returned data can later be used to restore
7
+ * viewport's view.
8
+ *
9
+ * @example
10
+ * import { captureSavedViewData, captureSavedViewThumbnail } from "@itwin/saved-views-react";
11
+ *
12
+ * async function saveViewport(viewport) {
13
+ * const { viewData, extensions = [] } = await captureSavedViewData({ viewport });
14
+ * const myExtensions = captureMyCustomState(viewport);
15
+ * const thumbnail = captureSavedViewThumbnail(viewport);
16
+ * return { thumbnail, viewData, extensions: extensions.concat(myExtensions) };
17
+ * }
18
+ */
4
19
  export async function captureSavedViewData(args) {
5
- const { captureHiddenModelsAndCategories = true } = args;
6
- const hiddenCategoriesPromise = captureHiddenModelsAndCategories
7
- ? getMissingCategories(args.viewport.iModel, new Set(args.viewport.view.categorySelector.toJSON().categories))
8
- : undefined;
9
- if (args.viewport.view.isSpatialView()) {
20
+ return {
21
+ viewData: await createSavedViewVariant(args.viewport),
22
+ extensions: [extensionHandlers.emphasizeElements, extensionHandlers.perModelCategoryVisibility]
23
+ .map((extension) => ({
24
+ extensionName: extension.extensionName,
25
+ data: extension.capture(args.viewport),
26
+ }))
27
+ .filter(({ data }) => data !== undefined),
28
+ };
29
+ }
30
+ async function createSavedViewVariant(viewport) {
31
+ const hiddenCategoriesPromise = getMissingCategories(viewport.iModel, new Set(viewport.view.categorySelector.toJSON().categories));
32
+ if (viewport.view.isSpatialView()) {
10
33
  const [hiddenCategories, hiddenModels] = await Promise.all([
11
34
  hiddenCategoriesPromise,
12
- captureHiddenModelsAndCategories
13
- ? getMissingModels(args.viewport.iModel, new Set(args.viewport.view.modelSelector.toJSON().models))
14
- : undefined,
35
+ getMissingModels(viewport.iModel, new Set(viewport.view.modelSelector.toJSON().models)),
15
36
  ]);
16
- return createSpatialSavedViewObject(args.viewport, hiddenCategories, hiddenModels);
37
+ return createSpatialSavedViewObject(viewport, hiddenCategories, hiddenModels);
17
38
  }
18
- if (args.viewport.view.isDrawingView()) {
19
- return createDrawingSavedViewObject(args.viewport, await hiddenCategoriesPromise);
39
+ if (viewport.view.isDrawingView()) {
40
+ return createDrawingSavedViewObject(viewport, await hiddenCategoriesPromise);
20
41
  }
21
- return createSheetSavedViewObject(args.viewport, await hiddenCategoriesPromise);
42
+ return createSheetSavedViewObject(viewport, await hiddenCategoriesPromise);
22
43
  }
23
44
  function createSpatialSavedViewObject(vp, hiddenCategories, hiddenModels) {
24
45
  const viewState = vp.view;