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

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 (49) hide show
  1. package/__mocks__/white-web-sdk.ts +10 -1
  2. package/dist/App/AppContext.d.ts +16 -15
  3. package/dist/App/AppPageStateImpl.d.ts +6 -2
  4. package/dist/App/AppProxy.d.ts +26 -4
  5. package/dist/App/AppViewSync.d.ts +11 -0
  6. package/dist/App/WhiteboardView.d.ts +24 -0
  7. package/dist/App/index.d.ts +1 -0
  8. package/dist/AppManager.d.ts +5 -3
  9. package/dist/AttributesDelegate.d.ts +6 -14
  10. package/dist/BoxManager.d.ts +2 -3
  11. package/dist/Helper.d.ts +12 -2
  12. package/dist/InternalEmitter.d.ts +4 -0
  13. package/dist/Page/PageController.d.ts +1 -0
  14. package/dist/ReconnectRefresher.d.ts +1 -1
  15. package/dist/Utils/Common.d.ts +1 -0
  16. package/dist/View/CameraSynchronizer.d.ts +7 -7
  17. package/dist/View/MainView.d.ts +0 -1
  18. package/dist/constants.d.ts +1 -0
  19. package/dist/index.cjs.js +12 -12
  20. package/dist/index.d.ts +6 -3
  21. package/dist/index.es.js +712 -648
  22. package/dist/index.umd.js +12 -12
  23. package/dist/style.css +1 -1
  24. package/dist/typings.d.ts +4 -0
  25. package/docs/app-context.md +98 -64
  26. package/docs/develop-app.md +2 -5
  27. package/package.json +3 -2
  28. package/pnpm-lock.yaml +11 -5
  29. package/src/App/AppContext.ts +71 -74
  30. package/src/App/AppPageStateImpl.ts +25 -6
  31. package/src/App/AppProxy.ts +206 -26
  32. package/src/App/AppViewSync.ts +73 -0
  33. package/src/App/Storage/index.ts +4 -4
  34. package/src/App/WhiteboardView.ts +89 -0
  35. package/src/App/index.ts +1 -0
  36. package/src/AppManager.ts +32 -23
  37. package/src/AttributesDelegate.ts +14 -17
  38. package/src/BoxManager.ts +5 -8
  39. package/src/Helper.ts +10 -1
  40. package/src/InternalEmitter.ts +4 -0
  41. package/src/Page/PageController.ts +1 -0
  42. package/src/ReconnectRefresher.ts +1 -0
  43. package/src/Utils/Common.ts +6 -0
  44. package/src/View/CameraSynchronizer.ts +32 -27
  45. package/src/View/MainView.ts +22 -39
  46. package/src/constants.ts +2 -0
  47. package/src/index.ts +18 -3
  48. package/src/style.css +9 -0
  49. 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,12 @@ 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 { isNumber } from "lodash";
31
33
 
32
- export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOptions = any>
33
- implements PageController
34
- {
34
+ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOptions = any> {
35
35
  public readonly emitter: Emittery<AppEmitterEvent<TAttributes>>;
36
36
  public readonly mobxUtils = {
37
37
  autorun,
@@ -48,6 +48,8 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
48
48
  private store = this.manager.store;
49
49
  public readonly isAddApp: boolean;
50
50
  public readonly isReplay = this.manager.isReplay;
51
+ private whiteBoardView?: WhiteBoardView;
52
+ public _viewWrapper?: HTMLElement;
51
53
 
52
54
  constructor(
53
55
  private manager: AppManager,
@@ -60,9 +62,13 @@ 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
+ }
68
+
69
+ public get destroyed() {
70
+ return this.appProxy.status === "destroyed";
71
+ }
66
72
 
67
73
  /** @deprecated Use context.storage.state instead. */
68
74
  public getAttributes = (): TAttributes | undefined => {
@@ -78,32 +84,58 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
78
84
  }
79
85
  };
80
86
 
81
- public getView = (): View | undefined => {
87
+ public get view(): View | undefined {
82
88
  return this.appProxy.view;
83
89
  };
84
90
 
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);
91
+ public createWhiteBoardView = (size?: number): WhiteBoardView => {
92
+ if (this.whiteBoardView) {
93
+ return this.whiteBoardView;
93
94
  }
94
- };
95
+ let view = this.view;
96
+ if (!view) {
97
+ view = this.appProxy.createAppDir();
98
+ }
99
+ const viewWrapper = document.createElement("div");
100
+ this._viewWrapper = viewWrapper;
101
+ viewWrapper.className = "window-manager-view-wrapper";
102
+ this.box.$content.parentElement?.appendChild(viewWrapper);
103
+ const removeViewWrapper = () => {
104
+ this.box.$content.parentElement?.removeChild(viewWrapper);
105
+ this._viewWrapper = undefined;
106
+ }
107
+ view.divElement = viewWrapper;
108
+ this.appProxy.fireMemberStateChange();
109
+ if (this.isAddApp) {
110
+ this.ensurePageSize(size);
111
+ }
112
+ this.whiteBoardView = new WhiteBoardView(view, this, this.appProxy, removeViewWrapper, this.ensurePageSize);
113
+ return this.whiteBoardView;
114
+ }
115
+
116
+ private ensurePageSize = (size?: number) => {
117
+ if (!isNumber(size)) return;
118
+ if (!this.appProxy.scenePath) return;
119
+ if (this.appProxy.pageState.length >= size) return;
120
+ if (size <= 0 || size >= MAX_PAGE_SIZE) {
121
+ throw Error(`[WindowManager]: size ${size} muse be in range [1, ${MAX_PAGE_SIZE}]`);
122
+ }
123
+ const needInsert = size - this.appProxy.pageState.length;
124
+ const scenes = new Array(needInsert).fill({});
125
+ this.room?.putScenes(this.appProxy.scenePath, scenes);
126
+ }
95
127
 
96
128
  public getInitScenePath = () => {
97
129
  return this.manager.getAppInitPath(this.appId);
98
130
  };
99
131
 
100
132
  /** Get App writable status. */
101
- public getIsWritable = (): boolean => {
102
- return this.manager.canOperate;
133
+ public get isWritable(): boolean {
134
+ return this.manager.canOperate && !this.destroyed;
103
135
  };
104
136
 
105
137
  /** Get the App Window UI box. */
106
- public getBox = (): ReadonlyTeleBox => {
138
+ public get box(): ReadonlyTeleBox {
107
139
  const box = this.boxManager.getBox(this.appId);
108
140
  if (box) {
109
141
  return box;
@@ -112,10 +144,25 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
112
144
  }
113
145
  };
114
146
 
115
- public getRoom = (): Room | undefined => {
147
+ public get room(): Room | undefined {
116
148
  return this.manager.room;
117
149
  };
118
150
 
151
+ public get members() {
152
+ return this.manager.members;
153
+ }
154
+
155
+ public get memberState(): Member {
156
+ const self = findMemberByUid(this.room, this.manager.uid);
157
+ if (!self) {
158
+ throw new Error(`Member ${this.manager.uid} not found.`);
159
+ }
160
+ return {
161
+ uid: this.manager.uid,
162
+ ...self,
163
+ }
164
+ }
165
+
119
166
  /** @deprecated Use context.storage.setState instead. */
120
167
  public setAttributes = (attributes: TAttributes) => {
121
168
  this.manager.safeSetAttributes({ [this.appId]: attributes });
@@ -128,11 +175,12 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
128
175
  }
129
176
  };
130
177
 
178
+ /** @deprecated Use Pages api instead. */
131
179
  public setScenePath = async (scenePath: string): Promise<void> => {
132
180
  if (!this.appProxy.box) return;
133
181
  this.appProxy.setFullPath(scenePath);
134
182
  // 兼容 15 版本 SDK 的切页
135
- this.getRoom()?.setScenePath(scenePath);
183
+ this.room?.setScenePath(scenePath);
136
184
  };
137
185
 
138
186
  /** Get the local App options. */
@@ -196,55 +244,4 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
196
244
  public removeMagixEventListener = this.manager.displayer.removeMagixEventListener.bind(
197
245
  this.manager.displayer
198
246
  ) 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
247
  }
@@ -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, ValManager } 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 , MemberState} from "white-web-sdk";
27
34
  import type { AppManager } from "../AppManager";
28
35
  import type { NetlessApp } from "../typings";
29
- import type { ReadonlyTeleBox } from "@netless/telebox-insider";
36
+ import type { ReadonlyTeleBox, TeleBoxRect } 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
- private status: "normal" | "destroyed" = "normal";
57
+ public status: "normal" | "destroyed" = "normal";
51
58
  private stateKey: string;
52
- private _pageState: AppPageStateImpl;
53
- private _prevFullPath: string | undefined;
59
+ public _pageState: AppPageStateImpl;
54
60
 
55
61
  public appResult?: NetlessApp<any>;
56
62
  public appContext?: AppContext<any, any>;
57
63
 
64
+ private sideEffectManager = new SideEffectManager();
65
+ private valManager = new ValManager();
66
+
67
+ private fullPath$ = this.valManager.attach(new Val<string | undefined>(undefined));
68
+ private appViewSync?: AppViewSync;
69
+
70
+ public camera$ = this.valManager.attach(new Val<ICamera | undefined>(undefined));
71
+ public size$ = this.valManager.attach(new Val<ISize | undefined>(undefined));
72
+ public box$ = this.valManager.attach(new Val<ReadonlyTeleBox | undefined>(undefined));
73
+ public view$ = this.valManager.attach(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,109 @@ 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.addCameraReaction();
114
+ this.addSizeReaction();
115
+ this.sideEffectManager.add(() =>
116
+ combine([this.box$, this.view$]).subscribe(([box, view]) => {
117
+ if (box && view) {
118
+ if (!this.camera$.value) {
119
+ this.storeCamera({
120
+ centerX: 0,
121
+ centerY: 0,
122
+ scale: 1,
123
+ id: this.uid,
124
+ });
125
+ this.camera$.setValue(toJS(this.appAttributes.camera));
126
+ }
127
+ if (!this.size$.value && box.contentStageRect) {
128
+ const initialRect = this.computedInitialRect(box.contentStageRect);
129
+ const width = initialRect?.width || box.contentStageRect.width;
130
+ const height = initialRect?.height || box.contentStageRect.height;
131
+ this.storeSize({
132
+ id: this.uid,
133
+ width,
134
+ height,
135
+ });
136
+ this.size$.setValue(toJS(this.appAttributes.size));
137
+ }
138
+ this.appViewSync = new AppViewSync(this);
139
+ this.sideEffectManager.add(() => () => this.appViewSync?.destroy());
140
+ }
141
+ })
142
+ );
143
+ this.sideEffectManager.add(() =>
144
+ emitter.on("memberStateChange", this.onMemberStateChange)
145
+ );
146
+ }
147
+
148
+ public fireMemberStateChange = () => {
149
+ if (this.manager.room) {
150
+ this.onMemberStateChange(this.manager.room.state.memberState);
151
+ }
152
+ }
153
+
154
+ private onMemberStateChange = (memberState: MemberState) => {
155
+ // clicker 教具把事件穿透给下层
156
+ const needPointerEventsNone = memberState.currentApplianceName === "clicker";
157
+ if (needPointerEventsNone) {
158
+ if (this.appContext?._viewWrapper) {
159
+ this.appContext._viewWrapper.style.pointerEvents = "none";
160
+ }
161
+ } else {
162
+ if (this.appContext?._viewWrapper) {
163
+ this.appContext._viewWrapper.style.pointerEvents = "auto";
164
+ }
165
+ }
166
+ }
167
+
168
+ private computedInitialRect = (boxRect: TeleBoxRect) => {
169
+ const managerRect = this.manager.boxManager?.stageRect;
170
+ if (managerRect) {
171
+ const { width, height } = managerRect;
172
+ const boxRatio = boxRect.height / boxRect.width;
173
+ if (height < 480) {
174
+ return {
175
+ width: 480 / boxRatio,
176
+ height: 480,
177
+ };
178
+ } else {
179
+ return {
180
+ width: width * 0.65,
181
+ height: height * 0.65,
182
+ };
183
+ }
184
+ }
185
+ }
186
+
187
+ public createAppDir() {
188
+ const scenePath = this.scenePath || this.appScenePath;
189
+ const sceneNode = this._pageState.createSceneNode(scenePath);
190
+ if (!sceneNode) {
191
+ putScenes(this.manager.room, scenePath, [{ name: "1" }]);
192
+ this._pageState.createSceneNode(scenePath);
193
+ this.setSceneIndex(0);
194
+ }
195
+ this.scenes = entireScenes(this.manager.displayer)[scenePath];
196
+ const view = this.createView();
197
+ this._pageState.setView(view);
198
+ return view;
84
199
  }
85
200
 
86
201
  private initScenes() {
@@ -96,7 +211,7 @@ export class AppProxy implements PageRemoveService {
96
211
  }
97
212
 
98
213
  public get view(): View | undefined {
99
- return this.manager.viewManager.getView(this.id);
214
+ return this.view$.value;
100
215
  }
101
216
 
102
217
  public get viewIndex(): number | undefined {
@@ -131,7 +246,7 @@ export class AppProxy implements PageRemoveService {
131
246
  }
132
247
 
133
248
  public setFullPath(path: string) {
134
- this.manager.safeUpdateAttributes(["apps", this.id, Fields.FullPath], path);
249
+ this.store.updateAppAttributes(this.id, Fields.FullPath, path);
135
250
  }
136
251
 
137
252
  public async baseInsertApp(skipUpdate = false): Promise<{ appId: string; app: NetlessApp }> {
@@ -160,7 +275,7 @@ export class AppProxy implements PageRemoveService {
160
275
  }
161
276
 
162
277
  public get box(): ReadonlyTeleBox | undefined {
163
- return this.boxManager?.getBox(this.id);
278
+ return this.box$.value;
164
279
  }
165
280
 
166
281
  private async setupApp(
@@ -194,13 +309,14 @@ export class AppProxy implements PageRemoveService {
194
309
  this.fixMobileSize();
195
310
  }, SETUP_APP_DELAY);
196
311
  });
197
- this.boxManager?.createBox({
312
+ const box = this.boxManager?.createBox({
198
313
  appId: appId,
199
314
  app,
200
315
  options,
201
316
  canOperate: this.manager.canOperate,
202
317
  smartPosition: this.isAddApp,
203
318
  });
319
+ this.box$.setValue(box);
204
320
  if (this.isAddApp && this.box) {
205
321
  this.store.updateAppState(appId, AppAttributes.ZIndex, this.box.zIndex);
206
322
  this.boxManager.focusBox({ appId }, false);
@@ -345,7 +461,7 @@ export class AppProxy implements PageRemoveService {
345
461
  }
346
462
 
347
463
  private appAttributesUpdateListener = (appId: string) => {
348
- this.manager.refresher?.add(appId, () => {
464
+ this.manager.refresher.add(appId, () => {
349
465
  return autorun(() => {
350
466
  const attrs = this.manager.attributes[appId];
351
467
  if (attrs) {
@@ -353,7 +469,7 @@ export class AppProxy implements PageRemoveService {
353
469
  }
354
470
  });
355
471
  });
356
- this.manager.refresher?.add(this.stateKey, () => {
472
+ this.manager.refresher.add(this.stateKey, () => {
357
473
  return autorun(() => {
358
474
  const appState = this.appAttributes?.state;
359
475
  if (appState?.zIndex > 0 && appState.zIndex !== this.box?.zIndex) {
@@ -361,13 +477,13 @@ export class AppProxy implements PageRemoveService {
361
477
  }
362
478
  });
363
479
  });
364
- this.manager.refresher?.add(`${appId}-fullPath`, () => {
480
+ this.manager.refresher.add(`${appId}-fullPath`, () => {
365
481
  return autorun(() => {
366
482
  const fullPath = this.appAttributes?.fullPath;
367
483
  this.setFocusScenePathHandler(fullPath);
368
- if (this._prevFullPath !== fullPath) {
484
+ if (this.fullPath$.value !== fullPath) {
369
485
  this.notifyPageStateChange();
370
- this._prevFullPath = fullPath;
486
+ this.fullPath$.setValue(fullPath);
371
487
  }
372
488
  });
373
489
  });
@@ -395,14 +511,17 @@ export class AppProxy implements PageRemoveService {
395
511
  return fullPath;
396
512
  }
397
513
 
398
- private async createView(): Promise<View> {
399
- const view = await this.viewManager.createView(this.id);
514
+ private createView(): View {
515
+ const view = this.viewManager.createView(this.id);
516
+ this.view$.setValue(view);
400
517
  this.setViewFocusScenePath();
401
518
  return view;
402
519
  }
403
520
 
404
521
  public notifyPageStateChange = debounce(() => {
405
- this.appEmitter.emit("pageStateChange", this.pageState);
522
+ if (this.pageState) {
523
+ this.appEmitter.emit("pageStateChange", this.pageState);
524
+ }
406
525
  }, 50);
407
526
 
408
527
  public get pageState(): PageState {
@@ -412,7 +531,7 @@ export class AppProxy implements PageRemoveService {
412
531
  // PageRemoveService
413
532
  public async removeSceneByIndex(index: number) {
414
533
  const scenePath = this._pageState.getFullPath(index);
415
- if (scenePath) {
534
+ if (scenePath && this.pageState) {
416
535
  const nextIndex = calculateNextIndex(index, this.pageState);
417
536
  // 只修改 focus path 不修改 FullPath
418
537
  this.setSceneIndexWithoutSync(nextIndex);
@@ -448,6 +567,31 @@ export class AppProxy implements PageRemoveService {
448
567
  }
449
568
  }
450
569
 
570
+ public storeCamera = (camera: ICamera) => {
571
+ this.store.updateAppAttributes(this.id, Fields.Camera, camera);
572
+ };
573
+
574
+ public storeSize = (size: ISize) => {
575
+ this.store.updateAppAttributes(this.id, Fields.Size, size);
576
+ };
577
+
578
+ public updateSize = (width: number, height: number) => {
579
+ const iSize = {
580
+ id: this.manager.uid,
581
+ width, height
582
+ }
583
+ this.store.updateAppAttributes(this.id, Fields.Size, iSize);
584
+ this.size$.setValue(iSize);
585
+ }
586
+
587
+ public moveCamera = (camera: Camera) => {
588
+ if (!this.camera$.value) {
589
+ return;
590
+ }
591
+ const nextCamera = { ...this.camera$.value, ...camera };
592
+ this.storeCamera(nextCamera);
593
+ };
594
+
451
595
  public async destroy(
452
596
  needCloseBox: boolean,
453
597
  cleanAttrs: boolean,
@@ -463,6 +607,7 @@ export class AppProxy implements PageRemoveService {
463
607
  console.error("[WindowManager]: notifyApp error", error.message, error.stack);
464
608
  }
465
609
  this.appEmitter.clearListeners();
610
+ this.sideEffectManager.flushAll();
466
611
  emitter.emit(`destroy-${this.id}` as any, { error });
467
612
  if (needCloseBox) {
468
613
  this.boxManager?.closeBox(this.id, skipUpdate);
@@ -474,14 +619,49 @@ export class AppProxy implements PageRemoveService {
474
619
  }
475
620
  }
476
621
  this.appProxies.delete(this.id);
477
- this._pageState.destroy();
478
622
 
479
623
  this.viewManager.destroyView(this.id);
480
624
  this.manager.appStatus.delete(this.id);
481
- this.manager.refresher?.remove(this.id);
482
- this.manager.refresher?.remove(this.stateKey);
483
- this.manager.refresher?.remove(`${this.id}-fullPath`);
484
- this._prevFullPath = undefined;
625
+ this.manager.refresher.remove(this.id);
626
+ this.manager.refresher.remove(this.stateKey);
627
+ this.manager.refresher.remove(`${this.id}-fullPath`);
628
+ this.valManager.destroy();
629
+ }
630
+
631
+ private addCameraReaction = () => {
632
+ this.sideEffectManager.add(() =>
633
+ this.manager.refresher.add(`${this.id}-camera`, () =>
634
+ reaction(
635
+ () => this.appAttributes?.camera,
636
+ camera => {
637
+ if (camera && camera.id !== this.uid) {
638
+ const rawCamera = toJS(camera);
639
+ if (rawCamera !== this.camera$.value) {
640
+ this.camera$.setValue(rawCamera);
641
+ }
642
+ }
643
+ }
644
+ )
645
+ )
646
+ , "camera");
647
+ }
648
+
649
+ private addSizeReaction = () => {
650
+ this.sideEffectManager.add(() =>
651
+ this.manager.refresher.add(`${this.id}-size`, () =>
652
+ reaction(
653
+ () => this.appAttributes?.size,
654
+ size => {
655
+ if (size && size.id !== this.uid) {
656
+ const rawSize = toJS(size);
657
+ if (this.size$.value !== rawSize) {
658
+ this.size$.setValue(rawSize);
659
+ }
660
+ }
661
+ }
662
+ )
663
+ )
664
+ , "size");
485
665
  }
486
666
 
487
667
  public close(): Promise<void> {