@netless/window-manager 0.4.31 → 0.4.32

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
@@ -1,5 +1,5 @@
1
1
  import type Emittery from "emittery";
2
- import type { AnimationMode, Displayer, DisplayerState, Player, Room, SceneDefinition, SceneState, View } from "white-web-sdk";
2
+ import type { AnimationMode, ApplianceNames, Displayer, DisplayerState, Player, Room, SceneDefinition, SceneState, View } from "white-web-sdk";
3
3
  import type { AppContext } from "./App";
4
4
  import type { ReadonlyTeleBox, TeleBoxRect } from "@netless/telebox-insider";
5
5
  import type { PageState } from "./Page";
@@ -69,6 +69,7 @@ export declare type RegisterParams<AppOptions = any, SetupResult = any, Attribut
69
69
  name?: string;
70
70
  };
71
71
  export declare type AppListenerKeys = keyof AppEmitterEvent;
72
+ export declare type ApplianceIcons = Partial<Record<ApplianceNames, string>>;
72
73
  export type { AppContext } from "./App/AppContext";
73
74
  export type { ReadonlyTeleBox, TeleBoxRect };
74
75
  export type { SceneState, SceneDefinition, View, AnimationMode, Displayer, Room, Player };
package/docs/api.md CHANGED
@@ -64,7 +64,7 @@ const manager = await WindowManager.mount(
64
64
  | disableCameraTransform | [optional] boolean | | 禁用主白板的相机移动 |
65
65
  | prefersColorScheme | [optional] string | light | auto, light, dark |
66
66
  | debug | [optional] boolean | false | 打印日志信息
67
-
67
+ | applianceIcons | [optional] {ApplianceNames, string} | | 配置光标使用的教具图片 |
68
68
 
69
69
  <h3 id="register">WindowManager.register</h3>
70
70
 
@@ -1,10 +1,11 @@
1
1
  ## AppContext
2
2
 
3
- - [api](#api)
3
+ - [api](#api)
4
4
  - [view](#view)
5
5
  - [page](#page)
6
6
  - [storage](#storage)
7
- - [events](#events)
7
+ - [events](#events)
8
+ - [Advanced](#Advanced)
8
9
 
9
10
  <h2 id="api">API</h2>
10
11
 
@@ -16,6 +17,12 @@
16
17
  const appId = context.appId;
17
18
  ```
18
19
 
20
+ - **context.isReplay**
21
+
22
+ 类型: `boolean`
23
+
24
+ 当前是否回放模式
25
+
19
26
  - **context.getDisplayer()**
20
27
 
21
28
  在默认情况下 `Displayer` 为白板的 `room` 实例
@@ -32,10 +39,10 @@
32
39
 
33
40
  - **context.getIsWritable()**
34
41
 
35
- 获取当前状态是否可写
42
+ 获取当前状态是否可写\
43
+ 可以通过监听 `writableChange` 事件获取可写状态的改变
36
44
 
37
45
  ```ts
38
- // isWritable === (room.isWritable && box.readonly)
39
46
  const isWritable = context.getIsWritable();
40
47
  ```
41
48
 
@@ -50,46 +57,31 @@
50
57
  box.$footer;
51
58
  ```
52
59
 
53
- <h3 id="view">View</h3>
54
-
55
- `view` 可以理解为一块白板,可以从 `context` 中拿到这个实例并挂载到 `Dom` 中
56
-
57
- - **context.getView()**
60
+ <h3 id="view">挂载白板</h3>
58
61
 
59
- 获取 `view` 实例
60
-
61
- ```ts
62
- const view = context.getView();
63
- ```
62
+ 当应用想要一个可以涂画的白板,可以使用以下接口
64
63
 
65
64
  - **context.mountView()**
66
65
 
67
- 挂载 view 到指定 dom
66
+ 挂载白板到指定 dom
68
67
 
69
68
  ```ts
70
69
  context.mountView(element);
71
70
  ```
72
71
 
73
- - **context.getScenes()**
74
-
75
- `scenes` 在 `addApp` 时传入 `scenePath` 会由 `WindowManager` 创建
76
-
77
- ```ts
78
- const scenes = context.getScenes();
79
- ```
80
-
81
- - **context.setScenePath()**
82
-
83
- 切换当前 `view` 到指定的 `scenePath`
84
-
85
- ```ts
86
- context.setScenePath("/page/2");
87
- ```
88
-
72
+ **注意** 在调用 `manager` 的 `addApp` 时必须填写 `scenePath` 才可以使用 `view`
73
+ ```ts
74
+ manager.addApp({
75
+ kind: "xxx",
76
+ options: { // 可选配置
77
+ scenePath: "/example-path"
78
+ }
79
+ })
80
+ ```
89
81
 
90
82
  <h3 id="page">Page</h3>
91
83
 
92
- `Page` 是封装后 `scenes` 的一些概念
84
+ 白板有多页的概念, 可以通过以下接口添加,切换,以及删除
93
85
 
94
86
  - **context.addPage()**
95
87
 
@@ -116,10 +108,19 @@
116
108
  ```ts
117
109
  context.prevPage();
118
110
  ```
111
+ - **context.removePage()**
112
+
113
+ 删除一页
114
+
115
+ ```ts
116
+ context.removePage() // 默认删除当前页
117
+ context.removePage(1) // 也可以指定 index 删除
118
+ ```
119
119
 
120
120
  - **context.pageState**
121
121
 
122
- 获取当前所在的 `index` 和一共有多少页
122
+ 获取当前所在的 `index` 和一共有多少页\
123
+ 当想要监听 `pageState` 的变化时, 可以监听 `pageStateChange` 事件获取最新的 `pageState`
123
124
 
124
125
  ```ts
125
126
  context.pageState;
@@ -141,17 +142,97 @@
141
142
  context.storage
142
143
  ```
143
144
 
144
- - **createStorage()**
145
+ - **context.createStorage(namespace)**
145
146
 
146
147
  同时你也可以创建多个 `storage` 实例
148
+
149
+ 返回: `Storage<State>`
147
150
 
148
151
  ```ts
149
- const defaultState = { count: 0 } // 可选
150
- const storage = context.createStorage("store1", defaultState);
152
+ type State = { count: number };
153
+ const defaultState = { count: 0 };
154
+ const storage = context.createStorage<State>("store1", defaultState);
151
155
  ```
152
156
 
157
+ - **storage.state**
158
+
159
+ 类型: `State`\
160
+ 默认值: `defaultState`
161
+
162
+ 在所有客户端之间同步的状态,调用 `storage.setState()` 来改变它。
163
+
164
+ - **storage.ensureState(partialState)**
165
+
166
+ 确保 `storage.state` 包含某些初始值,类似于执行了:
167
+
168
+ ```js
169
+ // 这段代码不能直接运行,因为 app.state 是只读的
170
+ storage.state = { ...partialState, ...storage.state };
171
+ ```
153
172
 
173
+ **partialState**
154
174
 
175
+ 类型: `Partial<State>`
176
+
177
+ ```js
178
+ storage.state; // { a: 1 }
179
+ storage.ensureState({ a: 0, b: 0 });
180
+ storage.state; // { a: 1, b: 0 }
181
+ ```
182
+
183
+ - **storage.setState(partialState)**
184
+
185
+ 和 React 的 `setState` 类似,更新 `storage.state` 并同步到所有客户端。
186
+
187
+ 当设置某个字段为 `undefined` 时,它会被从 `storage.state` 里删除。
188
+
189
+ > - 状态同步所需的时间和网络状态与数据大小有关,建议只在 state 里存储必须的数据。
190
+
191
+ **partialState**
192
+
193
+ 类型: `Partial<State>`
194
+
195
+ ```js
196
+ storage.state; //=> { count: 0, a: 1 }
197
+ storage.setState({ count: storage.state.count + 1, b: 2 });
198
+ storage.state; //=> { count: 1, a: 1, b: 2 }
199
+ ```
200
+
201
+ - **storage.addStateChangedListener(listener)**
202
+
203
+ 它在有人调用 `storage.setState()` 后触发 (包含当前 `storage`)
204
+
205
+ 返回: `() => void`
206
+
207
+ ```js
208
+ const disposer = storage.addStateChangedListener(diff => {
209
+ console.log("state changed", diff.oldValue, diff.newValue);
210
+ disposer(); // remove listener by calling disposer
211
+ });
212
+ ```
213
+
214
+ - **context.dispatchMagixEvent(event, payload)**
215
+
216
+ 向其他客户端广播事件消息
217
+
218
+ ```js
219
+ context.dispatchMagixEvent("click", { data: "data" });
220
+ ```
221
+
222
+ - **context.addMagixEventListener(event, listener)**
223
+
224
+ 当接收来自其他客户端的消息时(当其他客户端调用'context.dispatchMagixEvent()`时), 它会被触发
225
+
226
+ 返回: `() => void` a disposer function.
227
+
228
+ ```js
229
+ const disposer = context.addMagixEventListener("click", ({ payload }) => {
230
+ console.log(payload.data);
231
+ disposer();
232
+ });
233
+
234
+ context.dispatchMagixEvent("click", { data: "data" });
235
+ ```
155
236
 
156
237
  <h2 id="events">events</h2>
157
238
 
@@ -203,3 +284,13 @@
203
284
  // { index: 0, length: 1 }
204
285
  });
205
286
  ```
287
+
288
+ <h2 id="Advanced">Advanced</h2>
289
+
290
+ - **context.getView()**
291
+
292
+ 获取 `view` 实例
293
+
294
+ ```ts
295
+ const view = context.getView();
296
+ ```
@@ -49,7 +49,7 @@ const Counter: NetlessApp<{ count: number }> = {
49
49
  countDom.innerText = storage.state.count.toString();
50
50
  $content.appendChild(countDom);
51
51
 
52
- // state 变化回调
52
+ // 监听 state 变化回调
53
53
  storage.addStateChangedListener(diff => {
54
54
  if (diff.count) {
55
55
  // diff 会给出 newValue 和 oldValue
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/window-manager",
3
- "version": "0.4.31",
3
+ "version": "0.4.32",
4
4
  "description": "",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
@@ -1,5 +1,4 @@
1
1
  import App from "./Cursor.svelte";
2
- import { ApplianceMap } from "./icons";
3
2
  import { ApplianceNames } from "white-web-sdk";
4
3
  import { findMemberByUid } from "../Helper";
5
4
  import { omit } from "lodash";
@@ -159,8 +158,9 @@ export class Cursor {
159
158
 
160
159
  private getIcon() {
161
160
  if (this.member) {
162
- const applianceSrc = ApplianceMap[this.memberApplianceName || ApplianceNames.shape];
163
- return applianceSrc || ApplianceMap[ApplianceNames.shape];
161
+ const icons = this.cursorManager.applianceIcons;
162
+ const applianceSrc = icons[this.memberApplianceName || ApplianceNames.shape];
163
+ return applianceSrc || icons[ApplianceNames.shape];
164
164
  }
165
165
  }
166
166
 
@@ -5,10 +5,11 @@ import { emitter } from "../InternalEmitter";
5
5
  import { SideEffectManager } from "side-effect-manager";
6
6
  import { throttle } from "lodash";
7
7
  import { WindowManager } from "../index";
8
- import type { CursorMovePayload } from "../index";
8
+ import type { CursorMovePayload , ApplianceIcons} from "../index";
9
9
  import type { PositionType } from "../AttributesDelegate";
10
10
  import type { Point, RoomMember, View } from "white-web-sdk";
11
11
  import type { AppManager } from "../AppManager";
12
+ import { ApplianceMap } from "./icons";
12
13
 
13
14
  export type EventType = {
14
15
  type: PositionType;
@@ -20,6 +21,7 @@ export type MoveCursorParams = {
20
21
  x: number;
21
22
  y: number;
22
23
  };
24
+
23
25
  export class CursorManager {
24
26
  public containerRect?: DOMRect;
25
27
  public wrapperRect?: DOMRect;
@@ -28,8 +30,9 @@ export class CursorManager {
28
30
  private mainViewElement?: HTMLDivElement;
29
31
  private sideEffectManager = new SideEffectManager();
30
32
  private store = this.manager.store;
33
+ public applianceIcons: ApplianceIcons = ApplianceMap;
31
34
 
32
- constructor(private manager: AppManager, private enableCursor: boolean) {
35
+ constructor(private manager: AppManager, private enableCursor: boolean, applianceIcons?: ApplianceIcons) {
33
36
  this.roomMembers = this.manager.room?.state.roomMembers;
34
37
  const wrapper = WindowManager.wrapper;
35
38
  if (wrapper) {
@@ -42,6 +45,9 @@ export class CursorManager {
42
45
  this.sideEffectManager.add(() => {
43
46
  return emitter.on("playgroundSizeChange", () => this.updateContainerRect());
44
47
  });
48
+ if (applianceIcons) {
49
+ this.applianceIcons = { ...ApplianceMap, ...applianceIcons };
50
+ }
45
51
  }
46
52
 
47
53
  private onCursorMove = (payload: CursorMovePayload) => {
package/src/index.ts CHANGED
@@ -29,13 +29,7 @@ import {
29
29
  wait,
30
30
  } from "./Utils/Common";
31
31
  import type { TELE_BOX_STATE, BoxManager } from "./BoxManager";
32
- import {
33
- AppCreateError,
34
- AppManagerNotInitError,
35
- BindContainerRoomPhaseInvalidError,
36
- InvalidScenePath,
37
- ParamsInvalidError,
38
- } from "./Utils/error";
32
+ import * as Errors from "./Utils/error";
39
33
  import type { Apps, Position } from "./AttributesDelegate";
40
34
  import type {
41
35
  Displayer,
@@ -54,7 +48,7 @@ import type {
54
48
  SceneState,
55
49
  } from "white-web-sdk";
56
50
  import type { AppListeners } from "./AppListener";
57
- import type { NetlessApp, RegisterParams } from "./typings";
51
+ import type { ApplianceIcons, NetlessApp, RegisterParams } from "./typings";
58
52
  import type { TeleBoxColorScheme, TeleBoxState } from "@netless/telebox-insider";
59
53
  import type { AppProxy } from "./App";
60
54
  import type { PublicEvent } from "./callback";
@@ -143,6 +137,7 @@ export type MountParams = {
143
137
  debug?: boolean;
144
138
  disableCameraTransform?: boolean;
145
139
  prefersColorScheme?: TeleBoxColorScheme;
140
+ applianceIcons?: ApplianceIcons;
146
141
  };
147
142
 
148
143
  export const reconnectRefresher = new ReconnectRefresher({ emitter });
@@ -238,7 +233,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
238
233
 
239
234
  manager.appManager = new AppManager(manager);
240
235
  manager._pageState = new PageStateImpl(manager.appManager);
241
- manager.cursorManager = new CursorManager(manager.appManager, Boolean(cursor));
236
+ manager.cursorManager = new CursorManager(manager.appManager, Boolean(cursor), params.applianceIcons);
242
237
  if (containerSizeRatio) {
243
238
  manager.containerSizeRatio = containerSizeRatio;
244
239
  }
@@ -322,7 +317,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
322
317
 
323
318
  public bindContainer(container: HTMLElement) {
324
319
  if (isRoom(this.displayer) && this.room.phase !== RoomPhase.Connected) {
325
- throw new BindContainerRoomPhaseInvalidError();
320
+ throw new Errors.BindContainerRoomPhaseInvalidError();
326
321
  }
327
322
  if (WindowManager.isCreated && WindowManager.container) {
328
323
  if (WindowManager.container.firstChild) {
@@ -408,19 +403,19 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
408
403
  return this._addApp(params);
409
404
  }
410
405
  } else {
411
- throw new AppManagerNotInitError();
406
+ throw new Errors.AppManagerNotInitError();
412
407
  }
413
408
  }
414
409
 
415
410
  private async _addApp<T = any>(params: AddAppParams<T>): Promise<string | undefined> {
416
411
  if (this.appManager) {
417
412
  if (!params.kind || typeof params.kind !== "string") {
418
- throw new ParamsInvalidError();
413
+ throw new Errors.ParamsInvalidError();
419
414
  }
420
415
  const appImpl = await appRegister.appClasses.get(params.kind)?.();
421
416
  if (appImpl && appImpl.config?.singleton) {
422
417
  if (this.appManager.appProxies.has(params.kind)) {
423
- throw new AppCreateError();
418
+ throw new Errors.AppCreateError();
424
419
  }
425
420
  }
426
421
  const isDynamicPPT = this.setupScenePath(params, this.appManager);
@@ -433,7 +428,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
433
428
  const appId = await this.appManager.addApp(params, Boolean(isDynamicPPT));
434
429
  return appId;
435
430
  } else {
436
- throw new AppManagerNotInitError();
431
+ throw new Errors.AppManagerNotInitError();
437
432
  }
438
433
  }
439
434
 
@@ -443,7 +438,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
443
438
  const { scenePath, scenes } = params.options;
444
439
  if (scenePath) {
445
440
  if (!isValidScenePath(scenePath)) {
446
- throw new InvalidScenePath();
441
+ throw new Errors.InvalidScenePath();
447
442
  }
448
443
  const apps = Object.keys(this.apps || {});
449
444
  for (const appId of apps) {
@@ -646,7 +641,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
646
641
  if (this.appManager) {
647
642
  return this.appManager.mainViewProxy.view;
648
643
  } else {
649
- throw new AppManagerNotInitError();
644
+ throw new Errors.AppManagerNotInitError();
650
645
  }
651
646
  }
652
647
 
@@ -654,7 +649,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
654
649
  if (this.appManager) {
655
650
  return this.appManager.mainViewProxy.view.camera;
656
651
  } else {
657
- throw new AppManagerNotInitError();
652
+ throw new Errors.AppManagerNotInitError();
658
653
  }
659
654
  }
660
655
 
@@ -662,7 +657,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
662
657
  if (this.appManager) {
663
658
  return this.appManager.mainViewProxy.cameraState;
664
659
  } else {
665
- throw new AppManagerNotInitError();
660
+ throw new Errors.AppManagerNotInitError();
666
661
  }
667
662
  }
668
663
 
@@ -674,7 +669,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
674
669
  if (this.appManager) {
675
670
  return this.appManager.boxManager?.boxState;
676
671
  } else {
677
- throw new AppManagerNotInitError();
672
+ throw new Errors.AppManagerNotInitError();
678
673
  }
679
674
  }
680
675
 
@@ -686,7 +681,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
686
681
  if (this.appManager) {
687
682
  return this.appManager.boxManager?.prefersColorScheme;
688
683
  } else {
689
- throw new AppManagerNotInitError();
684
+ throw new Errors.AppManagerNotInitError();
690
685
  }
691
686
  }
692
687
 
@@ -706,7 +701,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
706
701
  if (this.appManager) {
707
702
  return this.appManager?.getMainViewSceneDir();
708
703
  } else {
709
- throw new AppManagerNotInitError();
704
+ throw new Errors.AppManagerNotInitError();
710
705
  }
711
706
  }
712
707
 
@@ -731,7 +726,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
731
726
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
732
727
  return this.appManager.sceneState!;
733
728
  } else {
734
- throw new AppManagerNotInitError();
729
+ throw new Errors.AppManagerNotInitError();
735
730
  }
736
731
  }
737
732
 
@@ -739,7 +734,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
739
734
  if (this._pageState) {
740
735
  return this._pageState.toObject();
741
736
  } else {
742
- throw new AppManagerNotInitError();
737
+ throw new Errors.AppManagerNotInitError();
743
738
  }
744
739
  }
745
740
 
package/src/typings.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type Emittery from "emittery";
2
2
  import type {
3
3
  AnimationMode,
4
+ ApplianceNames,
4
5
  Displayer,
5
6
  DisplayerState,
6
7
  Player,
@@ -75,6 +76,8 @@ export type RegisterParams<AppOptions = any, SetupResult = any, Attributes = any
75
76
 
76
77
  export type AppListenerKeys = keyof AppEmitterEvent;
77
78
 
79
+ export type ApplianceIcons = Partial<Record<ApplianceNames, string>>;
80
+
78
81
  export type { AppContext } from "./App/AppContext";
79
82
  export type { ReadonlyTeleBox, TeleBoxRect };
80
83
  export type { SceneState, SceneDefinition, View, AnimationMode, Displayer, Room, Player };