@netless/window-manager 0.4.26 → 0.4.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/typings.d.ts CHANGED
@@ -74,3 +74,4 @@ export type { ReadonlyTeleBox, TeleBoxRect };
74
74
  export type { SceneState, SceneDefinition, View, AnimationMode, Displayer, Room, Player };
75
75
  export type { Storage, StorageStateChangedEvent, StorageStateChangedListener } from "./App/Storage";
76
76
  export * from "./Page";
77
+ export * from "./Utils/error";
package/docs/advanced.md CHANGED
@@ -6,6 +6,7 @@
6
6
  - [判断是否打开某种 APP](#has-kind)
7
7
  - [页面控制器](#page-control)
8
8
  - [视角](#view-mode)
9
+ - [插入图片到当前app](#insert-image-to-app)
9
10
 
10
11
 
11
12
  <h3 id="redo-undo">撤销重做</h3>
@@ -109,3 +110,28 @@ manager.addPage()
109
110
  同时其他为 `broadcaster` 模式的人也会影响我的视角
110
111
 
111
112
  在 `isWritable` 为 `false` 时只会跟随其他 `broadcaster` 的视角
113
+
114
+ <br>
115
+
116
+ <h3 id="insert-image-to-app">插入图片到当前 app</h3>
117
+
118
+ ```ts
119
+ // 判断当前是否为最大化
120
+ if (manager.boxState === "maximized") {
121
+ // `focused` 的值的会根据当前 focus 的 app 不同而变化
122
+ const app = manager.queryOne(manager.focused)
123
+ // 有 view 的 app 才可以插入图片, 像是 视频,音频之类的 app 是没有 view 的
124
+ if (app.view) {
125
+ var imageInformation = {
126
+ uuid: uuid,
127
+ centerX: centerX,
128
+ centerY: centerY,
129
+ width: width,
130
+ height: height,
131
+ locked: false,
132
+ };
133
+ app.view.insertImage(imageInformation);
134
+ app.view.completeImageUpload(uuid, src);
135
+ }
136
+ }
137
+ ```
package/docs/api.md CHANGED
@@ -27,6 +27,7 @@
27
27
  - [`nextPage`](#nextPage)
28
28
  - [`prevPage`](#prevPage)
29
29
  - [`addPage`](#addPage)
30
+ - [`removePage`](#removePage)
30
31
  - [`refresh`](#refresh)
31
32
  - [`setContainerSizeRatio`](#setContainerSizeRatio)
32
33
  - [实例属性](#prototypes)
@@ -214,6 +215,14 @@ manager.addPage({ after: true }) // 在当前页后添加一页
214
215
  manager.addPage({ scene: { name: "page2" } }) // 传入 page 信息
215
216
  ```
216
217
 
218
+ <h3 id="removePage">removePage</h3>
219
+
220
+ > 移除一页
221
+
222
+ ```ts
223
+ const success = await manager.removePage(1)
224
+ ```
225
+
217
226
  <h3 id="refresh">refresh</h3>
218
227
 
219
228
  > 刷新 `manager` 的内部状态, 用于从其他房间 `copy` `attributes`
@@ -1,156 +1,205 @@
1
1
  ## AppContext
2
2
 
3
- - [api](#api)
4
- - [events](#events)
3
+ - [api](#api)
4
+ - [view](#view)
5
+ - [page](#page)
6
+ - [storage](#storage)
7
+ - [events](#events)
5
8
 
6
9
  <h2 id="api">API</h2>
7
10
 
8
- ### appId
11
+ - **context.appId**
9
12
 
10
- 插入 `app` 时生成的唯一 ID
13
+ 插入 `app` 时生成的唯一 ID
11
14
 
12
- ```ts
13
- const appId = context.appId
14
- ```
15
+ ```ts
16
+ const appId = context.appId;
17
+ ```
15
18
 
16
- ### getDisplayer
19
+ - **context.getDisplayer()**
17
20
 
18
- 在默认情况下 `Displayer` 为白板的 `room` 实例
21
+ 在默认情况下 `Displayer` 为白板的 `room` 实例
19
22
 
20
- 回放时则为 `Player` 实例
23
+ 回放时则为 `Player` 实例
21
24
 
22
- ```ts
23
- const displayer = context.getDisplayer()
25
+ ```ts
26
+ const displayer = context.getDisplayer();
24
27
 
25
- assert(displayer, room) // 互动房间
26
- assert(displayer, player) // 回放房间
27
- ```
28
+ assert(displayer, room); // 互动房间
29
+ assert(displayer, player); // 回放房间
30
+ ```
28
31
 
29
- ### getScenes
30
32
 
31
- `scenes` 在 `addApp` 时传入 `scenePath` 会由 `WindowManager` 创建
33
+ - **context.getIsWritable()**
32
34
 
33
- ```ts
34
- const scenes = context.getScenes()
35
- ```
35
+ 获取当前状态是否可写
36
36
 
37
- ### getView
37
+ ```ts
38
+ // isWritable === (room.isWritable && box.readonly)
39
+ const isWritable = context.getIsWritable();
40
+ ```
38
41
 
39
- `View` 为白板中一块可标注部分
42
+ - **context.getBox()**
40
43
 
41
- ```ts
42
- const view = context.getView()
43
- ```
44
+ 获取当前 app 的 box
44
45
 
45
- ### getIsWritable
46
+ ```ts
47
+ const box = context.getBox();
46
48
 
47
- 获取当前状态是否可写
49
+ box.$content; // box 的 main element
50
+ box.$footer;
51
+ ```
48
52
 
49
- ```ts
50
- // isWritable === (room.isWritable && box.readonly)
51
- const isWritable = context.getIsWritable()
52
- ```
53
+ <h3 id="view">View</h3>
53
54
 
54
- ### getBox
55
+ `view` 可以理解为一块白板,可以从 `context` 中拿到这个实例并挂载到 `Dom` 中
55
56
 
56
- 获取当前 app 的 box
57
+ - **context.getView()**
57
58
 
58
- ```ts
59
- const box = context.getBox()
59
+ 获取 `view` 实例
60
60
 
61
- box.$content // box 的 main element
62
- box.$footer
63
- ```
61
+ ```ts
62
+ const view = context.getView();
63
+ ```
64
64
 
65
- ### setScenePath
65
+ - **context.mountView()**
66
66
 
67
- 切换当前 `view` `scenePath`
67
+ 挂载 view 到指定 dom
68
68
 
69
- ```ts
70
- context.setScenePath("/page/2")
71
- ```
69
+ ```ts
70
+ context.mountView(element);
71
+ ```
72
72
 
73
- ### mountView
73
+ - **context.getScenes()**
74
74
 
75
- 挂载 view 到指定 dom
75
+ `scenes` `addApp` 时传入 `scenePath` 会由 `WindowManager` 创建
76
76
 
77
- ```ts
78
- context.mountView(ref)
79
- ```
77
+ ```ts
78
+ const scenes = context.getScenes();
79
+ ```
80
80
 
81
- ### addPage
81
+ - **context.setScenePath()**
82
82
 
83
- ```ts
84
- context.addPage()
85
- ```
83
+ 切换当前 `view` 到指定的 `scenePath`
86
84
 
87
- ### nextPage
85
+ ```ts
86
+ context.setScenePath("/page/2");
87
+ ```
88
88
 
89
- ```ts
90
- context.nextPage()
91
- ```
92
89
 
93
- ### prevPage
90
+ <h3 id="page">Page</h3>
94
91
 
95
- ```ts
96
- context.prevPage()
97
- ```
92
+ `Page` 是封装后 `scenes` 的一些概念
98
93
 
99
- ### pageState
94
+ - **context.addPage()**
100
95
 
101
- ```ts
102
- context.pageState
103
- ```
96
+ 添加一页至 `view`
104
97
 
98
+ ```ts
99
+ context.addPage() // 默认在最后添加一页
100
+ context.addPage({ after: true }) // 在当前页后添加一页
101
+ context.addPage({ scene: { name: "page2" } }) // 传入 page 信息
102
+ ```
105
103
 
106
- <h2 id="events">events</h2>
104
+ - **context.nextPage()**
105
+
106
+ 上一页
107
+
108
+ ```ts
109
+ context.nextPage();
110
+ ```
111
+
112
+ - **context.prevPage()**
113
+
114
+ 下一页
115
+
116
+ ```ts
117
+ context.prevPage();
118
+ ```
119
+
120
+ - **context.pageState**
121
+
122
+ 获取当前所在的 `index` 和一共有多少页
123
+
124
+ ```ts
125
+ context.pageState;
126
+ // {
127
+ // index: number,
128
+ // length: number,
129
+ // }
130
+ ```
131
+
132
+ <h3 id="storage">storage</h3>
133
+
134
+ 存储和同步状态,以及发送事件的一系列集合
107
135
 
108
- ### destroy
136
+ - **context.storage**
137
+
138
+ 默认创建的 storage 实例
139
+
140
+ ```ts
141
+ context.storage
142
+ ```
143
+
144
+ - **createStorage()**
145
+
146
+ 同时你也可以创建多个 `storage` 实例
147
+
148
+ ```ts
149
+ const defaultState = { count: 0 } // 可选
150
+ const storage = context.createStorage("store1", defaultState);
151
+ ```
152
+
153
+
154
+
155
+
156
+ <h2 id="events">events</h2>
109
157
 
110
- app 被关闭时发送的事件
158
+ - **destroy**
111
159
 
112
- ```ts
113
- context.emitter.on("destroy", () => {
114
- // release your listeners
115
- })
116
- ```
160
+ app 被关闭时发送
117
161
 
118
- ### writableChange
162
+ ```ts
163
+ context.emitter.on("destroy", () => {
164
+ // release your listeners
165
+ });
166
+ ```
119
167
 
120
- 白板可写状态切换时触发
168
+ - **writableChange**
121
169
 
122
- ```ts
123
- context.emitter.on("writableChange", isWritable => {
124
- //
125
- })
126
- ```
170
+ 白板可写状态切换时触发
127
171
 
128
- ### focus
172
+ ```ts
173
+ context.emitter.on("writableChange", isWritable => {
174
+ //
175
+ });
176
+ ```
129
177
 
130
- 当前 app 获得焦点或者失去焦点时触发
178
+ - **focus**
131
179
 
132
- ```ts
133
- context.emitter.on("focus", focus => {
134
- //
135
- })
136
- ```
180
+ 当前 app 获得焦点或者失去焦点时触发
137
181
 
182
+ ```ts
183
+ context.emitter.on("focus", focus => {
184
+ //
185
+ });
186
+ ```
138
187
 
139
- ### pageStateChange
188
+ - **pageStateChange**
140
189
 
141
- #### PageState
190
+ `PageState`
142
191
 
143
- ```ts
144
- type PateState {
145
- index: number;
146
- length: number;
147
- }
148
- ```
192
+ ```ts
193
+ type PateState {
194
+ index: number;
195
+ length: number;
196
+ }
197
+ ```
149
198
 
150
- 当前页数和总页数变化时触发
199
+ 当前页数和总页数变化时触发
151
200
 
152
- ```ts
153
- context.emitter.on("pageStateChange", pageState => {
154
- // { index: 0, length: 1 }
155
- })
156
- ```
201
+ ```ts
202
+ context.emitter.on("pageStateChange", pageState => {
203
+ // { index: 0, length: 1 }
204
+ });
205
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/window-manager",
3
- "version": "0.4.26",
3
+ "version": "0.4.28",
4
4
  "description": "",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
@@ -82,6 +82,17 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
82
82
  return this.appProxy.view;
83
83
  };
84
84
 
85
+ public mountView = (dom: HTMLElement): void => {
86
+ const view = this.getView();
87
+ if (view) {
88
+ view.divElement = dom as HTMLDivElement;
89
+ setTimeout(() => {
90
+ // 渲染需要时间,延迟 refresh
91
+ this.getRoom()?.refreshViewSize();
92
+ }, 1000);
93
+ }
94
+ };
95
+
85
96
  public getInitScenePath = () => {
86
97
  return this.manager.getAppInitPath(this.appId);
87
98
  };
@@ -124,17 +135,6 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
124
135
  this.getRoom()?.setScenePath(scenePath);
125
136
  };
126
137
 
127
- public mountView = (dom: HTMLElement): void => {
128
- const view = this.getView();
129
- if (view) {
130
- view.divElement = dom as HTMLDivElement;
131
- setTimeout(() => {
132
- // 渲染需要时间,延迟 refresh
133
- this.getRoom()?.refreshViewSize();
134
- }, 1000);
135
- }
136
- };
137
-
138
138
  /** Get the local App options. */
139
139
  public getAppOptions = (): TAppOptions | undefined => {
140
140
  return typeof this.appOptions === "function"
@@ -231,6 +231,15 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
231
231
  }
232
232
  };
233
233
 
234
+ public removePage = async (index: number): Promise<boolean> => {
235
+ if (index < 0 || index >= this.pageState.length) {
236
+ console.warn(`[WindowManager]: page index ${index} out of range`);
237
+ return false;
238
+ }
239
+ this.appProxy.removeSceneByIndex(index);
240
+ return true;
241
+ }
242
+
234
243
  public get pageState(): PageState {
235
244
  return this.appProxy.pageState;
236
245
  }
@@ -27,11 +27,11 @@ import type { SceneState, View, SceneDefinition } from "white-web-sdk";
27
27
  import type { AppManager } from "../AppManager";
28
28
  import type { NetlessApp } from "../typings";
29
29
  import type { ReadonlyTeleBox } from "@netless/telebox-insider";
30
- import type { PageState } from "../Page";
30
+ import { calculateNextIndex, PageRemoveService, PageState } from "../Page";
31
31
 
32
32
  export type AppEmitter = Emittery<AppEmitterEvent>;
33
33
 
34
- export class AppProxy {
34
+ export class AppProxy implements PageRemoveService {
35
35
  public kind: string;
36
36
  public id: string;
37
37
  public scenePath?: string;
@@ -249,6 +249,24 @@ export class AppProxy {
249
249
  this.boxManager?.updateBoxState(currentAppState);
250
250
  }
251
251
 
252
+ public async onRemoveScene(scenePath: string) {
253
+ if (this.scenePath && scenePath.startsWith(this.scenePath + "/")) {
254
+ let nextIndex = this.pageState.index;
255
+ let fullPath = this._pageState.getFullPath(nextIndex);
256
+ if (!fullPath) {
257
+ nextIndex = 0;
258
+ fullPath = this._pageState.getFullPath(nextIndex);
259
+ }
260
+ if (fullPath) {
261
+ this.setFullPath(fullPath);
262
+ }
263
+ this.setViewFocusScenePath();
264
+ if (this.view) {
265
+ this.view.focusSceneIndex = nextIndex;
266
+ }
267
+ }
268
+ }
269
+
252
270
  public getAppInitState = (id: string) => {
253
271
  const attrs = this.store.getAppState(id);
254
272
  if (!attrs) return;
@@ -385,14 +403,47 @@ export class AppProxy {
385
403
  return view;
386
404
  }
387
405
 
388
- public notifyPageStateChange = () => {
406
+ public notifyPageStateChange = debounce(() => {
389
407
  this.appEmitter.emit("pageStateChange", this.pageState);
390
- };
408
+ }, 50);
391
409
 
392
410
  public get pageState(): PageState {
393
411
  return this._pageState.toObject();
394
412
  }
395
413
 
414
+ // PageRemoveService
415
+ public async removeSceneByIndex(index: number) {
416
+ const scenePath = this._pageState.getFullPath(index);
417
+ if (scenePath) {
418
+ // 不能删除所有场景
419
+ if (this.pageState.length <= 1) {
420
+ return false;
421
+ }
422
+ const nextIndex = calculateNextIndex(index, this.pageState);
423
+ // 只修改 focus path 不修改 FullPath
424
+ this.setSceneIndexWithoutSync(nextIndex);
425
+ this.manager.dispatchInternalEvent(Events.SetAppFocusIndex, {
426
+ type: "app",
427
+ appID: this.id,
428
+ index: nextIndex,
429
+ });
430
+ // 手动添加一个延迟, 让 app 切换场景后再删除以避免闪烁
431
+ setTimeout(() => {
432
+ removeScenes(this.manager.room, scenePath, index);
433
+ }, 100);
434
+ return true;
435
+ } else {
436
+ return false;
437
+ }
438
+ }
439
+
440
+ public setSceneIndexWithoutSync(index: number) {
441
+ if (this.view) {
442
+ this.view.focusSceneIndex = index;
443
+ }
444
+ }
445
+ // PageRemoveService end
446
+
396
447
  public setSceneIndex(index: number) {
397
448
  if (this.view) {
398
449
  this.view.focusSceneIndex = index;
@@ -37,15 +37,21 @@ export class Storage<TState extends Record<string, any> = any> implements Storag
37
37
  this._state = {} as TState;
38
38
  const rawState = this._getRawState(this._state);
39
39
 
40
- if (this.id !== null && this._context.getIsWritable()) {
41
- if (rawState === this._state || !isObject(rawState)) {
42
- if (!get(this._context.getAttributes(), [STORAGE_NS])) {
43
- this._context.updateAttributes([STORAGE_NS], {});
40
+ if (this._context.getIsWritable()) {
41
+ if (this.id === null) {
42
+ if (context.isAddApp && defaultState) {
43
+ this.setState(defaultState);
44
+ }
45
+ } else {
46
+ if (rawState === this._state || !isObject(rawState)) {
47
+ if (!get(this._context.getAttributes(), [STORAGE_NS])) {
48
+ this._context.updateAttributes([STORAGE_NS], {});
49
+ }
50
+ this._context.updateAttributes([STORAGE_NS, this.id], this._state);
51
+ if (defaultState) {
52
+ this.setState(defaultState);
53
+ }
44
54
  }
45
- this._context.updateAttributes([STORAGE_NS, this.id], this._state);
46
- }
47
- if (defaultState) {
48
- this.setState(defaultState);
49
55
  }
50
56
  }
51
57
 
@@ -6,6 +6,13 @@ import { setViewFocusScenePath } from "./Utils/Common";
6
6
  import type { AnimationMode, Camera, Event } from "white-web-sdk";
7
7
  import type { AppManager } from "./AppManager";
8
8
  import type { TeleBoxState } from "@netless/telebox-insider";
9
+
10
+ type SetAppFocusIndex = {
11
+ type: "main" | "app";
12
+ appID?: string;
13
+ index: number;
14
+ }
15
+
9
16
  export class AppListeners {
10
17
  private displayer = this.manager.displayer;
11
18
 
@@ -67,6 +74,10 @@ export class AppListeners {
67
74
  this.initMainViewCameraHandler();
68
75
  break;
69
76
  }
77
+ case Events.SetAppFocusIndex: {
78
+ this.setAppFocusViewIndexHandler(data.payload);
79
+ break;
80
+ }
70
81
  default:
71
82
  break;
72
83
  }
@@ -119,4 +130,15 @@ export class AppListeners {
119
130
  private initMainViewCameraHandler = () => {
120
131
  this.manager.mainViewProxy.addCameraReaction();
121
132
  }
133
+
134
+ private setAppFocusViewIndexHandler = (payload: SetAppFocusIndex) => {
135
+ if (payload.type === "main") {
136
+ this.manager.setSceneIndexWithoutSync(payload.index);
137
+ } else if (payload.type === "app" && payload.appID) {
138
+ const app = this.manager.appProxies.get(payload.appID);
139
+ if (app) {
140
+ app.setSceneIndexWithoutSync(payload.index);
141
+ }
142
+ }
143
+ }
122
144
  }