@netless/window-manager 0.4.32 → 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 (56) 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 -5
  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 +9 -8
  11. package/dist/Helper.d.ts +12 -4
  12. package/dist/InternalEmitter.d.ts +6 -1
  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 +17 -0
  17. package/dist/View/MainView.d.ts +4 -6
  18. package/dist/constants.d.ts +1 -0
  19. package/dist/index.cjs.js +21 -22
  20. package/dist/index.d.ts +6 -5
  21. package/dist/index.es.js +2512 -2059
  22. package/dist/index.umd.js +21 -22
  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 +4 -3
  28. package/pnpm-lock.yaml +90 -97
  29. package/src/App/AppContext.ts +72 -75
  30. package/src/App/AppPageStateImpl.ts +25 -6
  31. package/src/App/AppProxy.ts +206 -35
  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 +107 -115
  39. package/src/Cursor/index.ts +5 -5
  40. package/src/Helper.ts +12 -16
  41. package/src/InternalEmitter.ts +10 -4
  42. package/src/Page/PageController.ts +1 -0
  43. package/src/ReconnectRefresher.ts +1 -0
  44. package/src/Utils/Common.ts +6 -0
  45. package/src/View/CameraSynchronizer.ts +72 -0
  46. package/src/View/MainView.ts +53 -78
  47. package/src/constants.ts +2 -0
  48. package/src/index.ts +31 -36
  49. package/src/style.css +9 -0
  50. package/src/typings.ts +4 -0
  51. package/vite.config.js +0 -1
  52. package/dist/ContainerResizeObserver.d.ts +0 -11
  53. package/dist/index.cjs.js.map +0 -1
  54. package/dist/index.es.js.map +0 -1
  55. package/dist/index.umd.js.map +0 -1
  56. package/src/ContainerResizeObserver.ts +0 -73
@@ -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(
@@ -191,17 +306,17 @@ export class AppProxy implements PageRemoveService {
191
306
  const result = await app.setup(context);
192
307
  this.appResult = result;
193
308
  appRegister.notifyApp(this.kind, "created", { appId, result });
194
- this.afterSetupApp(boxInitState);
195
309
  this.fixMobileSize();
196
310
  }, SETUP_APP_DELAY);
197
311
  });
198
- this.boxManager?.createBox({
312
+ const box = this.boxManager?.createBox({
199
313
  appId: appId,
200
314
  app,
201
315
  options,
202
316
  canOperate: this.manager.canOperate,
203
317
  smartPosition: this.isAddApp,
204
318
  });
319
+ this.box$.setValue(box);
205
320
  if (this.isAddApp && this.box) {
206
321
  this.store.updateAppState(appId, AppAttributes.ZIndex, this.box.zIndex);
207
322
  this.boxManager.focusBox({ appId }, false);
@@ -225,14 +340,6 @@ export class AppProxy implements PageRemoveService {
225
340
  }
226
341
  }
227
342
 
228
- private afterSetupApp(boxInitState: AppInitState | undefined): void {
229
- if (boxInitState) {
230
- if (!boxInitState?.x || !boxInitState.y) {
231
- this.boxManager?.setBoxInitState(this.id);
232
- }
233
- }
234
- }
235
-
236
343
  public async onSeek(time: number) {
237
344
  this.appEmitter.emit("seek", time).catch(err => {
238
345
  console.log(`[WindowManager]: emit seek error: ${err.message}`);
@@ -354,7 +461,7 @@ export class AppProxy implements PageRemoveService {
354
461
  }
355
462
 
356
463
  private appAttributesUpdateListener = (appId: string) => {
357
- this.manager.refresher?.add(appId, () => {
464
+ this.manager.refresher.add(appId, () => {
358
465
  return autorun(() => {
359
466
  const attrs = this.manager.attributes[appId];
360
467
  if (attrs) {
@@ -362,7 +469,7 @@ export class AppProxy implements PageRemoveService {
362
469
  }
363
470
  });
364
471
  });
365
- this.manager.refresher?.add(this.stateKey, () => {
472
+ this.manager.refresher.add(this.stateKey, () => {
366
473
  return autorun(() => {
367
474
  const appState = this.appAttributes?.state;
368
475
  if (appState?.zIndex > 0 && appState.zIndex !== this.box?.zIndex) {
@@ -370,13 +477,13 @@ export class AppProxy implements PageRemoveService {
370
477
  }
371
478
  });
372
479
  });
373
- this.manager.refresher?.add(`${appId}-fullPath`, () => {
480
+ this.manager.refresher.add(`${appId}-fullPath`, () => {
374
481
  return autorun(() => {
375
482
  const fullPath = this.appAttributes?.fullPath;
376
483
  this.setFocusScenePathHandler(fullPath);
377
- if (this._prevFullPath !== fullPath) {
484
+ if (this.fullPath$.value !== fullPath) {
378
485
  this.notifyPageStateChange();
379
- this._prevFullPath = fullPath;
486
+ this.fullPath$.setValue(fullPath);
380
487
  }
381
488
  });
382
489
  });
@@ -404,14 +511,17 @@ export class AppProxy implements PageRemoveService {
404
511
  return fullPath;
405
512
  }
406
513
 
407
- private async createView(): Promise<View> {
408
- 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);
409
517
  this.setViewFocusScenePath();
410
518
  return view;
411
519
  }
412
520
 
413
521
  public notifyPageStateChange = debounce(() => {
414
- this.appEmitter.emit("pageStateChange", this.pageState);
522
+ if (this.pageState) {
523
+ this.appEmitter.emit("pageStateChange", this.pageState);
524
+ }
415
525
  }, 50);
416
526
 
417
527
  public get pageState(): PageState {
@@ -421,7 +531,7 @@ export class AppProxy implements PageRemoveService {
421
531
  // PageRemoveService
422
532
  public async removeSceneByIndex(index: number) {
423
533
  const scenePath = this._pageState.getFullPath(index);
424
- if (scenePath) {
534
+ if (scenePath && this.pageState) {
425
535
  const nextIndex = calculateNextIndex(index, this.pageState);
426
536
  // 只修改 focus path 不修改 FullPath
427
537
  this.setSceneIndexWithoutSync(nextIndex);
@@ -457,6 +567,31 @@ export class AppProxy implements PageRemoveService {
457
567
  }
458
568
  }
459
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
+
460
595
  public async destroy(
461
596
  needCloseBox: boolean,
462
597
  cleanAttrs: boolean,
@@ -472,6 +607,7 @@ export class AppProxy implements PageRemoveService {
472
607
  console.error("[WindowManager]: notifyApp error", error.message, error.stack);
473
608
  }
474
609
  this.appEmitter.clearListeners();
610
+ this.sideEffectManager.flushAll();
475
611
  emitter.emit(`destroy-${this.id}` as any, { error });
476
612
  if (needCloseBox) {
477
613
  this.boxManager?.closeBox(this.id, skipUpdate);
@@ -483,14 +619,49 @@ export class AppProxy implements PageRemoveService {
483
619
  }
484
620
  }
485
621
  this.appProxies.delete(this.id);
486
- this._pageState.destroy();
487
622
 
488
623
  this.viewManager.destroyView(this.id);
489
624
  this.manager.appStatus.delete(this.id);
490
- this.manager.refresher?.remove(this.id);
491
- this.manager.refresher?.remove(this.stateKey);
492
- this.manager.refresher?.remove(`${this.id}-fullPath`);
493
- 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");
494
665
  }
495
666
 
496
667
  public close(): Promise<void> {
@@ -0,0 +1,73 @@
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
+ import { combine } from "value-enhancer";
7
+
8
+ export class AppViewSync {
9
+ private sem = new SideEffectManager();
10
+ private synchronizer: CameraSynchronizer;
11
+
12
+ constructor(private appProxy: AppProxy) {
13
+ this.synchronizer = new CameraSynchronizer((camera: Camera) => {
14
+ this.appProxy.storeCamera({
15
+ id: this.appProxy.uid,
16
+ ...camera,
17
+ });
18
+ });
19
+ this.bindView(appProxy.view);
20
+ this.sem.add(() => this.appProxy.camera$.subscribe(camera => {
21
+ const size = this.appProxy.size$.value;
22
+ if (camera && size) {
23
+ this.synchronizer.onRemoteUpdate(camera, size);
24
+ }
25
+ }));
26
+ this.sem.add(() => this.appProxy.size$.subscribe(size => {
27
+ if (size) {
28
+ this.synchronizer.onRemoteSizeUpdate(size);
29
+ }
30
+ }));
31
+ const box = this.appProxy.box;
32
+ if (box && box.contentStageRect) {
33
+ this.synchronizer.setRect(box.contentStageRect);
34
+ this.sem.add(() =>
35
+ box._contentStageRect$.subscribe(rect => {
36
+ if (rect) {
37
+ this.synchronizer.setRect(rect);
38
+ }
39
+ }),
40
+ );
41
+ }
42
+ this.sem.add(() => combine([this.appProxy.camera$, this.appProxy.size$]).subscribe(([camera, size]) => {
43
+ if (camera && size) {
44
+ this.synchronizer.onRemoteUpdate(camera, size);
45
+ }
46
+ }));
47
+ }
48
+
49
+ public bindView = (view?: View) => {
50
+ if (!view) return;
51
+ this.synchronizer.setView(view);
52
+ this.sem.add(() => {
53
+ view.callbacks.on("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
54
+ return () =>
55
+ view.callbacks.off("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
56
+ });
57
+ };
58
+
59
+ private onCameraUpdatedByDevice = (camera: Camera) => {
60
+ this.synchronizer.onLocalCameraUpdate(camera);
61
+ const stage = this.appProxy.box?.contentStageRect;
62
+ if (stage) {
63
+ const size = { width: stage.width, height: stage.height, id: this.appProxy.uid };
64
+ if (!isEqual(size, this.appProxy.size$.value)) {
65
+ this.appProxy.storeSize(size);
66
+ }
67
+ }
68
+ };
69
+
70
+ public destroy() {
71
+ this.sem.flushAll();
72
+ }
73
+ }
@@ -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
  }
@@ -0,0 +1,89 @@
1
+ import { putScenes } from "../Utils/Common";
2
+ import { Val } from "value-enhancer";
3
+
4
+ import type { ReadonlyVal } from "value-enhancer";
5
+ import type { AddPageParams, PageController, PageState } from "../Page";
6
+ import type { AppProxy } from "./AppProxy";
7
+ import type { AppContext } from "./AppContext";
8
+ import type { Camera, View } from "white-web-sdk";
9
+ import type { TeleBoxRect } from "@netless/telebox-insider";
10
+
11
+ export class WhiteBoardView implements PageController {
12
+ public readonly pageState$: ReadonlyVal<PageState>;
13
+
14
+ constructor(
15
+ public view: View,
16
+ protected appContext: AppContext,
17
+ protected appProxy: AppProxy,
18
+ private removeViewWrapper: () => void,
19
+ public ensureSize: (size: number) => void
20
+ ) {
21
+ const pageState$ = new Val<PageState>(appProxy.pageState);
22
+ this.pageState$ = pageState$;
23
+ appProxy.appEmitter.on("pageStateChange", pageState => {
24
+ pageState$.setValue(pageState);
25
+ });
26
+ }
27
+
28
+ public get pageState() {
29
+ return this.pageState$.value;
30
+ }
31
+
32
+ public moveCamera(camera: Camera) {
33
+ this.appProxy.moveCamera(camera);
34
+ }
35
+
36
+ public nextPage = async (): Promise<boolean> => {
37
+ const nextIndex = this.pageState.index + 1;
38
+ return this.jumpPage(nextIndex);
39
+ };
40
+
41
+ public prevPage = async (): Promise<boolean> => {
42
+ const nextIndex = this.pageState.index - 1;
43
+ return this.jumpPage(nextIndex);
44
+ };
45
+
46
+ public jumpPage = async (index: number): Promise<boolean> => {
47
+ if (index < 0 || index >= this.pageState.length) {
48
+ console.warn(`[WindowManager]: index ${index} out of range`);
49
+ return false;
50
+ }
51
+ this.appProxy.setSceneIndex(index);
52
+ return true;
53
+ };
54
+
55
+ public addPage = async (params?: AddPageParams) => {
56
+ const after = params?.after;
57
+ const scene = params?.scene;
58
+ const scenePath = this.appProxy.scenePath;
59
+ if (!scenePath) return;
60
+ if (after) {
61
+ const nextIndex = this.pageState.index + 1;
62
+ putScenes(this.appContext.room, scenePath, [scene || {}], nextIndex);
63
+ } else {
64
+ putScenes(this.appContext.room, scenePath, [scene || {}]);
65
+ }
66
+ };
67
+
68
+ public removePage = async (index?: number): Promise<boolean> => {
69
+ const needRemoveIndex = index === undefined ? this.pageState.index : index;
70
+ if (this.pageState.length === 1) {
71
+ console.warn(`[WindowManager]: can not remove the last page`);
72
+ return false;
73
+ }
74
+ if (needRemoveIndex < 0 || needRemoveIndex >= this.pageState.length) {
75
+ console.warn(`[WindowManager]: page index ${index} out of range`);
76
+ return false;
77
+ }
78
+ return this.appProxy.removeSceneByIndex(needRemoveIndex);
79
+ };
80
+
81
+ public setRect(rect: Omit<TeleBoxRect, "x" | "y">) {
82
+ this.appProxy.updateSize(rect.width, rect.height);
83
+ }
84
+
85
+ public destroy() {
86
+ this.pageState$.destroy();
87
+ this.removeViewWrapper();
88
+ }
89
+ }
package/src/App/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./AppProxy";
2
2
  export * from "./AppContext";
3
+ export * from "./WhiteboardView";