@netless/window-manager 0.3.18 → 0.4.0-canary.11

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 (75) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +4 -43
  3. package/dist/App/MagixEvent/index.d.ts +28 -0
  4. package/dist/App/Storage/StorageEvent.d.ts +8 -0
  5. package/dist/App/Storage/index.d.ts +38 -0
  6. package/dist/App/Storage/typings.d.ts +21 -0
  7. package/dist/App/Storage/utils.d.ts +5 -0
  8. package/dist/AppContext.d.ts +42 -17
  9. package/dist/AppListener.d.ts +2 -2
  10. package/dist/AppManager.d.ts +18 -14
  11. package/dist/AppProxy.d.ts +5 -3
  12. package/dist/AttributesDelegate.d.ts +19 -11
  13. package/dist/Base/Context.d.ts +0 -1
  14. package/dist/Base/index.d.ts +1 -2
  15. package/dist/BoxManager.d.ts +24 -7
  16. package/dist/BuiltinApps.d.ts +6 -0
  17. package/dist/ContainerResizeObserver.d.ts +10 -0
  18. package/dist/Cursor/Cursor.d.ts +2 -3
  19. package/dist/Cursor/index.d.ts +9 -5
  20. package/dist/Helper.d.ts +6 -0
  21. package/dist/ReconnectRefresher.d.ts +9 -3
  22. package/dist/Utils/Common.d.ts +3 -1
  23. package/dist/Utils/Reactive.d.ts +1 -1
  24. package/dist/Utils/RoomHacker.d.ts +2 -2
  25. package/dist/Utils/error.d.ts +3 -0
  26. package/dist/{MainView.d.ts → View/MainView.d.ts} +3 -4
  27. package/dist/View/ViewManager.d.ts +13 -0
  28. package/dist/constants.d.ts +3 -7
  29. package/dist/index.d.ts +25 -27
  30. package/dist/index.es.js +1 -1
  31. package/dist/index.es.js.map +1 -1
  32. package/dist/index.umd.js +1 -1
  33. package/dist/index.umd.js.map +1 -1
  34. package/dist/style.css +1 -1
  35. package/dist/typings.d.ts +3 -2
  36. package/docs/api.md +17 -0
  37. package/docs/migrate.md +42 -0
  38. package/package.json +6 -4
  39. package/src/App/MagixEvent/index.ts +66 -0
  40. package/src/App/Storage/StorageEvent.ts +21 -0
  41. package/src/App/Storage/index.ts +284 -0
  42. package/src/App/Storage/typings.ts +21 -0
  43. package/src/App/Storage/utils.ts +17 -0
  44. package/src/AppContext.ts +61 -21
  45. package/src/AppListener.ts +15 -11
  46. package/src/AppManager.ts +141 -95
  47. package/src/AppProxy.ts +49 -52
  48. package/src/AttributesDelegate.ts +76 -49
  49. package/src/Base/Context.ts +2 -6
  50. package/src/Base/index.ts +2 -2
  51. package/src/BoxManager.ts +89 -31
  52. package/src/BuiltinApps.ts +24 -0
  53. package/src/ContainerResizeObserver.ts +62 -0
  54. package/src/Cursor/Cursor.ts +35 -39
  55. package/src/Cursor/index.ts +79 -43
  56. package/src/Helper.ts +30 -0
  57. package/src/ReconnectRefresher.ts +25 -10
  58. package/src/Utils/Common.ts +35 -13
  59. package/src/Utils/Reactive.ts +9 -3
  60. package/src/Utils/RoomHacker.ts +20 -5
  61. package/src/Utils/error.ts +6 -1
  62. package/src/{MainView.ts → View/MainView.ts} +19 -27
  63. package/src/View/ViewManager.ts +53 -0
  64. package/src/constants.ts +2 -3
  65. package/src/index.ts +144 -171
  66. package/src/shim.d.ts +4 -0
  67. package/src/style.css +7 -1
  68. package/src/typings.ts +3 -2
  69. package/vite.config.js +4 -1
  70. package/dist/Utils/CameraStore.d.ts +0 -15
  71. package/dist/ViewManager.d.ts +0 -29
  72. package/dist/sdk.d.ts +0 -14
  73. package/src/Utils/CameraStore.ts +0 -72
  74. package/src/sdk.ts +0 -39
  75. package/src/viewManager.ts +0 -177
package/src/AppContext.ts CHANGED
@@ -6,7 +6,7 @@ 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
11
  import type { Room, SceneDefinition, View } from "white-web-sdk";
12
12
  import type { ReadonlyTeleBox } from "@netless/telebox-insider";
@@ -15,9 +15,11 @@ 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,
@@ -29,31 +31,32 @@ export class AppContext<TAttrs extends Record<string, any>, AppOptions = any> {
29
31
  listenDisposed,
30
32
  unlistenDisposed
31
33
  };
32
- private boxManager: BoxManager;
34
+
33
35
  private store = this.manager.store;
34
36
  public readonly isAddApp: boolean;
35
37
  public readonly isReplay = this.manager.isReplay;
36
38
 
37
39
  constructor(
38
40
  private manager: AppManager,
41
+ private boxManager: BoxManager,
39
42
  public appId: string,
40
43
  private appProxy: AppProxy,
41
- private appOptions?: AppOptions | (() => AppOptions),
44
+ private appOptions?: TAppOptions | (() => TAppOptions),
42
45
  ) {
43
46
  this.emitter = appProxy.appEmitter;
44
- this.boxManager = this.manager.boxManager;
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
62
  const appProxy = this.manager.appProxies.get(this.appId);
@@ -65,19 +68,21 @@ export class AppContext<TAttrs extends Record<string, any>, AppOptions = any> {
65
68
  }
66
69
  }
67
70
 
68
- public getView(): View | undefined {
71
+ public getView = (): View | undefined => {
69
72
  return this.appProxy.view;
70
73
  }
71
74
 
72
- public getInitScenePath() {
75
+ public getInitScenePath = () => {
73
76
  return this.manager.getAppInitPath(this.appId);
74
77
  }
75
78
 
76
- public getIsWritable(): boolean {
79
+ /** Get App writable status. */
80
+ public getIsWritable = (): boolean => {
77
81
  return this.manager.canOperate;
78
82
  }
79
83
 
80
- public getBox(): ReadonlyTeleBox {
84
+ /** Get the App Window UI box. */
85
+ public getBox = (): ReadonlyTeleBox => {
81
86
  const box = this.boxManager.getBox(this.appId);
82
87
  if (box) {
83
88
  return box;
@@ -86,27 +91,28 @@ export class AppContext<TAttrs extends Record<string, any>, AppOptions = any> {
86
91
  }
87
92
  }
88
93
 
89
- public getRoom(): Room | undefined {
94
+ public getRoom = (): Room | undefined => {
90
95
  return this.manager.room;
91
96
  }
92
97
 
93
- public setAttributes(attributes: TAttrs) {
98
+ /** @deprecated Use context.storage.setState instead. */
99
+ public setAttributes = (attributes: TAttributes) => {
94
100
  this.manager.safeSetAttributes({ [this.appId]: attributes });
95
101
  }
96
102
 
97
- public updateAttributes(keys: string[], value: any) {
103
+ /** @deprecated Use context.storage.setState instead. */
104
+ public updateAttributes = (keys: string[], value: any) => {
98
105
  if (this.manager.attributes[this.appId]) {
99
106
  this.manager.safeUpdateAttributes([this.appId, ...keys], value);
100
107
  }
101
108
  }
102
109
 
103
- public async setScenePath(scenePath: string): Promise<void> {
110
+ public setScenePath = async (scenePath: string): Promise<void> => {
104
111
  if (!this.appProxy.box) return;
105
112
  this.appProxy.setFullPath(scenePath);
106
- this.appProxy.context.switchAppToWriter(this.appId);
107
113
  }
108
114
 
109
- public mountView(dom: HTMLDivElement): void {
115
+ public mountView = (dom: HTMLDivElement): void => {
110
116
  const view = this.getView();
111
117
  if (view) {
112
118
  view.divElement = dom;
@@ -117,7 +123,41 @@ export class AppContext<TAttrs extends Record<string, any>, AppOptions = any> {
117
123
  }
118
124
  }
119
125
 
120
- public getAppOptions(): AppOptions | undefined {
121
- return typeof this.appOptions === 'function' ? (this.appOptions as () => AppOptions)() : this.appOptions
126
+ /** Get the local App options. */
127
+ public getAppOptions = (): TAppOptions | undefined => {
128
+ return typeof this.appOptions === 'function' ? (this.appOptions as () => TAppOptions)() : this.appOptions
129
+ }
130
+
131
+ private _storage?: Storage<TAttributes>
132
+
133
+ /** Main Storage for attributes. */
134
+ public get storage(): Storage<TAttributes> {
135
+ if (!this._storage) {
136
+ this._storage = new Storage(this);
137
+ }
138
+ return this._storage;
139
+ }
140
+
141
+ /**
142
+ * Create separated storages for flexible state management.
143
+ * @param storeId Namespace for the storage. Storages of the same namespace share the same data.
144
+ * @param defaultState Default state for initial storage creation.
145
+ * @returns
146
+ */
147
+ public createStorage = <TState>(storeId: string, defaultState?: TState): Storage<TState> => {
148
+ const storage = new Storage(this, storeId, defaultState);
149
+ this.emitter.on("destroy", () => {
150
+ storage.destroy();
151
+ });
152
+ return storage;
122
153
  }
154
+
155
+ /** Dispatch events to other clients (and self). */
156
+ public dispatchMagixEvent: MagixEventDispatcher<TMagixEventPayloads> = (this.manager.displayer as Room).dispatchMagixEvent.bind(this.manager.displayer)
157
+
158
+ /** Listen to events from others clients (and self messages). */
159
+ public addMagixEventListener: MagixEventAddListener<TMagixEventPayloads> = this.manager.displayer.addMagixEventListener.bind(this.manager.displayer)
160
+
161
+ /** Remove a Magix event listener. */
162
+ public removeMagixEventListener = this.manager.displayer.removeMagixEventListener.bind(this.manager.displayer) as MagixEventRemoveListener<TMagixEventPayloads>
123
163
  }
@@ -7,10 +7,13 @@ import { setViewFocusScenePath } from './Utils/Common';
7
7
 
8
8
  export class AppListeners {
9
9
  private displayer = this.manager.displayer;
10
- private boxManager = this.manager.boxManager;
11
10
 
12
11
  constructor(private manager: AppManager) {}
13
12
 
13
+ private get boxManager() {
14
+ return this.manager.boxManager;
15
+ }
16
+
14
17
  public addListeners() {
15
18
  this.displayer.addMagixEventListener(MagixEventName, this.mainMagixEventListener);
16
19
  }
@@ -31,10 +34,6 @@ export class AppListeners {
31
34
  this.appResizeHandler(data.payload);
32
35
  break;
33
36
  }
34
- case Events.SwitchViewsToFreedom: {
35
- this.switchViewsToFreedomHandler();
36
- break;
37
- }
38
37
  case Events.AppBoxStateChange: {
39
38
  this.boxStateChangeHandler(data.payload);
40
39
  break;
@@ -43,6 +42,10 @@ export class AppListeners {
43
42
  this.setMainViewScenePathHandler(data.payload);
44
43
  break;
45
44
  }
45
+ case Events.MoveCameraToContain: {
46
+ this.moveCameraToContainHandler(data.payload);
47
+ break;
48
+ }
46
49
  default:
47
50
  break;
48
51
  }
@@ -50,23 +53,24 @@ export class AppListeners {
50
53
  };
51
54
 
52
55
  private appMoveHandler = (payload: any) => {
53
- this.boxManager.moveBox(payload);
56
+ this.boxManager?.moveBox(payload);
54
57
  };
55
58
 
56
59
  private appResizeHandler = (payload: any) => {
57
- this.boxManager.resizeBox(Object.assign(payload, { skipUpdate: true }));
60
+ this.boxManager?.resizeBox(Object.assign(payload, { skipUpdate: true }));
58
61
  this.manager.room?.refreshViewSize();
59
62
  };
60
63
 
61
- private switchViewsToFreedomHandler = () => {
62
- this.manager.viewManager.freedomAllViews();
63
- };
64
-
65
64
  private boxStateChangeHandler = (state: TeleBoxState) => {
66
65
  callbacks.emit("boxStateChange", state);
67
66
  }
68
67
 
69
68
  private setMainViewScenePathHandler = ({ nextScenePath }: { nextScenePath: string }) => {
70
69
  setViewFocusScenePath(this.manager.mainView, nextScenePath);
70
+ callbacks.emit("mainViewScenePathChange", nextScenePath);
71
+ }
72
+
73
+ private moveCameraToContainHandler = (payload: any) => {
74
+ this.manager.mainView.moveCameraToContain(payload);
71
75
  }
72
76
  }
package/src/AppManager.ts CHANGED
@@ -1,66 +1,56 @@
1
1
  import pRetry from "p-retry";
2
- import {
3
- AppAttributes,
4
- AppStatus,
5
- Events,
6
- MagixEventName
7
- } from "./constants";
2
+ import { AppAttributes, AppStatus, Events, MagixEventName } from "./constants";
8
3
  import { AppListeners } from "./AppListener";
9
4
  import { AppProxy } from "./AppProxy";
10
- import { AttributesDelegate } from "./AttributesDelegate";
11
- import {
12
- autorun,
13
- isPlayer,
14
- isRoom,
15
- ScenePathType,
16
- ViewVisionMode
17
- } from "white-web-sdk";
18
- import { BoxManager } from "./BoxManager";
19
- import { callbacks, emitter } from "./index";
20
- import { CameraStore } from "./Utils/CameraStore";
21
- 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 { genAppId, makeValidScenePath, setScenePath, setViewFocusScenePath } from "./Utils/Common";
22
8
  import { log } from "./Utils/log";
23
- import { MainViewProxy } from "./MainView";
9
+ import { MainViewProxy } from "./View/MainView";
24
10
  import { onObjectRemoved, safeListenPropsUpdated } from "./Utils/Reactive";
25
- import { ReconnectRefresher } from "./ReconnectRefresher";
26
- import { ViewManager } from "./ViewManager";
11
+ import { get, sortBy } from "lodash";
12
+ import { store } from "./AttributesDelegate";
13
+ import { ViewManager } from "./View/ViewManager";
14
+ import type { ReconnectRefresher } from "./ReconnectRefresher";
15
+ import type { BoxManager } from "./BoxManager";
27
16
  import type { Displayer, DisplayerState, Room } from "white-web-sdk";
28
- import type { CreateTeleBoxManagerConfig } from "./BoxManager";
29
- import type {
30
- AddAppParams,
31
- BaseInsertParams,
32
- WindowManager,
33
- TeleBoxRect,
34
- EmitterEvent,
35
- } from "./index";
17
+ import type { AddAppParams, BaseInsertParams, TeleBoxRect, EmitterEvent } from "./index";
18
+
36
19
  export class AppManager {
37
20
  public displayer: Displayer;
38
- public boxManager: BoxManager;
39
- public cameraStore: CameraStore;
40
21
  public viewManager: ViewManager;
41
22
  public appProxies: Map<string, AppProxy> = new Map();
42
23
  public appStatus: Map<string, AppStatus> = new Map();
43
- public store = new AttributesDelegate(this);
24
+ public store = store;
44
25
  public mainViewProxy: MainViewProxy;
45
26
  public refresher?: ReconnectRefresher;
46
27
  public isReplay = this.windowManger.isReplay;
47
28
 
48
29
  private appListeners: AppListeners;
30
+ public boxManager?: BoxManager;
31
+
32
+ private _prevSceneIndex: number | undefined;
33
+ private _prevFocused: string | undefined;
49
34
 
50
- constructor(public windowManger: WindowManager, options: CreateTeleBoxManagerConfig) {
35
+ constructor(public windowManger: WindowManager) {
51
36
  this.displayer = windowManger.displayer;
52
- this.cameraStore = new CameraStore();
37
+ this.store.setContext({
38
+ getAttributes: () => this.attributes,
39
+ safeSetAttributes: attributes => this.safeSetAttributes(attributes),
40
+ safeUpdateAttributes: (keys, val) => this.safeUpdateAttributes(keys, val),
41
+ });
53
42
  this.mainViewProxy = new MainViewProxy(this);
54
- this.viewManager = new ViewManager(this);
55
- this.boxManager = new BoxManager(this, options);
43
+ this.viewManager = new ViewManager(this.displayer);
56
44
  this.appListeners = new AppListeners(this);
57
45
  this.displayer.callbacks.on(this.eventName, this.displayerStateListener);
58
46
  this.appListeners.addListeners();
59
47
 
60
- this.refresher = new ReconnectRefresher(this.room, this);
48
+ this.refresher = reconnectRefresher;
49
+ this.refresher.setRoom(this.room);
50
+ this.refresher.setContext({ emitter });
61
51
 
62
52
  emitter.once("onCreated").then(() => this.onCreated());
63
-
53
+ emitter.on("onReconnected", () => this.onReconnected());
64
54
  if (isPlayer(this.displayer)) {
65
55
  emitter.on("seek", time => {
66
56
  this.appProxies.forEach(appProxy => {
@@ -74,12 +64,15 @@ export class AppManager {
74
64
 
75
65
  private async onCreated() {
76
66
  await this.attributesUpdateCallback(this.attributes.apps);
77
- this.boxManager.updateManagerRect();
67
+ this.boxManager?.updateManagerRect();
78
68
  emitter.onAny(this.boxEventListener);
79
69
  this.refresher?.add("apps", () => {
80
- return safeListenPropsUpdated(() => this.attributes.apps, () => {
81
- this.attributesUpdateCallback(this.attributes.apps);
82
- });
70
+ return safeListenPropsUpdated(
71
+ () => this.attributes.apps,
72
+ () => {
73
+ this.attributesUpdateCallback(this.attributes.apps);
74
+ }
75
+ );
83
76
  });
84
77
  this.refresher?.add("appsClose", () => {
85
78
  return onObjectRemoved(this.attributes.apps, () => {
@@ -89,24 +82,40 @@ export class AppManager {
89
82
  this.refresher?.add("maximized", () => {
90
83
  return autorun(() => {
91
84
  const maximized = this.attributes.maximized;
92
- if (this.boxManager.maximized !== maximized) {
93
- this.boxManager.setMaximized(Boolean(maximized));
94
- }
85
+ this.boxManager?.setMaximized(Boolean(maximized));
95
86
  });
96
87
  });
97
88
  this.refresher?.add("minimized", () => {
98
89
  return autorun(() => {
99
90
  const minimized = this.attributes.minimized;
100
- if (this.boxManager.minimized !== minimized) {
91
+ if (this.boxManager?.minimized !== minimized) {
101
92
  if (minimized === true) {
102
- this.boxManager.blurAllBox();
93
+ this.boxManager?.blurAllBox();
103
94
  }
104
95
  setTimeout(() => {
105
- this.boxManager.setMinimized(Boolean(minimized));
96
+ this.boxManager?.setMinimized(Boolean(minimized));
106
97
  }, 0);
107
98
  }
108
99
  });
109
100
  });
101
+ this.refresher?.add("mainViewIndex", () => {
102
+ return autorun(() => {
103
+ const mainSceneIndex = get(this.attributes, "_mainSceneIndex");
104
+ if (mainSceneIndex !== undefined && this._prevSceneIndex !== mainSceneIndex) {
105
+ callbacks.emit("mainViewSceneIndexChange", mainSceneIndex);
106
+ this._prevSceneIndex = mainSceneIndex;
107
+ }
108
+ });
109
+ });
110
+ this.refresher?.add("focusedChange", () => {
111
+ return autorun(() => {
112
+ const focused = get(this.attributes, "focus");
113
+ if (this._prevFocused !== focused) {
114
+ callbacks.emit("focusedChange", focused);
115
+ this._prevFocused = focused;
116
+ }
117
+ });
118
+ })
110
119
  if (!this.attributes.apps || Object.keys(this.attributes.apps).length === 0) {
111
120
  const mainScenePath = this.store.getMainViewScenePath();
112
121
  if (!mainScenePath) return;
@@ -117,6 +126,7 @@ export class AppManager {
117
126
  }
118
127
  this.displayerWritableListener(!this.room?.isWritable);
119
128
  this.displayer.callbacks.on("onEnableWriteNowChanged", this.displayerWritableListener);
129
+ this._prevFocused = this.attributes.focus;
120
130
  }
121
131
 
122
132
  /**
@@ -126,29 +136,39 @@ export class AppManager {
126
136
  * @memberof WindowManager
127
137
  */
128
138
  public async attributesUpdateCallback(apps: any) {
129
- if (apps) {
130
- for (const id in apps) {
139
+ if (apps && WindowManager.container) {
140
+ const appIds = Object.keys(apps);
141
+ const appsWithCreatedAt = appIds.map(appId => {
142
+ return {
143
+ id: appId,
144
+ createdAt: apps[appId].createdAt,
145
+ };
146
+ });
147
+ for (const { id } of sortBy(appsWithCreatedAt, "createdAt")) {
131
148
  if (!this.appProxies.has(id) && !this.appStatus.has(id)) {
132
149
  const app = apps[id];
133
150
 
134
- pRetry(async () => {
135
- this.appStatus.set(id, AppStatus.StartCreate);
136
- // 防御 appAttributes 有可能为 undefined 的情况,这里做一个重试
137
- const appAttributes = this.attributes[id];
138
- if (!appAttributes) {
139
- throw new Error("appAttributes is undefined");
140
- }
141
- await this.baseInsertApp(
142
- {
143
- kind: app.kind,
144
- options: app.options,
145
- isDynamicPPT: app.isDynamicPPT,
146
- },
147
- id,
148
- false
149
- );
150
- this.focusByAttributes(apps);
151
- }, { retries: 3 }).catch(err => {
151
+ pRetry(
152
+ async () => {
153
+ this.appStatus.set(id, AppStatus.StartCreate);
154
+ // 防御 appAttributes 有可能为 undefined 的情况,这里做一个重试
155
+ const appAttributes = this.attributes[id];
156
+ if (!appAttributes) {
157
+ throw new Error("appAttributes is undefined");
158
+ }
159
+ await this.baseInsertApp(
160
+ {
161
+ kind: app.kind,
162
+ options: app.options,
163
+ isDynamicPPT: app.isDynamicPPT,
164
+ },
165
+ id,
166
+ false
167
+ );
168
+ this.focusByAttributes(apps);
169
+ },
170
+ { retries: 3 }
171
+ ).catch(err => {
152
172
  console.warn(`[WindowManager]: Insert App Error`, err);
153
173
  this.appStatus.delete(id);
154
174
  });
@@ -157,6 +177,22 @@ export class AppManager {
157
177
  }
158
178
  }
159
179
 
180
+ public refresh() {
181
+ this.attributesUpdateCallback(this.attributes.apps);
182
+ }
183
+
184
+ public setBoxManager(boxManager: BoxManager) {
185
+ this.boxManager = boxManager;
186
+ }
187
+
188
+ public resetMaximized() {
189
+ this.boxManager?.setMaximized(Boolean(this.store.getMaximized()));
190
+ }
191
+
192
+ public resetMinimized() {
193
+ this.boxManager?.setMinimized(Boolean(this.store.getMinimized()));
194
+ }
195
+
160
196
  private onAppDelete = (apps: any) => {
161
197
  const ids = Object.keys(apps);
162
198
  this.appProxies.forEach((appProxy, id) => {
@@ -171,15 +207,18 @@ export class AppManager {
171
207
  mainView.disableCameraTransform = disableCameraTransform;
172
208
  mainView.divElement = divElement;
173
209
  if (!mainView.focusScenePath) {
174
- this.store.setMainViewFocusPath();
175
- }
176
- if (this.store.focus === undefined && mainView.mode !== ViewVisionMode.Writable) {
177
- this.viewManager.switchMainViewToWriter();
210
+ this.setMainViewFocusPath();
178
211
  }
179
- this.mainViewProxy.addMainViewListener();
180
212
  emitter.emit("mainViewMounted");
181
213
  }
182
214
 
215
+ public setMainViewFocusPath() {
216
+ const scenePath = this.store.getMainViewScenePath();
217
+ if (scenePath) {
218
+ setViewFocusScenePath(this.mainView, scenePath);
219
+ }
220
+ }
221
+
183
222
  public async addApp(params: AddAppParams, isDynamicPPT: boolean): Promise<string | undefined> {
184
223
  log("addApp", params);
185
224
  const { appId, needFocus } = await this.beforeAddApp(params, isDynamicPPT);
@@ -194,7 +233,7 @@ export class AppManager {
194
233
  const attrs = params.attributes ?? {};
195
234
  this.safeUpdateAttributes([appId], attrs);
196
235
  this.store.setupAppAttributes(params, appId, isDynamicPPT);
197
- const needFocus = !this.boxManager.minimized;
236
+ const needFocus = !this.boxManager?.minimized;
198
237
  if (needFocus) {
199
238
  this.store.setAppFocus(appId, true);
200
239
  }
@@ -211,8 +250,8 @@ export class AppManager {
211
250
  });
212
251
  this.store.updateAppState(appProxy.id, AppAttributes.ZIndex, box.zIndex);
213
252
  }
214
- if (this.boxManager.minimized) {
215
- this.boxManager.setMinimized(false, false);
253
+ if (this.boxManager?.minimized) {
254
+ this.boxManager?.setMinimized(false, false);
216
255
  }
217
256
  }
218
257
 
@@ -265,22 +304,19 @@ export class AppManager {
265
304
  emitter.emit("observerIdChange", this.displayer.observerId);
266
305
  };
267
306
 
268
- private displayerWritableListener = (isReadonly: boolean) => {
307
+ public displayerWritableListener = (isReadonly: boolean) => {
269
308
  const isWritable = !isReadonly;
270
309
  const isManualWritable =
271
310
  this.windowManger.readonly === undefined || this.windowManger.readonly === false;
272
311
  if (this.windowManger.readonly === undefined) {
273
- this.boxManager.setReadonly(isReadonly);
312
+ this.boxManager?.setReadonly(isReadonly);
274
313
  } else {
275
- this.boxManager.setReadonly(!(isWritable && isManualWritable));
314
+ this.boxManager?.setReadonly(!(isWritable && isManualWritable));
276
315
  }
277
316
  this.appProxies.forEach(appProxy => {
278
317
  appProxy.emitAppIsWritableChange();
279
318
  });
280
319
  if (isWritable === true) {
281
- if (!this.store.focus) {
282
- this.mainViewProxy.switchViewModeToWriter();
283
- }
284
320
  this.mainView.disableCameraTransform = false;
285
321
  } else {
286
322
  this.mainView.disableCameraTransform = true;
@@ -330,28 +366,37 @@ export class AppManager {
330
366
  await this._setMainViewScenePath(scenePath);
331
367
  } else if (scenePathType === ScenePathType.Dir) {
332
368
  const validScenePath = makeValidScenePath(this.displayer, scenePath);
333
- await this._setMainViewScenePath(validScenePath);
369
+ if (validScenePath) {
370
+ await this._setMainViewScenePath(validScenePath);
371
+ }
334
372
  }
335
373
  }
336
374
  }
337
375
 
338
376
  private async _setMainViewScenePath(scenePath: string) {
339
377
  this.safeSetAttributes({ _mainScenePath: scenePath });
340
- await this.viewManager.switchMainViewToWriter();
341
- setScenePath(this.room, scenePath);
342
- this.store.setMainViewFocusPath();
378
+ this.setMainViewFocusPath();
379
+ this.store.setMainViewFocusPath(this.mainView);
343
380
  this.dispatchInternalEvent(Events.SetMainViewScenePath, { nextScenePath: scenePath });
344
381
  }
345
382
 
346
383
  public async setMainViewSceneIndex(index: number) {
347
384
  if (this.room) {
348
385
  this.safeSetAttributes({ _mainSceneIndex: index });
349
- await this.viewManager.switchMainViewToWriter();
350
- this.room.setSceneIndex(index);
351
- const nextScenePath = this.room.state.sceneState.scenePath;
352
- this.store.setMainViewScenePath(nextScenePath);
353
- this.store.setMainViewFocusPath();
354
- this.dispatchInternalEvent(Events.SetMainViewScenePath, { nextScenePath });
386
+ const mainViewScenePath = this.store.getMainViewScenePath() as string;
387
+ if (mainViewScenePath) {
388
+ const sceneList = mainViewScenePath.split("/");
389
+ sceneList.pop();
390
+ let sceneDir = sceneList.join("/");
391
+ if (sceneDir === "") {
392
+ sceneDir = "/";
393
+ }
394
+ const scenePath = makeValidScenePath(this.displayer, sceneDir, index);
395
+ if (scenePath) {
396
+ this.store.setMainViewScenePath(scenePath);
397
+ this.setMainViewFocusPath();
398
+ }
399
+ }
355
400
  }
356
401
  }
357
402
 
@@ -409,7 +454,7 @@ export class AppManager {
409
454
  };
410
455
 
411
456
  public focusByAttributes(apps: any) {
412
- if (apps && Object.keys(apps).length === this.boxManager.appBoxMap.size) {
457
+ if (apps && Object.keys(apps).length === this.boxManager?.boxSize) {
413
458
  const focusAppId = this.store.focus;
414
459
  if (focusAppId) {
415
460
  this.boxManager.focusBox({ appId: focusAppId });
@@ -417,7 +462,7 @@ export class AppManager {
417
462
  }
418
463
  }
419
464
 
420
- public async notifyReconnected() {
465
+ public async onReconnected() {
421
466
  const appProxies = Array.from(this.appProxies.values());
422
467
  const reconnected = appProxies.map(appProxy => {
423
468
  return appProxy.onReconnected();
@@ -450,9 +495,10 @@ export class AppManager {
450
495
  });
451
496
  }
452
497
  this.viewManager.destroy();
453
- this.boxManager.destroy();
498
+ this.boxManager?.destroy();
454
499
  this.refresher?.destroy();
455
500
  this.mainViewProxy.destroy();
456
501
  callbacks.clearListeners();
502
+ this._prevSceneIndex = undefined;
457
503
  }
458
504
  }