@netless/window-manager 1.0.0-canary.1 → 1.0.0-canary.4

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 (48) hide show
  1. package/__mocks__/white-web-sdk.ts +10 -1
  2. package/dist/App/AppContext.d.ts +14 -15
  3. package/dist/App/AppPageStateImpl.d.ts +6 -2
  4. package/dist/App/AppProxy.d.ts +17 -2
  5. package/dist/App/AppViewSync.d.ts +11 -0
  6. package/dist/App/WhiteboardView.d.ts +21 -0
  7. package/dist/App/index.d.ts +1 -0
  8. package/dist/AppManager.d.ts +3 -1
  9. package/dist/AttributesDelegate.d.ts +6 -14
  10. package/dist/BoxManager.d.ts +1 -1
  11. package/dist/Helper.d.ts +12 -2
  12. package/dist/InternalEmitter.d.ts +2 -0
  13. package/dist/ReconnectRefresher.d.ts +1 -1
  14. package/dist/Utils/Common.d.ts +1 -0
  15. package/dist/View/CameraSynchronizer.d.ts +4 -4
  16. package/dist/View/ViewSync.d.ts +7 -0
  17. package/dist/constants.d.ts +1 -0
  18. package/dist/index.cjs.js +12 -12
  19. package/dist/index.d.ts +3 -1
  20. package/dist/index.es.js +563 -576
  21. package/dist/index.umd.js +12 -12
  22. package/dist/style.css +1 -1
  23. package/dist/typings.d.ts +4 -0
  24. package/docs/app-context.md +98 -64
  25. package/docs/develop-app.md +2 -5
  26. package/package.json +3 -2
  27. package/pnpm-lock.yaml +11 -5
  28. package/src/App/AppContext.ts +65 -72
  29. package/src/App/AppPageStateImpl.ts +25 -6
  30. package/src/App/AppProxy.ts +110 -13
  31. package/src/App/AppViewSync.ts +69 -0
  32. package/src/App/Storage/index.ts +4 -4
  33. package/src/App/WhiteboardView.ts +85 -0
  34. package/src/App/index.ts +1 -0
  35. package/src/AppManager.ts +10 -2
  36. package/src/AttributesDelegate.ts +14 -17
  37. package/src/BoxManager.ts +3 -2
  38. package/src/Helper.ts +10 -1
  39. package/src/InternalEmitter.ts +2 -0
  40. package/src/ReconnectRefresher.ts +1 -0
  41. package/src/Utils/Common.ts +6 -0
  42. package/src/View/CameraSynchronizer.ts +15 -8
  43. package/src/View/MainView.ts +9 -13
  44. package/src/View/ViewSync.ts +10 -0
  45. package/src/constants.ts +2 -0
  46. package/src/index.ts +3 -1
  47. package/src/style.css +9 -0
  48. package/src/typings.ts +4 -0
@@ -1,5 +1,4 @@
1
1
  import { BoxNotCreatedError } from "../Utils/error";
2
- import { putScenes } from "../Utils/Common";
3
2
  import { Storage } from "./Storage";
4
3
  import {
5
4
  autorun,
@@ -19,7 +18,7 @@ import type {
19
18
  import type { ReadonlyTeleBox } from "@netless/telebox-insider";
20
19
  import type Emittery from "emittery";
21
20
  import type { BoxManager } from "../BoxManager";
22
- import type { AppEmitterEvent } from "../index";
21
+ import type { AppEmitterEvent, Member } from "../index";
23
22
  import type { AppManager } from "../AppManager";
24
23
  import type { AppProxy } from "./AppProxy";
25
24
  import type {
@@ -27,11 +26,13 @@ import type {
27
26
  MagixEventDispatcher,
28
27
  MagixEventRemoveListener,
29
28
  } from "./MagixEvent";
30
- import type { AddPageParams, PageController, PageState } from "../Page";
29
+ import { WhiteBoardView } from "./WhiteboardView";
30
+ import { findMemberByUid } from "../Helper";
31
+ import { MAX_PAGE_SIZE } from "../constants";
32
+ import { putScenes } from "../Utils/Common";
33
+ import { isNumber } from "lodash";
31
34
 
32
- export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOptions = any>
33
- implements PageController
34
- {
35
+ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOptions = any> {
35
36
  public readonly emitter: Emittery<AppEmitterEvent<TAttributes>>;
36
37
  public readonly mobxUtils = {
37
38
  autorun,
@@ -48,6 +49,7 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
48
49
  private store = this.manager.store;
49
50
  public readonly isAddApp: boolean;
50
51
  public readonly isReplay = this.manager.isReplay;
52
+ private whiteBoardView?: WhiteBoardView;
51
53
 
52
54
  constructor(
53
55
  private manager: AppManager,
@@ -60,7 +62,7 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
60
62
  this.isAddApp = appProxy.isAddApp;
61
63
  }
62
64
 
63
- public getDisplayer = () => {
65
+ public get displayer(){
64
66
  return this.manager.displayer;
65
67
  };
66
68
 
@@ -78,32 +80,58 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
78
80
  }
79
81
  };
80
82
 
81
- public getView = (): View | undefined => {
83
+ public get view(): View | undefined {
82
84
  return this.appProxy.view;
83
85
  };
84
86
 
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);
87
+ public createWhiteBoardView = (size?: number): WhiteBoardView => {
88
+ if (this.whiteBoardView) {
89
+ return this.whiteBoardView;
93
90
  }
94
- };
91
+ let view = this.view;
92
+ if (!view) {
93
+ view = this.appProxy.createAppDir();
94
+ }
95
+ const viewWrapper = document.createElement("div");
96
+ viewWrapper.className = "window-manager-view-wrapper";
97
+ this.box.$content.parentElement?.appendChild(viewWrapper);
98
+ const removeViewWrapper = () => {
99
+ this.box.$content.parentElement?.removeChild(viewWrapper);
100
+ }
101
+ view.divElement = viewWrapper;
102
+ if (this.isAddApp) {
103
+ this.initPageSize(size);
104
+ }
105
+ this.whiteBoardView = new WhiteBoardView(this, this.appProxy, removeViewWrapper);
106
+ return this.whiteBoardView;
107
+ }
108
+
109
+ private initPageSize = (size?: number) => {
110
+ if (!isNumber(size)) return;
111
+ if (!this.appProxy.scenePath) return;
112
+ if (this.appProxy.pageState.length >= size) return;
113
+ if (size <= 0 || size >= MAX_PAGE_SIZE) {
114
+ throw Error(`[WindowManager]: size ${size} muse be in range [1, ${MAX_PAGE_SIZE}]`);
115
+ }
116
+ const needInsert = size - this.appProxy.pageState.length;
117
+ const startPageNumber = this.appProxy.pageState.length;
118
+ const scenes = new Array(needInsert).fill({}).map((_, index) => {
119
+ return { name: `${startPageNumber + index + 1}` };
120
+ });
121
+ putScenes(this.room, this.appProxy.scenePath, scenes);
122
+ }
95
123
 
96
124
  public getInitScenePath = () => {
97
125
  return this.manager.getAppInitPath(this.appId);
98
126
  };
99
127
 
100
128
  /** Get App writable status. */
101
- public getIsWritable = (): boolean => {
129
+ public get isWritable(): boolean {
102
130
  return this.manager.canOperate;
103
131
  };
104
132
 
105
133
  /** Get the App Window UI box. */
106
- public getBox = (): ReadonlyTeleBox => {
134
+ public get box(): ReadonlyTeleBox {
107
135
  const box = this.boxManager.getBox(this.appId);
108
136
  if (box) {
109
137
  return box;
@@ -112,10 +140,25 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
112
140
  }
113
141
  };
114
142
 
115
- public getRoom = (): Room | undefined => {
143
+ public get room(): Room | undefined {
116
144
  return this.manager.room;
117
145
  };
118
146
 
147
+ public get members() {
148
+ return this.manager.members;
149
+ }
150
+
151
+ public get memberState(): Member {
152
+ const self = findMemberByUid(this.room, this.manager.uid);
153
+ if (!self) {
154
+ throw new Error(`Member ${this.manager.uid} not found.`);
155
+ }
156
+ return {
157
+ uid: this.manager.uid,
158
+ ...self,
159
+ }
160
+ }
161
+
119
162
  /** @deprecated Use context.storage.setState instead. */
120
163
  public setAttributes = (attributes: TAttributes) => {
121
164
  this.manager.safeSetAttributes({ [this.appId]: attributes });
@@ -128,11 +171,12 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
128
171
  }
129
172
  };
130
173
 
174
+ /** @deprecated Use Pages api instead. */
131
175
  public setScenePath = async (scenePath: string): Promise<void> => {
132
176
  if (!this.appProxy.box) return;
133
177
  this.appProxy.setFullPath(scenePath);
134
178
  // 兼容 15 版本 SDK 的切页
135
- this.getRoom()?.setScenePath(scenePath);
179
+ this.room?.setScenePath(scenePath);
136
180
  };
137
181
 
138
182
  /** Get the local App options. */
@@ -196,55 +240,4 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
196
240
  public removeMagixEventListener = this.manager.displayer.removeMagixEventListener.bind(
197
241
  this.manager.displayer
198
242
  ) as MagixEventRemoveListener<TMagixEventPayloads>;
199
-
200
- /** PageController */
201
- public nextPage = async (): Promise<boolean> => {
202
- const nextIndex = this.pageState.index + 1;
203
- if (nextIndex > this.pageState.length - 1) {
204
- console.warn("[WindowManager] nextPage: index out of range");
205
- return false;
206
- }
207
- this.appProxy.setSceneIndex(nextIndex);
208
- return true;
209
- };
210
-
211
- public prevPage = async (): Promise<boolean> => {
212
- const nextIndex = this.pageState.index - 1;
213
- if (nextIndex < 0) {
214
- console.warn("[WindowManager] prevPage: index out of range");
215
- return false;
216
- }
217
- this.appProxy.setSceneIndex(nextIndex);
218
- return true;
219
- };
220
-
221
- public addPage = async (params?: AddPageParams) => {
222
- const after = params?.after;
223
- const scene = params?.scene;
224
- const scenePath = this.appProxy.scenePath;
225
- if (!scenePath) return;
226
- if (after) {
227
- const nextIndex = this.pageState.index + 1;
228
- putScenes(this.manager.room, scenePath, [scene || {}], nextIndex);
229
- } else {
230
- putScenes(this.manager.room, scenePath, [scene || {}]);
231
- }
232
- };
233
-
234
- public removePage = async (index?: number): Promise<boolean> => {
235
- const needRemoveIndex = index === undefined ? this.pageState.index : index;
236
- if (this.pageState.length === 1) {
237
- console.warn(`[WindowManager]: can not remove the last page`);
238
- return false;
239
- }
240
- if (needRemoveIndex < 0 || needRemoveIndex >= this.pageState.length) {
241
- console.warn(`[WindowManager]: page index ${index} out of range`);
242
- return false;
243
- }
244
- return this.appProxy.removeSceneByIndex(needRemoveIndex);;
245
- }
246
-
247
- public get pageState(): PageState {
248
- return this.appProxy.pageState;
249
- }
250
243
  }
@@ -9,11 +9,15 @@ export type AppPageStateParams = {
9
9
  };
10
10
 
11
11
  export class AppPageStateImpl {
12
- private sceneNode: ScenesCallbacksNode | null = null;
12
+ public sceneNode: ScenesCallbacksNode | null = null;
13
+ private scenePath?: string;
14
+ private view?: View;
13
15
 
14
16
  constructor(private params: AppPageStateParams) {
15
17
  const { displayer, scenePath } = this.params;
18
+ this.view = this.params.view;
16
19
  if (scenePath) {
20
+ this.scenePath = scenePath;
17
21
  this.sceneNode = displayer.createScenesCallback(scenePath, {
18
22
  onAddScene: this.onSceneChange,
19
23
  onRemoveScene: this.onSceneChange,
@@ -21,24 +25,39 @@ export class AppPageStateImpl {
21
25
  }
22
26
  }
23
27
 
24
- private onSceneChange = (node: ScenesCallbacksNode) => {
25
- this.sceneNode = node;
28
+ public createSceneNode = (scenePath: string) => {
29
+ this.scenePath = scenePath;
30
+ if (this.sceneNode) {
31
+ this.sceneNode.dispose();
32
+ }
33
+ this.sceneNode = this.params.displayer.createScenesCallback(scenePath, {
34
+ onAddScene: this.onSceneChange,
35
+ onRemoveScene: this.onSceneChange,
36
+ });
37
+ return this.sceneNode;
38
+ }
39
+
40
+ public setView(view: View) {
41
+ this.view = view;
42
+ }
43
+
44
+ private onSceneChange = () => {
26
45
  this.params.notifyPageStateChange();
27
46
  };
28
47
 
29
48
  public getFullPath(index: number) {
30
49
  const scenes = this.sceneNode?.scenes;
31
- if (this.params.scenePath && scenes) {
50
+ if (this.scenePath && scenes) {
32
51
  const name = scenes[index];
33
52
  if (name) {
34
- return `${this.params.scenePath}/${name}`;
53
+ return `${this.scenePath}/${name}`;
35
54
  }
36
55
  }
37
56
  }
38
57
 
39
58
  public toObject(): PageState {
40
59
  return {
41
- index: this.params.view?.focusSceneIndex || 0,
60
+ index: this.view?.focusSceneIndex || 0,
42
61
  length: this.sceneNode?.scenes.length || 0,
43
62
  };
44
63
  }
@@ -3,15 +3,22 @@ import { AppAttributes, AppEvents, Events, SETUP_APP_DELAY } from "../constants"
3
3
  import { AppContext } from "./AppContext";
4
4
  import { AppPageStateImpl } from "./AppPageStateImpl";
5
5
  import { appRegister } from "../Register";
6
- import { autorun } from "white-web-sdk";
6
+ import { AppViewSync } from "./AppViewSync";
7
+ import { autorun, reaction, toJS } from "white-web-sdk";
8
+ import { boxEmitter } from "../BoxEmitter";
7
9
  import { BoxManagerNotFoundError } from "../Utils/error";
10
+ import { calculateNextIndex } from "../Page";
11
+ import { combine, Val } from "value-enhancer";
8
12
  import { debounce, get } from "lodash";
9
13
  import { emitter } from "../InternalEmitter";
10
14
  import { Fields } from "../AttributesDelegate";
11
15
  import { log } from "../Utils/log";
16
+ import { SideEffectManager } from "side-effect-manager";
17
+ import type { ICamera, ISize } from "../AttributesDelegate";
12
18
  import {
13
19
  entireScenes,
14
20
  getScenePath,
21
+ putScenes,
15
22
  removeScenes,
16
23
  setScenePath,
17
24
  setViewFocusScenePath,
@@ -23,13 +30,11 @@ import type {
23
30
  setAppOptions,
24
31
  AppListenerKeys,
25
32
  } from "../index";
26
- import type { SceneState, View, SceneDefinition } from "white-web-sdk";
33
+ import type { SceneState, View, SceneDefinition, Camera } from "white-web-sdk";
27
34
  import type { AppManager } from "../AppManager";
28
35
  import type { NetlessApp } from "../typings";
29
36
  import type { ReadonlyTeleBox } from "@netless/telebox-insider";
30
37
  import type { PageRemoveService, PageState } from "../Page";
31
- import { calculateNextIndex } from "../Page";
32
- import { boxEmitter } from "../BoxEmitter";
33
38
 
34
39
  export type AppEmitter = Emittery<AppEmitterEvent>;
35
40
 
@@ -37,6 +42,7 @@ export class AppProxy implements PageRemoveService {
37
42
  public kind: string;
38
43
  public id: string;
39
44
  public scenePath?: string;
45
+ private appScenePath: string;
40
46
  public appEmitter: AppEmitter;
41
47
  public scenes?: SceneDefinition[];
42
48
 
@@ -45,16 +51,27 @@ export class AppProxy implements PageRemoveService {
45
51
  private appProxies = this.manager.appProxies;
46
52
  private viewManager = this.manager.viewManager;
47
53
  private store = this.manager.store;
54
+ public uid = this.manager.uid;
48
55
 
49
56
  public isAddApp: boolean;
50
57
  private status: "normal" | "destroyed" = "normal";
51
58
  private stateKey: string;
52
- private _pageState: AppPageStateImpl;
59
+ public _pageState: AppPageStateImpl;
53
60
  private _prevFullPath: string | undefined;
54
61
 
55
62
  public appResult?: NetlessApp<any>;
56
63
  public appContext?: AppContext<any, any>;
57
64
 
65
+ private sideEffectManager = new SideEffectManager();
66
+
67
+ public camera$ = new Val<ICamera | undefined>(undefined);
68
+ public size$ = new Val<ISize | undefined>(undefined);
69
+
70
+ private appViewSync?: AppViewSync;
71
+
72
+ public box$ = new Val<ReadonlyTeleBox | undefined>(undefined);
73
+ public view$ = new Val<View | undefined>(undefined);
74
+
58
75
  constructor(
59
76
  private params: BaseInsertParams,
60
77
  private manager: AppManager,
@@ -63,6 +80,7 @@ export class AppProxy implements PageRemoveService {
63
80
  ) {
64
81
  this.kind = params.kind;
65
82
  this.id = appId;
83
+ this.appScenePath = `/${this.id}-app-dir`;
66
84
  this.stateKey = `${this.id}_state`;
67
85
  this.appProxies.set(this.id, this);
68
86
  this.appEmitter = new Emittery();
@@ -75,12 +93,68 @@ export class AppProxy implements PageRemoveService {
75
93
  // 只有传入了 scenePath 的 App 才会创建 View
76
94
  this.createView();
77
95
  }
96
+ if (!this.scenePath) {
97
+ this.scenePath = this.appScenePath;
98
+ }
78
99
  this._pageState = new AppPageStateImpl({
79
100
  displayer: this.manager.displayer,
80
101
  scenePath: this.scenePath,
81
102
  view: this.view,
82
103
  notifyPageStateChange: this.notifyPageStateChange,
83
104
  });
105
+ this.sideEffectManager.add(() => () => this._pageState.destroy());
106
+ this.sideEffectManager.add(() =>
107
+ emitter.on("roomMembersChange", members => {
108
+ this.appEmitter.emit("roomMembersChange", members);
109
+ })
110
+ );
111
+ this.camera$.setValue(toJS(this.appAttributes.camera));
112
+ this.size$.setValue(toJS(this.appAttributes.size));
113
+ this.sideEffectManager.add(() => {
114
+ return this.manager.refresher.add(`${this.id}-camera`, () => {
115
+ return reaction(
116
+ () => this.appAttributes?.camera,
117
+ camera => {
118
+ if (camera && camera.id !== this.uid) {
119
+ this.camera$.setValue(toJS(camera));
120
+ }
121
+ }
122
+ );
123
+ });
124
+ });
125
+ this.sideEffectManager.add(() => {
126
+ return this.manager.refresher.add(`${this.id}-size`, () => {
127
+ return reaction(
128
+ () => this.appAttributes?.size,
129
+ size => {
130
+ if (size && size.id !== this.uid) {
131
+ this.size$.setValue(toJS(size));
132
+ }
133
+ }
134
+ );
135
+ });
136
+ });
137
+ combine([this.box$, this.view$]).subscribe(([box, view]) => {
138
+ if (box && view) {
139
+ const appViewSync = new AppViewSync(this);
140
+ this.appViewSync = appViewSync;
141
+ this.sideEffectManager.add(() => () => appViewSync.destroy());
142
+ }
143
+ });
144
+ }
145
+
146
+ public createAppDir() {
147
+ const scenePath = this.scenePath || this.appScenePath;
148
+ const sceneNode = this._pageState.createSceneNode(scenePath);
149
+ if (!sceneNode) {
150
+ putScenes(this.manager.room, scenePath, [{ name: "1" }]);
151
+ this._pageState.createSceneNode(scenePath);
152
+ this.setSceneIndex(0);
153
+ }
154
+ this.scenes = entireScenes(this.manager.displayer)[scenePath];
155
+ const view = this.createView();
156
+ this._pageState.setView(view);
157
+ return view;
84
158
  }
85
159
 
86
160
  private initScenes() {
@@ -131,7 +205,7 @@ export class AppProxy implements PageRemoveService {
131
205
  }
132
206
 
133
207
  public setFullPath(path: string) {
134
- this.manager.safeUpdateAttributes(["apps", this.id, Fields.FullPath], path);
208
+ this.store.updateAppAttributes(this.id, Fields.FullPath, path);
135
209
  }
136
210
 
137
211
  public async baseInsertApp(skipUpdate = false): Promise<{ appId: string; app: NetlessApp }> {
@@ -160,7 +234,7 @@ export class AppProxy implements PageRemoveService {
160
234
  }
161
235
 
162
236
  public get box(): ReadonlyTeleBox | undefined {
163
- return this.boxManager?.getBox(this.id);
237
+ return this.box$.value;
164
238
  }
165
239
 
166
240
  private async setupApp(
@@ -194,13 +268,14 @@ export class AppProxy implements PageRemoveService {
194
268
  this.fixMobileSize();
195
269
  }, SETUP_APP_DELAY);
196
270
  });
197
- this.boxManager?.createBox({
271
+ const box = this.boxManager?.createBox({
198
272
  appId: appId,
199
273
  app,
200
274
  options,
201
275
  canOperate: this.manager.canOperate,
202
276
  smartPosition: this.isAddApp,
203
277
  });
278
+ this.box$.setValue(box);
204
279
  if (this.isAddApp && this.box) {
205
280
  this.store.updateAppState(appId, AppAttributes.ZIndex, this.box.zIndex);
206
281
  this.boxManager.focusBox({ appId }, false);
@@ -395,14 +470,17 @@ export class AppProxy implements PageRemoveService {
395
470
  return fullPath;
396
471
  }
397
472
 
398
- private async createView(): Promise<View> {
399
- const view = await this.viewManager.createView(this.id);
473
+ private createView(): View {
474
+ const view = this.viewManager.createView(this.id);
475
+ this.view$.setValue(view);
400
476
  this.setViewFocusScenePath();
401
477
  return view;
402
478
  }
403
479
 
404
480
  public notifyPageStateChange = debounce(() => {
405
- this.appEmitter.emit("pageStateChange", this.pageState);
481
+ if (this.pageState) {
482
+ this.appEmitter.emit("pageStateChange", this.pageState);
483
+ }
406
484
  }, 50);
407
485
 
408
486
  public get pageState(): PageState {
@@ -412,7 +490,7 @@ export class AppProxy implements PageRemoveService {
412
490
  // PageRemoveService
413
491
  public async removeSceneByIndex(index: number) {
414
492
  const scenePath = this._pageState.getFullPath(index);
415
- if (scenePath) {
493
+ if (scenePath && this.pageState) {
416
494
  const nextIndex = calculateNextIndex(index, this.pageState);
417
495
  // 只修改 focus path 不修改 FullPath
418
496
  this.setSceneIndexWithoutSync(nextIndex);
@@ -448,6 +526,22 @@ export class AppProxy implements PageRemoveService {
448
526
  }
449
527
  }
450
528
 
529
+ public storeCamera = (camera: ICamera) => {
530
+ this.store.updateAppAttributes(this.id, Fields.Camera, camera);
531
+ };
532
+
533
+ public storeSize = (size: ISize) => {
534
+ this.store.updateAppAttributes(this.id, Fields.Size, size);
535
+ };
536
+
537
+ public moveCamera = (camera: Camera) => {
538
+ if (!this.camera$.value) {
539
+ return;
540
+ }
541
+ const nextCamera = { ...this.camera$.value, ...camera };
542
+ this.storeCamera(nextCamera);
543
+ };
544
+
451
545
  public async destroy(
452
546
  needCloseBox: boolean,
453
547
  cleanAttrs: boolean,
@@ -463,6 +557,7 @@ export class AppProxy implements PageRemoveService {
463
557
  console.error("[WindowManager]: notifyApp error", error.message, error.stack);
464
558
  }
465
559
  this.appEmitter.clearListeners();
560
+ this.sideEffectManager.flushAll();
466
561
  emitter.emit(`destroy-${this.id}` as any, { error });
467
562
  if (needCloseBox) {
468
563
  this.boxManager?.closeBox(this.id, skipUpdate);
@@ -474,7 +569,6 @@ export class AppProxy implements PageRemoveService {
474
569
  }
475
570
  }
476
571
  this.appProxies.delete(this.id);
477
- this._pageState.destroy();
478
572
 
479
573
  this.viewManager.destroyView(this.id);
480
574
  this.manager.appStatus.delete(this.id);
@@ -482,6 +576,9 @@ export class AppProxy implements PageRemoveService {
482
576
  this.manager.refresher?.remove(this.stateKey);
483
577
  this.manager.refresher?.remove(`${this.id}-fullPath`);
484
578
  this._prevFullPath = undefined;
579
+ this.camera$.destroy();
580
+ this.size$.destroy();
581
+ this.box$.destroy();
485
582
  }
486
583
 
487
584
  public close(): Promise<void> {
@@ -0,0 +1,69 @@
1
+ import { CameraSynchronizer } from "../View/CameraSynchronizer";
2
+ import { SideEffectManager } from "side-effect-manager";
3
+ import type { Camera, View } from "white-web-sdk";
4
+ import type { AppProxy } from "./AppProxy";
5
+ import { isEqual } from "lodash";
6
+
7
+ export class AppViewSync {
8
+ private sem = new SideEffectManager();
9
+ private synchronizer: CameraSynchronizer;
10
+
11
+ constructor(private appProxy: AppProxy) {
12
+ this.synchronizer = new CameraSynchronizer((camera: Camera) => {
13
+ this.appProxy.storeCamera({
14
+ id: this.appProxy.uid,
15
+ ...camera,
16
+ });
17
+ });
18
+ this.bindView(appProxy.view);
19
+ this.sem.add(() => this.appProxy.camera$.subscribe(camera => {
20
+ const size = this.appProxy.size$.value;
21
+ if (camera && size) {
22
+ this.synchronizer.onRemoteUpdate(camera, size);
23
+ }
24
+ }));
25
+ const box = this.appProxy.box;
26
+ if (box && box.contentStageRect) {
27
+ this.synchronizer.setRect(box.contentStageRect);
28
+ this.sem.add(() =>
29
+ box._contentStageRect$.subscribe(rect => {
30
+ if (rect) {
31
+ this.synchronizer.setRect(rect);
32
+ }
33
+ }),
34
+ );
35
+ if (!this.appProxy.size$.value) {
36
+ this.appProxy.storeSize({
37
+ id: this.appProxy.uid,
38
+ width: box.contentStageRect.width,
39
+ height: box.contentStageRect.height,
40
+ });
41
+ }
42
+ }
43
+ }
44
+
45
+ public bindView = (view?: View) => {
46
+ if (!view) return;
47
+ this.synchronizer.setView(view);
48
+ this.sem.add(() => {
49
+ view.callbacks.on("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
50
+ return () =>
51
+ view.callbacks.off("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
52
+ });
53
+ };
54
+
55
+ private onCameraUpdatedByDevice = (camera: Camera) => {
56
+ this.synchronizer.onLocalCameraUpdate(camera);
57
+ const stage = this.appProxy.box?.contentStageRect;
58
+ if (stage) {
59
+ const size = { width: stage.width, height: stage.height, id: this.appProxy.uid };
60
+ if (!isEqual(size, this.appProxy.size$.value)) {
61
+ this.appProxy.storeSize(size);
62
+ }
63
+ }
64
+ };
65
+
66
+ public destroy() {
67
+ this.sem.flushAll();
68
+ }
69
+ }
@@ -37,7 +37,7 @@ 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._context.getIsWritable()) {
40
+ if (this._context.isWritable) {
41
41
  if (this.id === null) {
42
42
  if (context.isAddApp && defaultState) {
43
43
  this.setState(defaultState);
@@ -115,7 +115,7 @@ export class Storage<TState extends Record<string, any> = any> implements Storag
115
115
  return;
116
116
  }
117
117
 
118
- if (!this._context.getIsWritable()) {
118
+ if (!this._context.isWritable) {
119
119
  console.error(new Error(`Cannot setState on Storage "${this.id}" without writable access`), state);
120
120
  return;
121
121
  }
@@ -165,7 +165,7 @@ export class Storage<TState extends Record<string, any> = any> implements Storag
165
165
  return;
166
166
  }
167
167
 
168
- if (!this._context.getIsWritable()) {
168
+ if (!this._context.isWritable) {
169
169
  console.error(new Error(`Cannot empty Storage "${this.id}" without writable access.`));
170
170
  return;
171
171
  }
@@ -181,7 +181,7 @@ export class Storage<TState extends Record<string, any> = any> implements Storag
181
181
  throw new Error(`Cannot delete main Storage`);
182
182
  }
183
183
 
184
- if (!this._context.getIsWritable()) {
184
+ if (!this._context.isWritable) {
185
185
  console.error(new Error(`Cannot delete Storage "${this.id}" without writable access.`));
186
186
  return;
187
187
  }