@netless/window-manager 1.0.0-canary.52 → 1.0.0-canary.54
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.
- package/dist/index.cjs.js +389 -445
- package/dist/index.es.js +434 -490
- package/dist/index.umd.js +389 -446
- package/dist/src/App/AppContext.d.ts +8 -8
- package/dist/src/AppManager.d.ts +5 -1
- package/dist/src/Cursor/index.d.ts +1 -0
- package/dist/src/Utils/Reactive.d.ts +1 -1
- package/dist/src/View/CameraSynchronizer.d.ts +4 -3
- package/dist/src/View/ScrollMode.d.ts +32 -0
- package/dist/src/View/ViewSync.d.ts +4 -2
- package/dist/src/callback.d.ts +3 -0
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/index.d.ts +21 -11
- package/dist/src/storage.d.ts +7 -0
- package/dist/src/typings.d.ts +5 -4
- package/dist/style.css +2 -1
- package/docs/api.md +10 -0
- package/package.json +4 -3
- package/pnpm-lock.yaml +28 -73
- package/src/App/AppContext.ts +19 -8
- package/src/App/WhiteboardView.ts +4 -2
- package/src/AppListener.ts +1 -10
- package/src/AppManager.ts +19 -1
- package/src/Cursor/index.ts +6 -2
- package/src/Utils/Reactive.ts +2 -1
- package/src/View/CameraSynchronizer.ts +35 -23
- package/src/View/MainView.ts +1 -0
- package/src/View/ScrollMode.ts +229 -0
- package/src/View/ViewSync.ts +31 -18
- package/src/callback.ts +3 -0
- package/src/constants.ts +3 -0
- package/src/index.ts +56 -63
- package/src/storage.ts +15 -0
- package/src/style.css +1 -1
- package/src/typings.ts +6 -3
- package/vite.config.js +1 -1
- package/dist/src/App/Storage/StorageEvent.d.ts +0 -8
- package/dist/src/App/Storage/index.d.ts +0 -39
- package/dist/src/App/Storage/typings.d.ts +0 -22
- package/dist/src/App/Storage/utils.d.ts +0 -5
- package/src/App/Storage/StorageEvent.ts +0 -21
- package/src/App/Storage/index.ts +0 -295
- package/src/App/Storage/typings.ts +0 -23
- package/src/App/Storage/utils.ts +0 -17
package/src/AppManager.ts
CHANGED
@@ -40,7 +40,7 @@ import type {
|
|
40
40
|
ScenesCallbacksNode,
|
41
41
|
SceneState,
|
42
42
|
RoomState,
|
43
|
-
} from "white-web-sdk";
|
43
|
+
Size} from "white-web-sdk";
|
44
44
|
import type { AddAppParams, BaseInsertParams, TeleBoxRect } from "./index";
|
45
45
|
import type {
|
46
46
|
BoxClosePayload,
|
@@ -50,6 +50,7 @@ import type {
|
|
50
50
|
BoxStateChangePayload,
|
51
51
|
} from "./BoxEmitter";
|
52
52
|
import type { Member } from "./Helper";
|
53
|
+
import { ScrollMode } from "./View/ScrollMode";
|
53
54
|
|
54
55
|
export class AppManager {
|
55
56
|
public displayer: Displayer;
|
@@ -64,12 +65,15 @@ export class AppManager {
|
|
64
65
|
|
65
66
|
private appListeners: AppListeners;
|
66
67
|
public boxManager?: BoxManager;
|
68
|
+
public scrollMode?: ScrollMode;
|
69
|
+
public scrollBaseSize$ = new Val<Size | null>(null);
|
67
70
|
|
68
71
|
private callbacksNode: ScenesCallbacksNode | null = null;
|
69
72
|
private appCreateQueue = new AppCreateQueue();
|
70
73
|
private sceneIndex$ = new Val<number | undefined>(undefined);
|
71
74
|
private focused$ = new Val<string | undefined>(undefined);
|
72
75
|
public members$ = new Val<Member[]>([]);
|
76
|
+
public isWritable$ = new Val<boolean>(Boolean(this.room?.isWritable));
|
73
77
|
|
74
78
|
private sideEffectManager = new SideEffectManager();
|
75
79
|
|
@@ -77,6 +81,7 @@ export class AppManager {
|
|
77
81
|
|
78
82
|
public rootDirRemoving = false;
|
79
83
|
|
84
|
+
|
80
85
|
constructor(public windowManger: WindowManager) {
|
81
86
|
this.displayer = windowManger.displayer;
|
82
87
|
this.store.setContext({
|
@@ -123,6 +128,17 @@ export class AppManager {
|
|
123
128
|
this.safeUpdateAttributes([Fields.Registered, payload.kind], payload);
|
124
129
|
});
|
125
130
|
this.members$.setValue(serializeRoomMembers(this.displayer.state.roomMembers));
|
131
|
+
|
132
|
+
emitter.on("mainViewMounted", () => {
|
133
|
+
this.windowManger.viewMode$.subscribe(viewMode => {
|
134
|
+
const playground = this.windowManger.playground$.value;
|
135
|
+
if (viewMode === "scroll" && playground) {
|
136
|
+
const scrollMode = new ScrollMode(this);
|
137
|
+
this.scrollMode = scrollMode;
|
138
|
+
scrollMode.setRoot(playground);
|
139
|
+
}
|
140
|
+
});
|
141
|
+
});
|
126
142
|
}
|
127
143
|
|
128
144
|
private onRemoveScenes = async (params: RemoveSceneParams) => {
|
@@ -581,6 +597,7 @@ export class AppManager {
|
|
581
597
|
public bindMainView(divElement: HTMLDivElement, disableCameraTransform: boolean) {
|
582
598
|
const mainView = this.mainViewProxy.view;
|
583
599
|
mainView.disableCameraTransform = disableCameraTransform;
|
600
|
+
console.log("bindMainView", mainView.disableCameraTransform);
|
584
601
|
// 延迟挂载 mainView 的 dom, 避免因为同步 camera 的闪动
|
585
602
|
wait(30).then(() => {
|
586
603
|
mainView.divElement = divElement;
|
@@ -713,6 +730,7 @@ export class AppManager {
|
|
713
730
|
}
|
714
731
|
}
|
715
732
|
emitter.emit("writableChange", isWritable);
|
733
|
+
this.isWritable$.setValue(isWritable);
|
716
734
|
};
|
717
735
|
|
718
736
|
public safeSetAttributes(attributes: any) {
|
package/src/Cursor/index.ts
CHANGED
@@ -32,9 +32,13 @@ export class CursorManager {
|
|
32
32
|
private store = this.manager.store;
|
33
33
|
public applianceIcons: ApplianceIcons = ApplianceMap;
|
34
34
|
|
35
|
+
public get playground$() {
|
36
|
+
return this.manager.windowManger.playground$;
|
37
|
+
}
|
38
|
+
|
35
39
|
constructor(private manager: AppManager, private enableCursor: boolean, applianceIcons?: ApplianceIcons) {
|
36
40
|
this.roomMembers = this.manager.room?.state.roomMembers;
|
37
|
-
const playground =
|
41
|
+
const playground = this.playground$.value;
|
38
42
|
if (playground) {
|
39
43
|
this.setupWrapper(playground);
|
40
44
|
}
|
@@ -65,7 +69,7 @@ export class CursorManager {
|
|
65
69
|
private initCursorInstance = (uid: string) => {
|
66
70
|
let cursorInstance = this.cursorInstances.get(uid);
|
67
71
|
if (!cursorInstance) {
|
68
|
-
cursorInstance = new Cursor(this.manager, uid, this,
|
72
|
+
cursorInstance = new Cursor(this.manager, uid, this, this.playground$.value);
|
69
73
|
this.cursorInstances.set(uid, cursorInstance);
|
70
74
|
}
|
71
75
|
return cursorInstance;
|
package/src/Utils/Reactive.ts
CHANGED
@@ -30,7 +30,8 @@ export const onObjectByEvent = (event: UpdateEventKind) => {
|
|
30
30
|
};
|
31
31
|
};
|
32
32
|
|
33
|
-
|
33
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
34
|
+
export const safeListenPropsUpdated = <T extends Object>(
|
34
35
|
getProps: () => T,
|
35
36
|
callback: AkkoObjectUpdatedListener<T>,
|
36
37
|
onDestroyed?: (props: unknown) => void
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { AnimationMode } from "white-web-sdk";
|
2
|
-
import { isEqual, pick, throttle } from "lodash";
|
2
|
+
import { isEmpty, isEqual, pick, throttle } from "lodash";
|
3
3
|
import type { TeleBoxRect } from "@netless/telebox-insider";
|
4
|
-
import type {
|
4
|
+
import type { View, Size } from "white-web-sdk";
|
5
5
|
import type { ICamera, ISize } from "../AttributesDelegate";
|
6
6
|
|
7
7
|
export type SaveCamera = (camera: ICamera) => void;
|
@@ -11,12 +11,13 @@ export class CameraSynchronizer {
|
|
11
11
|
public remoteSize?: ISize;
|
12
12
|
protected rect?: TeleBoxRect;
|
13
13
|
protected view?: View;
|
14
|
+
protected scale = 1;
|
14
15
|
|
15
16
|
constructor(protected saveCamera: SaveCamera) {}
|
16
17
|
|
17
|
-
public setRect = (rect: TeleBoxRect) => {
|
18
|
+
public setRect = (rect: TeleBoxRect, updateCamera = true) => {
|
18
19
|
this.rect = rect;
|
19
|
-
if (this.remoteCamera && this.remoteSize) {
|
20
|
+
if (this.remoteCamera && this.remoteSize && updateCamera) {
|
20
21
|
this.onRemoteUpdate(this.remoteCamera, this.remoteSize);
|
21
22
|
}
|
22
23
|
}
|
@@ -30,19 +31,12 @@ export class CameraSynchronizer {
|
|
30
31
|
this.remoteCamera = camera;
|
31
32
|
this.remoteSize = size;
|
32
33
|
if (this.remoteSize && this.rect) {
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
}
|
37
|
-
if (camera.centerX !== null) {
|
38
|
-
config.centerX = camera.centerX;
|
39
|
-
}
|
40
|
-
if (camera.centerY !== null) {
|
41
|
-
config.centerY = camera.centerY;
|
42
|
-
}
|
43
|
-
this.moveCamera(config);
|
34
|
+
requestAnimationFrame(() => {
|
35
|
+
this.moveCameraToContian(size);
|
36
|
+
this.moveCamera(camera);
|
37
|
+
});
|
44
38
|
}
|
45
|
-
},
|
39
|
+
}, 32);
|
46
40
|
|
47
41
|
public onRemoteSizeUpdate(size: ISize) {
|
48
42
|
this.remoteSize = size;
|
@@ -64,13 +58,31 @@ export class CameraSynchronizer {
|
|
64
58
|
this.remoteCamera = camera;
|
65
59
|
}
|
66
60
|
|
67
|
-
private
|
68
|
-
|
61
|
+
private moveCameraToContian(size: Size): void {
|
62
|
+
if (!isEmpty(size) && this.view) {
|
63
|
+
this.view.moveCameraToContain({
|
64
|
+
width: size.width,
|
65
|
+
height: size.height,
|
66
|
+
originX: -size.width / 2,
|
67
|
+
originY: -size.height / 2,
|
68
|
+
animationMode: AnimationMode.Immediately,
|
69
|
+
});
|
70
|
+
this.scale = this.view.camera.scale;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
private moveCamera(camera: ICamera): void {
|
75
|
+
if (!isEmpty(camera) && this.view && camera.centerX && camera.centerY) {
|
76
|
+
if (isEqual(camera, this.view.camera)) return;
|
77
|
+
const { centerX, centerY, scale } = camera;
|
78
|
+
const needScale = scale * (this.scale || 1);
|
79
|
+
this.view.moveCamera({
|
80
|
+
centerX: centerX,
|
81
|
+
centerY: centerY,
|
82
|
+
scale: needScale,
|
83
|
+
animationMode: AnimationMode.Immediately,
|
84
|
+
});
|
85
|
+
}
|
69
86
|
}
|
70
87
|
}
|
71
88
|
|
72
|
-
export const computedMinScale = (remoteSize: Size, currentSize: Size) => {
|
73
|
-
const wScale = currentSize.width / remoteSize.width;
|
74
|
-
const hScale = currentSize.height / remoteSize.height;
|
75
|
-
return Math.min(wScale, hScale);
|
76
|
-
}
|
package/src/View/MainView.ts
CHANGED
@@ -0,0 +1,229 @@
|
|
1
|
+
import { AnimationMode } from "white-web-sdk";
|
2
|
+
import { callbacks } from "../callback";
|
3
|
+
import { combine, derive, Val } from "value-enhancer";
|
4
|
+
import { createScrollStorage } from "../storage";
|
5
|
+
import { SCROLL_MODE_BASE_HEIGHT, SCROLL_MODE_BASE_WIDTH } from "../constants";
|
6
|
+
import { SideEffectManager } from "side-effect-manager";
|
7
|
+
import type { ReadonlyVal } from "value-enhancer";
|
8
|
+
import type { AppManager } from "../AppManager";
|
9
|
+
import type { ScrollStorage } from "../storage";
|
10
|
+
import type { Camera, Size, View } from "white-web-sdk";
|
11
|
+
|
12
|
+
function clamp(x: number, min: number, max: number): number {
|
13
|
+
return x < min ? min : x > max ? max : x;
|
14
|
+
}
|
15
|
+
|
16
|
+
export type ScrollState = {
|
17
|
+
scrollTop: number;
|
18
|
+
page: number;
|
19
|
+
maxScrollPage: number;
|
20
|
+
};
|
21
|
+
|
22
|
+
export class ScrollMode {
|
23
|
+
public readonly sideEffect = new SideEffectManager();
|
24
|
+
|
25
|
+
private readonly _root$: Val<HTMLElement | null>;
|
26
|
+
private readonly _whiteboard$: ReadonlyVal<HTMLElement | null>;
|
27
|
+
private readonly _scrollTop$: Val<number>;
|
28
|
+
public readonly _page$: ReadonlyVal<number>;
|
29
|
+
private readonly _scale$: ReadonlyVal<number>;
|
30
|
+
private readonly _size$: Val<Size>;
|
31
|
+
private readonly _mainView$: Val<View>;
|
32
|
+
|
33
|
+
private baseWidth = SCROLL_MODE_BASE_WIDTH;
|
34
|
+
private baseHeight = SCROLL_MODE_BASE_HEIGHT;
|
35
|
+
|
36
|
+
public scrollStorage: ScrollStorage;
|
37
|
+
public readonly scrollState$: ReadonlyVal<ScrollState>;
|
38
|
+
|
39
|
+
public setRoot(root: HTMLElement): void {
|
40
|
+
this._root$.setValue(root);
|
41
|
+
}
|
42
|
+
|
43
|
+
constructor(private manager: AppManager) {
|
44
|
+
this._root$ = new Val<HTMLElement | null>(null);
|
45
|
+
this._mainView$ = new Val<View>(this.manager.mainView);
|
46
|
+
this._mainView$.value.disableCameraTransform = true;
|
47
|
+
|
48
|
+
if (manager.scrollBaseSize$?.value) {
|
49
|
+
this.baseWidth = manager.scrollBaseSize$.value.width;
|
50
|
+
this.baseHeight = manager.scrollBaseSize$.value.height;
|
51
|
+
}
|
52
|
+
|
53
|
+
this.scrollStorage = createScrollStorage(manager);
|
54
|
+
const scrollTop$ = new Val(this.scrollStorage.state.scrollTop);
|
55
|
+
this._scrollTop$ = scrollTop$;
|
56
|
+
|
57
|
+
this.sideEffect.push(
|
58
|
+
this.scrollStorage.on("stateChanged", () => {
|
59
|
+
this._scrollTop$.setValue(this.scrollStorage.state.scrollTop);
|
60
|
+
})
|
61
|
+
);
|
62
|
+
|
63
|
+
const size$ = new Val<Size>(
|
64
|
+
{ width: 0, height: 0 },
|
65
|
+
{ compare: (a, b) => a.width === b.width && a.height === b.height }
|
66
|
+
);
|
67
|
+
this._size$ = size$;
|
68
|
+
this.sideEffect.add(() => {
|
69
|
+
const onSizeUpdated = size$.setValue.bind(size$);
|
70
|
+
onSizeUpdated(this._mainView$.value.size);
|
71
|
+
this._mainView$.value.callbacks.on("onSizeUpdated", onSizeUpdated);
|
72
|
+
return () => this._mainView$.value.callbacks.off("onSizeUpdated", onSizeUpdated);
|
73
|
+
});
|
74
|
+
|
75
|
+
this.sideEffect.add(() => {
|
76
|
+
const onCameraUpdated = (camera: Camera): void => {
|
77
|
+
const halfWbHeight = size$.value.height / 2 / scale$.value;
|
78
|
+
const scrollTop = camera.centerY;
|
79
|
+
this.scrollStorage.setState({
|
80
|
+
scrollTop: clamp(scrollTop, halfWbHeight, this.baseHeight - halfWbHeight),
|
81
|
+
});
|
82
|
+
callbacks.emit("userScroll");
|
83
|
+
};
|
84
|
+
this._mainView$.value.callbacks.on("onCameraUpdatedByDevice", onCameraUpdated);
|
85
|
+
return () =>
|
86
|
+
this._mainView$.value.callbacks.off("onCameraUpdatedByDevice", onCameraUpdated);
|
87
|
+
});
|
88
|
+
|
89
|
+
const scale$ = derive(size$, size => size.width / this.baseWidth);
|
90
|
+
this._scale$ = scale$;
|
91
|
+
|
92
|
+
const page$ = new Val(0);
|
93
|
+
this.sideEffect.push(
|
94
|
+
combine([scrollTop$, size$, scale$]).subscribe(([scrollTop, size, scale]) => {
|
95
|
+
if (scale > 0) {
|
96
|
+
const wbHeight = size.height / scale;
|
97
|
+
page$.setValue(Math.max(scrollTop / wbHeight - 0.5, 0));
|
98
|
+
}
|
99
|
+
})
|
100
|
+
);
|
101
|
+
this._page$ = page$;
|
102
|
+
|
103
|
+
// 5. bound$ = { contentMode: () => scale$, centerX: W / 2, centerY: H / 2, width: W, height: H }
|
104
|
+
this.sideEffect.push(
|
105
|
+
combine([scrollTop$, scale$]).subscribe(([scrollTop, scale]) => {
|
106
|
+
this.updateBound(scrollTop, size$.value, scale);
|
107
|
+
})
|
108
|
+
);
|
109
|
+
|
110
|
+
this.sideEffect.push(
|
111
|
+
size$.reaction(() => {
|
112
|
+
this.updateScroll(scrollTop$.value);
|
113
|
+
})
|
114
|
+
);
|
115
|
+
|
116
|
+
const whiteboard$ = derive(this._root$, this.getWhiteboardElement);
|
117
|
+
this._whiteboard$ = whiteboard$;
|
118
|
+
this.sideEffect.push(
|
119
|
+
whiteboard$.reaction(el => {
|
120
|
+
if (el?.parentElement) {
|
121
|
+
this.sideEffect.addEventListener(
|
122
|
+
el.parentElement,
|
123
|
+
"wheel",
|
124
|
+
this.onWheel,
|
125
|
+
{ capture: true, passive: false },
|
126
|
+
"wheel"
|
127
|
+
);
|
128
|
+
}
|
129
|
+
})
|
130
|
+
);
|
131
|
+
|
132
|
+
this.sideEffect.push(
|
133
|
+
scale$.reaction(scale => {
|
134
|
+
if (scale > 0) {
|
135
|
+
this.sideEffect.flush("initScroll");
|
136
|
+
// XXX: wait window-manager's sync behavior then we reset the camera
|
137
|
+
this.sideEffect.setTimeout(this.initScroll, 0);
|
138
|
+
}
|
139
|
+
}),
|
140
|
+
"initScroll"
|
141
|
+
);
|
142
|
+
|
143
|
+
const maxScrollPage$ = combine([this._size$, this._scale$], ([size, scale]) => {
|
144
|
+
const halfWbHeight = size.height / 2 / scale;
|
145
|
+
return (this.baseHeight - halfWbHeight) / halfWbHeight / 2 - 0.51;
|
146
|
+
});
|
147
|
+
|
148
|
+
this.scrollState$ = combine(
|
149
|
+
[this._scrollTop$, this._page$, maxScrollPage$],
|
150
|
+
([scrollTop, page, maxScrollPage]) => {
|
151
|
+
return {
|
152
|
+
scrollTop,
|
153
|
+
page,
|
154
|
+
maxScrollPage,
|
155
|
+
};
|
156
|
+
}
|
157
|
+
);
|
158
|
+
|
159
|
+
this.updateScroll(scrollTop$.value);
|
160
|
+
this.sideEffect.push(
|
161
|
+
this.scrollState$.subscribe(state => callbacks.emit("scrollStateChange", state))
|
162
|
+
);
|
163
|
+
}
|
164
|
+
|
165
|
+
private initScroll = (): void => {
|
166
|
+
const halfWbHeight = this._size$.value.height / 2 / this._scale$.value;
|
167
|
+
const scrollTop = this._scrollTop$.value;
|
168
|
+
// HACK: set a different value (+0.01) to trigger all effects above
|
169
|
+
this._scrollTop$.setValue(
|
170
|
+
clamp(scrollTop, halfWbHeight, this.baseHeight - halfWbHeight) - 0.01
|
171
|
+
);
|
172
|
+
};
|
173
|
+
|
174
|
+
private updateScroll(scrollTop: number): void {
|
175
|
+
this._mainView$.value.moveCamera({
|
176
|
+
centerY: scrollTop,
|
177
|
+
animationMode: AnimationMode.Immediately,
|
178
|
+
});
|
179
|
+
}
|
180
|
+
|
181
|
+
private updateBound(scrollTop: number, { height }: Size, scale: number): void {
|
182
|
+
if (scale > 0) {
|
183
|
+
this._mainView$.value.moveCameraToContain({
|
184
|
+
originX: 0,
|
185
|
+
originY: scrollTop - height / scale / 2,
|
186
|
+
width: this.baseWidth,
|
187
|
+
height: height / scale,
|
188
|
+
animationMode: AnimationMode.Immediately,
|
189
|
+
});
|
190
|
+
|
191
|
+
this._mainView$.value.setCameraBound({
|
192
|
+
damping: 1,
|
193
|
+
maxContentMode: () => scale,
|
194
|
+
minContentMode: () => scale,
|
195
|
+
centerX: this.baseWidth / 2,
|
196
|
+
centerY: this.baseHeight / 2,
|
197
|
+
width: this.baseWidth,
|
198
|
+
height: this.baseHeight,
|
199
|
+
});
|
200
|
+
}
|
201
|
+
}
|
202
|
+
|
203
|
+
public dispose(): void {
|
204
|
+
this.sideEffect.flushAll();
|
205
|
+
}
|
206
|
+
|
207
|
+
private getWhiteboardElement = (root: HTMLElement | null): HTMLElement | null => {
|
208
|
+
const className = ".netless-window-manager-main-view";
|
209
|
+
return root && root.querySelector(className);
|
210
|
+
};
|
211
|
+
|
212
|
+
private onWheel = (ev: WheelEvent): void => {
|
213
|
+
const target = ev.target as HTMLElement | null;
|
214
|
+
if (this.manager.canOperate && this._whiteboard$.value?.contains(target)) {
|
215
|
+
ev.preventDefault();
|
216
|
+
ev.stopPropagation();
|
217
|
+
const dy = ev.deltaY || 0;
|
218
|
+
const { width } = this._size$.value;
|
219
|
+
if (dy && width > 0) {
|
220
|
+
const halfWbHeight = this._size$.value.height / 2 / this._scale$.value;
|
221
|
+
const scrollTop = this._scrollTop$.value + dy / this._scale$.value;
|
222
|
+
this.scrollStorage.setState({
|
223
|
+
scrollTop: clamp(scrollTop, halfWbHeight, this.baseHeight - halfWbHeight),
|
224
|
+
});
|
225
|
+
callbacks.emit("userScroll");
|
226
|
+
}
|
227
|
+
}
|
228
|
+
};
|
229
|
+
}
|
package/src/View/ViewSync.ts
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
-
import {
|
2
|
-
import { CameraSynchronizer
|
3
|
-
import { combine } from "value-enhancer";
|
1
|
+
import { AnimationMode, ViewMode } from "white-web-sdk";
|
2
|
+
import { CameraSynchronizer } from "./CameraSynchronizer";
|
3
|
+
import { combine, derive } from "value-enhancer";
|
4
|
+
import { isEqual } from "lodash";
|
4
5
|
import { SideEffectManager } from "side-effect-manager";
|
5
6
|
import type { Camera, View } from "white-web-sdk";
|
6
7
|
import type { Val, ReadonlyVal } from "value-enhancer";
|
7
8
|
import type { ICamera, ISize } from "../AttributesDelegate";
|
8
9
|
import type { TeleBoxRect } from "@netless/telebox-insider";
|
10
|
+
import type { ManagerViewMode } from "../typings";
|
9
11
|
|
10
12
|
export type ViewSyncContext = {
|
11
13
|
uid: string;
|
@@ -16,7 +18,7 @@ export type ViewSyncContext = {
|
|
16
18
|
|
17
19
|
stageRect$: ReadonlyVal<TeleBoxRect>;
|
18
20
|
|
19
|
-
viewMode$?: Val<
|
21
|
+
viewMode$?: Val<ManagerViewMode>;
|
20
22
|
|
21
23
|
storeCamera: (camera: ICamera) => void;
|
22
24
|
|
@@ -28,6 +30,7 @@ export type ViewSyncContext = {
|
|
28
30
|
export class ViewSync {
|
29
31
|
private sem = new SideEffectManager();
|
30
32
|
private synchronizer: CameraSynchronizer;
|
33
|
+
private needRecoverCamera$?: ReadonlyVal<boolean>;
|
31
34
|
|
32
35
|
constructor(private context: ViewSyncContext) {
|
33
36
|
this.synchronizer = this.createSynchronizer();
|
@@ -38,15 +41,22 @@ export class ViewSync {
|
|
38
41
|
this.subscribeSize(),
|
39
42
|
this.subscribeStageRect(),
|
40
43
|
]);
|
44
|
+
if (context.viewMode$) {
|
45
|
+
this.needRecoverCamera$ = derive(context.viewMode$, mode => mode !== "scroll");
|
46
|
+
}
|
41
47
|
const camera$size$ = combine([this.context.camera$, this.context.size$]);
|
42
48
|
camera$size$.reaction(([camera, size]) => {
|
43
|
-
if (camera && size) {
|
49
|
+
if (camera && size && this.needRecoverCamera$?.value) {
|
44
50
|
this.synchronizer.onRemoteUpdate(camera, size);
|
45
51
|
camera$size$.destroy();
|
46
52
|
}
|
47
53
|
});
|
48
54
|
}
|
49
55
|
|
56
|
+
private get isBroadcastMode() {
|
57
|
+
return this.context.viewMode$?.value === ViewMode.Broadcaster;
|
58
|
+
}
|
59
|
+
|
50
60
|
private createSynchronizer = () => {
|
51
61
|
return new CameraSynchronizer((camera: ICamera) => {
|
52
62
|
this.context.camera$.setValue(camera, true);
|
@@ -58,12 +68,12 @@ export class ViewSync {
|
|
58
68
|
this.context.storeCamera(camera);
|
59
69
|
}
|
60
70
|
});
|
61
|
-
}
|
71
|
+
};
|
62
72
|
|
63
73
|
private subscribeView = () => {
|
64
74
|
return this.context.view$.subscribe(view => {
|
65
75
|
const currentCamera = this.context.camera$.value;
|
66
|
-
if (currentCamera && this.context.size$.value) {
|
76
|
+
if (currentCamera && this.context.size$.value && this.needRecoverCamera$?.value) {
|
67
77
|
view?.moveCamera({
|
68
78
|
scale: 1,
|
69
79
|
animationMode: AnimationMode.Immediately,
|
@@ -72,7 +82,7 @@ export class ViewSync {
|
|
72
82
|
}
|
73
83
|
this.bindView(view);
|
74
84
|
});
|
75
|
-
}
|
85
|
+
};
|
76
86
|
|
77
87
|
private subscribeCamera = () => {
|
78
88
|
return this.context.camera$.subscribe((camera, skipUpdate) => {
|
@@ -82,23 +92,23 @@ export class ViewSync {
|
|
82
92
|
this.synchronizer.onRemoteUpdate(camera, size);
|
83
93
|
}
|
84
94
|
});
|
85
|
-
}
|
95
|
+
};
|
86
96
|
|
87
97
|
private subscribeSize = () => {
|
88
98
|
return this.context.size$.subscribe(size => {
|
89
|
-
if (size) {
|
99
|
+
if (size && this.isBroadcastMode) {
|
90
100
|
this.synchronizer.onRemoteSizeUpdate(size);
|
91
101
|
}
|
92
102
|
});
|
93
|
-
}
|
103
|
+
};
|
94
104
|
|
95
105
|
private subscribeStageRect = () => {
|
96
106
|
return this.context.stageRect$.subscribe(rect => {
|
97
107
|
if (rect) {
|
98
|
-
this.synchronizer.setRect(rect);
|
108
|
+
this.synchronizer.setRect(rect, this.isBroadcastMode);
|
99
109
|
}
|
100
110
|
});
|
101
|
-
}
|
111
|
+
};
|
102
112
|
|
103
113
|
public bindView = (view?: View) => {
|
104
114
|
if (!view) return;
|
@@ -113,11 +123,14 @@ export class ViewSync {
|
|
113
123
|
|
114
124
|
private onCameraUpdatedByDevice = (camera: Camera) => {
|
115
125
|
if (!camera) return;
|
116
|
-
if (this.
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
126
|
+
if (!this.isBroadcastMode) return;
|
127
|
+
const { size$, stageRect$, view$ } = this.context;
|
128
|
+
if (size$.value && stageRect$.value && view$.value) {
|
129
|
+
this.synchronizer.onLocalCameraUpdate({ ...camera, id: this.context.uid });
|
130
|
+
const newSize = { ...view$.value.size, id: this.context.uid };
|
131
|
+
if (!isEqual(size$.value, newSize)) {
|
132
|
+
this.context.storeSize(newSize);
|
133
|
+
}
|
121
134
|
}
|
122
135
|
};
|
123
136
|
|
package/src/callback.ts
CHANGED
@@ -4,6 +4,7 @@ import type { CameraState, SceneState, ViewVisionMode } from "white-web-sdk";
|
|
4
4
|
import type { LoadAppEvent } from "./Register";
|
5
5
|
import type { PageState } from "./Page";
|
6
6
|
import type { ICamera, ISize } from "./AttributesDelegate";
|
7
|
+
import type { ScrollState } from "./View/ScrollMode";
|
7
8
|
|
8
9
|
export type PublicEvent = {
|
9
10
|
mainViewModeChange: ViewVisionMode;
|
@@ -25,6 +26,8 @@ export type PublicEvent = {
|
|
25
26
|
baseCameraChange: ICamera;
|
26
27
|
baseSizeChange: ISize;
|
27
28
|
fullscreenChange: TeleBoxFullscreen;
|
29
|
+
userScroll: undefined;
|
30
|
+
scrollStateChange: ScrollState;
|
28
31
|
};
|
29
32
|
|
30
33
|
export type CallbacksType = Emittery<PublicEvent>;
|