@netless/window-manager 0.4.0-canary.9 → 0.4.2

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 (74) 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 +43 -2
  7. package/README.md +3 -0
  8. package/dist/App/MagixEvent/index.d.ts +29 -0
  9. package/dist/App/Storage/index.d.ts +19 -6
  10. package/dist/App/Storage/typings.d.ts +1 -0
  11. package/dist/AppContext.d.ts +39 -17
  12. package/dist/AppListener.d.ts +2 -0
  13. package/dist/AppManager.d.ts +22 -8
  14. package/dist/AppProxy.d.ts +5 -5
  15. package/dist/AttributesDelegate.d.ts +2 -2
  16. package/dist/BoxManager.d.ts +6 -4
  17. package/dist/BuiltinApps.d.ts +0 -1
  18. package/dist/Cursor/Cursor.d.ts +10 -12
  19. package/dist/Cursor/index.d.ts +6 -16
  20. package/dist/Helper.d.ts +1 -0
  21. package/dist/Register/index.d.ts +5 -0
  22. package/dist/Register/storage.d.ts +5 -1
  23. package/dist/Utils/AppCreateQueue.d.ts +11 -0
  24. package/dist/Utils/Common.d.ts +4 -1
  25. package/dist/Utils/RoomHacker.d.ts +3 -3
  26. package/dist/View/MainView.d.ts +4 -3
  27. package/dist/constants.d.ts +5 -2
  28. package/dist/index.d.ts +32 -6
  29. package/dist/index.es.js +41 -1
  30. package/dist/index.es.js.map +1 -1
  31. package/dist/index.umd.js +41 -1
  32. package/dist/index.umd.js.map +1 -1
  33. package/dist/style.css +1 -1
  34. package/dist/typings.d.ts +2 -2
  35. package/docs/advanced.md +53 -0
  36. package/docs/api.md +79 -6
  37. package/docs/concept.md +9 -0
  38. package/docs/replay.md +40 -0
  39. package/package.json +8 -9
  40. package/src/App/MagixEvent/index.ts +68 -0
  41. package/src/App/Storage/index.ts +89 -43
  42. package/src/App/Storage/typings.ts +4 -2
  43. package/src/AppContext.ts +61 -24
  44. package/src/AppListener.ts +27 -8
  45. package/src/AppManager.ts +231 -70
  46. package/src/AppProxy.ts +40 -29
  47. package/src/AttributesDelegate.ts +2 -2
  48. package/src/BoxManager.ts +33 -19
  49. package/src/BuiltinApps.ts +0 -1
  50. package/src/ContainerResizeObserver.ts +3 -3
  51. package/src/Cursor/Cursor.svelte +25 -21
  52. package/src/Cursor/Cursor.ts +25 -38
  53. package/src/Cursor/icons.ts +2 -0
  54. package/src/Cursor/index.ts +45 -139
  55. package/src/Helper.ts +12 -1
  56. package/src/Register/index.ts +32 -17
  57. package/src/Register/loader.ts +28 -13
  58. package/src/Register/storage.ts +6 -1
  59. package/src/Utils/AppCreateQueue.ts +54 -0
  60. package/src/Utils/Common.ts +35 -2
  61. package/src/Utils/RoomHacker.ts +33 -18
  62. package/src/View/MainView.ts +19 -12
  63. package/src/View/ViewManager.ts +1 -2
  64. package/src/constants.ts +6 -2
  65. package/src/image/laser-pointer-cursor.svg +17 -0
  66. package/src/index.ts +150 -33
  67. package/src/shim.d.ts +2 -1
  68. package/src/style.css +6 -1
  69. package/src/typings.ts +2 -2
  70. package/vite.config.js +7 -4
  71. package/dist/Base/Context.d.ts +0 -12
  72. package/dist/Base/index.d.ts +0 -7
  73. package/src/Base/Context.ts +0 -45
  74. package/src/Base/index.ts +0 -10
package/src/AppContext.ts CHANGED
@@ -6,9 +6,9 @@ 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";
@@ -16,9 +16,10 @@ import type { AppEmitterEvent } from "./index";
16
16
  import type { AppManager } from "./AppManager";
17
17
  import type { AppProxy } from "./AppProxy";
18
18
  import { Storage } from './App/Storage';
19
+ import type { MagixEventAddListener, MagixEventDispatcher, MagixEventRemoveListener } from './App/MagixEvent';
19
20
 
20
- export class AppContext<TAttrs extends Record<string, any> = any, AppOptions = any> {
21
- public readonly emitter: Emittery<AppEmitterEvent<TAttrs>>;
21
+ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOptions = any> {
22
+ public readonly emitter: Emittery<AppEmitterEvent<TAttributes>>;
22
23
  public readonly mobxUtils = {
23
24
  autorun,
24
25
  reaction,
@@ -40,45 +41,45 @@ export class AppContext<TAttrs extends Record<string, any> = any, AppOptions = a
40
41
  private boxManager: BoxManager,
41
42
  public appId: string,
42
43
  private appProxy: AppProxy,
43
- private appOptions?: AppOptions | (() => AppOptions),
44
+ private appOptions?: TAppOptions | (() => TAppOptions),
44
45
  ) {
45
46
  this.emitter = appProxy.appEmitter;
46
47
  this.isAddApp = appProxy.isAddApp;
47
48
  }
48
49
 
49
- public getDisplayer() {
50
+ public getDisplayer = () => {
50
51
  return this.manager.displayer;
51
52
  }
52
53
 
53
- public getAttributes(): TAttrs | undefined {
54
+ /** @deprecated Use context.storage.state instead. */
55
+ public getAttributes = (): TAttributes | undefined => {
54
56
  return this.appProxy.attributes;
55
57
  }
56
58
 
57
- public getScenes(): SceneDefinition[] | undefined {
59
+ public getScenes = (): SceneDefinition[] | undefined => {
58
60
  const appAttr = this.store.getAppAttributes(this.appId);
59
61
  if (appAttr?.isDynamicPPT) {
60
- const appProxy = this.manager.appProxies.get(this.appId);
61
- if (appProxy) {
62
- return appProxy.scenes;
63
- }
62
+ return this.appProxy.scenes;
64
63
  } else {
65
64
  return appAttr?.options["scenes"];
66
65
  }
67
66
  }
68
67
 
69
- public getView(): View | undefined {
68
+ public getView = (): View | undefined => {
70
69
  return this.appProxy.view;
71
70
  }
72
71
 
73
- public getInitScenePath() {
72
+ public getInitScenePath = () => {
74
73
  return this.manager.getAppInitPath(this.appId);
75
74
  }
76
75
 
77
- public getIsWritable(): boolean {
76
+ /** Get App writable status. */
77
+ public getIsWritable = (): boolean => {
78
78
  return this.manager.canOperate;
79
79
  }
80
80
 
81
- public getBox(): ReadonlyTeleBox {
81
+ /** Get the App Window UI box. */
82
+ public getBox = (): ReadonlyTeleBox => {
82
83
  const box = this.boxManager.getBox(this.appId);
83
84
  if (box) {
84
85
  return box;
@@ -87,26 +88,30 @@ export class AppContext<TAttrs extends Record<string, any> = any, AppOptions = a
87
88
  }
88
89
  }
89
90
 
90
- public getRoom(): Room | undefined {
91
+ public getRoom = (): Room | undefined => {
91
92
  return this.manager.room;
92
93
  }
93
94
 
94
- public setAttributes(attributes: TAttrs) {
95
+ /** @deprecated Use context.storage.setState instead. */
96
+ public setAttributes = (attributes: TAttributes) => {
95
97
  this.manager.safeSetAttributes({ [this.appId]: attributes });
96
98
  }
97
99
 
98
- public updateAttributes(keys: string[], value: any) {
100
+ /** @deprecated Use context.storage.setState instead. */
101
+ public updateAttributes = (keys: string[], value: any) => {
99
102
  if (this.manager.attributes[this.appId]) {
100
103
  this.manager.safeUpdateAttributes([this.appId, ...keys], value);
101
104
  }
102
105
  }
103
106
 
104
- public async setScenePath(scenePath: string): Promise<void> {
107
+ public setScenePath = async (scenePath: string): Promise<void> => {
105
108
  if (!this.appProxy.box) return;
106
109
  this.appProxy.setFullPath(scenePath);
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,15 +122,47 @@ export class AppContext<TAttrs extends Record<string, any> = any, AppOptions = a
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
  }
123
129
 
124
- public createStorage<TState>(storeId: string, defaultState?: TState): Storage<TState> {
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> => {
125
147
  const storage = new Storage(this, storeId, defaultState);
126
148
  this.emitter.on("destroy", () => {
127
149
  storage.destroy();
128
150
  });
129
151
  return storage;
130
152
  }
153
+
154
+ /** Dispatch events to other clients (and self). */
155
+ public dispatchMagixEvent: MagixEventDispatcher<TMagixEventPayloads> = (...args) => {
156
+ // can't dispatch events on replay mode
157
+ return this.manager.room?.dispatchMagixEvent(...args);
158
+ }
159
+
160
+ /** Listen to events from others clients (and self messages). */
161
+ public addMagixEventListener: MagixEventAddListener<TMagixEventPayloads> = (event, handler, options) => {
162
+ this.manager.displayer.addMagixEventListener(event, handler as WhiteEventListener, options);
163
+ return () => this.manager.displayer.removeMagixEventListener(event, handler as WhiteEventListener);
164
+ }
165
+
166
+ /** Remove a Magix event listener. */
167
+ public removeMagixEventListener = this.manager.displayer.removeMagixEventListener.bind(this.manager.displayer) as MagixEventRemoveListener<TMagixEventPayloads>
131
168
  }
@@ -1,10 +1,10 @@
1
- import { callbacks } from './index';
2
- import { Events, MagixEventName } from './constants';
3
- import type { Event } from "white-web-sdk";
1
+ import { callbacks, emitter } from "./index";
2
+ import { Events, MagixEventName } from "./constants";
3
+ import { isEqual, omit } from "lodash";
4
+ import { setViewFocusScenePath } from "./Utils/Common";
5
+ import type { AnimationMode, Camera, Event } from "white-web-sdk";
4
6
  import type { AppManager } from "./AppManager";
5
7
  import type { TeleBoxState } from "@netless/telebox-insider";
6
- import { setViewFocusScenePath } from './Utils/Common';
7
-
8
8
  export class AppListeners {
9
9
  private displayer = this.manager.displayer;
10
10
 
@@ -42,10 +42,18 @@ export class AppListeners {
42
42
  this.setMainViewScenePathHandler(data.payload);
43
43
  break;
44
44
  }
45
+ case Events.MoveCamera: {
46
+ this.moveCameraHandler(data.payload);
47
+ break;
48
+ }
45
49
  case Events.MoveCameraToContain: {
46
50
  this.moveCameraToContainHandler(data.payload);
47
51
  break;
48
52
  }
53
+ case Events.CursorMove: {
54
+ this.cursorMoveHandler(data.payload);
55
+ break;
56
+ }
49
57
  default:
50
58
  break;
51
59
  }
@@ -63,14 +71,25 @@ export class AppListeners {
63
71
 
64
72
  private boxStateChangeHandler = (state: TeleBoxState) => {
65
73
  callbacks.emit("boxStateChange", state);
66
- }
74
+ };
67
75
 
68
76
  private setMainViewScenePathHandler = ({ nextScenePath }: { nextScenePath: string }) => {
69
77
  setViewFocusScenePath(this.manager.mainView, nextScenePath);
70
78
  callbacks.emit("mainViewScenePathChange", nextScenePath);
71
- }
79
+ };
80
+
81
+ private moveCameraHandler = (
82
+ payload: Camera & { animationMode?: AnimationMode | undefined }
83
+ ) => {
84
+ if (isEqual(omit(payload, ["animationMode"]), { ...this.manager.mainView.camera })) return;
85
+ this.manager.mainView.moveCamera(payload);
86
+ };
72
87
 
73
88
  private moveCameraToContainHandler = (payload: any) => {
74
89
  this.manager.mainView.moveCameraToContain(payload);
75
- }
90
+ };
91
+
92
+ private cursorMoveHandler = (payload: any) => {
93
+ emitter.emit("cursorMove", payload);
94
+ };
76
95
  }
package/src/AppManager.ts CHANGED
@@ -1,19 +1,27 @@
1
- import pRetry from "p-retry";
2
- import { AppAttributes, AppStatus, Events, MagixEventName } from "./constants";
1
+ import { AppAttributes, AppStatus, Events, MagixEventName, ROOT_DIR } from "./constants";
2
+ import { AppCreateQueue } from "./Utils/AppCreateQueue";
3
3
  import { AppListeners } from "./AppListener";
4
4
  import { AppProxy } from "./AppProxy";
5
+ import { appRegister } from "./Register";
5
6
  import { autorun, isPlayer, isRoom, ScenePathType } from "white-web-sdk";
6
- import { callbacks, emitter, WindowManager, reconnectRefresher } from "./index";
7
- import { genAppId, makeValidScenePath, setScenePath, setViewFocusScenePath } from "./Utils/Common";
7
+ import { callbacks, emitter, reconnectRefresher, WindowManager } from "./index";
8
+ import { get, isInteger, orderBy } from "lodash";
8
9
  import { log } from "./Utils/log";
9
10
  import { MainViewProxy } from "./View/MainView";
10
11
  import { onObjectRemoved, safeListenPropsUpdated } from "./Utils/Reactive";
11
- import { get, sortBy } from "lodash";
12
12
  import { store } from "./AttributesDelegate";
13
13
  import { ViewManager } from "./View/ViewManager";
14
+ import {
15
+ entireScenes,
16
+ genAppId,
17
+ makeValidScenePath,
18
+ parseSceneDir,
19
+ setScenePath,
20
+ setViewFocusScenePath,
21
+ } from "./Utils/Common";
14
22
  import type { ReconnectRefresher } from "./ReconnectRefresher";
15
23
  import type { BoxManager } from "./BoxManager";
16
- import type { Displayer, DisplayerState, Room } from "white-web-sdk";
24
+ import type { Displayer, DisplayerState, Room, ScenesCallbacksNode, View } from "white-web-sdk";
17
25
  import type { AddAppParams, BaseInsertParams, TeleBoxRect, EmitterEvent } from "./index";
18
26
 
19
27
  export class AppManager {
@@ -25,11 +33,15 @@ export class AppManager {
25
33
  public mainViewProxy: MainViewProxy;
26
34
  public refresher?: ReconnectRefresher;
27
35
  public isReplay = this.windowManger.isReplay;
36
+ public mainViewScenesLength = 0;
28
37
 
29
38
  private appListeners: AppListeners;
30
39
  public boxManager?: BoxManager;
31
40
 
32
41
  private _prevSceneIndex: number | undefined;
42
+ private _prevFocused: string | undefined;
43
+ private callbacksNode: ScenesCallbacksNode | null;
44
+ private appCreateQueue = new AppCreateQueue();
33
45
 
34
46
  constructor(public windowManger: WindowManager) {
35
47
  this.displayer = windowManger.displayer;
@@ -59,6 +71,70 @@ export class AppManager {
59
71
  this.onAppDelete(this.attributes.apps);
60
72
  });
61
73
  }
74
+ emitter.on("removeScenes", scenePath => {
75
+ if (scenePath === ROOT_DIR) {
76
+ this.setMainViewScenePath(ROOT_DIR);
77
+ return;
78
+ }
79
+ const mainViewScenePath = this.store.getMainViewScenePath();
80
+ if (this.room && mainViewScenePath) {
81
+ if (mainViewScenePath === scenePath) {
82
+ this.setMainViewScenePath(ROOT_DIR);
83
+ }
84
+ }
85
+ });
86
+ this.callbacksNode = this.displayer.createScenesCallback(ROOT_DIR, {
87
+ onAddScene: scenesCallback => {
88
+ this.mainViewScenesLength = scenesCallback.scenes.length;
89
+ callbacks.emit("mainViewScenesLengthChange", this.mainViewScenesLength);
90
+ },
91
+ onRemoveScene: scenesCallback => {
92
+ this.mainViewScenesLength = scenesCallback.scenes.length;
93
+ callbacks.emit("mainViewScenesLengthChange", this.mainViewScenesLength);
94
+ },
95
+ });
96
+ if (this.callbacksNode) {
97
+ this.mainViewScenesLength = this.callbacksNode.scenes.length;
98
+ }
99
+ }
100
+
101
+ private get eventName() {
102
+ return isRoom(this.displayer) ? "onRoomStateChanged" : "onPlayerStateChanged";
103
+ }
104
+
105
+ public get attributes() {
106
+ return this.windowManger.attributes;
107
+ }
108
+
109
+ public get canOperate() {
110
+ return this.windowManger.canOperate;
111
+ }
112
+
113
+ public get room() {
114
+ return isRoom(this.displayer) ? (this.displayer as Room) : undefined;
115
+ }
116
+
117
+ public get mainView() {
118
+ return this.mainViewProxy.view;
119
+ }
120
+
121
+ public get focusApp() {
122
+ if (this.store.focus) {
123
+ return this.appProxies.get(this.store.focus);
124
+ }
125
+ }
126
+
127
+ public get uid() {
128
+ return this.room?.uid || "";
129
+ }
130
+
131
+ public getMainViewSceneDir() {
132
+ const scenePath = this.store.getMainViewScenePath();
133
+ if (scenePath) {
134
+ return parseSceneDir(scenePath);
135
+ } else {
136
+ throw new Error("[WindowManager]: mainViewSceneDir not found");
137
+ }
62
138
  }
63
139
 
64
140
  private async onCreated() {
@@ -106,6 +182,29 @@ export class AppManager {
106
182
  }
107
183
  });
108
184
  });
185
+ this.refresher?.add("focusedChange", () => {
186
+ return autorun(() => {
187
+ const focused = get(this.attributes, "focus");
188
+ if (this._prevFocused !== focused) {
189
+ callbacks.emit("focusedChange", focused);
190
+ this.disposePrevFocusViewRedoUndoListeners(this._prevFocused);
191
+ setTimeout(() => {
192
+ this.addRedoUndoListeners(focused);
193
+ }, 0);
194
+ this._prevFocused = focused;
195
+ if (focused !== undefined) {
196
+ this.boxManager?.focusBox({ appId: focused });
197
+ // 确保 focus 修改的时候, appProxy 已经创建
198
+ setTimeout(() => {
199
+ const appProxy = this.appProxies.get(focused);
200
+ if (appProxy) {
201
+ appRegister.notifyApp(appProxy.kind, "focus", { appId: focused });
202
+ }
203
+ }, 0);
204
+ }
205
+ }
206
+ });
207
+ });
109
208
  if (!this.attributes.apps || Object.keys(this.attributes.apps).length === 0) {
110
209
  const mainScenePath = this.store.getMainViewScenePath();
111
210
  if (!mainScenePath) return;
@@ -116,8 +215,61 @@ export class AppManager {
116
215
  }
117
216
  this.displayerWritableListener(!this.room?.isWritable);
118
217
  this.displayer.callbacks.on("onEnableWriteNowChanged", this.displayerWritableListener);
218
+ this._prevFocused = this.attributes.focus;
219
+ this.addRedoUndoListeners(this.attributes.focus);
119
220
  }
120
221
 
222
+ private disposePrevFocusViewRedoUndoListeners = (prevFocused: string | undefined) => {
223
+ if (prevFocused === undefined) {
224
+ this.mainView.callbacks.off("onCanRedoStepsUpdate", this.onCanRedoStepsUpdate);
225
+ this.mainView.callbacks.off("onCanUndoStepsUpdate", this.onCanRedoStepsUpdate);
226
+ } else {
227
+ const appProxy = this.appProxies.get(prevFocused);
228
+ if (appProxy) {
229
+ appProxy.view?.callbacks.off("onCanRedoStepsUpdate", this.onCanRedoStepsUpdate);
230
+ appProxy.view?.callbacks.off("onCanUndoStepsUpdate", this.onCanUndoStepsUpdate);
231
+ }
232
+ }
233
+ };
234
+
235
+ private addRedoUndoListeners = (focused: string | undefined) => {
236
+ if (focused === undefined) {
237
+ this.addViewCallbacks(
238
+ this.mainView,
239
+ this.onCanRedoStepsUpdate,
240
+ this.onCanUndoStepsUpdate
241
+ );
242
+ } else {
243
+ const focusApp = this.appProxies.get(focused);
244
+ if (focusApp && focusApp.view) {
245
+ this.addViewCallbacks(
246
+ focusApp.view,
247
+ this.onCanRedoStepsUpdate,
248
+ this.onCanUndoStepsUpdate
249
+ );
250
+ }
251
+ }
252
+ };
253
+
254
+ private addViewCallbacks = (
255
+ view: View,
256
+ redoListener: (steps: number) => void,
257
+ undoListener: (steps: number) => void
258
+ ) => {
259
+ redoListener(view.canRedoSteps);
260
+ undoListener(view.canUndoSteps);
261
+ view.callbacks.on("onCanRedoStepsUpdate", redoListener);
262
+ view.callbacks.on("onCanUndoStepsUpdate", undoListener);
263
+ };
264
+
265
+ private onCanRedoStepsUpdate = (steps: number) => {
266
+ callbacks.emit("canRedoStepsChange", steps);
267
+ };
268
+
269
+ private onCanUndoStepsUpdate = (steps: number) => {
270
+ callbacks.emit("canUndoStepsChange", steps);
271
+ };
272
+
121
273
  /**
122
274
  * 插件更新 attributes 时的回调
123
275
  *
@@ -133,19 +285,18 @@ export class AppManager {
133
285
  createdAt: apps[appId].createdAt,
134
286
  };
135
287
  });
136
- for (const { id } of sortBy(appsWithCreatedAt, "createdAt")) {
288
+ for (const { id } of orderBy(appsWithCreatedAt, "createdAt", "asc")) {
137
289
  if (!this.appProxies.has(id) && !this.appStatus.has(id)) {
138
290
  const app = apps[id];
139
291
 
140
- pRetry(
141
- async () => {
142
- this.appStatus.set(id, AppStatus.StartCreate);
143
- // 防御 appAttributes 有可能为 undefined 的情况,这里做一个重试
144
- const appAttributes = this.attributes[id];
145
- if (!appAttributes) {
146
- throw new Error("appAttributes is undefined");
147
- }
148
- await this.baseInsertApp(
292
+ this.appStatus.set(id, AppStatus.StartCreate);
293
+ try {
294
+ const appAttributes = this.attributes[id];
295
+ if (!appAttributes) {
296
+ throw new Error("appAttributes is undefined");
297
+ }
298
+ this.appCreateQueue.push(() => {
299
+ return this.baseInsertApp(
149
300
  {
150
301
  kind: app.kind,
151
302
  options: app.options,
@@ -154,13 +305,11 @@ export class AppManager {
154
305
  id,
155
306
  false
156
307
  );
157
- this.focusByAttributes(apps);
158
- },
159
- { retries: 3 }
160
- ).catch(err => {
161
- console.warn(`[WindowManager]: Insert App Error`, err);
162
- this.appStatus.delete(id);
163
- });
308
+ });
309
+ this.focusByAttributes(apps);
310
+ } catch (error) {
311
+ console.warn(`[WindowManager]: Insert App Error`, error);
312
+ }
164
313
  }
165
314
  }
166
315
  }
@@ -201,10 +350,11 @@ export class AppManager {
201
350
  emitter.emit("mainViewMounted");
202
351
  }
203
352
 
204
- public setMainViewFocusPath() {
205
- const scenePath = this.store.getMainViewScenePath();
206
- if (scenePath) {
207
- setViewFocusScenePath(this.mainView, scenePath);
353
+ public setMainViewFocusPath(scenePath?: string) {
354
+ const focusScenePath = scenePath || this.store.getMainViewScenePath();
355
+ if (focusScenePath) {
356
+ const view = setViewFocusScenePath(this.mainView, focusScenePath);
357
+ return view?.focusScenePath === focusScenePath;
208
358
  }
209
359
  }
210
360
 
@@ -283,10 +433,6 @@ export class AppManager {
283
433
  }
284
434
  });
285
435
  }
286
- if (state.roomMembers) {
287
- this.windowManger.cursorManager?.setRoomMembers(state.roomMembers);
288
- this.windowManger.cursorManager?.cleanMemberAttributes(state.roomMembers);
289
- }
290
436
  this.appProxies.forEach(appProxy => {
291
437
  appProxy.appEmitter.emit("roomStateChange", state);
292
438
  });
@@ -307,37 +453,14 @@ export class AppManager {
307
453
  });
308
454
  if (isWritable === true) {
309
455
  this.mainView.disableCameraTransform = false;
456
+ if (this.room && this.room.disableSerialization === true) {
457
+ this.room.disableSerialization = false;
458
+ }
310
459
  } else {
311
460
  this.mainView.disableCameraTransform = true;
312
461
  }
313
462
  };
314
463
 
315
- private get eventName() {
316
- return isRoom(this.displayer) ? "onRoomStateChanged" : "onPlayerStateChanged";
317
- }
318
-
319
- public get attributes() {
320
- return this.windowManger.attributes;
321
- }
322
-
323
- public get canOperate() {
324
- return this.windowManger.canOperate;
325
- }
326
-
327
- public get room() {
328
- return isRoom(this.displayer) ? (this.displayer as Room) : undefined;
329
- }
330
-
331
- public get mainView() {
332
- return this.mainViewProxy.view;
333
- }
334
-
335
- public get focusApp() {
336
- if (this.store.focus) {
337
- return this.appProxies.get(this.store.focus);
338
- }
339
- }
340
-
341
464
  public safeSetAttributes(attributes: any) {
342
465
  this.windowManger.safeSetAttributes(attributes);
343
466
  }
@@ -349,6 +472,10 @@ export class AppManager {
349
472
  public async setMainViewScenePath(scenePath: string) {
350
473
  if (this.room) {
351
474
  const scenePathType = this.displayer.scenePathType(scenePath);
475
+ const sceneDir = parseSceneDir(scenePath);
476
+ if (sceneDir !== ROOT_DIR) {
477
+ throw new Error(`[WindowManager]: main view scenePath must in root dir "/"`);
478
+ }
352
479
  if (scenePathType === ScenePathType.None) {
353
480
  throw new Error(`[WindowManager]: ${scenePath} not valid scene`);
354
481
  } else if (scenePathType === ScenePathType.Page) {
@@ -363,32 +490,56 @@ export class AppManager {
363
490
  }
364
491
 
365
492
  private async _setMainViewScenePath(scenePath: string) {
366
- this.safeSetAttributes({ _mainScenePath: scenePath });
367
- this.setMainViewFocusPath();
368
- this.store.setMainViewFocusPath(this.mainView);
369
- this.dispatchInternalEvent(Events.SetMainViewScenePath, { nextScenePath: scenePath });
493
+ const success = this.setMainViewFocusPath(scenePath);
494
+ if (success) {
495
+ this.safeSetAttributes({ _mainScenePath: scenePath });
496
+ this.store.setMainViewFocusPath(this.mainView);
497
+ this.updateSceneIndex();
498
+ this.dispatchSetMainViewScenePath(scenePath);
499
+ }
370
500
  }
371
501
 
502
+ private updateSceneIndex = () => {
503
+ const scenePath = this.store.getMainViewScenePath() as string;
504
+ const sceneDir = parseSceneDir(scenePath);
505
+ const scenes = entireScenes(this.displayer)[sceneDir];
506
+ if (scenes.length) {
507
+ // "/ppt3/1" -> "1"
508
+ const pageName = scenePath.replace(sceneDir, "").replace("/", "");
509
+ const index = scenes.findIndex(scene => scene.name === pageName);
510
+ if (isInteger(index) && index >= 0) {
511
+ this.safeSetAttributes({ _mainSceneIndex: index });
512
+ }
513
+ }
514
+ };
515
+
372
516
  public async setMainViewSceneIndex(index: number) {
373
517
  if (this.room) {
374
- this.safeSetAttributes({ _mainSceneIndex: index });
518
+ if (this.store.getMainViewSceneIndex() === index) return;
375
519
  const mainViewScenePath = this.store.getMainViewScenePath() as string;
376
520
  if (mainViewScenePath) {
377
- const sceneList = mainViewScenePath.split("/");
378
- sceneList.pop();
379
- let sceneDir = sceneList.join("/");
380
- if (sceneDir === "") {
381
- sceneDir = "/";
382
- }
521
+ const sceneDir = parseSceneDir(mainViewScenePath);
383
522
  const scenePath = makeValidScenePath(this.displayer, sceneDir, index);
384
523
  if (scenePath) {
385
- this.store.setMainViewScenePath(scenePath);
386
- this.setMainViewFocusPath();
524
+ const success = this.setMainViewFocusPath(scenePath);
525
+ if (success) {
526
+ this.store.setMainViewScenePath(scenePath);
527
+ this.safeSetAttributes({ _mainSceneIndex: index });
528
+ this.dispatchSetMainViewScenePath(scenePath);
529
+ }
530
+ } else {
531
+ throw new Error(`[WindowManager]: ${sceneDir}: ${index} not valid index`);
387
532
  }
388
533
  }
389
534
  }
390
535
  }
391
536
 
537
+ private dispatchSetMainViewScenePath(scenePath: string): void {
538
+ this.dispatchInternalEvent(Events.SetMainViewScenePath, { nextScenePath: scenePath });
539
+ // 兼容 15 的 SDK, 需要 room 的当前 ScenePath
540
+ setScenePath(this.room, scenePath);
541
+ }
542
+
392
543
  public getAppInitPath(appId: string): string | undefined {
393
544
  const attrs = this.store.getAppAttributes(appId);
394
545
  if (attrs) {
@@ -456,6 +607,7 @@ export class AppManager {
456
607
  const reconnected = appProxies.map(appProxy => {
457
608
  return appProxy.onReconnected();
458
609
  });
610
+ this.mainViewProxy.onReconnect();
459
611
  await Promise.all(reconnected);
460
612
  }
461
613
 
@@ -472,6 +624,11 @@ export class AppManager {
472
624
  });
473
625
  }
474
626
 
627
+ public findMemberByUid = (uid: string) => {
628
+ const roomMembers = this.room?.state.roomMembers;
629
+ return roomMembers?.find(member => member.payload?.uid === uid);
630
+ };
631
+
475
632
  public destroy() {
476
633
  this.displayer.callbacks.off(this.eventName, this.displayerStateListener);
477
634
  this.displayer.callbacks.off("onEnableWriteNowChanged", this.displayerWritableListener);
@@ -488,6 +645,10 @@ export class AppManager {
488
645
  this.refresher?.destroy();
489
646
  this.mainViewProxy.destroy();
490
647
  callbacks.clearListeners();
648
+ this.callbacksNode?.dispose();
649
+ this.appCreateQueue.destroy();
650
+ this.disposePrevFocusViewRedoUndoListeners(this._prevFocused);
651
+ this._prevFocused = undefined;
491
652
  this._prevSceneIndex = undefined;
492
653
  }
493
654
  }