@netless/window-manager 1.0.0-canary.15 → 1.0.0-canary.18

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.
@@ -1,14 +1,16 @@
1
1
  import { callbacks } from "../callback";
2
- import { CameraSynchronizer } from "./CameraSynchronizer";
3
2
  import { createView } from "./ViewManager";
4
3
  import { debounce, get, isEqual } from "lodash";
5
4
  import { emitter } from "../InternalEmitter";
6
5
  import { Events } from "../constants";
7
6
  import { Fields } from "../AttributesDelegate";
8
- import { reaction } from "white-web-sdk";
9
- import { releaseView, setViewFocusScenePath } from "../Utils/Common";
7
+ import { reaction, toJS } from "white-web-sdk";
8
+ import { releaseView, setScenePath, setViewFocusScenePath } from "../Utils/Common";
10
9
  import { SideEffectManager } from "side-effect-manager";
11
- import type { Camera, Size, View } from "white-web-sdk";
10
+ import { Val } from "value-enhancer";
11
+ import { ViewSync } from "./ViewSync";
12
+ import type { ICamera, ISize } from "../AttributesDelegate";
13
+ import type { Size, View } from "white-web-sdk";
12
14
  import type { AppManager } from "../AppManager";
13
15
 
14
16
  export class MainViewProxy {
@@ -16,16 +18,17 @@ export class MainViewProxy {
16
18
  private mainViewIsAddListener = false;
17
19
  private mainView: View;
18
20
  private store = this.manager.store;
19
- private synchronizer: CameraSynchronizer;
20
21
 
21
22
  private sideEffectManager = new SideEffectManager();
22
23
 
24
+ public camera$ = new Val<ICamera | undefined>(undefined);
25
+ public size$ = new Val<ISize | undefined>(undefined);
26
+ public view$ = new Val<View | undefined>(undefined);
27
+
28
+ public viewSync?: ViewSync;
29
+
23
30
  constructor(private manager: AppManager) {
24
- this.synchronizer = new CameraSynchronizer(camera =>
25
- this.store.setMainViewCamera({ ...camera, id: this.manager.uid })
26
- );
27
31
  this.mainView = this.createMainView();
28
- this.moveCameraSizeByAttributes();
29
32
  emitter.once("mainViewMounted").then(() => {
30
33
  this.addMainViewListener();
31
34
  this.start();
@@ -33,20 +36,36 @@ export class MainViewProxy {
33
36
  this.startListenWritableChange();
34
37
  });
35
38
  this.sideEffectManager.add(() => [
36
- emitter.on("containerSizeRatioUpdate", this.onUpdateContainerSizeRatio),
37
39
  emitter.on("startReconnect", () => {
38
40
  releaseView(this.mainView);
39
41
  }),
40
- emitter.on("playgroundSizeChange", rect => {
41
- this.synchronizer.setRect(rect);
42
- })
43
42
  ]);
44
- const rect = this.manager.boxManager?.stageRect;
45
- if (rect) {
46
- this.synchronizer.setRect(rect);
47
- }
43
+ this.createViewSync();
44
+ this.sideEffectManager.add(() => emitter.on("focusedChange", ({ focused }) => {
45
+ if (focused === undefined) {
46
+ const scenePath = this.store.getMainViewScenePath();
47
+ if (scenePath) {
48
+ setScenePath(this.manager.room, scenePath);
49
+ }
50
+ }
51
+ }));
48
52
  }
49
53
 
54
+ public createViewSync = () => {
55
+ if (this.manager.boxManager && !this.viewSync) {
56
+ this.viewSync = new ViewSync({
57
+ uid: this.manager.uid,
58
+ view$: this.view$,
59
+ camera$: this.camera$,
60
+ size$: this.size$,
61
+ stageRect$: this.manager.boxManager?.stageRect$,
62
+ viewMode$: this.manager.windowManger.viewMode$,
63
+ storeCamera: this.storeCamera,
64
+ storeSize: this.storeSize,
65
+ });
66
+ }
67
+ };
68
+
50
69
  private startListenWritableChange = () => {
51
70
  this.sideEffectManager.add(() =>
52
71
  emitter.on("writableChange", isWritable => {
@@ -60,7 +79,18 @@ export class MainViewProxy {
60
79
  public ensureCameraAndSize() {
61
80
  if (!this.mainViewCamera || !this.mainViewSize) {
62
81
  this.manager.dispatchInternalEvent(Events.InitMainViewCamera);
63
- this.setCameraAndSize();
82
+ this.storeCamera({
83
+ id: this.manager.uid,
84
+ ...this.view.camera
85
+ });
86
+ const stageRect = this.manager.boxManager?.stageRect;
87
+ if (stageRect && !this.mainViewSize) {
88
+ this.storeSize({
89
+ id: this.manager.uid,
90
+ width: stageRect.width,
91
+ height: stageRect.height
92
+ });
93
+ }
64
94
  }
65
95
  }
66
96
 
@@ -76,10 +106,6 @@ export class MainViewProxy {
76
106
  return get(this.view, ["didRelease"]);
77
107
  }
78
108
 
79
- private moveCameraSizeByAttributes() {
80
- this.synchronizer.onRemoteUpdate(this.mainViewCamera, this.mainViewSize);
81
- }
82
-
83
109
  public start() {
84
110
  if (this.started) return;
85
111
  this.addCameraListener();
@@ -89,34 +115,64 @@ export class MainViewProxy {
89
115
 
90
116
  public addCameraReaction = () => {
91
117
  this.manager.refresher.add(Fields.MainViewCamera, this.cameraReaction);
118
+ this.manager.refresher.add(Fields.MainViewSize, this.sizeReaction);
92
119
  };
93
120
 
94
- public setCameraAndSize(): void {
95
- const stageSize = this.getStageSize();
96
- if (stageSize) {
97
- const camera = { ...this.mainView.camera, id: this.manager.uid };
98
- const size = { ...stageSize, id: this.manager.uid };
99
- this.store.setMainViewCameraAndSize(camera, size);
121
+ public storeCurrentCamera = () => {
122
+ const iCamera = this.view.camera;
123
+ this.storeCamera({
124
+ id: this.manager.uid,
125
+ ...iCamera
126
+ });
127
+ }
128
+
129
+ public storeCurrentSize = () => {
130
+ const rect = this.manager.boxManager?.stageRect;
131
+ if (rect) {
132
+ this.storeSize({
133
+ id: this.manager.uid,
134
+ width: rect.width,
135
+ height: rect.height
136
+ });
100
137
  }
101
138
  }
102
139
 
140
+ public storeCamera = (camera: ICamera) => {
141
+ this.store.setMainViewCamera(camera);
142
+ };
143
+
144
+ public storeSize = (size: ISize) => {
145
+ this.store.setMainViewSize(size);
146
+ };
147
+
103
148
  private cameraReaction = () => {
104
149
  return reaction(
105
150
  () => this.mainViewCamera,
106
151
  camera => {
107
- if (camera && camera.id !== this.manager.uid) {
108
- this.synchronizer.onRemoteUpdate(camera, this.mainViewSize);
152
+ if (camera) {
153
+ const rawCamera = toJS(camera);
154
+ if (!isEqual(rawCamera, this.camera$.value)) {
155
+ this.camera$.setValue(rawCamera);
156
+ }
109
157
  }
110
158
  },
111
159
  { fireImmediately: true }
112
160
  );
113
161
  };
114
162
 
115
- public onUpdateContainerSizeRatio = () => {
116
- const size = this.store.getMainViewSize();
117
- if (size.id === this.manager.uid) {
118
- this.setCameraAndSize();
119
- }
163
+ private sizeReaction = () => {
164
+ return reaction(
165
+ () => this.mainViewSize,
166
+ size => {
167
+ if (size) {
168
+ const rawSize = toJS(size);
169
+ if (!isEqual(rawSize, this.size$.value)) {
170
+ this.size$.setValue(rawSize);
171
+ }
172
+ }
173
+ },
174
+ { fireImmediately: true }
175
+ );
120
176
  };
121
177
 
122
178
  public get view(): View {
@@ -133,7 +189,7 @@ export class MainViewProxy {
133
189
  if (mainViewScenePath) {
134
190
  setViewFocusScenePath(mainView, mainViewScenePath);
135
191
  }
136
- this.synchronizer.setView(mainView);
192
+ this.view$.setValue(mainView);
137
193
  return mainView;
138
194
  }
139
195
 
@@ -165,21 +221,6 @@ export class MainViewProxy {
165
221
  this.start();
166
222
  }
167
223
 
168
- private onCameraUpdatedByDevice = (camera: Camera) => {
169
- this.synchronizer.onLocalCameraUpdate(camera);
170
- const size = this.getStageSize();
171
- if (size && !isEqual(size, this.mainViewSize)) {
172
- this.setMainViewSize(size);
173
- }
174
- };
175
-
176
- private getStageSize(): Size | undefined {
177
- const stage = this.manager.boxManager?.stageRect;
178
- if (stage) {
179
- return { width: stage.width, height: stage.height };
180
- }
181
- }
182
-
183
224
  public addMainViewListener(): void {
184
225
  if (this.mainViewIsAddListener) return;
185
226
  if (this.view.divElement) {
@@ -212,13 +253,11 @@ export class MainViewProxy {
212
253
  }, 50);
213
254
 
214
255
  private addCameraListener() {
215
- this.view.callbacks.on("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
216
256
  this.view.callbacks.on("onCameraUpdated", this.onCameraOrSizeUpdated);
217
257
  this.view.callbacks.on("onSizeUpdated", this.onCameraOrSizeUpdated);
218
258
  }
219
259
 
220
260
  private removeCameraListener() {
221
- this.view.callbacks.off("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
222
261
  this.view.callbacks.off("onCameraUpdated", this.onCameraOrSizeUpdated);
223
262
  this.view.callbacks.off("onSizeUpdated", this.onCameraOrSizeUpdated);
224
263
  }
@@ -235,6 +274,9 @@ export class MainViewProxy {
235
274
  }
236
275
 
237
276
  public destroy() {
277
+ this.camera$.destroy();
278
+ this.size$.destroy();
279
+ this.view$.destroy();
238
280
  this.removeMainViewListener();
239
281
  this.stop();
240
282
  this.sideEffectManager.flushAll();
@@ -0,0 +1,119 @@
1
+ import { AnimationMode, ViewMode } from "white-web-sdk";
2
+ import { CameraSynchronizer } from "./CameraSynchronizer";
3
+ import { combine } from "value-enhancer";
4
+ import { isEqual } from "lodash";
5
+ import { SideEffectManager } from "side-effect-manager";
6
+ import type { Camera, View } from "white-web-sdk";
7
+ import type { Val, ReadonlyVal } from "value-enhancer";
8
+ import type { ICamera, ISize } from "../AttributesDelegate";
9
+ import type { TeleBoxRect } from "@netless/telebox-insider";
10
+
11
+ export type ViewSyncContext = {
12
+ uid: string;
13
+ // 远端 camera
14
+ camera$: Val<ICamera | undefined, boolean>;
15
+ // 远端 size
16
+ size$: Val<ISize | undefined>;
17
+
18
+ stageRect$: ReadonlyVal<TeleBoxRect>;
19
+
20
+ viewMode$?: Val<ViewMode>;
21
+
22
+ storeCamera: (camera: ICamera) => void;
23
+
24
+ storeSize: (size: ISize) => void;
25
+
26
+ view$: Val<View | undefined>;
27
+ };
28
+
29
+ export class ViewSync {
30
+ private sem = new SideEffectManager();
31
+ private synchronizer: CameraSynchronizer;
32
+
33
+ constructor(private context: ViewSyncContext) {
34
+ this.synchronizer = new CameraSynchronizer((camera: Camera) => {
35
+ const iCamera = {
36
+ id: this.context.uid,
37
+ ...camera,
38
+ };
39
+ this.context.camera$.setValue(iCamera, true);
40
+ const notStoreCamera =
41
+ this.context.viewMode$ && this.context.viewMode$.value === ViewMode.Freedom;
42
+ if (notStoreCamera) {
43
+ return;
44
+ } else {
45
+ this.context.storeCamera(iCamera);
46
+ }
47
+ });
48
+ this.bindView(this.context.view$.value);
49
+ this.sem.add(() =>
50
+ this.context.view$.subscribe(view => {
51
+ const currentCamera = this.context.camera$.value;
52
+ if (currentCamera && this.context.size$.value) {
53
+ view?.moveCamera({
54
+ scale: 1,
55
+ animationMode: AnimationMode.Immediately,
56
+ });
57
+ this.synchronizer.onRemoteUpdate(currentCamera, this.context.size$.value);
58
+ }
59
+
60
+ this.bindView(view);
61
+ })
62
+ );
63
+ this.sem.add(() =>
64
+ this.context.camera$.subscribe((camera, skipUpdate) => {
65
+ const size = this.context.size$.value;
66
+ if (camera && size && !skipUpdate) {
67
+ this.synchronizer.onRemoteUpdate(camera, size);
68
+ }
69
+ })
70
+ );
71
+ this.sem.add(() =>
72
+ this.context.size$.subscribe(size => {
73
+ if (size) {
74
+ this.synchronizer.onRemoteSizeUpdate(size);
75
+ }
76
+ })
77
+ );
78
+ this.sem.add(() =>
79
+ this.context.stageRect$.reaction(rect => {
80
+ if (rect) {
81
+ this.synchronizer.setRect(rect);
82
+ }
83
+ })
84
+ );
85
+ const camera$size$ = combine([this.context.camera$, this.context.size$]);
86
+ camera$size$.reaction(([camera, size]) => {
87
+ if (camera && size) {
88
+ this.synchronizer.onRemoteUpdate(camera, size);
89
+ camera$size$.destroy();
90
+ }
91
+ });
92
+ }
93
+
94
+ public bindView = (view?: View) => {
95
+ if (!view) return;
96
+ this.synchronizer.setView(view);
97
+ this.sem.flush("view");
98
+ this.sem.add(() => {
99
+ view.callbacks.on("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
100
+ return () =>
101
+ view.callbacks.off("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
102
+ }, "view");
103
+ };
104
+
105
+ private onCameraUpdatedByDevice = (camera: Camera) => {
106
+ this.synchronizer.onLocalCameraUpdate(camera);
107
+ const stage = this.context.stageRect$.value;
108
+ if (stage) {
109
+ const size = { width: stage.width, height: stage.height, id: this.context.uid };
110
+ if (!isEqual(size, this.context.size$.value)) {
111
+ this.context.storeSize(size);
112
+ }
113
+ }
114
+ };
115
+
116
+ public destroy() {
117
+ this.sem.flushAll();
118
+ }
119
+ }
package/src/index.ts CHANGED
@@ -54,6 +54,7 @@ import type { PublicEvent } from "./callback";
54
54
  import type Emittery from "emittery";
55
55
  import type { PageController, AddPageParams, PageState } from "./Page";
56
56
  import { boxEmitter } from "./BoxEmitter";
57
+ import { Val } from "value-enhancer";
57
58
 
58
59
  export type WindowMangerAttributes = {
59
60
  modelValue?: string;
@@ -162,6 +163,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
162
163
  public appManager?: AppManager;
163
164
  public cursorManager?: CursorManager;
164
165
  public viewMode = ViewMode.Broadcaster;
166
+ public viewMode$ = new Val<ViewMode>(ViewMode.Broadcaster);
165
167
  public isReplay = isPlayer(this.displayer);
166
168
  private _pageState?: PageStateImpl;
167
169
 
@@ -592,16 +594,19 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
592
594
  * 设置 ViewMode
593
595
  */
594
596
  public setViewMode(mode: ViewMode): void {
597
+ log("setViewMode", mode);
598
+ const mainViewProxy = this.appManager?.mainViewProxy;
595
599
  if (mode === ViewMode.Broadcaster) {
596
600
  if (this.canOperate) {
597
- this.appManager?.mainViewProxy.setCameraAndSize();
601
+ mainViewProxy?.storeCurrentCamera();
598
602
  }
599
- this.appManager?.mainViewProxy.start();
603
+ mainViewProxy?.start();
600
604
  }
601
605
  if (mode === ViewMode.Freedom) {
602
- this.appManager?.mainViewProxy.stop();
606
+ mainViewProxy?.stop();
603
607
  }
604
608
  this.viewMode = mode;
609
+ this.viewMode$.setValue(mode);
605
610
  }
606
611
 
607
612
  public setBoxState(boxState: TeleBoxState): void {
@@ -764,7 +769,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
764
769
  this.mainView.moveCamera(camera);
765
770
  this.appManager?.dispatchInternalEvent(Events.MoveCamera, camera);
766
771
  setTimeout(() => {
767
- this.appManager?.mainViewProxy.setCameraAndSize();
772
+ this.appManager?.mainViewProxy.storeCurrentCamera();
768
773
  }, 500);
769
774
  }
770
775
 
@@ -777,7 +782,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
777
782
  this.mainView.moveCameraToContain(rectangle);
778
783
  this.appManager?.dispatchInternalEvent(Events.MoveCameraToContain, rectangle);
779
784
  setTimeout(() => {
780
- this.appManager?.mainViewProxy.setCameraAndSize();
785
+ this.appManager?.mainViewProxy.storeCurrentCamera();
781
786
  }, 500);
782
787
  }
783
788
 
@@ -918,6 +923,24 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> imple
918
923
  emitter.emit("containerSizeRatioUpdate", ratio);
919
924
  }
920
925
 
926
+ public createPPTHandler() {
927
+ return {
928
+ onPageJumpTo: (_pptUUID: string, index: number) => {
929
+ this.appManager?.focusApp?.appContext?.whiteBoardView?.jumpPage(index);
930
+ },
931
+ onPageToNext: () => {
932
+ if (this.focused) {
933
+ this.appManager?.focusApp?.appContext?.whiteBoardView?.nextPage();
934
+ }
935
+ },
936
+ onPageToPrev: () => {
937
+ if (this.focused) {
938
+ this.appManager?.focusApp?.appContext?.whiteBoardView?.prevPage();
939
+ }
940
+ }
941
+ }
942
+ }
943
+
921
944
  private isDynamicPPT(scenes: SceneDefinition[]) {
922
945
  const sceneSrc = scenes[0]?.ppt?.src;
923
946
  return sceneSrc?.startsWith("pptx://");
package/src/style.css CHANGED
@@ -180,7 +180,6 @@
180
180
  }
181
181
 
182
182
  .window-manager-view-wrapper {
183
- z-index: 5000;
184
183
  width: 100%;
185
184
  height: 100%;
186
185
  position: absolute;
@@ -1,11 +0,0 @@
1
- import type { View } from "white-web-sdk";
2
- import type { AppProxy } from "./AppProxy";
3
- export declare class AppViewSync {
4
- private appProxy;
5
- private sem;
6
- private synchronizer;
7
- constructor(appProxy: AppProxy);
8
- bindView: (view?: View | undefined) => void;
9
- private onCameraUpdatedByDevice;
10
- destroy(): void;
11
- }
@@ -1,73 +0,0 @@
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
- }