@netless/window-manager 0.4.0-canary.2 → 0.4.0-canary.20

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 (82) hide show
  1. package/.idea/inspectionProfiles/Project_Default.xml +7 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/.idea/window-manager.iml +12 -0
  5. package/.vscode/settings.json +1 -0
  6. package/CHANGELOG.md +29 -1
  7. package/README.md +1 -0
  8. package/dist/App/MagixEvent/index.d.ts +29 -0
  9. package/dist/App/Storage/StorageEvent.d.ts +8 -0
  10. package/dist/App/Storage/index.d.ts +39 -0
  11. package/dist/App/Storage/typings.d.ts +22 -0
  12. package/dist/App/Storage/utils.d.ts +5 -0
  13. package/dist/AppContext.d.ts +40 -16
  14. package/dist/AppListener.d.ts +1 -1
  15. package/dist/AppManager.d.ts +15 -11
  16. package/dist/AppProxy.d.ts +6 -5
  17. package/dist/AttributesDelegate.d.ts +2 -2
  18. package/dist/BoxManager.d.ts +6 -3
  19. package/dist/BuiltinApps.d.ts +5 -0
  20. package/dist/ContainerResizeObserver.d.ts +10 -0
  21. package/dist/Cursor/Cursor.d.ts +8 -11
  22. package/dist/Cursor/index.d.ts +5 -16
  23. package/dist/Helper.d.ts +6 -0
  24. package/dist/ReconnectRefresher.d.ts +0 -1
  25. package/dist/Register/storage.d.ts +5 -1
  26. package/dist/Utils/Common.d.ts +7 -2
  27. package/dist/Utils/Reactive.d.ts +1 -1
  28. package/dist/Utils/RoomHacker.d.ts +1 -1
  29. package/dist/{MainView.d.ts → View/MainView.d.ts} +5 -6
  30. package/dist/View/ViewManager.d.ts +13 -0
  31. package/dist/constants.d.ts +3 -7
  32. package/dist/index.d.ts +25 -10
  33. package/dist/index.es.js +41 -1
  34. package/dist/index.es.js.map +1 -1
  35. package/dist/index.umd.js +41 -1
  36. package/dist/index.umd.js.map +1 -1
  37. package/dist/style.css +1 -1
  38. package/dist/typings.d.ts +3 -2
  39. package/docs/api.md +36 -6
  40. package/docs/concept.md +9 -0
  41. package/package.json +7 -6
  42. package/src/App/MagixEvent/index.ts +68 -0
  43. package/src/App/Storage/StorageEvent.ts +21 -0
  44. package/src/App/Storage/index.ts +289 -0
  45. package/src/App/Storage/typings.ts +23 -0
  46. package/src/App/Storage/utils.ts +17 -0
  47. package/src/AppContext.ts +66 -24
  48. package/src/AppListener.ts +15 -14
  49. package/src/AppManager.ts +141 -63
  50. package/src/AppProxy.ts +50 -52
  51. package/src/AttributesDelegate.ts +2 -2
  52. package/src/BoxManager.ts +40 -24
  53. package/src/BuiltinApps.ts +23 -0
  54. package/src/ContainerResizeObserver.ts +62 -0
  55. package/src/Cursor/Cursor.ts +22 -36
  56. package/src/Cursor/index.ts +33 -139
  57. package/src/Helper.ts +30 -0
  58. package/src/ReconnectRefresher.ts +0 -5
  59. package/src/Register/index.ts +25 -16
  60. package/src/Register/loader.ts +1 -1
  61. package/src/Register/storage.ts +6 -1
  62. package/src/Utils/Common.ts +66 -13
  63. package/src/Utils/Reactive.ts +9 -3
  64. package/src/Utils/RoomHacker.ts +42 -13
  65. package/src/{MainView.ts → View/MainView.ts} +25 -36
  66. package/src/View/ViewManager.ts +52 -0
  67. package/src/constants.ts +3 -4
  68. package/src/index.ts +96 -72
  69. package/src/shim.d.ts +5 -0
  70. package/src/style.css +7 -1
  71. package/src/typings.ts +3 -2
  72. package/vite.config.js +8 -2
  73. package/dist/Base/Context.d.ts +0 -13
  74. package/dist/Base/index.d.ts +0 -7
  75. package/dist/Utils/CameraStore.d.ts +0 -15
  76. package/dist/ViewManager.d.ts +0 -29
  77. package/dist/sdk.d.ts +0 -14
  78. package/src/Base/Context.ts +0 -49
  79. package/src/Base/index.ts +0 -10
  80. package/src/Utils/CameraStore.ts +0 -72
  81. package/src/sdk.ts +0 -39
  82. package/src/viewManager.ts +0 -177
package/src/AppContext.ts CHANGED
@@ -6,18 +6,20 @@ import {
6
6
  unlistenDisposed,
7
7
  unlistenUpdated,
8
8
  toJS
9
- } from 'white-web-sdk';
9
+ } from 'white-web-sdk';
10
10
  import { BoxNotCreatedError } from './Utils/error';
11
- import type { Room, SceneDefinition, View } from "white-web-sdk";
11
+ import type { Room, SceneDefinition, View, EventListener as WhiteEventListener } from "white-web-sdk";
12
12
  import type { ReadonlyTeleBox } from "@netless/telebox-insider";
13
13
  import type Emittery from "emittery";
14
14
  import type { BoxManager } from "./BoxManager";
15
15
  import type { AppEmitterEvent } from "./index";
16
16
  import type { AppManager } from "./AppManager";
17
17
  import type { AppProxy } from "./AppProxy";
18
+ import { Storage } from './App/Storage';
19
+ import type { MagixEventAddListener, MagixEventDispatcher, MagixEventRemoveListener } from './App/MagixEvent';
18
20
 
19
- export class AppContext<TAttrs extends Record<string, any>, AppOptions = any> {
20
- public readonly emitter: Emittery<AppEmitterEvent<TAttrs>>;
21
+ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOptions = any> {
22
+ public readonly emitter: Emittery<AppEmitterEvent<TAttributes>>;
21
23
  public readonly mobxUtils = {
22
24
  autorun,
23
25
  reaction,
@@ -39,45 +41,45 @@ export class AppContext<TAttrs extends Record<string, any>, AppOptions = any> {
39
41
  private boxManager: BoxManager,
40
42
  public appId: string,
41
43
  private appProxy: AppProxy,
42
- private appOptions?: AppOptions | (() => AppOptions),
44
+ private appOptions?: TAppOptions | (() => TAppOptions),
43
45
  ) {
44
46
  this.emitter = appProxy.appEmitter;
45
47
  this.isAddApp = appProxy.isAddApp;
46
48
  }
47
49
 
48
- public getDisplayer() {
50
+ public getDisplayer = () => {
49
51
  return this.manager.displayer;
50
52
  }
51
53
 
52
- public getAttributes(): TAttrs | undefined {
54
+ /** @deprecated Use context.storage.state instead. */
55
+ public getAttributes = (): TAttributes | undefined => {
53
56
  return this.appProxy.attributes;
54
57
  }
55
58
 
56
- public getScenes(): SceneDefinition[] | undefined {
59
+ public getScenes = (): SceneDefinition[] | undefined => {
57
60
  const appAttr = this.store.getAppAttributes(this.appId);
58
61
  if (appAttr?.isDynamicPPT) {
59
- const appProxy = this.manager.appProxies.get(this.appId);
60
- if (appProxy) {
61
- return appProxy.scenes;
62
- }
62
+ return this.appProxy.scenes;
63
63
  } else {
64
64
  return appAttr?.options["scenes"];
65
65
  }
66
66
  }
67
67
 
68
- public getView(): View | undefined {
68
+ public getView = (): View | undefined => {
69
69
  return this.appProxy.view;
70
70
  }
71
71
 
72
- public getInitScenePath() {
72
+ public getInitScenePath = () => {
73
73
  return this.manager.getAppInitPath(this.appId);
74
74
  }
75
75
 
76
- public getIsWritable(): boolean {
76
+ /** Get App writable status. */
77
+ public getIsWritable = (): boolean => {
77
78
  return this.manager.canOperate;
78
79
  }
79
80
 
80
- public getBox(): ReadonlyTeleBox {
81
+ /** Get the App Window UI box. */
82
+ public getBox = (): ReadonlyTeleBox => {
81
83
  const box = this.boxManager.getBox(this.appId);
82
84
  if (box) {
83
85
  return box;
@@ -86,27 +88,30 @@ export class AppContext<TAttrs extends Record<string, any>, AppOptions = any> {
86
88
  }
87
89
  }
88
90
 
89
- public getRoom(): Room | undefined {
91
+ public getRoom = (): Room | undefined => {
90
92
  return this.manager.room;
91
93
  }
92
94
 
93
- public setAttributes(attributes: TAttrs) {
95
+ /** @deprecated Use context.storage.setState instead. */
96
+ public setAttributes = (attributes: TAttributes) => {
94
97
  this.manager.safeSetAttributes({ [this.appId]: attributes });
95
98
  }
96
99
 
97
- public updateAttributes(keys: string[], value: any) {
100
+ /** @deprecated Use context.storage.setState instead. */
101
+ public updateAttributes = (keys: string[], value: any) => {
98
102
  if (this.manager.attributes[this.appId]) {
99
103
  this.manager.safeUpdateAttributes([this.appId, ...keys], value);
100
104
  }
101
105
  }
102
106
 
103
- public async setScenePath(scenePath: string): Promise<void> {
107
+ public setScenePath = async (scenePath: string): Promise<void> => {
104
108
  if (!this.appProxy.box) return;
105
109
  this.appProxy.setFullPath(scenePath);
106
- this.appProxy.context.switchAppToWriter(this.appId);
110
+ // 兼容 15 版本 SDK 的切页
111
+ this.getRoom()?.setScenePath(scenePath);
107
112
  }
108
113
 
109
- public mountView(dom: HTMLDivElement): void {
114
+ public mountView = (dom: HTMLDivElement): void => {
110
115
  const view = this.getView();
111
116
  if (view) {
112
117
  view.divElement = dom;
@@ -117,7 +122,44 @@ export class AppContext<TAttrs extends Record<string, any>, AppOptions = any> {
117
122
  }
118
123
  }
119
124
 
120
- public getAppOptions(): AppOptions | undefined {
121
- return typeof this.appOptions === 'function' ? (this.appOptions as () => AppOptions)() : this.appOptions
125
+ /** Get the local App options. */
126
+ public getAppOptions = (): TAppOptions | undefined => {
127
+ return typeof this.appOptions === 'function' ? (this.appOptions as () => TAppOptions)() : this.appOptions
122
128
  }
129
+
130
+ private _storage?: Storage<TAttributes>
131
+
132
+ /** Main Storage for attributes. */
133
+ public get storage(): Storage<TAttributes> {
134
+ if (!this._storage) {
135
+ this._storage = new Storage(this);
136
+ }
137
+ return this._storage;
138
+ }
139
+
140
+ /**
141
+ * Create separated storages for flexible state management.
142
+ * @param storeId Namespace for the storage. Storages of the same namespace share the same data.
143
+ * @param defaultState Default state for initial storage creation.
144
+ * @returns
145
+ */
146
+ public createStorage = <TState>(storeId: string, defaultState?: TState): Storage<TState> => {
147
+ const storage = new Storage(this, storeId, defaultState);
148
+ this.emitter.on("destroy", () => {
149
+ storage.destroy();
150
+ });
151
+ return storage;
152
+ }
153
+
154
+ /** Dispatch events to other clients (and self). */
155
+ public dispatchMagixEvent: MagixEventDispatcher<TMagixEventPayloads> = (this.manager.displayer as Room).dispatchMagixEvent.bind(this.manager.displayer)
156
+
157
+ /** Listen to events from others clients (and self messages). */
158
+ public addMagixEventListener: MagixEventAddListener<TMagixEventPayloads> = (event, handler, options) => {
159
+ this.manager.displayer.addMagixEventListener(event, handler as WhiteEventListener, options);
160
+ return () => this.manager.displayer.removeMagixEventListener(event, handler as WhiteEventListener);
161
+ }
162
+
163
+ /** Remove a Magix event listener. */
164
+ public removeMagixEventListener = this.manager.displayer.removeMagixEventListener.bind(this.manager.displayer) as MagixEventRemoveListener<TMagixEventPayloads>
123
165
  }
@@ -1,9 +1,9 @@
1
- import { callbacks } from './index';
2
- import { Events, MagixEventName } from './constants';
1
+ import { callbacks, emitter } from "./index";
2
+ import { Events, MagixEventName } from "./constants";
3
+ import { setViewFocusScenePath } from "./Utils/Common";
3
4
  import type { Event } from "white-web-sdk";
4
5
  import type { AppManager } from "./AppManager";
5
6
  import type { TeleBoxState } from "@netless/telebox-insider";
6
- import { setViewFocusScenePath } from './Utils/Common';
7
7
 
8
8
  export class AppListeners {
9
9
  private displayer = this.manager.displayer;
@@ -34,10 +34,6 @@ export class AppListeners {
34
34
  this.appResizeHandler(data.payload);
35
35
  break;
36
36
  }
37
- case Events.SwitchViewsToFreedom: {
38
- this.switchViewsToFreedomHandler();
39
- break;
40
- }
41
37
  case Events.AppBoxStateChange: {
42
38
  this.boxStateChangeHandler(data.payload);
43
39
  break;
@@ -50,6 +46,10 @@ export class AppListeners {
50
46
  this.moveCameraToContainHandler(data.payload);
51
47
  break;
52
48
  }
49
+ case Events.CursorMove: {
50
+ this.cursorMoveHandler(data.payload);
51
+ break;
52
+ }
53
53
  default:
54
54
  break;
55
55
  }
@@ -65,19 +65,20 @@ export class AppListeners {
65
65
  this.manager.room?.refreshViewSize();
66
66
  };
67
67
 
68
- private switchViewsToFreedomHandler = () => {
69
- this.manager.viewManager.freedomAllViews();
70
- };
71
-
72
68
  private boxStateChangeHandler = (state: TeleBoxState) => {
73
69
  callbacks.emit("boxStateChange", state);
74
- }
70
+ };
75
71
 
76
72
  private setMainViewScenePathHandler = ({ nextScenePath }: { nextScenePath: string }) => {
77
73
  setViewFocusScenePath(this.manager.mainView, nextScenePath);
78
- }
74
+ callbacks.emit("mainViewScenePathChange", nextScenePath);
75
+ };
79
76
 
80
77
  private moveCameraToContainHandler = (payload: any) => {
81
78
  this.manager.mainView.moveCameraToContain(payload);
82
- }
79
+ };
80
+
81
+ private cursorMoveHandler = (payload: any) => {
82
+ emitter.emit("cursorMove", payload);
83
+ };
83
84
  }
package/src/AppManager.ts CHANGED
@@ -2,17 +2,22 @@ import pRetry from "p-retry";
2
2
  import { AppAttributes, AppStatus, Events, MagixEventName } from "./constants";
3
3
  import { AppListeners } from "./AppListener";
4
4
  import { AppProxy } from "./AppProxy";
5
- import { autorun, isPlayer, isRoom, ScenePathType, ViewVisionMode } from "white-web-sdk";
6
- import { callbacks, emitter, WindowManager } from "./index";
7
- import { CameraStore } from "./Utils/CameraStore";
8
- import { genAppId, makeValidScenePath, setScenePath } from "./Utils/Common";
5
+ import { autorun, isPlayer, isRoom, ScenePathType } from "white-web-sdk";
6
+ import { callbacks, emitter, WindowManager, reconnectRefresher } from "./index";
7
+ import {
8
+ entireScenes,
9
+ genAppId,
10
+ makeValidScenePath,
11
+ parseSceneDir,
12
+ setScenePath,
13
+ setViewFocusScenePath,
14
+ } from "./Utils/Common";
9
15
  import { log } from "./Utils/log";
10
- import { MainViewProxy } from "./MainView";
16
+ import { MainViewProxy } from "./View/MainView";
11
17
  import { onObjectRemoved, safeListenPropsUpdated } from "./Utils/Reactive";
12
- import { reconnectRefresher } from "./ReconnectRefresher";
13
- import { sortBy } from "lodash";
18
+ import { get, isInteger, sortBy } from "lodash";
14
19
  import { store } from "./AttributesDelegate";
15
- import { ViewManager } from "./ViewManager";
20
+ import { ViewManager } from "./View/ViewManager";
16
21
  import type { ReconnectRefresher } from "./ReconnectRefresher";
17
22
  import type { BoxManager } from "./BoxManager";
18
23
  import type { Displayer, DisplayerState, Room } from "white-web-sdk";
@@ -20,7 +25,6 @@ import type { AddAppParams, BaseInsertParams, TeleBoxRect, EmitterEvent } from "
20
25
 
21
26
  export class AppManager {
22
27
  public displayer: Displayer;
23
- public cameraStore: CameraStore;
24
28
  public viewManager: ViewManager;
25
29
  public appProxies: Map<string, AppProxy> = new Map();
26
30
  public appStatus: Map<string, AppStatus> = new Map();
@@ -32,6 +36,9 @@ export class AppManager {
32
36
  private appListeners: AppListeners;
33
37
  public boxManager?: BoxManager;
34
38
 
39
+ private _prevSceneIndex: number | undefined;
40
+ private _prevFocused: string | undefined;
41
+
35
42
  constructor(public windowManger: WindowManager) {
36
43
  this.displayer = windowManger.displayer;
37
44
  this.store.setContext({
@@ -39,9 +46,8 @@ export class AppManager {
39
46
  safeSetAttributes: attributes => this.safeSetAttributes(attributes),
40
47
  safeUpdateAttributes: (keys, val) => this.safeUpdateAttributes(keys, val),
41
48
  });
42
- this.cameraStore = new CameraStore();
43
49
  this.mainViewProxy = new MainViewProxy(this);
44
- this.viewManager = new ViewManager(this);
50
+ this.viewManager = new ViewManager(this.displayer);
45
51
  this.appListeners = new AppListeners(this);
46
52
  this.displayer.callbacks.on(this.eventName, this.displayerStateListener);
47
53
  this.appListeners.addListeners();
@@ -61,6 +67,48 @@ export class AppManager {
61
67
  this.onAppDelete(this.attributes.apps);
62
68
  });
63
69
  }
70
+ emitter.on("removeScenes", scenePath => {
71
+ if (scenePath === "/") {
72
+ this.setMainViewScenePath("/");
73
+ return;
74
+ }
75
+ const mainViewScenePath = this.store.getMainViewScenePath();
76
+ if (this.room && mainViewScenePath) {
77
+ if (mainViewScenePath === scenePath) {
78
+ this.setMainViewScenePath("/");
79
+ }
80
+ }
81
+ });
82
+ }
83
+
84
+ private get eventName() {
85
+ return isRoom(this.displayer) ? "onRoomStateChanged" : "onPlayerStateChanged";
86
+ }
87
+
88
+ public get attributes() {
89
+ return this.windowManger.attributes;
90
+ }
91
+
92
+ public get canOperate() {
93
+ return this.windowManger.canOperate;
94
+ }
95
+
96
+ public get room() {
97
+ return isRoom(this.displayer) ? (this.displayer as Room) : undefined;
98
+ }
99
+
100
+ public get mainView() {
101
+ return this.mainViewProxy.view;
102
+ }
103
+
104
+ public get focusApp() {
105
+ if (this.store.focus) {
106
+ return this.appProxies.get(this.store.focus);
107
+ }
108
+ }
109
+
110
+ public get uid() {
111
+ return this.room?.uid || "";
64
112
  }
65
113
 
66
114
  private async onCreated() {
@@ -99,6 +147,25 @@ export class AppManager {
99
147
  }
100
148
  });
101
149
  });
150
+ this.refresher?.add("mainViewIndex", () => {
151
+ return autorun(() => {
152
+ const mainSceneIndex = get(this.attributes, "_mainSceneIndex");
153
+ if (mainSceneIndex !== undefined && this._prevSceneIndex !== mainSceneIndex) {
154
+ callbacks.emit("mainViewSceneIndexChange", mainSceneIndex);
155
+ this._prevSceneIndex = mainSceneIndex;
156
+ }
157
+ });
158
+ });
159
+ this.refresher?.add("focusedChange", () => {
160
+ return autorun(() => {
161
+ const focused = get(this.attributes, "focus");
162
+ if (this._prevFocused !== focused) {
163
+ this.boxManager?.focusBox({ appId: focused });
164
+ callbacks.emit("focusedChange", focused);
165
+ this._prevFocused = focused;
166
+ }
167
+ });
168
+ });
102
169
  if (!this.attributes.apps || Object.keys(this.attributes.apps).length === 0) {
103
170
  const mainScenePath = this.store.getMainViewScenePath();
104
171
  if (!mainScenePath) return;
@@ -109,6 +176,7 @@ export class AppManager {
109
176
  }
110
177
  this.displayerWritableListener(!this.room?.isWritable);
111
178
  this.displayer.callbacks.on("onEnableWriteNowChanged", this.displayerWritableListener);
179
+ this._prevFocused = this.attributes.focus;
112
180
  }
113
181
 
114
182
  /**
@@ -189,15 +257,19 @@ export class AppManager {
189
257
  mainView.disableCameraTransform = disableCameraTransform;
190
258
  mainView.divElement = divElement;
191
259
  if (!mainView.focusScenePath) {
192
- this.store.setMainViewFocusPath(mainView);
193
- }
194
- if (this.store.focus === undefined && mainView.mode !== ViewVisionMode.Writable) {
195
- this.viewManager.switchMainViewToWriter();
260
+ this.setMainViewFocusPath();
196
261
  }
197
- this.mainViewProxy.addMainViewListener();
198
262
  emitter.emit("mainViewMounted");
199
263
  }
200
264
 
265
+ public setMainViewFocusPath(scenePath?: string) {
266
+ const focusScenePath = scenePath || this.store.getMainViewScenePath();
267
+ if (focusScenePath) {
268
+ const view = setViewFocusScenePath(this.mainView, focusScenePath);
269
+ return view?.focusScenePath === focusScenePath;
270
+ }
271
+ }
272
+
201
273
  public async addApp(params: AddAppParams, isDynamicPPT: boolean): Promise<string | undefined> {
202
274
  log("addApp", params);
203
275
  const { appId, needFocus } = await this.beforeAddApp(params, isDynamicPPT);
@@ -273,17 +345,13 @@ export class AppManager {
273
345
  }
274
346
  });
275
347
  }
276
- if (state.roomMembers) {
277
- this.windowManger.cursorManager?.setRoomMembers(state.roomMembers);
278
- this.windowManger.cursorManager?.cleanMemberAttributes(state.roomMembers);
279
- }
280
348
  this.appProxies.forEach(appProxy => {
281
349
  appProxy.appEmitter.emit("roomStateChange", state);
282
350
  });
283
351
  emitter.emit("observerIdChange", this.displayer.observerId);
284
352
  };
285
353
 
286
- private displayerWritableListener = (isReadonly: boolean) => {
354
+ public displayerWritableListener = (isReadonly: boolean) => {
287
355
  const isWritable = !isReadonly;
288
356
  const isManualWritable =
289
357
  this.windowManger.readonly === undefined || this.windowManger.readonly === false;
@@ -296,41 +364,15 @@ export class AppManager {
296
364
  appProxy.emitAppIsWritableChange();
297
365
  });
298
366
  if (isWritable === true) {
299
- if (!this.store.focus) {
300
- this.mainViewProxy.switchViewModeToWriter();
301
- }
302
367
  this.mainView.disableCameraTransform = false;
368
+ if (this.room && this.room.disableSerialization === true) {
369
+ this.room.disableSerialization = false;
370
+ }
303
371
  } else {
304
372
  this.mainView.disableCameraTransform = true;
305
373
  }
306
374
  };
307
375
 
308
- private get eventName() {
309
- return isRoom(this.displayer) ? "onRoomStateChanged" : "onPlayerStateChanged";
310
- }
311
-
312
- public get attributes() {
313
- return this.windowManger.attributes;
314
- }
315
-
316
- public get canOperate() {
317
- return this.windowManger.canOperate;
318
- }
319
-
320
- public get room() {
321
- return isRoom(this.displayer) ? (this.displayer as Room) : undefined;
322
- }
323
-
324
- public get mainView() {
325
- return this.mainViewProxy.view;
326
- }
327
-
328
- public get focusApp() {
329
- if (this.store.focus) {
330
- return this.appProxies.get(this.store.focus);
331
- }
332
- }
333
-
334
376
  public safeSetAttributes(attributes: any) {
335
377
  this.windowManger.safeSetAttributes(attributes);
336
378
  }
@@ -348,28 +390,57 @@ export class AppManager {
348
390
  await this._setMainViewScenePath(scenePath);
349
391
  } else if (scenePathType === ScenePathType.Dir) {
350
392
  const validScenePath = makeValidScenePath(this.displayer, scenePath);
351
- await this._setMainViewScenePath(validScenePath);
393
+ if (validScenePath) {
394
+ await this._setMainViewScenePath(validScenePath);
395
+ }
352
396
  }
353
397
  }
354
398
  }
355
399
 
356
400
  private async _setMainViewScenePath(scenePath: string) {
357
- this.safeSetAttributes({ _mainScenePath: scenePath });
358
- await this.viewManager.switchMainViewToWriter();
359
- setScenePath(this.room, scenePath);
360
- this.store.setMainViewFocusPath(this.mainView);
361
- this.dispatchInternalEvent(Events.SetMainViewScenePath, { nextScenePath: scenePath });
401
+ const success = this.setMainViewFocusPath(scenePath);
402
+ if (success) {
403
+ this.safeSetAttributes({ _mainScenePath: scenePath });
404
+ this.store.setMainViewFocusPath(this.mainView);
405
+ this.updateSceneIndex();
406
+ this.dispatchInternalEvent(Events.SetMainViewScenePath, { nextScenePath: scenePath });
407
+ }
362
408
  }
363
409
 
410
+ private updateSceneIndex = () => {
411
+ const scenePath = this.store.getMainViewScenePath() as string;
412
+ const sceneDir = parseSceneDir(scenePath);
413
+ const scenes = entireScenes(this.displayer)[sceneDir];
414
+ if (scenes.length) {
415
+ // "/ppt3/1" -> "1"
416
+ const pageName = scenePath.replace(sceneDir, "").replace("/", "");
417
+ const index = scenes.findIndex(scene => scene.name === pageName);
418
+ if (isInteger(index) && index >= 0) {
419
+ this.safeSetAttributes({ _mainSceneIndex: index });
420
+ }
421
+ }
422
+ };
423
+
364
424
  public async setMainViewSceneIndex(index: number) {
365
425
  if (this.room) {
366
- this.safeSetAttributes({ _mainSceneIndex: index });
367
- await this.viewManager.switchMainViewToWriter();
368
- this.room.setSceneIndex(index);
369
- const nextScenePath = this.room.state.sceneState.scenePath;
370
- this.store.setMainViewScenePath(nextScenePath);
371
- this.store.setMainViewFocusPath(this.mainView);
372
- this.dispatchInternalEvent(Events.SetMainViewScenePath, { nextScenePath });
426
+ if (this.store.getMainViewSceneIndex() === index) return;
427
+ const mainViewScenePath = this.store.getMainViewScenePath() as string;
428
+ if (mainViewScenePath) {
429
+ const sceneDir = parseSceneDir(mainViewScenePath);
430
+ const scenePath = makeValidScenePath(this.displayer, sceneDir, index);
431
+ if (scenePath) {
432
+ const success = this.setMainViewFocusPath(scenePath);
433
+ if (success) {
434
+ this.store.setMainViewScenePath(scenePath);
435
+ this.safeSetAttributes({ _mainSceneIndex: index });
436
+ this.dispatchInternalEvent(Events.SetMainViewScenePath, {
437
+ nextScenePath: scenePath,
438
+ });
439
+ }
440
+ } else {
441
+ throw new Error(`[WindowManager]: ${sceneDir}: ${index} not valid index`);
442
+ }
443
+ }
373
444
  }
374
445
  }
375
446
 
@@ -440,6 +511,7 @@ export class AppManager {
440
511
  const reconnected = appProxies.map(appProxy => {
441
512
  return appProxy.onReconnected();
442
513
  });
514
+ this.mainViewProxy.onReconnect();
443
515
  await Promise.all(reconnected);
444
516
  }
445
517
 
@@ -456,6 +528,11 @@ export class AppManager {
456
528
  });
457
529
  }
458
530
 
531
+ public findMemberByUid = (uid: string) => {
532
+ const roomMembers = this.room?.state.roomMembers;
533
+ return roomMembers?.find(member => member.payload?.uid === uid);
534
+ };
535
+
459
536
  public destroy() {
460
537
  this.displayer.callbacks.off(this.eventName, this.displayerStateListener);
461
538
  this.displayer.callbacks.off("onEnableWriteNowChanged", this.displayerWritableListener);
@@ -472,5 +549,6 @@ export class AppManager {
472
549
  this.refresher?.destroy();
473
550
  this.mainViewProxy.destroy();
474
551
  callbacks.clearListeners();
552
+ this._prevSceneIndex = undefined;
475
553
  }
476
554
  }