@netless/window-manager 1.0.0-canary.6 → 1.0.0-canary.61
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/README.md +30 -6
- package/dist/index.js +13539 -0
- package/dist/index.mjs +13536 -0
- package/dist/index.umd.js +13534 -46
- package/dist/{App → src/App}/AppContext.d.ts +16 -14
- package/dist/{App → src/App}/AppPageStateImpl.d.ts +0 -0
- package/dist/{App → src/App}/AppProxy.d.ts +30 -11
- package/dist/{App → src/App}/MagixEvent/index.d.ts +0 -0
- package/dist/src/App/WhiteboardView.d.ts +27 -0
- package/dist/{App → src/App}/index.d.ts +1 -0
- package/dist/src/App/type.d.ts +21 -0
- package/dist/{AppListener.d.ts → src/AppListener.d.ts} +2 -2
- package/dist/{AppManager.d.ts → src/AppManager.d.ts} +11 -6
- package/dist/{AttributesDelegate.d.ts → src/AttributesDelegate.d.ts} +5 -2
- package/dist/{BoxEmitter.d.ts → src/BoxEmitter.d.ts} +0 -0
- package/dist/{BoxManager.d.ts → src/BoxManager.d.ts} +12 -6
- package/dist/{BuiltinApps.d.ts → src/BuiltinApps.d.ts} +3 -0
- package/dist/{Cursor → src/Cursor}/Cursor.d.ts +0 -0
- package/dist/{Cursor → src/Cursor}/icons.d.ts +0 -0
- package/dist/{Cursor → src/Cursor}/index.d.ts +4 -3
- package/dist/{Helper.d.ts → src/Helper.d.ts} +4 -8
- package/dist/{InternalEmitter.d.ts → src/InternalEmitter.d.ts} +1 -4
- package/dist/{Page → src/Page}/PageController.d.ts +2 -1
- package/dist/{Page → src/Page}/index.d.ts +0 -0
- package/dist/{PageState.d.ts → src/PageState.d.ts} +0 -0
- package/dist/{ReconnectRefresher.d.ts → src/ReconnectRefresher.d.ts} +0 -0
- package/dist/{RedoUndo.d.ts → src/RedoUndo.d.ts} +0 -0
- package/dist/{Register → src/Register}/index.d.ts +4 -2
- package/dist/{Register → src/Register}/loader.d.ts +1 -1
- package/dist/src/Register/storage.d.ts +11 -0
- package/dist/{Utils → src/Utils}/AppCreateQueue.d.ts +0 -0
- package/dist/{Utils → src/Utils}/Common.d.ts +0 -0
- package/dist/{Utils → src/Utils}/Reactive.d.ts +1 -1
- package/dist/{Utils → src/Utils}/RoomHacker.d.ts +0 -0
- package/dist/{Utils → src/Utils}/error.d.ts +1 -1
- package/dist/{Utils → src/Utils}/log.d.ts +0 -0
- package/dist/src/View/CameraSynchronizer.d.ts +20 -0
- package/dist/{View → src/View}/MainView.d.ts +18 -7
- package/dist/src/View/ScrollMode.d.ts +32 -0
- package/dist/{View → src/View}/ViewManager.d.ts +0 -0
- package/dist/src/View/ViewSync.d.ts +32 -0
- package/dist/{callback.d.ts → src/callback.d.ts} +13 -1
- package/dist/{constants.d.ts → src/constants.d.ts} +12 -5
- package/dist/src/image.d.ts +19 -0
- package/dist/{index.d.ts → src/index.d.ts} +66 -17
- package/dist/src/shim.d.ts +11 -0
- package/dist/src/storage.d.ts +7 -0
- package/dist/{typings.d.ts → src/typings.d.ts} +18 -8
- package/dist/style.css +810 -1
- package/docs/api.md +10 -0
- package/docs/app-context.md +155 -27
- package/docs/mirgrate-to-1.0.md +68 -0
- package/package.json +27 -22
- package/playwright.config.ts +29 -0
- package/pnpm-lock.yaml +3141 -4483
- package/src/App/AppContext.ts +81 -46
- package/src/App/AppProxy.ts +249 -139
- package/src/App/WhiteboardView.ts +38 -14
- package/src/App/index.ts +1 -0
- package/src/App/type.ts +22 -0
- package/src/AppListener.ts +21 -21
- package/src/AppManager.ts +84 -43
- package/src/AttributesDelegate.ts +6 -3
- package/src/BoxManager.ts +76 -38
- package/src/BuiltinApps.ts +9 -8
- package/src/Cursor/Cursor.svelte +6 -2
- package/src/Cursor/Cursor.ts +16 -5
- package/src/Cursor/icons.ts +6 -0
- package/src/Cursor/index.ts +13 -10
- package/src/Helper.ts +25 -7
- package/src/InternalEmitter.ts +1 -4
- package/src/Page/PageController.ts +2 -1
- package/src/PageState.ts +1 -1
- package/src/ReconnectRefresher.ts +6 -2
- package/src/Register/index.ts +36 -14
- package/src/Register/loader.ts +20 -9
- package/src/Register/storage.ts +26 -5
- package/src/Utils/Common.ts +3 -0
- package/src/Utils/Reactive.ts +29 -27
- package/src/Utils/RoomHacker.ts +3 -0
- package/src/Utils/error.ts +2 -2
- package/src/View/CameraSynchronizer.ts +52 -37
- package/src/View/MainView.ts +118 -76
- package/src/View/ScrollMode.ts +239 -0
- package/src/View/ViewSync.ts +139 -6
- package/src/callback.ts +9 -1
- package/src/constants.ts +11 -3
- package/src/image/pencil-eraser-1.svg +3 -0
- package/src/image/pencil-eraser-2.svg +3 -0
- package/src/image/pencil-eraser-3.svg +3 -0
- package/src/index.ts +202 -58
- package/src/storage.ts +15 -0
- package/src/style.css +18 -47
- package/src/typings.ts +21 -7
- package/vite.config.js +12 -7
- package/dist/App/AppViewSync.d.ts +0 -11
- package/dist/App/Storage/StorageEvent.d.ts +0 -8
- package/dist/App/Storage/index.d.ts +0 -39
- package/dist/App/Storage/typings.d.ts +0 -22
- package/dist/App/Storage/utils.d.ts +0 -5
- package/dist/App/WhiteboardView.d.ts +0 -21
- package/dist/Register/storage.d.ts +0 -8
- package/dist/View/CameraSynchronizer.d.ts +0 -17
- package/dist/View/ViewSync.d.ts +0 -7
- package/dist/index.cjs.js +0 -46
- package/dist/index.es.js +0 -16159
- package/src/App/AppViewSync.ts +0 -68
- 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/View/MainView.ts
CHANGED
@@ -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 { AnimationMode, reaction, toJS } from "white-web-sdk";
|
8
|
+
import { releaseView, setScenePath, setViewFocusScenePath } from "../Utils/Common";
|
10
9
|
import { SideEffectManager } from "side-effect-manager";
|
11
|
-
import
|
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,56 +18,90 @@ 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();
|
32
35
|
this.ensureCameraAndSize();
|
33
36
|
this.startListenWritableChange();
|
34
37
|
});
|
35
|
-
this.sideEffectManager.add(() =>
|
36
|
-
|
37
|
-
});
|
38
|
-
this.sideEffectManager.add(() => {
|
39
|
-
return emitter.on("startReconnect", () => {
|
38
|
+
this.sideEffectManager.add(() => [
|
39
|
+
emitter.on("startReconnect", () => {
|
40
40
|
releaseView(this.mainView);
|
41
|
-
})
|
41
|
+
}),
|
42
|
+
]);
|
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
|
+
}));
|
52
|
+
this.camera$.reaction(camera => {
|
53
|
+
if (camera) {
|
54
|
+
callbacks.emit("baseCameraChange", camera);
|
55
|
+
}
|
42
56
|
});
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
this.sideEffectManager.add(() => {
|
48
|
-
return emitter.on("playgroundSizeChange", rect => {
|
49
|
-
this.synchronizer.setRect(rect);
|
50
|
-
// this.synchronizer.onLocalSizeUpdate(rect);
|
51
|
-
});
|
57
|
+
this.size$.reaction(size => {
|
58
|
+
if (size) {
|
59
|
+
callbacks.emit("baseSizeChange", size);
|
60
|
+
}
|
52
61
|
});
|
53
62
|
}
|
54
63
|
|
64
|
+
public createViewSync = () => {
|
65
|
+
if (this.manager.boxManager && !this.viewSync) {
|
66
|
+
this.viewSync = new ViewSync({
|
67
|
+
uid: this.manager.uid,
|
68
|
+
view$: this.view$,
|
69
|
+
camera$: this.camera$,
|
70
|
+
size$: this.size$,
|
71
|
+
stageRect$: this.manager.boxManager?.stageRect$,
|
72
|
+
viewMode$: this.manager.windowManger.viewMode$,
|
73
|
+
storeCamera: this.storeCamera,
|
74
|
+
storeSize: this.storeSize,
|
75
|
+
});
|
76
|
+
}
|
77
|
+
};
|
78
|
+
|
55
79
|
private startListenWritableChange = () => {
|
56
|
-
this.sideEffectManager.add(() =>
|
57
|
-
|
80
|
+
this.sideEffectManager.add(() =>
|
81
|
+
emitter.on("writableChange", isWritable => {
|
58
82
|
if (isWritable) {
|
59
83
|
this.ensureCameraAndSize();
|
60
84
|
}
|
61
|
-
})
|
62
|
-
|
85
|
+
})
|
86
|
+
);
|
63
87
|
};
|
64
88
|
|
65
89
|
public ensureCameraAndSize() {
|
66
90
|
if (!this.mainViewCamera || !this.mainViewSize) {
|
67
91
|
this.manager.dispatchInternalEvent(Events.InitMainViewCamera);
|
68
|
-
this.
|
92
|
+
this.storeCamera({
|
93
|
+
id: this.manager.uid,
|
94
|
+
...this.view.camera
|
95
|
+
});
|
96
|
+
// FIX 没有 mainViewSize 需要初始化一个 baseSize
|
97
|
+
const stageRect = this.manager.boxManager?.stageRect;
|
98
|
+
if (stageRect && !this.mainViewSize) {
|
99
|
+
this.storeSize({
|
100
|
+
id: this.manager.uid,
|
101
|
+
width: stageRect.width,
|
102
|
+
height: stageRect.height
|
103
|
+
});
|
104
|
+
}
|
69
105
|
}
|
70
106
|
}
|
71
107
|
|
@@ -81,55 +117,74 @@ export class MainViewProxy {
|
|
81
117
|
return get(this.view, ["didRelease"]);
|
82
118
|
}
|
83
119
|
|
84
|
-
private moveCameraSizeByAttributes() {
|
85
|
-
this.synchronizer.onRemoteUpdate(this.mainViewCamera, this.mainViewSize);
|
86
|
-
}
|
87
|
-
|
88
120
|
public start() {
|
89
121
|
if (this.started) return;
|
90
|
-
this.
|
122
|
+
this.removeCameraListener();
|
91
123
|
this.addCameraListener();
|
92
124
|
this.addCameraReaction();
|
93
125
|
this.started = true;
|
94
126
|
}
|
95
127
|
|
96
128
|
public addCameraReaction = () => {
|
97
|
-
this.manager.refresher
|
129
|
+
this.manager.refresher.add(Fields.MainViewCamera, this.cameraReaction);
|
130
|
+
this.manager.refresher.add(Fields.MainViewSize, this.sizeReaction);
|
98
131
|
};
|
99
132
|
|
100
|
-
public
|
101
|
-
const
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
133
|
+
public storeCurrentCamera = () => {
|
134
|
+
const iCamera = this.view.camera;
|
135
|
+
this.storeCamera({
|
136
|
+
id: this.manager.uid,
|
137
|
+
...iCamera
|
138
|
+
});
|
139
|
+
}
|
140
|
+
|
141
|
+
public storeCurrentSize = () => {
|
142
|
+
const rect = this.manager.boxManager?.stageRect;
|
143
|
+
if (rect) {
|
144
|
+
this.storeSize({
|
145
|
+
id: this.manager.uid,
|
146
|
+
width: rect.width,
|
147
|
+
height: rect.height
|
148
|
+
});
|
106
149
|
}
|
107
150
|
}
|
108
151
|
|
152
|
+
public storeCamera = (camera: ICamera) => {
|
153
|
+
this.store.setMainViewCamera(camera);
|
154
|
+
};
|
155
|
+
|
156
|
+
public storeSize = (size: ISize) => {
|
157
|
+
this.store.setMainViewSize(size);
|
158
|
+
};
|
159
|
+
|
109
160
|
private cameraReaction = () => {
|
110
161
|
return reaction(
|
111
162
|
() => this.mainViewCamera,
|
112
163
|
camera => {
|
113
|
-
if (camera
|
114
|
-
|
164
|
+
if (camera) {
|
165
|
+
const rawCamera = toJS(camera);
|
166
|
+
if (!isEqual(rawCamera, this.camera$.value)) {
|
167
|
+
this.camera$.setValue(rawCamera);
|
168
|
+
}
|
115
169
|
}
|
116
170
|
},
|
117
171
|
{ fireImmediately: true }
|
118
172
|
);
|
119
173
|
};
|
120
174
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
175
|
+
private sizeReaction = () => {
|
176
|
+
return reaction(
|
177
|
+
() => this.mainViewSize,
|
178
|
+
size => {
|
179
|
+
if (size) {
|
180
|
+
const rawSize = toJS(size);
|
181
|
+
if (!isEqual(rawSize, this.size$.value)) {
|
182
|
+
this.size$.setValue(rawSize);
|
183
|
+
}
|
184
|
+
}
|
185
|
+
},
|
186
|
+
{ fireImmediately: true }
|
187
|
+
);
|
133
188
|
};
|
134
189
|
|
135
190
|
public get view(): View {
|
@@ -146,7 +201,7 @@ export class MainViewProxy {
|
|
146
201
|
if (mainViewScenePath) {
|
147
202
|
setViewFocusScenePath(mainView, mainViewScenePath);
|
148
203
|
}
|
149
|
-
this.
|
204
|
+
this.view$.setValue(mainView);
|
150
205
|
return mainView;
|
151
206
|
}
|
152
207
|
|
@@ -168,31 +223,18 @@ export class MainViewProxy {
|
|
168
223
|
public rebind(): void {
|
169
224
|
const divElement = this.mainView.divElement;
|
170
225
|
const disableCameraTransform = this.mainView.disableCameraTransform;
|
226
|
+
const camera = { ...this.mainView.camera };
|
171
227
|
this.stop();
|
172
228
|
releaseView(this.mainView);
|
173
229
|
this.removeMainViewListener();
|
174
230
|
this.mainView = this.createMainView();
|
175
231
|
this.mainView.disableCameraTransform = disableCameraTransform;
|
176
232
|
this.mainView.divElement = divElement;
|
233
|
+
this.mainView.moveCamera({ ...camera, animationMode: AnimationMode.Immediately });
|
177
234
|
this.addMainViewListener();
|
178
235
|
this.start();
|
179
236
|
}
|
180
237
|
|
181
|
-
private onCameraUpdatedByDevice = (camera: Camera) => {
|
182
|
-
this.synchronizer.onLocalCameraUpdate(camera);
|
183
|
-
const size = this.getStageSize();
|
184
|
-
if (size && !isEqual(size, this.mainViewSize)) {
|
185
|
-
this.setMainViewSize(size);
|
186
|
-
}
|
187
|
-
};
|
188
|
-
|
189
|
-
private getStageSize(): Size | undefined {
|
190
|
-
const stage = this.manager.boxManager?.stageRect;
|
191
|
-
if (stage) {
|
192
|
-
return { width: stage.width, height: stage.height };
|
193
|
-
}
|
194
|
-
}
|
195
|
-
|
196
238
|
public addMainViewListener(): void {
|
197
239
|
if (this.mainViewIsAddListener) return;
|
198
240
|
if (this.view.divElement) {
|
@@ -225,13 +267,11 @@ export class MainViewProxy {
|
|
225
267
|
}, 50);
|
226
268
|
|
227
269
|
private addCameraListener() {
|
228
|
-
this.view.callbacks.on("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
|
229
270
|
this.view.callbacks.on("onCameraUpdated", this.onCameraOrSizeUpdated);
|
230
271
|
this.view.callbacks.on("onSizeUpdated", this.onCameraOrSizeUpdated);
|
231
272
|
}
|
232
273
|
|
233
274
|
private removeCameraListener() {
|
234
|
-
this.view.callbacks.off("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
|
235
275
|
this.view.callbacks.off("onCameraUpdated", this.onCameraOrSizeUpdated);
|
236
276
|
this.view.callbacks.off("onSizeUpdated", this.onCameraOrSizeUpdated);
|
237
277
|
}
|
@@ -241,13 +281,15 @@ export class MainViewProxy {
|
|
241
281
|
};
|
242
282
|
|
243
283
|
public stop() {
|
244
|
-
this.
|
245
|
-
this.manager.refresher
|
246
|
-
this.manager.refresher?.remove(Fields.MainViewSize);
|
284
|
+
this.manager.refresher.remove(Fields.MainViewCamera);
|
285
|
+
this.manager.refresher.remove(Fields.MainViewSize);
|
247
286
|
this.started = false;
|
248
287
|
}
|
249
288
|
|
250
289
|
public destroy() {
|
290
|
+
this.camera$.destroy();
|
291
|
+
this.size$.destroy();
|
292
|
+
this.view$.destroy();
|
251
293
|
this.removeMainViewListener();
|
252
294
|
this.stop();
|
253
295
|
this.sideEffectManager.flushAll();
|
@@ -0,0 +1,239 @@
|
|
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 { round } from "lodash";
|
8
|
+
import type { ReadonlyVal } from "value-enhancer";
|
9
|
+
import type { AppManager } from "../AppManager";
|
10
|
+
import type { ScrollStorage } from "../storage";
|
11
|
+
import type { Camera, Size, View } from "white-web-sdk";
|
12
|
+
|
13
|
+
function clamp(x: number, min: number, max: number): number {
|
14
|
+
return x < min ? min : x > max ? max : x;
|
15
|
+
}
|
16
|
+
|
17
|
+
export type ScrollState = {
|
18
|
+
scrollTop: number;
|
19
|
+
page: number;
|
20
|
+
maxScrollPage: number;
|
21
|
+
};
|
22
|
+
|
23
|
+
export class ScrollMode {
|
24
|
+
public readonly sideEffect = new SideEffectManager();
|
25
|
+
|
26
|
+
private readonly _root$: Val<HTMLElement | null>;
|
27
|
+
private readonly _whiteboard$: ReadonlyVal<HTMLElement | null>;
|
28
|
+
private readonly _scrollTop$: Val<number>;
|
29
|
+
public readonly _page$: ReadonlyVal<number>;
|
30
|
+
private readonly _scale$: ReadonlyVal<number>;
|
31
|
+
private readonly _size$: Val<Size>;
|
32
|
+
private readonly _mainView$: Val<View>;
|
33
|
+
|
34
|
+
private baseWidth = SCROLL_MODE_BASE_WIDTH;
|
35
|
+
private baseHeight = SCROLL_MODE_BASE_HEIGHT;
|
36
|
+
|
37
|
+
public scrollStorage: ScrollStorage;
|
38
|
+
public readonly scrollState$: ReadonlyVal<ScrollState>;
|
39
|
+
|
40
|
+
public setRoot(root: HTMLElement): void {
|
41
|
+
this._root$.setValue(root);
|
42
|
+
}
|
43
|
+
|
44
|
+
constructor(private manager: AppManager) {
|
45
|
+
this._root$ = new Val<HTMLElement | null>(null);
|
46
|
+
this._mainView$ = new Val<View>(this.manager.mainView);
|
47
|
+
// 滚动模式下确保 disableCameraTransform 为 false, 否则触摸屏无法滚动
|
48
|
+
this._mainView$.value.disableCameraTransform = false;
|
49
|
+
|
50
|
+
if (manager.scrollBaseSize$?.value) {
|
51
|
+
this.baseWidth = manager.scrollBaseSize$.value.width;
|
52
|
+
this.baseHeight = manager.scrollBaseSize$.value.height;
|
53
|
+
}
|
54
|
+
|
55
|
+
this.scrollStorage = createScrollStorage(manager);
|
56
|
+
const scrollTop$ = new Val(this.scrollStorage.state.scrollTop);
|
57
|
+
this._scrollTop$ = scrollTop$;
|
58
|
+
|
59
|
+
this.sideEffect.push(
|
60
|
+
this.scrollStorage.on("stateChanged", () => {
|
61
|
+
this._scrollTop$.setValue(this.scrollStorage.state.scrollTop);
|
62
|
+
})
|
63
|
+
);
|
64
|
+
|
65
|
+
const size$ = new Val<Size>(
|
66
|
+
{ width: 0, height: 0 },
|
67
|
+
{ compare: (a, b) => a.width === b.width && a.height === b.height }
|
68
|
+
);
|
69
|
+
this._size$ = size$;
|
70
|
+
this.sideEffect.add(() => {
|
71
|
+
const onSizeUpdated = size$.setValue.bind(size$);
|
72
|
+
onSizeUpdated(this._mainView$.value.size);
|
73
|
+
this._mainView$.value.callbacks.on("onSizeUpdated", onSizeUpdated);
|
74
|
+
return () => this._mainView$.value.callbacks.off("onSizeUpdated", onSizeUpdated);
|
75
|
+
});
|
76
|
+
|
77
|
+
this.sideEffect.add(() => {
|
78
|
+
const onCameraUpdated = (camera: Camera): void => {
|
79
|
+
const halfWbHeight = size$.value.height / 2 / scale$.value;
|
80
|
+
const scrollTop = camera.centerY;
|
81
|
+
this.scrollStorage.setState({
|
82
|
+
scrollTop: clamp(scrollTop, halfWbHeight, this.baseHeight - halfWbHeight),
|
83
|
+
});
|
84
|
+
callbacks.emit("userScroll");
|
85
|
+
};
|
86
|
+
this._mainView$.value.callbacks.on("onCameraUpdatedByDevice", onCameraUpdated);
|
87
|
+
return () =>
|
88
|
+
this._mainView$.value.callbacks.off("onCameraUpdatedByDevice", onCameraUpdated);
|
89
|
+
});
|
90
|
+
|
91
|
+
const scale$ = derive(size$, size => size.width / this.baseWidth);
|
92
|
+
this._scale$ = scale$;
|
93
|
+
|
94
|
+
const page$ = new Val(0);
|
95
|
+
this.sideEffect.push(
|
96
|
+
combine([scrollTop$, size$, scale$]).subscribe(([scrollTop, size, scale]) => {
|
97
|
+
if (scale > 0) {
|
98
|
+
const wbHeight = size.height / scale;
|
99
|
+
page$.setValue(Math.max(scrollTop / wbHeight - 0.5, 0));
|
100
|
+
}
|
101
|
+
})
|
102
|
+
);
|
103
|
+
this._page$ = page$;
|
104
|
+
|
105
|
+
// 5. bound$ = { contentMode: () => scale$, centerX: W / 2, centerY: H / 2, width: W, height: H }
|
106
|
+
this.sideEffect.push(
|
107
|
+
combine([scrollTop$, scale$]).subscribe(([scrollTop, scale]) => {
|
108
|
+
this.updateBound(scrollTop, size$.value, scale);
|
109
|
+
})
|
110
|
+
);
|
111
|
+
|
112
|
+
this.sideEffect.push(
|
113
|
+
size$.reaction(() => {
|
114
|
+
this.updateScroll(scrollTop$.value);
|
115
|
+
})
|
116
|
+
);
|
117
|
+
|
118
|
+
const whiteboard$ = derive(this._root$, this.getWhiteboardElement);
|
119
|
+
this._whiteboard$ = whiteboard$;
|
120
|
+
this.sideEffect.push(
|
121
|
+
whiteboard$.reaction(el => {
|
122
|
+
if (el?.parentElement) {
|
123
|
+
this.sideEffect.addEventListener(
|
124
|
+
el.parentElement,
|
125
|
+
"wheel",
|
126
|
+
this.onWheel,
|
127
|
+
{ capture: true, passive: false },
|
128
|
+
"wheel"
|
129
|
+
);
|
130
|
+
}
|
131
|
+
})
|
132
|
+
);
|
133
|
+
|
134
|
+
const maxScrollPage$ = combine([this._size$, this._scale$], ([size, scale]) => {
|
135
|
+
const halfWbHeight = size.height / 2 / scale;
|
136
|
+
return (this.baseHeight - halfWbHeight) / halfWbHeight / 2 - 0.51;
|
137
|
+
});
|
138
|
+
|
139
|
+
this.scrollState$ = combine(
|
140
|
+
[this._scrollTop$, this._page$, maxScrollPage$],
|
141
|
+
([scrollTop, page, maxScrollPage]) => {
|
142
|
+
return {
|
143
|
+
scrollTop: round(scrollTop, 2),
|
144
|
+
page: round(page, 2),
|
145
|
+
maxScrollPage: round(maxScrollPage, 2),
|
146
|
+
};
|
147
|
+
}
|
148
|
+
);
|
149
|
+
|
150
|
+
this.updateScroll(scrollTop$.value);
|
151
|
+
this.sideEffect.push(
|
152
|
+
this.scrollState$.subscribe(state => callbacks.emit("scrollStateChange", state))
|
153
|
+
);
|
154
|
+
|
155
|
+
this.sideEffect.push(
|
156
|
+
combine([this._size$, this._scale$]).subscribe(([size, scale]) => {
|
157
|
+
if (size.height > 0 && scale > 0) {
|
158
|
+
this.initScroll();
|
159
|
+
this.sideEffect.flush("initScroll");
|
160
|
+
}
|
161
|
+
}),
|
162
|
+
"initScroll"
|
163
|
+
);
|
164
|
+
}
|
165
|
+
|
166
|
+
private initScroll = (): void => {
|
167
|
+
const halfWbHeight = this._size$.value.height / 2 / this._scale$.value;
|
168
|
+
const scrollTop = this._scrollTop$.value;
|
169
|
+
// HACK: set a different value (+0.01) to trigger all effects above
|
170
|
+
this._scrollTop$.setValue(
|
171
|
+
clamp(scrollTop, halfWbHeight, this.baseHeight - halfWbHeight) - 0.01
|
172
|
+
);
|
173
|
+
};
|
174
|
+
|
175
|
+
private updateScroll(scrollTop: number): void {
|
176
|
+
this._mainView$.value.moveCamera({
|
177
|
+
centerY: scrollTop,
|
178
|
+
animationMode: AnimationMode.Immediately,
|
179
|
+
});
|
180
|
+
}
|
181
|
+
|
182
|
+
private updateBound(scrollTop: number, { height }: Size, scale: number): void {
|
183
|
+
if (scale > 0) {
|
184
|
+
this._mainView$.value.moveCameraToContain({
|
185
|
+
originX: 0,
|
186
|
+
originY: scrollTop - height / scale / 2,
|
187
|
+
width: this.baseWidth,
|
188
|
+
height: height / scale,
|
189
|
+
animationMode: AnimationMode.Immediately,
|
190
|
+
});
|
191
|
+
|
192
|
+
this._mainView$.value.setCameraBound({
|
193
|
+
damping: 1,
|
194
|
+
maxContentMode: () => scale,
|
195
|
+
minContentMode: () => scale,
|
196
|
+
centerX: this.baseWidth / 2,
|
197
|
+
centerY: this.baseHeight / 2,
|
198
|
+
width: this.baseWidth,
|
199
|
+
height: this.baseHeight,
|
200
|
+
});
|
201
|
+
}
|
202
|
+
}
|
203
|
+
|
204
|
+
public dispose(): void {
|
205
|
+
this.sideEffect.flushAll();
|
206
|
+
this.scrollStorage.disconnect();
|
207
|
+
this._root$.destroy();
|
208
|
+
this._scale$.destroy();
|
209
|
+
this._scrollTop$.destroy();
|
210
|
+
this._whiteboard$.destroy();
|
211
|
+
this.scrollState$.destroy();
|
212
|
+
this._page$.destroy();
|
213
|
+
this._size$.destroy();
|
214
|
+
this._mainView$.destroy();
|
215
|
+
}
|
216
|
+
|
217
|
+
private getWhiteboardElement = (root: HTMLElement | null): HTMLElement | null => {
|
218
|
+
const className = ".netless-window-manager-main-view";
|
219
|
+
return root && root.querySelector(className);
|
220
|
+
};
|
221
|
+
|
222
|
+
private onWheel = (ev: WheelEvent): void => {
|
223
|
+
const target = ev.target as HTMLElement | null;
|
224
|
+
if (this.manager.canOperate && this._whiteboard$.value?.contains(target)) {
|
225
|
+
ev.preventDefault();
|
226
|
+
ev.stopPropagation();
|
227
|
+
const dy = ev.deltaY || 0;
|
228
|
+
const { width } = this._size$.value;
|
229
|
+
if (dy && width > 0) {
|
230
|
+
const halfWbHeight = this._size$.value.height / 2 / this._scale$.value;
|
231
|
+
const scrollTop = this._scrollTop$.value + dy / this._scale$.value;
|
232
|
+
this.scrollStorage.setState({
|
233
|
+
scrollTop: clamp(scrollTop, halfWbHeight, this.baseHeight - halfWbHeight),
|
234
|
+
});
|
235
|
+
callbacks.emit("userScroll");
|
236
|
+
}
|
237
|
+
}
|
238
|
+
};
|
239
|
+
}
|