@netless/window-manager 0.4.24 → 0.4.27-canary.0

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 (46) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/__mocks__/white-web-sdk.ts +41 -0
  3. package/dist/App/AppContext.d.ts +2 -1
  4. package/dist/App/AppProxy.d.ts +7 -3
  5. package/dist/AppListener.d.ts +1 -0
  6. package/dist/AppManager.d.ts +2 -0
  7. package/dist/ContainerResizeObserver.d.ts +2 -1
  8. package/dist/InternalEmitter.d.ts +8 -2
  9. package/dist/Page/PageController.d.ts +5 -0
  10. package/dist/Page/index.d.ts +2 -0
  11. package/dist/Utils/Common.d.ts +2 -1
  12. package/dist/Utils/error.d.ts +3 -0
  13. package/dist/View/MainView.d.ts +4 -1
  14. package/dist/constants.d.ts +1 -0
  15. package/dist/index.cjs.js +12 -12
  16. package/dist/index.cjs.js.map +1 -1
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.es.js +263 -61
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/index.umd.js +12 -12
  21. package/dist/index.umd.js.map +1 -1
  22. package/dist/typings.d.ts +1 -0
  23. package/docs/advanced.md +29 -4
  24. package/docs/api.md +17 -0
  25. package/docs/app-context.md +149 -100
  26. package/package.json +5 -9
  27. package/pnpm-lock.yaml +793 -2690
  28. package/src/App/AppContext.ts +20 -11
  29. package/src/App/AppProxy.ts +61 -5
  30. package/src/App/Storage/index.ts +14 -8
  31. package/src/AppListener.ts +22 -0
  32. package/src/AppManager.ts +57 -24
  33. package/src/ContainerResizeObserver.ts +12 -1
  34. package/src/InternalEmitter.ts +7 -2
  35. package/src/Page/PageController.ts +6 -0
  36. package/src/Page/index.ts +18 -0
  37. package/src/PageState.ts +2 -1
  38. package/src/ReconnectRefresher.ts +4 -1
  39. package/src/Utils/Common.ts +14 -2
  40. package/src/Utils/RoomHacker.ts +2 -2
  41. package/src/Utils/error.ts +4 -0
  42. package/src/View/MainView.ts +37 -8
  43. package/src/constants.ts +1 -0
  44. package/src/index.ts +26 -1
  45. package/src/typings.ts +1 -0
  46. package/vite.config.js +9 -1
@@ -82,6 +82,17 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
82
82
  return this.appProxy.view;
83
83
  };
84
84
 
85
+ public mountView = (dom: HTMLElement): void => {
86
+ const view = this.getView();
87
+ if (view) {
88
+ view.divElement = dom as HTMLDivElement;
89
+ setTimeout(() => {
90
+ // 渲染需要时间,延迟 refresh
91
+ this.getRoom()?.refreshViewSize();
92
+ }, 1000);
93
+ }
94
+ };
95
+
85
96
  public getInitScenePath = () => {
86
97
  return this.manager.getAppInitPath(this.appId);
87
98
  };
@@ -124,17 +135,6 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
124
135
  this.getRoom()?.setScenePath(scenePath);
125
136
  };
126
137
 
127
- public mountView = (dom: HTMLElement): void => {
128
- const view = this.getView();
129
- if (view) {
130
- view.divElement = dom as HTMLDivElement;
131
- setTimeout(() => {
132
- // 渲染需要时间,延迟 refresh
133
- this.getRoom()?.refreshViewSize();
134
- }, 1000);
135
- }
136
- };
137
-
138
138
  /** Get the local App options. */
139
139
  public getAppOptions = (): TAppOptions | undefined => {
140
140
  return typeof this.appOptions === "function"
@@ -231,6 +231,15 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
231
231
  }
232
232
  };
233
233
 
234
+ public removePage = async (index: number): Promise<boolean> => {
235
+ if (index < 0 || index >= this.pageState.length) {
236
+ console.warn(`[WindowManager]: page index ${index} out of range`);
237
+ return false;
238
+ }
239
+ this.appProxy.removeSceneByIndex(index);
240
+ return true;
241
+ }
242
+
234
243
  public get pageState(): PageState {
235
244
  return this.appProxy.pageState;
236
245
  }
@@ -27,11 +27,11 @@ import type { SceneState, View, SceneDefinition } from "white-web-sdk";
27
27
  import type { AppManager } from "../AppManager";
28
28
  import type { NetlessApp } from "../typings";
29
29
  import type { ReadonlyTeleBox } from "@netless/telebox-insider";
30
- import type { PageState } from "../Page";
30
+ import { calculateNextIndex, PageRemoveService, PageState } from "../Page";
31
31
 
32
32
  export type AppEmitter = Emittery<AppEmitterEvent>;
33
33
 
34
- export class AppProxy {
34
+ export class AppProxy implements PageRemoveService {
35
35
  public kind: string;
36
36
  public id: string;
37
37
  public scenePath?: string;
@@ -48,6 +48,7 @@ export class AppProxy {
48
48
  private status: "normal" | "destroyed" = "normal";
49
49
  private stateKey: string;
50
50
  private _pageState: AppPageStateImpl;
51
+ private _prevFullPath: string | undefined;
51
52
 
52
53
  public appResult?: NetlessApp<any>;
53
54
  public appContext?: AppContext<any, any>;
@@ -248,6 +249,24 @@ export class AppProxy {
248
249
  this.boxManager?.updateBoxState(currentAppState);
249
250
  }
250
251
 
252
+ public async onRemoveScene(scenePath: string) {
253
+ if (this.scenePath && scenePath.startsWith(this.scenePath + "/")) {
254
+ let nextIndex = this.pageState.index;
255
+ let fullPath = this._pageState.getFullPath(nextIndex);
256
+ if (!fullPath) {
257
+ nextIndex = 0;
258
+ fullPath = this._pageState.getFullPath(nextIndex);
259
+ }
260
+ if (fullPath) {
261
+ this.setFullPath(fullPath);
262
+ }
263
+ this.setViewFocusScenePath();
264
+ if (this.view) {
265
+ this.view.focusSceneIndex = nextIndex;
266
+ }
267
+ }
268
+ }
269
+
251
270
  public getAppInitState = (id: string) => {
252
271
  const attrs = this.store.getAppState(id);
253
272
  if (!attrs) return;
@@ -348,7 +367,10 @@ export class AppProxy {
348
367
  return autorun(() => {
349
368
  const fullPath = this.appAttributes?.fullPath;
350
369
  this.setFocusScenePathHandler(fullPath);
351
- this.notifyPageStateChange();
370
+ if (this._prevFullPath !== fullPath) {
371
+ this.notifyPageStateChange();
372
+ this._prevFullPath = fullPath;
373
+ }
352
374
  });
353
375
  });
354
376
  };
@@ -381,14 +403,47 @@ export class AppProxy {
381
403
  return view;
382
404
  }
383
405
 
384
- public notifyPageStateChange = () => {
406
+ public notifyPageStateChange = debounce(() => {
385
407
  this.appEmitter.emit("pageStateChange", this.pageState);
386
- };
408
+ }, 50);
387
409
 
388
410
  public get pageState(): PageState {
389
411
  return this._pageState.toObject();
390
412
  }
391
413
 
414
+ // PageRemoveService
415
+ public async removeSceneByIndex(index: number) {
416
+ const scenePath = this._pageState.getFullPath(index);
417
+ if (scenePath) {
418
+ // 不能删除所有场景
419
+ if (this.pageState.length <= 1) {
420
+ return false;
421
+ }
422
+ const nextIndex = calculateNextIndex(index, this.pageState);
423
+ // 只修改 focus path 不修改 FullPath
424
+ this.setSceneIndexWithoutSync(nextIndex);
425
+ this.manager.dispatchInternalEvent(Events.SetAppFocusIndex, {
426
+ type: "app",
427
+ appID: this.id,
428
+ index: nextIndex,
429
+ });
430
+ // 手动添加一个延迟, 让 app 切换场景后再删除以避免闪烁
431
+ setTimeout(() => {
432
+ removeScenes(this.manager.room, scenePath, index);
433
+ }, 100);
434
+ return true;
435
+ } else {
436
+ return false;
437
+ }
438
+ }
439
+
440
+ public setSceneIndexWithoutSync(index: number) {
441
+ if (this.view) {
442
+ this.view.focusSceneIndex = index;
443
+ }
444
+ }
445
+ // PageRemoveService end
446
+
392
447
  public setSceneIndex(index: number) {
393
448
  if (this.view) {
394
449
  this.view.focusSceneIndex = index;
@@ -432,6 +487,7 @@ export class AppProxy {
432
487
  this.manager.refresher?.remove(this.id);
433
488
  this.manager.refresher?.remove(this.stateKey);
434
489
  this.manager.refresher?.remove(`${this.id}-fullPath`);
490
+ this._prevFullPath = undefined;
435
491
  }
436
492
 
437
493
  public close(): Promise<void> {
@@ -37,15 +37,21 @@ export class Storage<TState extends Record<string, any> = any> implements Storag
37
37
  this._state = {} as TState;
38
38
  const rawState = this._getRawState(this._state);
39
39
 
40
- if (this.id !== null && this._context.getIsWritable()) {
41
- if (rawState === this._state || !isObject(rawState)) {
42
- if (!get(this._context.getAttributes(), [STORAGE_NS])) {
43
- this._context.updateAttributes([STORAGE_NS], {});
40
+ if (this._context.getIsWritable()) {
41
+ if (this.id === null) {
42
+ if (context.isAddApp && defaultState) {
43
+ this.setState(defaultState);
44
+ }
45
+ } else {
46
+ if (rawState === this._state || !isObject(rawState)) {
47
+ if (!get(this._context.getAttributes(), [STORAGE_NS])) {
48
+ this._context.updateAttributes([STORAGE_NS], {});
49
+ }
50
+ this._context.updateAttributes([STORAGE_NS, this.id], this._state);
51
+ if (defaultState) {
52
+ this.setState(defaultState);
53
+ }
44
54
  }
45
- this._context.updateAttributes([STORAGE_NS, this.id], this._state);
46
- }
47
- if (defaultState) {
48
- this.setState(defaultState);
49
55
  }
50
56
  }
51
57
 
@@ -6,6 +6,13 @@ import { setViewFocusScenePath } from "./Utils/Common";
6
6
  import type { AnimationMode, Camera, Event } from "white-web-sdk";
7
7
  import type { AppManager } from "./AppManager";
8
8
  import type { TeleBoxState } from "@netless/telebox-insider";
9
+
10
+ type SetAppFocusIndex = {
11
+ type: "main" | "app";
12
+ appID?: string;
13
+ index: number;
14
+ }
15
+
9
16
  export class AppListeners {
10
17
  private displayer = this.manager.displayer;
11
18
 
@@ -67,6 +74,10 @@ export class AppListeners {
67
74
  this.initMainViewCameraHandler();
68
75
  break;
69
76
  }
77
+ case Events.SetAppFocusIndex: {
78
+ this.setAppFocusViewIndexHandler(data.payload);
79
+ break;
80
+ }
70
81
  default:
71
82
  break;
72
83
  }
@@ -119,4 +130,15 @@ export class AppListeners {
119
130
  private initMainViewCameraHandler = () => {
120
131
  this.manager.mainViewProxy.addCameraReaction();
121
132
  }
133
+
134
+ private setAppFocusViewIndexHandler = (payload: SetAppFocusIndex) => {
135
+ if (payload.type === "main") {
136
+ this.manager.setSceneIndexWithoutSync(payload.index);
137
+ } else if (payload.type === "app" && payload.appID) {
138
+ const app = this.manager.appProxies.get(payload.appID);
139
+ if (app) {
140
+ app.setSceneIndexWithoutSync(payload.index);
141
+ }
142
+ }
143
+ }
122
144
  }
package/src/AppManager.ts CHANGED
@@ -6,7 +6,7 @@ import { appRegister } from "./Register";
6
6
  import { autorun, isPlayer, isRoom, ScenePathType } from "white-web-sdk";
7
7
  import { callbacks } from "./callback";
8
8
  import { debounce, get, isInteger, orderBy } from "lodash";
9
- import { emitter } from "./InternalEmitter";
9
+ import { emitter, RemoveSceneParams } from "./InternalEmitter";
10
10
  import { Fields, store } from "./AttributesDelegate";
11
11
  import { log } from "./Utils/log";
12
12
  import { MainViewProxy } from "./View/MainView";
@@ -20,8 +20,10 @@ import type { EmitterEvent } from "./InternalEmitter";
20
20
  import {
21
21
  entireScenes,
22
22
  genAppId,
23
+ isRootDirPage,
23
24
  makeValidScenePath,
24
25
  parseSceneDir,
26
+ removeScenes,
25
27
  setScenePath,
26
28
  setViewFocusScenePath,
27
29
  } from "./Utils/Common";
@@ -35,6 +37,7 @@ import type {
35
37
  SceneState,
36
38
  } from "white-web-sdk";
37
39
  import type { AddAppParams, BaseInsertParams, TeleBoxRect } from "./index";
40
+ import { calculateNextIndex } from "./Page";
38
41
 
39
42
  export class AppManager {
40
43
  public displayer: Displayer;
@@ -108,20 +111,29 @@ export class AppManager {
108
111
  });
109
112
  }
110
113
 
111
- private onRemoveScenes = async (scenePath: string) => {
114
+ private onRemoveScenes = async (params: RemoveSceneParams) => {
115
+ const { scenePath } = params;
112
116
  // 如果移除根目录就把 scenePath 设置为初始值
113
117
  if (scenePath === ROOT_DIR) {
114
118
  await this.onRootDirRemoved();
115
119
  this.dispatchInternalEvent(Events.RootDirRemoved);
116
120
  return;
117
121
  }
118
- // 如果移除的 path 跟 MainViewScenePath 相同就取当前目录的当前 index
119
- const mainViewScenePath = this.store.getMainViewScenePath();
120
- if (this.room && mainViewScenePath) {
121
- if (mainViewScenePath === scenePath) {
122
- const nextPath = this.callbacksNode?.scenes[this.store.getMainViewSceneIndex()];
123
- this.setMainViewScenePath(`/${nextPath}` || INIT_DIR);
122
+ if (isRootDirPage(scenePath)) {
123
+ let nextIndex = this.mainView.focusSceneIndex || 0;
124
+ let sceneName = this.callbacksNode?.scenes[nextIndex];
125
+ if (!sceneName) {
126
+ nextIndex = 0;
127
+ sceneName = this.callbacksNode?.scenes[nextIndex];
124
128
  }
129
+ if (sceneName) {
130
+ this.setMainViewScenePath(`${ROOT_DIR}${sceneName}`);
131
+ }
132
+ await this.setMainViewSceneIndex(nextIndex);
133
+ } else {
134
+ this.appProxies.forEach(app => {
135
+ app.onRemoveScene(scenePath);
136
+ });
125
137
  }
126
138
  };
127
139
 
@@ -180,6 +192,26 @@ export class AppManager {
180
192
  }
181
193
  };
182
194
 
195
+ public removeSceneByIndex = async (index: number) => {
196
+ const nextIndex = calculateNextIndex(index, this.windowManger.pageState);
197
+ this.setSceneIndexWithoutSync(nextIndex);
198
+ this.dispatchInternalEvent(Events.SetAppFocusIndex, { type: "main", index: nextIndex });
199
+ setTimeout(() => {
200
+ const scene = this.callbacksNode?.scenes[index];
201
+ if (scene) {
202
+ removeScenes(this.room, `${ROOT_DIR}${scene}`, index)
203
+ }
204
+ }, 100);
205
+ return true;
206
+ }
207
+
208
+ public setSceneIndexWithoutSync = (index: number) => {
209
+ const sceneName = this.callbacksNode?.scenes[index];
210
+ if (sceneName) {
211
+ this.mainViewProxy.setFocusScenePath(`${ROOT_DIR}${sceneName}`);
212
+ }
213
+ }
214
+
183
215
  private onSceneChange = (node: ScenesCallbacksNode) => {
184
216
  this.mainViewScenesLength = node.scenes.length;
185
217
  this.updateSceneState(node);
@@ -193,7 +225,10 @@ export class AppManager {
193
225
 
194
226
  private updateSceneState = (node: ScenesCallbacksNode) => {
195
227
  const currentIndex = this.store.getMainViewSceneIndex() || 0;
196
- const sceneName = node.scenes[currentIndex];
228
+ let sceneName = node.scenes[currentIndex];
229
+ if (!sceneName) {
230
+ sceneName = node.scenes[this.mainView.focusSceneIndex || 0];
231
+ }
197
232
  this.sceneState = {
198
233
  scenePath: `${ROOT_DIR}${sceneName}`,
199
234
  contextPath: node.path,
@@ -322,6 +357,7 @@ export class AppManager {
322
357
  });
323
358
  };
324
359
 
360
+
325
361
  private onMainViewIndexChange = (index: number) => {
326
362
  if (index !== undefined && this._prevSceneIndex !== index) {
327
363
  callbacks.emit("mainViewSceneIndexChange", index);
@@ -472,8 +508,8 @@ export class AppManager {
472
508
  public setMainViewFocusPath(scenePath?: string) {
473
509
  const focusScenePath = scenePath || this.store.getMainViewScenePath();
474
510
  if (focusScenePath) {
475
- const view = setViewFocusScenePath(this.mainView, focusScenePath);
476
- return view?.focusScenePath === focusScenePath;
511
+ setViewFocusScenePath(this.mainView, focusScenePath);
512
+ return this.mainView?.focusScenePath === focusScenePath;
477
513
  }
478
514
  }
479
515
 
@@ -643,20 +679,17 @@ export class AppManager {
643
679
  public async setMainViewSceneIndex(index: number) {
644
680
  if (this.room) {
645
681
  if (this.store.getMainViewSceneIndex() === index) return;
646
- const mainViewScenePath = this.store.getMainViewScenePath() as string;
647
- if (mainViewScenePath) {
648
- const sceneDir = parseSceneDir(mainViewScenePath);
649
- const scenePath = makeValidScenePath(this.displayer, sceneDir, index);
650
- if (scenePath) {
651
- const success = this.setMainViewFocusPath(scenePath);
652
- if (success) {
653
- this.store.setMainViewScenePath(scenePath);
654
- this.safeSetAttributes({ _mainSceneIndex: index });
655
- this.dispatchSetMainViewScenePath(scenePath);
656
- }
657
- } else {
658
- throw new Error(`[WindowManager]: ${sceneDir}: ${index} not valid index`);
682
+ const sceneName = this.callbacksNode?.scenes[index];
683
+ const scenePath =`${ROOT_DIR}${sceneName}`;
684
+ if (sceneName) {
685
+ const success = this.setMainViewFocusPath(scenePath);
686
+ if (success) {
687
+ this.store.setMainViewScenePath(scenePath);
688
+ this.safeSetAttributes({ _mainSceneIndex: index });
689
+ this.dispatchSetMainViewScenePath(scenePath);
659
690
  }
691
+ } else {
692
+ throw new Error(`[WindowManager]: ${index} not valid index`);
660
693
  }
661
694
  }
662
695
  }
@@ -1,11 +1,14 @@
1
1
  import { ResizeObserver as ResizeObserverPolyfill } from "@juggle/resize-observer";
2
+ import { isFunction } from "lodash";
2
3
  import { WindowManager } from "./index";
3
4
  import type { EmitterType } from "./InternalEmitter";
5
+ import type { UnsubscribeFn } from "emittery";
4
6
 
5
7
  const ResizeObserver = window.ResizeObserver || ResizeObserverPolyfill;
6
8
 
7
9
  export class ContainerResizeObserver {
8
10
  private containerResizeObserver?: ResizeObserver;
11
+ private disposer?: UnsubscribeFn;
9
12
 
10
13
  constructor(private emitter: EmitterType) {}
11
14
 
@@ -35,10 +38,14 @@ export class ContainerResizeObserver {
35
38
  }
36
39
  });
37
40
 
41
+ this.disposer = this.emitter.on("containerSizeRatioUpdate", () => {
42
+ this.updateSizer(container.getBoundingClientRect(), sizer, wrapper);
43
+ });
44
+
38
45
  this.containerResizeObserver.observe(container);
39
46
  }
40
47
 
41
- private updateSizer(
48
+ public updateSizer(
42
49
  { width, height }: DOMRectReadOnly,
43
50
  sizer: HTMLElement,
44
51
  wrapper: HTMLDivElement
@@ -58,5 +65,9 @@ export class ContainerResizeObserver {
58
65
 
59
66
  public disconnect() {
60
67
  this.containerResizeObserver?.disconnect();
68
+ if (isFunction(this.disposer)) {
69
+ this.disposer();
70
+ this.disposer = undefined;
71
+ }
61
72
  }
62
73
  }
@@ -1,6 +1,9 @@
1
1
  import Emittery from "emittery";
2
2
  import type { AppInitState, CursorMovePayload } from "./index";
3
3
 
4
+ export type RemoveSceneParams = {
5
+ scenePath: string, index?: number
6
+ }
4
7
 
5
8
  export type EmitterEvent = {
6
9
  onCreated: undefined;
@@ -16,8 +19,9 @@ export type EmitterEvent = {
16
19
  observerIdChange: number;
17
20
  boxStateChange: string;
18
21
  playgroundSizeChange: DOMRect;
19
- onReconnected: void;
20
- removeScenes: string;
22
+ startReconnect: undefined;
23
+ onReconnected: undefined;
24
+ removeScenes: RemoveSceneParams;
21
25
  cursorMove: CursorMovePayload;
22
26
  updateManagerRect: undefined;
23
27
  focusedChange: { focused: string | undefined; prev: string | undefined };
@@ -25,6 +29,7 @@ export type EmitterEvent = {
25
29
  setReadonly: boolean;
26
30
  changePageState: undefined;
27
31
  writableChange: boolean;
32
+ containerSizeRatioUpdate: number;
28
33
  };
29
34
 
30
35
  export type EmitterType = Emittery<EmitterEvent>;
@@ -14,5 +14,11 @@ export interface PageController {
14
14
  nextPage: () => Promise<boolean>;
15
15
  prevPage: () => Promise<boolean>;
16
16
  addPage: (params?: AddPageParams) => Promise<void>;
17
+ removePage: (index: number) => Promise<boolean>;
17
18
  pageState: PageState;
18
19
  }
20
+
21
+ export interface PageRemoveService {
22
+ removeSceneByIndex: (index: number) => Promise<boolean>;
23
+ setSceneIndexWithoutSync: (index: number) => void;
24
+ }
package/src/Page/index.ts CHANGED
@@ -1 +1,19 @@
1
+ import type { PageState } from "./PageController";
2
+
1
3
  export * from "./PageController";
4
+
5
+ export const calculateNextIndex = (index: number, pageState: PageState) => {
6
+ let nextIndex = 0;
7
+ if (index === 0) {
8
+ return index + 1;
9
+ }
10
+ if (pageState.index !== 0 && index !== 0) {
11
+ const maxIndex = pageState.length - 1;
12
+ if (index === maxIndex) {
13
+ nextIndex = maxIndex - 1;
14
+ } else if (index > 0 && index < maxIndex) {
15
+ nextIndex = index + 1;
16
+ }
17
+ }
18
+ return nextIndex;
19
+ }
package/src/PageState.ts CHANGED
@@ -19,8 +19,9 @@ export class PageStateImpl {
19
19
  }
20
20
 
21
21
  public toObject(): PageState {
22
+ const index = this.index >= this.length ? this.length - 1 : this.index;
22
23
  return {
23
- index: this.index,
24
+ index,
24
25
  length: this.length,
25
26
  };
26
27
  }
@@ -29,6 +29,9 @@ export class ReconnectRefresher {
29
29
  }
30
30
 
31
31
  private onPhaseChanged = (phase: RoomPhase) => {
32
+ if (phase === RoomPhase.Reconnecting) {
33
+ this.ctx.emitter.emit("startReconnect");
34
+ }
32
35
  if (phase === RoomPhase.Connected && this.phase === RoomPhase.Reconnecting) {
33
36
  this.onReconnected();
34
37
  }
@@ -47,7 +50,7 @@ export class ReconnectRefresher {
47
50
  this.disposers.set(id, func());
48
51
  }
49
52
  });
50
- this.ctx.emitter.emit("onReconnected", undefined);
53
+ this.ctx.emitter.emit("onReconnected");
51
54
  }
52
55
 
53
56
  private releaseDisposers() {
@@ -53,11 +53,11 @@ export const getScenePath = (
53
53
  }
54
54
  };
55
55
 
56
- export const removeScenes = (room: Room | undefined, scenePath: string) => {
56
+ export const removeScenes = (room: Room | undefined, scenePath: string, index?: number) => {
57
57
  if (room) {
58
58
  const type = room.scenePathType(scenePath);
59
59
  if (type !== ScenePathType.None) {
60
- room.removeScenes(scenePath);
60
+ (room.removeScenes as any)(scenePath, index);
61
61
  }
62
62
  }
63
63
  };
@@ -140,3 +140,15 @@ export const getVersionNumber = (version: string) => {
140
140
  };
141
141
 
142
142
  export const wait = (time: number) => new Promise(resolve => setTimeout(resolve, time));
143
+
144
+ // rootDirPage: /page1 || / page2
145
+ // notRootDirPage: /dir1/page1 || /dir1/page2
146
+ export const isRootDirPage = (scenePath: string) => {
147
+ const delimiterCount = scenePath.split("").reduce((prev, cur) => {
148
+ if (cur === ROOT_DIR) {
149
+ prev += 1;
150
+ }
151
+ return prev;
152
+ }, 0);
153
+ return delimiterCount === 1;
154
+ }
@@ -61,12 +61,12 @@ export const replaceRoomFunction = (room: Room | Player, manager: WindowManager)
61
61
 
62
62
  const delegateRemoveScenes = (room: Room, manager: WindowManager) => {
63
63
  const originRemoveScenes = room.removeScenes;
64
- room.removeScenes = (scenePath: string) => {
64
+ room.removeScenes = (scenePath: string, index?: number) => {
65
65
  if (scenePath === ROOT_DIR) {
66
66
  manager.appManager?.updateRootDirRemoving(true);
67
67
  }
68
68
  const result = originRemoveScenes.call(room, scenePath);
69
- emitter.emit("removeScenes", scenePath);
69
+ emitter.emit("removeScenes", { scenePath, index });
70
70
  return result;
71
71
  };
72
72
  };
@@ -34,3 +34,7 @@ export class InvalidScenePath extends Error {
34
34
  export class BoxManagerNotFoundError extends Error {
35
35
  override message = "[WindowManager]: boxManager not found";
36
36
  }
37
+
38
+ export class BindContainerRoomPhaseInvalidError extends Error {
39
+ override message = "[WindowManager]: room phase only Connected can be bindContainer";
40
+ }
@@ -1,7 +1,7 @@
1
1
  import { AnimationMode, reaction } from "white-web-sdk";
2
2
  import { callbacks } from "../callback";
3
3
  import { createView } from "./ViewManager";
4
- import { debounce, isEmpty, isEqual } from "lodash";
4
+ import { debounce, get, isEmpty, isEqual } from "lodash";
5
5
  import { emitter } from "../InternalEmitter";
6
6
  import { Fields } from "../AttributesDelegate";
7
7
  import { setViewFocusScenePath } from "../Utils/Common";
@@ -32,8 +32,15 @@ export class MainViewProxy {
32
32
  this.sizeChangeHandler(this.mainViewSize);
33
33
  };
34
34
  this.sideEffectManager.add(() => {
35
- emitter.on("playgroundSizeChange", playgroundSizeChangeListener);
36
- return () => emitter.off("playgroundSizeChange", playgroundSizeChangeListener);
35
+ return emitter.on("playgroundSizeChange", playgroundSizeChangeListener);
36
+ });
37
+ this.sideEffectManager.add(() => {
38
+ return emitter.on("containerSizeRatioUpdate", this.onUpdateContainerSizeRatio);
39
+ });
40
+ this.sideEffectManager.add(() => {
41
+ return emitter.on("startReconnect", () => {
42
+ this.mainView.release();
43
+ });
37
44
  });
38
45
  }
39
46
 
@@ -62,6 +69,10 @@ export class MainViewProxy {
62
69
  return this.store.getMainViewSize();
63
70
  }
64
71
 
72
+ private get didRelease(): boolean {
73
+ return get(this.view, ["didRelease"]);
74
+ }
75
+
65
76
  private moveCameraSizeByAttributes() {
66
77
  this.moveCameraToContian(this.mainViewSize);
67
78
  this.moveCamera(this.mainViewCamera);
@@ -98,13 +109,21 @@ export class MainViewProxy {
98
109
  );
99
110
  };
100
111
 
101
- private sizeChangeHandler = debounce((size: Size) => {
112
+ public sizeChangeHandler = debounce((size: Size) => {
102
113
  if (size) {
103
114
  this.moveCameraToContian(size);
104
115
  this.moveCamera(this.mainViewCamera);
105
116
  }
106
117
  }, 30);
107
118
 
119
+ public onUpdateContainerSizeRatio = () => {
120
+ const size = this.store.getMainViewSize();
121
+ this.sizeChangeHandler(size);
122
+ if (size.id === this.manager.uid) {
123
+ this.setCameraAndSize();
124
+ }
125
+ }
126
+
108
127
  public get view(): View {
109
128
  return this.mainView;
110
129
  }
@@ -123,9 +142,17 @@ export class MainViewProxy {
123
142
  }
124
143
 
125
144
  public onReconnect(): void {
126
- const mainViewScenePath = this.store.getMainViewScenePath();
127
- if (mainViewScenePath) {
128
- setViewFocusScenePath(this.view, mainViewScenePath);
145
+ if (this.didRelease) {
146
+ this.rebind();
147
+ } else {
148
+ const mainViewScenePath = this.store.getMainViewScenePath();
149
+ this.setFocusScenePath(mainViewScenePath);
150
+ }
151
+ }
152
+
153
+ public setFocusScenePath(path: string | undefined) {
154
+ if (path) {
155
+ return setViewFocusScenePath(this.view, path);
129
156
  }
130
157
  }
131
158
 
@@ -133,7 +160,9 @@ export class MainViewProxy {
133
160
  const divElement = this.mainView.divElement;
134
161
  const disableCameraTransform = this.mainView.disableCameraTransform;
135
162
  this.stop();
136
- this.mainView.release();
163
+ if (!this.didRelease) {
164
+ this.mainView.release();
165
+ }
137
166
  this.removeMainViewListener();
138
167
  this.mainView = this.createMainView();
139
168
  this.mainView.disableCameraTransform = disableCameraTransform;