@netless/window-manager 0.3.17 → 0.4.0-canary.10

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 (73) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +4 -43
  3. package/dist/App/Storage/StorageEvent.d.ts +8 -0
  4. package/dist/App/Storage/index.d.ts +26 -0
  5. package/dist/App/Storage/typings.d.ts +21 -0
  6. package/dist/App/Storage/utils.d.ts +5 -0
  7. package/dist/AppContext.d.ts +6 -3
  8. package/dist/AppListener.d.ts +2 -2
  9. package/dist/AppManager.d.ts +17 -14
  10. package/dist/AppProxy.d.ts +5 -3
  11. package/dist/AttributesDelegate.d.ts +19 -11
  12. package/dist/Base/Context.d.ts +0 -1
  13. package/dist/Base/index.d.ts +1 -2
  14. package/dist/BoxManager.d.ts +25 -8
  15. package/dist/BuiltinApps.d.ts +6 -0
  16. package/dist/ContainerResizeObserver.d.ts +10 -0
  17. package/dist/Cursor/Cursor.d.ts +2 -3
  18. package/dist/Cursor/index.d.ts +9 -5
  19. package/dist/Helper.d.ts +6 -0
  20. package/dist/ReconnectRefresher.d.ts +9 -3
  21. package/dist/Utils/Common.d.ts +3 -1
  22. package/dist/Utils/Reactive.d.ts +1 -1
  23. package/dist/Utils/RoomHacker.d.ts +2 -2
  24. package/dist/Utils/error.d.ts +3 -0
  25. package/dist/{MainView.d.ts → View/MainView.d.ts} +3 -4
  26. package/dist/View/ViewManager.d.ts +13 -0
  27. package/dist/constants.d.ts +3 -7
  28. package/dist/index.d.ts +24 -27
  29. package/dist/index.es.js +1 -1
  30. package/dist/index.es.js.map +1 -1
  31. package/dist/index.umd.js +1 -1
  32. package/dist/index.umd.js.map +1 -1
  33. package/dist/style.css +1 -1
  34. package/dist/typings.d.ts +1 -0
  35. package/docs/api.md +17 -0
  36. package/docs/migrate.md +42 -0
  37. package/package.json +6 -4
  38. package/src/App/Storage/StorageEvent.ts +21 -0
  39. package/src/App/Storage/index.ts +243 -0
  40. package/src/App/Storage/typings.ts +21 -0
  41. package/src/App/Storage/utils.ts +17 -0
  42. package/src/AppContext.ts +12 -4
  43. package/src/AppListener.ts +15 -11
  44. package/src/AppManager.ts +132 -95
  45. package/src/AppProxy.ts +49 -52
  46. package/src/AttributesDelegate.ts +76 -49
  47. package/src/Base/Context.ts +2 -6
  48. package/src/Base/index.ts +2 -2
  49. package/src/BoxManager.ts +95 -35
  50. package/src/BuiltinApps.ts +24 -0
  51. package/src/ContainerResizeObserver.ts +62 -0
  52. package/src/Cursor/Cursor.ts +35 -39
  53. package/src/Cursor/index.ts +79 -43
  54. package/src/Helper.ts +30 -0
  55. package/src/ReconnectRefresher.ts +25 -10
  56. package/src/Utils/Common.ts +35 -13
  57. package/src/Utils/Reactive.ts +9 -3
  58. package/src/Utils/RoomHacker.ts +20 -5
  59. package/src/Utils/error.ts +6 -1
  60. package/src/{MainView.ts → View/MainView.ts} +19 -27
  61. package/src/View/ViewManager.ts +53 -0
  62. package/src/constants.ts +2 -3
  63. package/src/index.ts +143 -171
  64. package/src/shim.d.ts +4 -0
  65. package/src/style.css +7 -1
  66. package/src/typings.ts +1 -0
  67. package/vite.config.js +4 -1
  68. package/dist/Utils/CameraStore.d.ts +0 -15
  69. package/dist/ViewManager.d.ts +0 -29
  70. package/dist/sdk.d.ts +0 -14
  71. package/src/Utils/CameraStore.ts +0 -72
  72. package/src/sdk.ts +0 -39
  73. package/src/viewManager.ts +0 -177
@@ -1,7 +1,6 @@
1
1
  import App from './Cursor.svelte';
2
- import pRetry from 'p-retry';
3
2
  import { ApplianceMap } from './icons';
4
- import { ApplianceNames, autorun } from 'white-web-sdk';
3
+ import { ApplianceNames } from 'white-web-sdk';
5
4
  import { CursorState } from '../constants';
6
5
  import { Fields } from '../AttributesDelegate';
7
6
  import { get, omit } from 'lodash';
@@ -19,64 +18,61 @@ export type Payload = {
19
18
 
20
19
  export class Cursor extends Base {
21
20
  private member?: RoomMember;
22
- private disposer: any;
23
21
  private timer?: number;
24
22
  private component?: SvelteComponent;
25
23
 
26
24
  constructor(
27
25
  manager: AppManager,
26
+ addCursorChangeListener: (uid: string, callback: (position: Position, state: CursorState) => void) => void,
28
27
  private cursors: any,
29
28
  private memberId: string,
30
29
  private cursorManager: CursorManager,
31
- private wrapper?: HTMLElement
30
+ private wrapper?: HTMLElement,
32
31
  ) {
33
32
  super(manager);
34
33
  this.setMember();
35
34
  this.createCursor();
36
- pRetry(() => {
37
- this.disposer && this.disposer();
38
- if (!this.cursorPosition) {
39
- console.warn(`${memberId} not exist`);
40
- }
41
- this.startReaction();
42
- }, { retries: 3 });
35
+ addCursorChangeListener(this.memberId, this.onCursorChange);
43
36
  this.autoHidden();
44
37
  }
45
38
 
46
- private startReaction() {
47
- this.disposer = autorun(() => {
48
- const cursor = this.cursorPosition;
49
- const state = this.cursorState;
50
- if (!cursor) return;
51
- if (cursor.type === "main") {
52
- const rect = this.cursorManager.wrapperRect;
53
- if (this.component && rect) {
54
- this.autoHidden();
55
- this.moveCursor(cursor, rect, this.manager.mainView);
56
- }
57
- } else {
58
- const focusView = this.cursorManager.focusView;
59
- const viewRect = focusView?.divElement?.getBoundingClientRect();
60
- const viewCamera = focusView?.camera;
61
- if (focusView && viewRect && viewCamera && this.component) {
62
- this.autoHidden();
63
- this.moveCursor(cursor, viewRect, focusView);
64
- }
39
+ private onCursorChange = (position: Position, state: CursorState) => {
40
+ if (position.type === "main") {
41
+ const rect = this.cursorManager.wrapperRect;
42
+ if (this.component && rect) {
43
+ this.autoHidden();
44
+ this.moveCursor(position, rect, this.manager.mainView);
65
45
  }
66
- if (state && state === CursorState.Leave) {
67
- this.hide();
46
+ } else {
47
+ const focusView = this.cursorManager.focusView;
48
+ // TODO 可以存一个当前 focusView 的 Rect 这样只有 focus 切换的时候才调用 getBoundingClientRect
49
+ const viewRect = focusView?.divElement?.getBoundingClientRect();
50
+ const viewCamera = focusView?.camera;
51
+ if (focusView && viewRect && viewCamera && this.component) {
52
+ this.autoHidden();
53
+ this.moveCursor(position, viewRect, focusView);
68
54
  }
69
- });
55
+ }
56
+ if (state && state === CursorState.Leave) {
57
+ this.hide();
58
+ }
70
59
  }
71
60
 
72
61
  private moveCursor(cursor: Position, rect: DOMRect, view: any) {
73
- const { x, y } = cursor;
62
+ const { x, y, type } = cursor;
74
63
  const point = view?.screen.convertPointToScreen(x, y);
75
64
  if (point) {
76
- const translateX = point.x + rect.x - 2;
77
- const translateY = point.y + rect.y - 18;
65
+ let translateX = point.x - 2;
66
+ let translateY = point.y - 18;
67
+ if (type === "app") {
68
+ const wrapperRect = this.cursorManager.wrapperRect;
69
+ if (wrapperRect) {
70
+ translateX = translateX + rect.x - wrapperRect.x;
71
+ translateY = translateY + rect.y - wrapperRect.y;
72
+ }
73
+ }
78
74
  if (point.x < 0 || point.x > rect.width || point.y < 0 || point.y > rect.height) {
79
- this.component?.$set({ visible: false });
75
+ this.component?.$set({ visible: false, x: translateX, y: translateY });
80
76
  } else {
81
77
  this.component?.$set({ visible: true, x: translateX, y: translateY });
82
78
  }
@@ -149,7 +145,7 @@ export class Cursor extends Base {
149
145
  private async createCursor() {
150
146
  if (this.member && this.wrapper) {
151
147
  this.component = new App({
152
- target: document.documentElement,
148
+ target: this.wrapper,
153
149
  props: this.initProps(),
154
150
  });
155
151
  }
@@ -189,10 +185,10 @@ export class Cursor extends Base {
189
185
  }
190
186
 
191
187
  public destroy() {
192
- this.disposer && this.disposer();
193
188
  if (this.component) {
194
189
  this.component.$destroy();
195
190
  }
191
+ this.manager.refresher?.remove(this.memberId);
196
192
  this.cursorManager.cursorInstances.delete(this.memberId);
197
193
  }
198
194
 
@@ -1,11 +1,13 @@
1
- import { Base } from '../Base';
2
- import { Cursor } from './Cursor';
3
- import { CursorState } from '../constants';
4
- import { compact, debounce, uniq } from 'lodash';
5
- import { Fields } from '../AttributesDelegate';
6
- import { onObjectInserted } from '../Utils/Reactive';
7
- import { WindowManager } from '../index';
8
- import type { PositionType } from "../AttributesDelegate";
1
+ import { autorun } from "white-web-sdk";
2
+ import { Base } from "../Base";
3
+ import { compact, debounce, get, uniq } from "lodash";
4
+ import { Cursor } from "./Cursor";
5
+ import { CursorState } from "../constants";
6
+ import { emitter, WindowManager } from "../index";
7
+ import { Fields } from "../AttributesDelegate";
8
+ import { onObjectInserted } from "../Utils/Reactive";
9
+ import { SideEffectManager } from "side-effect-manager";
10
+ import type { PositionType, Position } from "../AttributesDelegate";
9
11
  import type { Point, RoomMember, View } from "white-web-sdk";
10
12
  import type { AppManager } from "../AppManager";
11
13
 
@@ -18,28 +20,45 @@ export type MoveCursorParams = {
18
20
  uid: string;
19
21
  x: number;
20
22
  y: number;
21
- }
23
+ };
22
24
  export class CursorManager extends Base {
23
25
  public containerRect?: DOMRect;
24
26
  public wrapperRect?: DOMRect;
25
27
  public cursorInstances: Map<string, Cursor> = new Map();
26
28
  public roomMembers?: readonly RoomMember[];
27
29
  private mainViewElement?: HTMLDivElement;
30
+ private sideEffectManager = new SideEffectManager();
28
31
 
29
32
  constructor(private appManager: AppManager) {
30
33
  super(appManager);
31
34
  this.roomMembers = this.appManager.room?.state.roomMembers;
32
35
  const wrapper = WindowManager.wrapper;
33
36
  if (wrapper) {
34
- wrapper.addEventListener("mousemove", this.mouseMoveListener);
35
- wrapper.addEventListener("touchstart", this.touchMoveListener);
36
- wrapper.addEventListener("touchmove", this.touchMoveListener);
37
- wrapper.addEventListener("mouseleave", this.mouseLeaveListener);
38
- wrapper.addEventListener("touchend", this.mouseLeaveListener);
39
- this.initCursorAttributes();
40
- this.wrapperRect = wrapper.getBoundingClientRect();
41
- this.startReaction(wrapper);
37
+ this.setupWrapper(wrapper);
38
+ }
39
+ emitter.on("onReconnected", () => {
40
+ this.onReconnect();
41
+ });
42
+ }
43
+
44
+ public setupWrapper(wrapper: HTMLElement) {
45
+ if (this.manager.refresher?.hasReactor("cursors")) {
46
+ this.destroy();
42
47
  }
48
+ this.sideEffectManager.add(() => {
49
+ wrapper.addEventListener("pointerenter", this.mouseMoveListener);
50
+ wrapper.addEventListener("pointermove", this.mouseMoveListener);
51
+ wrapper.addEventListener("pointerleave", this.mouseLeaveListener);
52
+ return () => {
53
+ wrapper.removeEventListener("pointerenter", this.mouseMoveListener);
54
+ wrapper.removeEventListener("pointermove", this.mouseMoveListener);
55
+ wrapper.removeEventListener("pointerleave", this.mouseLeaveListener);
56
+ };
57
+ });
58
+
59
+ this.initCursorAttributes();
60
+ this.wrapperRect = wrapper.getBoundingClientRect();
61
+ this.startReaction(wrapper);
43
62
  }
44
63
 
45
64
  public setMainViewDivElement(div: HTMLDivElement) {
@@ -51,27 +70,25 @@ export class CursorManager extends Base {
51
70
  return onObjectInserted(this.cursors, () => {
52
71
  this.handleRoomMembersChange(wrapper);
53
72
  });
54
- })
73
+ });
55
74
  }
56
75
 
57
76
  private getUids = (members: readonly RoomMember[] | undefined) => {
58
77
  return compact(uniq(members?.map(member => member.payload?.uid)));
59
- }
78
+ };
60
79
 
61
80
  private handleRoomMembersChange = debounce((wrapper: HTMLElement) => {
62
81
  const uids = this.getUids(this.roomMembers);
63
82
  const cursors = Object.keys(this.cursors);
64
83
  if (uids?.length) {
65
84
  cursors.map(uid => {
66
- if (
67
- uids.includes(uid) &&
68
- !this.cursorInstances.has(uid)
69
- ) {
85
+ if (uids.includes(uid) && !this.cursorInstances.has(uid)) {
70
86
  if (uid === this.context.uid) {
71
87
  return;
72
88
  }
73
89
  const component = new Cursor(
74
90
  this.appManager,
91
+ this.addCursorChangeListener,
75
92
  this.cursors,
76
93
  uid,
77
94
  this,
@@ -79,7 +96,7 @@ export class CursorManager extends Base {
79
96
  );
80
97
  this.cursorInstances.set(uid, component);
81
98
  }
82
- })
99
+ });
83
100
  }
84
101
  }, 100);
85
102
 
@@ -99,13 +116,6 @@ export class CursorManager extends Base {
99
116
  this.updateCursor(this.getType(event), event.clientX, event.clientY);
100
117
  }, 5);
101
118
 
102
- private touchMoveListener = debounce((event: TouchEvent) => {
103
- if (event.touches.length === 1) {
104
- const touchEvent = event.touches[0];
105
- this.updateCursor(this.getType(touchEvent), touchEvent.clientX, touchEvent.clientY);
106
- }
107
- }, 5);
108
-
109
119
  private updateCursor(event: EventType, clientX: number, clientY: number) {
110
120
  if (this.wrapperRect && this.manager.canOperate) {
111
121
  const view = event.type === "main" ? this.appManager.mainView : this.focusView;
@@ -121,7 +131,11 @@ export class CursorManager extends Base {
121
131
  }
122
132
  }
123
133
 
124
- private getPoint = (view: View | undefined, clientX: number, clientY: number): Point | undefined => {
134
+ private getPoint = (
135
+ view: View | undefined,
136
+ clientX: number,
137
+ clientY: number
138
+ ): Point | undefined => {
125
139
  const rect = view?.divElement?.getBoundingClientRect();
126
140
  if (rect) {
127
141
  const point = view?.convertToPointInWorld({
@@ -130,7 +144,7 @@ export class CursorManager extends Base {
130
144
  });
131
145
  return point;
132
146
  }
133
- }
147
+ };
134
148
 
135
149
  /**
136
150
  * 因为窗口内框在不同分辨率下的大小不一样,所以这里通过来鼠标事件的 target 来判断是在主白板还是在 APP 中
@@ -140,7 +154,7 @@ export class CursorManager extends Base {
140
154
  const focusApp = this.appManager.focusApp;
141
155
  switch (target.parentElement) {
142
156
  case this.mainViewElement: {
143
- return { type: "main" };
157
+ return { type: "main" };
144
158
  }
145
159
  case focusApp?.view?.divElement: {
146
160
  return { type: "app" };
@@ -217,19 +231,41 @@ export class CursorManager extends Base {
217
231
  });
218
232
  }
219
233
 
220
- public destroy() {
221
- const wrapper = WindowManager.wrapper;
222
- if (wrapper) {
223
- wrapper.removeEventListener("mousemove", this.mouseMoveListener);
224
- wrapper.removeEventListener("touchstart", this.touchMoveListener);
225
- wrapper.removeEventListener("touchmove", this.touchMoveListener);
226
- wrapper.removeEventListener("mouseleave", this.mouseLeaveListener);
227
- wrapper.removeEventListener("touchend", this.mouseLeaveListener);
228
- }
234
+ public onReconnect() {
229
235
  if (this.cursorInstances.size) {
230
236
  this.cursorInstances.forEach(cursor => cursor.destroy());
231
237
  this.cursorInstances.clear();
232
238
  }
239
+ this.roomMembers = this.appManager.room?.state.roomMembers;
240
+ if (WindowManager.wrapper) {
241
+ this.handleRoomMembersChange(WindowManager.wrapper);
242
+ }
243
+ }
244
+
245
+ public addCursorChangeListener = (
246
+ uid: string,
247
+ callback: (position: Position, state: CursorState) => void
248
+ ) => {
249
+ this.manager.refresher?.add(uid, () => {
250
+ const disposer = autorun(() => {
251
+ const position = get(this.cursors, [uid, Fields.Position]);
252
+ const state = get(this.cursors, [uid, Fields.CursorState]);
253
+ if (position) {
254
+ callback(position, state);
255
+ }
256
+ });
257
+ return disposer;
258
+ });
259
+ };
260
+
261
+ public destroy() {
262
+ this.sideEffectManager.flushAll();
263
+ if (this.cursorInstances.size) {
264
+ this.cursorInstances.forEach(cursor => {
265
+ cursor.destroy();
266
+ });
267
+ this.cursorInstances.clear();
268
+ }
233
269
  this.manager.refresher?.remove("cursors");
234
270
  }
235
271
  }
package/src/Helper.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { WindowManager } from "./index";
2
+
3
+ export const setupWrapper = (
4
+ root: HTMLElement
5
+ ): {
6
+ playground: HTMLDivElement;
7
+ wrapper: HTMLDivElement;
8
+ sizer: HTMLDivElement;
9
+ mainViewElement: HTMLDivElement;
10
+ } => {
11
+ const playground = document.createElement("div");
12
+ playground.className = "netless-window-manager-playground";
13
+
14
+ const sizer = document.createElement("div");
15
+ sizer.className = "netless-window-manager-sizer";
16
+
17
+ const wrapper = document.createElement("div");
18
+ wrapper.className = "netless-window-manager-wrapper";
19
+
20
+ const mainViewElement = document.createElement("div");
21
+ mainViewElement.className = "netless-window-manager-main-view";
22
+
23
+ playground.appendChild(sizer);
24
+ sizer.appendChild(wrapper);
25
+ wrapper.appendChild(mainViewElement);
26
+ root.appendChild(playground);
27
+ WindowManager.wrapper = wrapper;
28
+
29
+ return { playground, wrapper, sizer, mainViewElement };
30
+ };
@@ -1,8 +1,12 @@
1
- import { isFunction } from 'lodash';
2
- import { RoomPhase } from 'white-web-sdk';
1
+ import { debounce, isFunction } from "lodash";
2
+ import { log } from "./Utils/log";
3
+ import { RoomPhase } from "white-web-sdk";
3
4
  import type { Room } from "white-web-sdk";
4
- import type { AppManager } from './AppManager';
5
- import { log } from './Utils/log';
5
+ import type { EmitterType } from "./index";
6
+
7
+ export type ReconnectRefresherContext = {
8
+ emitter: EmitterType;
9
+ };
6
10
 
7
11
  // 白板重连之后会刷新所有的对象,导致 listener 失效, 所以这里在重连之后重新对所有对象进行监听
8
12
  export class ReconnectRefresher {
@@ -11,20 +15,27 @@ export class ReconnectRefresher {
11
15
  private reactors: Map<string, any> = new Map();
12
16
  private disposers: Map<string, any> = new Map();
13
17
 
14
- constructor(room: Room | undefined, private manager: AppManager) {
18
+ constructor(private ctx: ReconnectRefresherContext) {}
19
+
20
+ public setRoom(room: Room | undefined) {
15
21
  this.room = room;
16
22
  this.phase = room?.phase;
23
+ room?.callbacks.off("onPhaseChanged", this.onPhaseChanged);
17
24
  room?.callbacks.on("onPhaseChanged", this.onPhaseChanged);
18
25
  }
19
26
 
27
+ public setContext(ctx: ReconnectRefresherContext) {
28
+ this.ctx = ctx;
29
+ }
30
+
20
31
  private onPhaseChanged = (phase: RoomPhase) => {
21
32
  if (phase === RoomPhase.Connected && this.phase === RoomPhase.Reconnecting) {
22
33
  this.onReconnected();
23
34
  }
24
35
  this.phase = phase;
25
- }
36
+ };
26
37
 
27
- private onReconnected = () => {
38
+ private onReconnected = debounce(() => {
28
39
  log("onReconnected refresh reactors");
29
40
  this.releaseDisposers();
30
41
  this.reactors.forEach((func, id) => {
@@ -32,15 +43,15 @@ export class ReconnectRefresher {
32
43
  this.disposers.set(id, func());
33
44
  }
34
45
  });
35
- this.manager.notifyReconnected();
36
- }
46
+ this.ctx.emitter.emit("onReconnected", undefined);
47
+ }, 3000);
37
48
 
38
49
  private releaseDisposers() {
39
50
  this.disposers.forEach(disposer => {
40
51
  if (isFunction(disposer)) {
41
52
  disposer();
42
53
  }
43
- })
54
+ });
44
55
  this.disposers.clear();
45
56
  }
46
57
 
@@ -64,6 +75,10 @@ export class ReconnectRefresher {
64
75
  }
65
76
  }
66
77
 
78
+ public hasReactor(id: string) {
79
+ return this.reactors.has(id);
80
+ }
81
+
67
82
  public destroy() {
68
83
  this.room?.callbacks.off("onPhaseChanged", this.onPhaseChanged);
69
84
  this.releaseDisposers();
@@ -1,7 +1,7 @@
1
- import { appRegister } from '../Register';
2
- import { debounce } from 'lodash';
3
- import { emitter } from '../index';
4
- import { v4 } from 'uuid';
1
+ import { appRegister } from "../Register";
2
+ import { debounce } from "lodash";
3
+ import { emitter } from "../index";
4
+ import { v4 } from "uuid";
5
5
  import type { PublicEvent } from "../index";
6
6
  import type { Displayer, ViewVisionMode, Room, View } from "white-web-sdk";
7
7
  import type Emittery from "emittery";
@@ -26,7 +26,21 @@ export const setScenePath = (room: Room | undefined, scenePath: string) => {
26
26
  room.setScenePath(scenePath);
27
27
  }
28
28
  }
29
- }
29
+ };
30
+
31
+ export const getScenePath = (
32
+ room: Room | undefined,
33
+ dir: string | undefined,
34
+ index: number
35
+ ): string | undefined => {
36
+ if (room && dir) {
37
+ const scenes = entireScenes(room);
38
+ const scene = scenes[dir]?.[index];
39
+ if (scene) {
40
+ return `${dir}/${scene.name}`;
41
+ }
42
+ }
43
+ };
30
44
 
31
45
  export const setViewMode = (view: View, mode: ViewVisionMode) => {
32
46
  if (!(view as any).didRelease && view.mode !== mode) {
@@ -44,7 +58,7 @@ export const emitError = (error: Error) => {
44
58
 
45
59
  export const addEmitterOnceListener = (event: any, listener: any) => {
46
60
  emitter.once(event).then(listener);
47
- }
61
+ };
48
62
 
49
63
  export const notifyMainViewModeChange = debounce(
50
64
  (callbacks: Emittery<PublicEvent>, mode: ViewVisionMode) => {
@@ -53,9 +67,10 @@ export const notifyMainViewModeChange = debounce(
53
67
  200
54
68
  );
55
69
 
56
- export const makeValidScenePath = (displayer: Displayer, scenePath: string) => {
57
- const scenes = displayer.entireScenes()[scenePath];
58
- const firstSceneName = scenes[0].name;
70
+ export const makeValidScenePath = (displayer: Displayer, scenePath: string, index = 0) => {
71
+ const scenes = entireScenes(displayer)[scenePath];
72
+ if (!scenes) return;
73
+ const firstSceneName = scenes[index].name;
59
74
  if (scenePath === "/") {
60
75
  return `/${firstSceneName}`;
61
76
  } else {
@@ -63,9 +78,13 @@ export const makeValidScenePath = (displayer: Displayer, scenePath: string) => {
63
78
  }
64
79
  };
65
80
 
81
+ export const entireScenes = (displayer: Displayer) => {
82
+ return displayer.entireScenes();
83
+ };
84
+
66
85
  export const isValidScenePath = (scenePath: string) => {
67
86
  return scenePath.startsWith("/");
68
- }
87
+ };
69
88
 
70
89
  export const ensureValidScenePath = (scenePath: string) => {
71
90
  if (scenePath.endsWith("/")) {
@@ -73,11 +92,14 @@ export const ensureValidScenePath = (scenePath: string) => {
73
92
  } else {
74
93
  return scenePath;
75
94
  }
76
- }
95
+ };
77
96
 
78
97
  export const getVersionNumber = (version: string) => {
79
- const versionString = version.split(".").map(s => s.padStart(2, "0")).join("");
98
+ const versionString = version
99
+ .split(".")
100
+ .map(s => s.padStart(2, "0"))
101
+ .join("");
80
102
  return parseInt(versionString);
81
103
  };
82
104
 
83
- export const wait = (time: number) => new Promise((resolve) => setTimeout(resolve, time));
105
+ export const wait = (time: number) => new Promise(resolve => setTimeout(resolve, time));
@@ -1,5 +1,6 @@
1
1
  import { listenUpdated, unlistenUpdated, reaction, UpdateEventKind } from "white-web-sdk";
2
2
  import type { AkkoObjectUpdatedProperty , AkkoObjectUpdatedListener } from "white-web-sdk";
3
+ import { isObject } from "lodash";
3
4
 
4
5
  // 兼容 13 和 14 版本 SDK
5
6
  export const onObjectByEvent = (event: UpdateEventKind) => {
@@ -30,7 +31,8 @@ export const onObjectByEvent = (event: UpdateEventKind) => {
30
31
 
31
32
  export const safeListenPropsUpdated = <T>(
32
33
  getProps: () => T,
33
- callback: AkkoObjectUpdatedListener<T>
34
+ callback: AkkoObjectUpdatedListener<T>,
35
+ onDestroyed?: (props: unknown) => void
34
36
  ) => {
35
37
  let disposeListenUpdated: (() => void) | null = null;
36
38
  const disposeReaction = reaction(
@@ -41,8 +43,12 @@ export const safeListenPropsUpdated = <T>(
41
43
  disposeListenUpdated = null;
42
44
  }
43
45
  const props = getProps();
44
- disposeListenUpdated = () => unlistenUpdated(props, callback);
45
- listenUpdated(props, callback);
46
+ if (isObject(props)) {
47
+ disposeListenUpdated = () => unlistenUpdated(props, callback);
48
+ listenUpdated(props, callback);
49
+ } else {
50
+ onDestroyed?.(props);
51
+ }
46
52
  },
47
53
  { fireImmediately: true }
48
54
  );
@@ -1,10 +1,10 @@
1
- import { emitter } from '../index';
2
- import { isPlayer } from 'white-web-sdk';
1
+ import { emitter } from "../index";
2
+ import { isPlayer } from "white-web-sdk";
3
+ import type { WindowManager } from '../index';
3
4
  import type { Camera, Room , Player , PlayerSeekingResult } from "white-web-sdk";
4
- import type { AppManager } from "../AppManager";
5
5
 
6
6
  // 修改多窗口状态下一些失效的方法实现到 manager 的 mainview 上, 降低迁移成本
7
- export const replaceRoomFunction = (room: Room, manager: AppManager) => {
7
+ export const replaceRoomFunction = (room: Room, manager: WindowManager) => {
8
8
  if (isPlayer(room)) {
9
9
  const player = room as unknown as Player;
10
10
  const originSeek = player.seekToProgressTime;
@@ -29,13 +29,28 @@ export const replaceRoomFunction = (room: Room, manager: AppManager) => {
29
29
  },
30
30
  });
31
31
 
32
+ Object.defineProperty(room, "canUndoSteps", {
33
+ get() {
34
+ return manager.mainView.canUndoSteps;
35
+ }
36
+ });
37
+
38
+ Object.defineProperty(room, "canRedoSteps", {
39
+ get() {
40
+ return manager.mainView.canRedoSteps;
41
+ }
42
+ });
43
+
32
44
  room.moveCamera = (camera: Camera) => manager.mainView.moveCamera(camera);
33
- room.moveCameraToContain = (...args) => manager.mainView.moveCameraToContain(...args);
45
+ room.moveCameraToContain = (...args) => manager.moveCameraToContain(...args);
34
46
  room.convertToPointInWorld = (...args) => manager.mainView.convertToPointInWorld(...args);
35
47
  room.setCameraBound = (...args) => manager.mainView.setCameraBound(...args);
36
48
  room.scenePreview = (...args) => manager.mainView.scenePreview(...args);
37
49
  room.fillSceneSnapshot = (...args) => manager.mainView.fillSceneSnapshot(...args);
38
50
  room.generateScreenshot = (...args) => manager.mainView.generateScreenshot(...args);
51
+ room.setMemberState = (...args) => manager.mainView.setMemberState(...args);
52
+ room.redo = () => manager.mainView.redo();
53
+ room.undo = () => manager.mainView.undo();
39
54
  }
40
55
 
41
56
  };
@@ -1,3 +1,4 @@
1
+
1
2
  export class AppCreateError extends Error {
2
3
  override message = "[WindowManager]: app duplicate exists and cannot be created again";
3
4
  }
@@ -28,4 +29,8 @@ export class BoxNotCreatedError extends Error {
28
29
 
29
30
  export class InvalidScenePath extends Error {
30
31
  override message = `[WindowManager]: ScenePath should start with "/"`;
31
- }
32
+ }
33
+
34
+ export class BoxManagerNotFoundError extends Error {
35
+ override message = "[WindowManager]: boxManager not found";
36
+ }