@netless/window-manager 1.0.12 → 1.0.13-bate.0
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.d.ts +26 -2
- package/dist/index.js +14 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +276 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/App/AppProxy.ts +10 -2
- package/src/AppListener.ts +0 -19
- package/src/AppManager.ts +46 -6
- package/src/AttributesDelegate.ts +3 -0
- package/src/ContainerResizeObserver.ts +15 -4
- package/src/InternalEmitter.ts +1 -0
- package/src/Utils/RoomHacker.ts +22 -1
- package/src/Utils/log.ts +37 -0
- package/src/View/MainView.ts +116 -5
- package/src/index.ts +61 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netless/window-manager",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13-bate.0",
|
|
4
4
|
"description": "Multi-window mode for Netless Whiteboard",
|
|
5
5
|
"author": "l1shen <lishen1635@gmail.com> (https://github.com/l1shen)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"jspdf": "2.5.1",
|
|
26
|
-
"white-web-sdk": "^2.16.
|
|
26
|
+
"white-web-sdk": "^2.16.53"
|
|
27
27
|
},
|
|
28
28
|
"peerDependenciesMeta": {
|
|
29
29
|
"jspdf": {
|
|
@@ -72,6 +72,6 @@
|
|
|
72
72
|
"typescript": "^4.5.5",
|
|
73
73
|
"vite": "^2.9.9",
|
|
74
74
|
"vitest": "^0.14.1",
|
|
75
|
-
"white-web-sdk": "^2.16.
|
|
75
|
+
"white-web-sdk": "^2.16.53"
|
|
76
76
|
}
|
|
77
77
|
}
|
package/src/App/AppProxy.ts
CHANGED
|
@@ -120,6 +120,10 @@ export class AppProxy implements PageRemoveService {
|
|
|
120
120
|
return this.store.getAppAttributes(this.id);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
public get Logger() {
|
|
124
|
+
return this.manager.windowManger.Logger;
|
|
125
|
+
}
|
|
126
|
+
|
|
123
127
|
public getFullScenePath(): string | undefined {
|
|
124
128
|
if (this.scenePath) {
|
|
125
129
|
return get(this.appAttributes, [Fields.FullPath]) || this.getFullScenePathFromScenes();
|
|
@@ -145,6 +149,7 @@ export class AppProxy implements PageRemoveService {
|
|
|
145
149
|
): Promise<{ appId: string; app: NetlessApp }> {
|
|
146
150
|
const params = this.params;
|
|
147
151
|
if (!params.kind) {
|
|
152
|
+
this.Logger && this.Logger.error(`[WindowManager]: kind require`);
|
|
148
153
|
throw new Error("[WindowManager]: kind require");
|
|
149
154
|
}
|
|
150
155
|
const appImpl = await appRegister.appClasses.get(params.kind)?.();
|
|
@@ -162,6 +167,7 @@ export class AppProxy implements PageRemoveService {
|
|
|
162
167
|
params.isDragContent
|
|
163
168
|
);
|
|
164
169
|
} else {
|
|
170
|
+
this.Logger && this.Logger.error(`[WindowManager]: app load failed ${params.kind} ${params.src}`);
|
|
165
171
|
throw new Error(`[WindowManager]: app load failed ${params.kind} ${params.src}`);
|
|
166
172
|
}
|
|
167
173
|
internalEmitter.emit("updateManagerRect");
|
|
@@ -210,7 +216,7 @@ export class AppProxy implements PageRemoveService {
|
|
|
210
216
|
}
|
|
211
217
|
setTimeout(async () => {
|
|
212
218
|
// 延迟执行 setup, 防止初始化的属性没有更新成功
|
|
213
|
-
|
|
219
|
+
this.Logger && this.Logger.info(`[WindowManager]: setup app ${this.kind}, appId: ${appId}`);
|
|
214
220
|
const result = await app.setup(context);
|
|
215
221
|
this.appResult = result;
|
|
216
222
|
appRegister.notifyApp(this.kind, "created", { appId, result });
|
|
@@ -245,7 +251,7 @@ export class AppProxy implements PageRemoveService {
|
|
|
245
251
|
this.boxManager.focusBox({ appId }, false);
|
|
246
252
|
}
|
|
247
253
|
} catch (error: any) {
|
|
248
|
-
|
|
254
|
+
this.Logger && this.Logger.error(`[WindowManager]: app setup error: ${error.message}`);
|
|
249
255
|
throw new Error(`[WindowManager]: app setup error: ${error.message}`);
|
|
250
256
|
}
|
|
251
257
|
}
|
|
@@ -532,6 +538,7 @@ export class AppProxy implements PageRemoveService {
|
|
|
532
538
|
await appRegister.notifyApp(this.kind, "destroy", { appId: this.id });
|
|
533
539
|
await this.appEmitter.emit("destroy", { error });
|
|
534
540
|
} catch (error) {
|
|
541
|
+
this.Logger && this.Logger.error(`[WindowManager]: notifyApp error: ${error.message}`);
|
|
535
542
|
console.error("[WindowManager]: notifyApp error", error.message, error.stack);
|
|
536
543
|
}
|
|
537
544
|
this.appEmitter.clearListeners();
|
|
@@ -554,6 +561,7 @@ export class AppProxy implements PageRemoveService {
|
|
|
554
561
|
this.manager.refresher.remove(this.stateKey);
|
|
555
562
|
this.manager.refresher.remove(`${this.id}-fullPath`);
|
|
556
563
|
this._prevFullPath = undefined;
|
|
564
|
+
this.Logger && this.Logger.info(`[WindowManager]: destroy app ${this.kind} appId: ${this.id}`);
|
|
557
565
|
}
|
|
558
566
|
|
|
559
567
|
public close(): Promise<void> {
|
package/src/AppListener.ts
CHANGED
|
@@ -50,14 +50,6 @@ export class AppListeners {
|
|
|
50
50
|
this.setMainViewScenePathHandler(data.payload);
|
|
51
51
|
break;
|
|
52
52
|
}
|
|
53
|
-
// case Events.MoveCamera: {
|
|
54
|
-
// this.moveCameraHandler(data.payload);
|
|
55
|
-
// break;
|
|
56
|
-
// }
|
|
57
|
-
// case Events.MoveCameraToContain: {
|
|
58
|
-
// this.moveCameraToContainHandler(data.payload);
|
|
59
|
-
// break;
|
|
60
|
-
// }
|
|
61
53
|
case Events.CursorMove: {
|
|
62
54
|
this.cursorMoveHandler(data.payload);
|
|
63
55
|
break;
|
|
@@ -102,17 +94,6 @@ export class AppListeners {
|
|
|
102
94
|
callbacks.emit("mainViewScenePathChange", nextScenePath);
|
|
103
95
|
};
|
|
104
96
|
|
|
105
|
-
// private moveCameraHandler = (
|
|
106
|
-
// payload: Camera & { animationMode?: AnimationMode | undefined }
|
|
107
|
-
// ) => {
|
|
108
|
-
// if (isEqual(omit(payload, ["animationMode"]), { ...this.manager.mainView.camera })) return;
|
|
109
|
-
// this.manager.mainView.moveCamera(payload);
|
|
110
|
-
// };
|
|
111
|
-
|
|
112
|
-
// private moveCameraToContainHandler = (payload: any) => {
|
|
113
|
-
// this.manager.mainView.moveCameraToContain(payload);
|
|
114
|
-
// };
|
|
115
|
-
|
|
116
97
|
private cursorMoveHandler = (payload: any) => {
|
|
117
98
|
internalEmitter.emit("cursorMove", payload);
|
|
118
99
|
};
|
package/src/AppManager.ts
CHANGED
|
@@ -94,11 +94,6 @@ export class AppManager {
|
|
|
94
94
|
|
|
95
95
|
constructor(public windowManger: WindowManager) {
|
|
96
96
|
this.displayer = windowManger.displayer;
|
|
97
|
-
// this.store.setContext({
|
|
98
|
-
// getAttributes: () => this.attributes,
|
|
99
|
-
// safeSetAttributes: attributes => this.safeSetAttributes(attributes),
|
|
100
|
-
// safeUpdateAttributes: (keys, val) => this.safeUpdateAttributes(keys, val),
|
|
101
|
-
// });
|
|
102
97
|
this.store = createAttributesDelegate(WindowManager.extendClass, {
|
|
103
98
|
getAttributes: () => this.attributes,
|
|
104
99
|
safeSetAttributes: attributes => this.safeSetAttributes(attributes),
|
|
@@ -150,6 +145,7 @@ export class AppManager {
|
|
|
150
145
|
const { scenePath } = params;
|
|
151
146
|
// 如果移除根目录就把 scenePath 设置为初始值
|
|
152
147
|
if (scenePath === ROOT_DIR) {
|
|
148
|
+
console.log("[window-manager] onRemoveScenes ROOT_DIR");
|
|
153
149
|
await this.onRootDirRemoved();
|
|
154
150
|
this.dispatchInternalEvent(Events.RootDirRemoved);
|
|
155
151
|
return;
|
|
@@ -162,6 +158,7 @@ export class AppManager {
|
|
|
162
158
|
sceneName = this.callbacksNode?.scenes[nextIndex];
|
|
163
159
|
}
|
|
164
160
|
if (sceneName) {
|
|
161
|
+
console.log(`[window-manager] onRemoveScenes setMainViewScenePath ${ROOT_DIR}${sceneName}`);
|
|
165
162
|
this.setMainViewScenePath(`${ROOT_DIR}${sceneName}`);
|
|
166
163
|
}
|
|
167
164
|
await this.setMainViewSceneIndex(nextIndex);
|
|
@@ -333,6 +330,10 @@ export class AppManager {
|
|
|
333
330
|
return this.room?.uid || "";
|
|
334
331
|
}
|
|
335
332
|
|
|
333
|
+
public get Logger() {
|
|
334
|
+
return this.windowManger.Logger;
|
|
335
|
+
}
|
|
336
|
+
|
|
336
337
|
public getMainViewSceneDir() {
|
|
337
338
|
const scenePath = this.store.getMainViewScenePath();
|
|
338
339
|
if (scenePath) {
|
|
@@ -603,6 +604,9 @@ export class AppManager {
|
|
|
603
604
|
try {
|
|
604
605
|
const appAttributes = this.attributes[id];
|
|
605
606
|
if (!appAttributes) {
|
|
607
|
+
this.Logger && this.Logger.error(
|
|
608
|
+
`[WindowManager]: appAttributes is undefined, appId: ${id}`
|
|
609
|
+
);
|
|
606
610
|
throw new Error("appAttributes is undefined");
|
|
607
611
|
}
|
|
608
612
|
|
|
@@ -706,6 +710,31 @@ export class AppManager {
|
|
|
706
710
|
}
|
|
707
711
|
internalEmitter.emit("mainViewMounted");
|
|
708
712
|
callbacks.emit("onMainViewMounted", mainView);
|
|
713
|
+
const hasRoot = this.hasRoot(mainView.divElement);
|
|
714
|
+
const rect = this.getRectByDivElement(mainView.divElement);
|
|
715
|
+
let log = `[window-manager] bindMainView hasRoot:${hasRoot}, rect:${JSON.stringify(rect)}, outerHeight:${window.outerHeight}, outerWidth:${window.outerWidth}`;
|
|
716
|
+
const visualViewport = window.visualViewport;
|
|
717
|
+
if (visualViewport) {
|
|
718
|
+
log += `, visualViewportWidth:${visualViewport.width}, visualViewportHeight:${visualViewport.height}, visualViewportOffsetLeft:${visualViewport.offsetLeft}, visualViewportOffsetTop:${visualViewport.offsetTop}`;
|
|
719
|
+
}
|
|
720
|
+
console.log(log);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
private hasRoot(divElement: HTMLDivElement){
|
|
724
|
+
let current = divElement;
|
|
725
|
+
while (current) {
|
|
726
|
+
if (current.parentElement === document.body) {
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
current = current.parentElement as HTMLDivElement;
|
|
730
|
+
}
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
private getRectByDivElement(divElement: HTMLDivElement){
|
|
735
|
+
// 获取当前divElement的矩形区域
|
|
736
|
+
const rect = divElement.getBoundingClientRect();
|
|
737
|
+
return rect;
|
|
709
738
|
}
|
|
710
739
|
|
|
711
740
|
public setMainViewFocusPath(scenePath?: string) {
|
|
@@ -725,6 +754,9 @@ export class AppManager {
|
|
|
725
754
|
|
|
726
755
|
public async addApp(params: AddAppParams, isDynamicPPT: boolean): Promise<string | undefined> {
|
|
727
756
|
log("addApp", params);
|
|
757
|
+
this.windowManger.Logger?.info(
|
|
758
|
+
`[WindowManager]: addApp ${params.kind}, isDynamicPPT: ${isDynamicPPT}`
|
|
759
|
+
);
|
|
728
760
|
const { appId, needFocus } = await this.beforeAddApp(params, isDynamicPPT);
|
|
729
761
|
const appProxy = await this.baseInsertApp(params, appId, true, needFocus);
|
|
730
762
|
this.afterAddApp(appProxy);
|
|
@@ -773,7 +805,9 @@ export class AppManager {
|
|
|
773
805
|
focus?: boolean
|
|
774
806
|
) {
|
|
775
807
|
if (this.appProxies.has(appId)) {
|
|
776
|
-
|
|
808
|
+
this.windowManger.Logger?.warn(
|
|
809
|
+
`[WindowManager]: app duplicate exists and cannot be created again, appId: ${appId}`
|
|
810
|
+
);
|
|
777
811
|
return;
|
|
778
812
|
}
|
|
779
813
|
const AppProxyClass = getExtendClass(AppProxy, WindowManager.extendClass);
|
|
@@ -784,6 +818,7 @@ export class AppManager {
|
|
|
784
818
|
return appProxy;
|
|
785
819
|
} else {
|
|
786
820
|
this.appStatus.delete(appId);
|
|
821
|
+
this.Logger && this.Logger.error(`[WindowManager]: initialize AppProxy failed, appId: ${appId}`);
|
|
787
822
|
throw new Error("[WindowManger]: initialize AppProxy failed");
|
|
788
823
|
}
|
|
789
824
|
}
|
|
@@ -823,12 +858,15 @@ export class AppManager {
|
|
|
823
858
|
const scenePathType = this.displayer.scenePathType(scenePath);
|
|
824
859
|
const sceneDir = parseSceneDir(scenePath);
|
|
825
860
|
if (sceneDir !== ROOT_DIR) {
|
|
861
|
+
this.Logger && this.Logger.error(`[WindowManager]: main view scenePath must in root dir "/"`);
|
|
826
862
|
throw new Error(`[WindowManager]: main view scenePath must in root dir "/"`);
|
|
827
863
|
}
|
|
828
864
|
if (scenePathType === ScenePathType.None) {
|
|
865
|
+
this.Logger && this.Logger.error(`[WindowManager]: ${scenePath} not valid scene`);
|
|
829
866
|
throw new Error(`[WindowManager]: ${scenePath} not valid scene`);
|
|
830
867
|
} else if (scenePathType === ScenePathType.Page) {
|
|
831
868
|
await this._setMainViewScenePath(scenePath);
|
|
869
|
+
|
|
832
870
|
} else if (scenePathType === ScenePathType.Dir) {
|
|
833
871
|
const validScenePath = makeValidScenePath(this.displayer, scenePath);
|
|
834
872
|
if (validScenePath) {
|
|
@@ -875,6 +913,7 @@ export class AppManager {
|
|
|
875
913
|
this.dispatchSetMainViewScenePath(scenePath);
|
|
876
914
|
}
|
|
877
915
|
} else {
|
|
916
|
+
this.Logger && this.Logger.error(`[WindowManager]: ${index} not valid index`);
|
|
878
917
|
throw new Error(`[WindowManager]: ${index} not valid index`);
|
|
879
918
|
}
|
|
880
919
|
}
|
|
@@ -961,3 +1000,4 @@ export class AppManager {
|
|
|
961
1000
|
this._resolveTimer = undefined;
|
|
962
1001
|
}
|
|
963
1002
|
}
|
|
1003
|
+
|
|
@@ -7,6 +7,7 @@ import type { Cursor } from "./Cursor/Cursor";
|
|
|
7
7
|
import { getExtendClass } from "./Utils/extendClass";
|
|
8
8
|
import type { ExtendClass } from "./Utils/extendClass";
|
|
9
9
|
import type { NotMinimizedBoxState, TeleBoxState } from "@netless/telebox-insider";
|
|
10
|
+
import { LocalConsole } from "./Utils/log";
|
|
10
11
|
|
|
11
12
|
export enum Fields {
|
|
12
13
|
Apps = "apps",
|
|
@@ -54,6 +55,7 @@ export type ISize = Size & { id: string };
|
|
|
54
55
|
|
|
55
56
|
export class AttributesDelegate {
|
|
56
57
|
static readonly kind = "AttributesDelegate";
|
|
58
|
+
private setMainViewCameraConsole = new LocalConsole("setMainViewCamera", 30);
|
|
57
59
|
constructor(private context: StoreContext) {}
|
|
58
60
|
|
|
59
61
|
public setContext(context: StoreContext) {
|
|
@@ -210,6 +212,7 @@ export class AttributesDelegate {
|
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
public setMainViewCamera(camera: ICamera) {
|
|
215
|
+
this.setMainViewCameraConsole.log(JSON.stringify(camera));
|
|
213
216
|
this.context.safeSetAttributes({ [Fields.MainViewCamera]: { ...camera } });
|
|
214
217
|
}
|
|
215
218
|
|
|
@@ -3,12 +3,15 @@ import { isFunction } from "lodash";
|
|
|
3
3
|
import { WindowManager } from "./index";
|
|
4
4
|
import type { EmitterType } from "./InternalEmitter";
|
|
5
5
|
import type { UnsubscribeFn } from "emittery";
|
|
6
|
+
import { LocalConsole } from "./Utils/log";
|
|
6
7
|
|
|
7
8
|
const ResizeObserver = window.ResizeObserver || ResizeObserverPolyfill;
|
|
8
9
|
|
|
9
10
|
export class ContainerResizeObserver {
|
|
10
11
|
private containerResizeObserver?: ResizeObserver;
|
|
11
12
|
private disposer?: UnsubscribeFn;
|
|
13
|
+
|
|
14
|
+
private updateSizerLocalConsole = new LocalConsole("updateSizer", 30);
|
|
12
15
|
|
|
13
16
|
constructor(private emitter: EmitterType) {}
|
|
14
17
|
|
|
@@ -28,19 +31,19 @@ export class ContainerResizeObserver {
|
|
|
28
31
|
sizer: HTMLElement,
|
|
29
32
|
wrapper: HTMLDivElement
|
|
30
33
|
) {
|
|
31
|
-
this.updateSizer(container.getBoundingClientRect(), sizer, wrapper);
|
|
34
|
+
this.updateSizer(container.getBoundingClientRect(), sizer, wrapper, 'observePlaygroundSize');
|
|
32
35
|
|
|
33
36
|
this.containerResizeObserver = new ResizeObserver(entries => {
|
|
34
37
|
const containerRect = entries[0]?.contentRect;
|
|
35
38
|
if (containerRect) {
|
|
36
|
-
this.updateSizer(containerRect, sizer, wrapper);
|
|
39
|
+
this.updateSizer(containerRect, sizer, wrapper, 'containerResizeObserver');
|
|
37
40
|
this.emitter.emit("playgroundSizeChange", containerRect);
|
|
38
41
|
}
|
|
39
42
|
});
|
|
40
43
|
|
|
41
44
|
this.disposer = this.emitter.on("containerSizeRatioUpdate", () => {
|
|
42
45
|
const containerRect = container.getBoundingClientRect();
|
|
43
|
-
this.updateSizer(containerRect, sizer, wrapper);
|
|
46
|
+
this.updateSizer(containerRect, sizer, wrapper, 'containerSizeRatioUpdate');
|
|
44
47
|
this.emitter.emit("playgroundSizeChange", containerRect);
|
|
45
48
|
});
|
|
46
49
|
|
|
@@ -50,7 +53,8 @@ export class ContainerResizeObserver {
|
|
|
50
53
|
public updateSizer(
|
|
51
54
|
{ width, height }: DOMRectReadOnly,
|
|
52
55
|
sizer: HTMLElement,
|
|
53
|
-
wrapper: HTMLDivElement
|
|
56
|
+
wrapper: HTMLDivElement,
|
|
57
|
+
origin?: string
|
|
54
58
|
) {
|
|
55
59
|
if (width && height) {
|
|
56
60
|
if (height / width > WindowManager.containerSizeRatio) {
|
|
@@ -62,6 +66,13 @@ export class ContainerResizeObserver {
|
|
|
62
66
|
}
|
|
63
67
|
wrapper.style.width = `${width}px`;
|
|
64
68
|
wrapper.style.height = `${height}px`;
|
|
69
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
70
|
+
this.updateSizerLocalConsole.log(`from ${origin}, traget size: ${JSON.stringify({ width, height })}, wrapperRect: ${wrapperRect.width} ${wrapperRect.height}`);
|
|
71
|
+
this.emitter.emit("wrapperRectChange", {
|
|
72
|
+
width: wrapperRect.width,
|
|
73
|
+
height: wrapperRect.height,
|
|
74
|
+
origin,
|
|
75
|
+
});
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
78
|
|
package/src/InternalEmitter.ts
CHANGED
|
@@ -29,6 +29,7 @@ export type EmitterEvent = {
|
|
|
29
29
|
changePageState: undefined;
|
|
30
30
|
writableChange: boolean;
|
|
31
31
|
containerSizeRatioUpdate: number;
|
|
32
|
+
wrapperRectChange: { width: number; height: number; origin?: string };
|
|
32
33
|
boxesStatusChange: Map<string, TeleBoxState>;
|
|
33
34
|
lastNotMinimizedBoxesStatusChange: Map<string, NotMinimizedBoxState>;
|
|
34
35
|
};
|
package/src/Utils/RoomHacker.ts
CHANGED
|
@@ -33,7 +33,28 @@ export const replaceRoomFunction = (room: Room | Player, manager: WindowManager)
|
|
|
33
33
|
return manager.canRedoSteps;
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
|
-
|
|
36
|
+
const _scalePptToFit = room.scalePptToFit;
|
|
37
|
+
room.scalePptToFit = (...args) => {
|
|
38
|
+
_scalePptToFit.call(room, ...args);
|
|
39
|
+
if (manager.appManager?.mainViewProxy) {
|
|
40
|
+
manager.appManager.mainViewProxy.setCameraAndSize();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const _putScenes = room.putScenes;
|
|
44
|
+
room.putScenes = (...args) => {
|
|
45
|
+
const [path, scenes] = args;
|
|
46
|
+
const currentScenePath = manager.mainView.focusScenePath;
|
|
47
|
+
if (currentScenePath && path && scenes) {
|
|
48
|
+
console.log("[window-manager] putScenes " + JSON.stringify(args));
|
|
49
|
+
for (const scene of scenes) {
|
|
50
|
+
if (`${path}${scene.name}` === currentScenePath) {
|
|
51
|
+
console.error(`[window-manager] putScenes: scene name can not be the same as the current scene path: ${currentScenePath}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return _putScenes.call(room, ...args);
|
|
57
|
+
};
|
|
37
58
|
room.moveCamera = (camera: Camera) => manager.moveCamera(camera);
|
|
38
59
|
room.moveCameraToContain = (...args) => manager.moveCameraToContain(...args);
|
|
39
60
|
room.convertToPointInWorld = (...args) => manager.mainView.convertToPointInWorld(...args);
|
package/src/Utils/log.ts
CHANGED
|
@@ -5,3 +5,40 @@ export const log = (...args: any[]): void => {
|
|
|
5
5
|
console.log(`[WindowManager]:`, ...args);
|
|
6
6
|
}
|
|
7
7
|
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 按 `[window-manager][tagName]` 前缀输出。
|
|
11
|
+
* 若传入 `debounceTime`(毫秒):窗口内多次 `log` 不立即输出,只在连续停止调用满 `debounceTime` 后输出**最后一次**的参数(尾部 debounce)。
|
|
12
|
+
*/
|
|
13
|
+
export class LocalConsole {
|
|
14
|
+
private pendingArgs: unknown[] | null = null;
|
|
15
|
+
private flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private readonly name: string,
|
|
19
|
+
private readonly debounceTime?: number,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
private flush(): void {
|
|
23
|
+
this.flushTimer = null;
|
|
24
|
+
const args = this.pendingArgs;
|
|
25
|
+
this.pendingArgs = null;
|
|
26
|
+
if (args === null) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.log(`[window-manager][${this.name}]: ${args.join(", ")}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
log(...args: unknown[]): void {
|
|
33
|
+
const ms = this.debounceTime;
|
|
34
|
+
if (ms != null && ms > 0) {
|
|
35
|
+
this.pendingArgs = args;
|
|
36
|
+
if (this.flushTimer != null) {
|
|
37
|
+
clearTimeout(this.flushTimer);
|
|
38
|
+
}
|
|
39
|
+
this.flushTimer = setTimeout(() => this.flush(), ms);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
console.log(`[window-manager][${this.name}]: ${args.join(", ")}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/View/MainView.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { SideEffectManager } from "side-effect-manager";
|
|
|
9
9
|
import type { Camera, Room, Size, View } from "white-web-sdk";
|
|
10
10
|
import type { AppManager } from "../AppManager";
|
|
11
11
|
import { Events } from "../constants";
|
|
12
|
+
import { LocalConsole } from "../Utils/log";
|
|
12
13
|
|
|
13
14
|
export class MainViewProxy {
|
|
14
15
|
/** Refresh the view's camera in an interval of 1.5s. */
|
|
@@ -17,12 +18,19 @@ export class MainViewProxy {
|
|
|
17
18
|
private scale?: number;
|
|
18
19
|
private started = false;
|
|
19
20
|
private mainViewIsAddListener = false;
|
|
21
|
+
private isForcingMainViewDivElement = false;
|
|
22
|
+
private wrapperRectWorkaroundFrame = 0;
|
|
23
|
+
private pendingWrapperRectChange?: { width: number; height: number; origin?: string };
|
|
20
24
|
private mainView: View;
|
|
21
25
|
private store = this.manager.store;
|
|
22
26
|
private viewMode = this.manager.windowManger.viewMode;
|
|
23
27
|
|
|
24
28
|
private sideEffectManager = new SideEffectManager();
|
|
25
29
|
|
|
30
|
+
private playgroundSizeChangeListenerLocalConsole = new LocalConsole("playgroundSizeChangeListener", 30);
|
|
31
|
+
private sizeUpdatedLocalConsole = new LocalConsole("sizeUpdated", 30);
|
|
32
|
+
private cameraUpdatedLocalConsole = new LocalConsole("cameraUpdated", 30);
|
|
33
|
+
|
|
26
34
|
constructor(private manager: AppManager) {
|
|
27
35
|
this.mainView = this.createMainView();
|
|
28
36
|
this.moveCameraSizeByAttributes();
|
|
@@ -33,6 +41,15 @@ export class MainViewProxy {
|
|
|
33
41
|
this.startListenWritableChange();
|
|
34
42
|
});
|
|
35
43
|
const playgroundSizeChangeListener = () => {
|
|
44
|
+
this.playgroundSizeChangeListenerLocalConsole.log(
|
|
45
|
+
JSON.stringify(this.mainView.camera),
|
|
46
|
+
JSON.stringify(this.mainView.size),
|
|
47
|
+
JSON.stringify(this.mainViewSize),
|
|
48
|
+
JSON.stringify(this.mainViewCamera),
|
|
49
|
+
window.outerHeight, window.outerWidth,
|
|
50
|
+
window.visualViewport?.width ?? "null", window.visualViewport?.height ?? "null",
|
|
51
|
+
window.visualViewport?.offsetLeft ?? "null", window.visualViewport?.offsetTop ?? "null",
|
|
52
|
+
);
|
|
36
53
|
this.sizeChangeHandler(this.mainViewSize);
|
|
37
54
|
};
|
|
38
55
|
this.sideEffectManager.add(() => {
|
|
@@ -41,6 +58,9 @@ export class MainViewProxy {
|
|
|
41
58
|
this.sideEffectManager.add(() => {
|
|
42
59
|
return internalEmitter.on("containerSizeRatioUpdate", this.onUpdateContainerSizeRatio);
|
|
43
60
|
});
|
|
61
|
+
this.sideEffectManager.add(() => {
|
|
62
|
+
return internalEmitter.on("wrapperRectChange", this.onWrapperRectChange);
|
|
63
|
+
});
|
|
44
64
|
this.sideEffectManager.add(() => {
|
|
45
65
|
return internalEmitter.on("startReconnect", () => {
|
|
46
66
|
if (!this.didRelease) {
|
|
@@ -96,13 +116,85 @@ export class MainViewProxy {
|
|
|
96
116
|
this.moveCamera(this.mainViewCamera);
|
|
97
117
|
}
|
|
98
118
|
|
|
119
|
+
private onWrapperRectChange = (payload: { width: number; height: number; origin?: string }) => {
|
|
120
|
+
this.pendingWrapperRectChange = payload;
|
|
121
|
+
if (this.wrapperRectWorkaroundFrame) {
|
|
122
|
+
cancelAnimationFrame(this.wrapperRectWorkaroundFrame);
|
|
123
|
+
}
|
|
124
|
+
this.wrapperRectWorkaroundFrame = requestAnimationFrame(this.runWrapperRectWorkaround);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
private runWrapperRectWorkaround = () => {
|
|
128
|
+
this.wrapperRectWorkaroundFrame = 0;
|
|
129
|
+
const payload = this.pendingWrapperRectChange;
|
|
130
|
+
const element = this.mainView.divElement;
|
|
131
|
+
this.pendingWrapperRectChange = undefined;
|
|
132
|
+
if (!payload || !element) return;
|
|
133
|
+
|
|
134
|
+
const rect = element.getBoundingClientRect();
|
|
135
|
+
const observedSize = { width: rect.width, height: rect.height };
|
|
136
|
+
const wrapperMatchesDom =
|
|
137
|
+
Math.abs(payload.width - observedSize.width) <= 0.5 &&
|
|
138
|
+
Math.abs(payload.height - observedSize.height) <= 0.5;
|
|
139
|
+
const viewIsStale =
|
|
140
|
+
Math.abs(this.mainView.size.width - observedSize.width) > 0.5 ||
|
|
141
|
+
Math.abs(this.mainView.size.height - observedSize.height) > 0.5;
|
|
142
|
+
|
|
143
|
+
if (wrapperMatchesDom && viewIsStale) {
|
|
144
|
+
this.forceSyncMainViewDivElement(
|
|
145
|
+
`wrapperRectChange:${payload.origin || "unknown"}`,
|
|
146
|
+
observedSize,
|
|
147
|
+
element
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
private forceSyncMainViewDivElement(
|
|
153
|
+
reason: string,
|
|
154
|
+
observedSize: Pick<Size, "width" | "height">,
|
|
155
|
+
element: HTMLDivElement
|
|
156
|
+
) {
|
|
157
|
+
const { width: viewWidth, height: viewHeight } = this.mainView.size;
|
|
158
|
+
if (
|
|
159
|
+
Math.abs(viewWidth - observedSize.width) <= 0.5 &&
|
|
160
|
+
Math.abs(viewHeight - observedSize.height) <= 0.5
|
|
161
|
+
) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (this.isForcingMainViewDivElement) {
|
|
165
|
+
console.log("[window-manager] skipForceSyncMainViewDivElement " + JSON.stringify({
|
|
166
|
+
reason,
|
|
167
|
+
observedSize,
|
|
168
|
+
viewSize: this.mainView.size,
|
|
169
|
+
}));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this.isForcingMainViewDivElement = true;
|
|
173
|
+
this.mainView.divElement = null;
|
|
174
|
+
this.mainView.divElement = element;
|
|
175
|
+
queueMicrotask(() => {
|
|
176
|
+
const rect = element.getBoundingClientRect();
|
|
177
|
+
console.log("[window-manager] forceSyncMainViewDivElementResult " + JSON.stringify({
|
|
178
|
+
reason,
|
|
179
|
+
viewSize: this.mainView.size,
|
|
180
|
+
rect: { width: rect.width, height: rect.height },
|
|
181
|
+
}));
|
|
182
|
+
this.isForcingMainViewDivElement = false;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
99
186
|
public start() {
|
|
187
|
+
console.log("[window-manager] start attributes size:" + JSON.stringify(this.mainViewSize));
|
|
100
188
|
this.sizeChangeHandler(this.mainViewSize);
|
|
101
189
|
if (this.started) return;
|
|
102
190
|
this.addCameraListener();
|
|
103
191
|
this.addCameraReaction();
|
|
104
192
|
if (this.manager.room) this.syncMainView(this.manager.room);
|
|
105
193
|
this.started = true;
|
|
194
|
+
if(this.mainView.focusScenePath) {
|
|
195
|
+
this.manager.windowManger.onMainViewScenePathChangeHandler(this.mainView.focusScenePath);
|
|
196
|
+
}
|
|
197
|
+
console.log("[window-manager] start end mainView size:" + JSON.stringify(this.mainView.size));
|
|
106
198
|
}
|
|
107
199
|
|
|
108
200
|
public addCameraReaction = () => {
|
|
@@ -120,6 +212,7 @@ export class MainViewProxy {
|
|
|
120
212
|
() => this.mainViewCamera,
|
|
121
213
|
camera => {
|
|
122
214
|
if (camera && camera.id !== this.manager.uid) {
|
|
215
|
+
console.log("[window-manager] cameraReaction " + JSON.stringify(camera) + JSON.stringify(this.mainViewSize));
|
|
123
216
|
this.moveCameraToContian(this.mainViewSize);
|
|
124
217
|
this.moveCamera(camera);
|
|
125
218
|
}
|
|
@@ -132,12 +225,15 @@ export class MainViewProxy {
|
|
|
132
225
|
if (size) {
|
|
133
226
|
this.moveCameraToContian(size);
|
|
134
227
|
this.moveCamera(this.mainViewCamera);
|
|
228
|
+
console.log("[window-manager] sizeChangeHandler current size and camera" + JSON.stringify(size) + JSON.stringify(this.mainViewCamera) +
|
|
229
|
+
JSON.stringify(this.mainView.camera) + JSON.stringify(this.mainView.size));
|
|
135
230
|
}
|
|
136
231
|
this.ensureMainViewSize();
|
|
137
232
|
}, 30);
|
|
138
233
|
|
|
139
234
|
public onUpdateContainerSizeRatio = () => {
|
|
140
235
|
const size = this.store.getMainViewSize();
|
|
236
|
+
console.log("[window-manager] onUpdateContainerSizeRatio " + JSON.stringify(size));
|
|
141
237
|
this.sizeChangeHandler(size);
|
|
142
238
|
};
|
|
143
239
|
|
|
@@ -230,18 +326,18 @@ export class MainViewProxy {
|
|
|
230
326
|
|
|
231
327
|
private addCameraListener() {
|
|
232
328
|
this.view.callbacks.on("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
|
|
233
|
-
this.view.callbacks.on("onCameraUpdated", this.
|
|
234
|
-
this.view.callbacks.on("onSizeUpdated", this.
|
|
329
|
+
this.view.callbacks.on("onCameraUpdated", this.onCameraUpdated);
|
|
330
|
+
this.view.callbacks.on("onSizeUpdated", this.onSizeUpdated);
|
|
235
331
|
}
|
|
236
332
|
|
|
237
333
|
private removeCameraListener() {
|
|
238
334
|
this.view.callbacks.off("onCameraUpdatedByDevice", this.onCameraUpdatedByDevice);
|
|
239
|
-
this.view.callbacks.off("onCameraUpdated", this.
|
|
240
|
-
this.view.callbacks.off("onSizeUpdated", this.
|
|
335
|
+
this.view.callbacks.off("onCameraUpdated", this.onCameraUpdated);
|
|
336
|
+
this.view.callbacks.off("onSizeUpdated", this.onSizeUpdated);
|
|
241
337
|
}
|
|
242
338
|
|
|
243
339
|
private _syncMainViewTimer = 0;
|
|
244
|
-
private
|
|
340
|
+
private handleCameraOrSizeUpdated = () => {
|
|
245
341
|
callbacks.emit("cameraStateChange", this.cameraState);
|
|
246
342
|
// sdk >= 2.16.43 的 syncMainView() 可以写入当前 main view 的 camera, 以修复复制粘贴元素的位置
|
|
247
343
|
// 注意到这个操作会发送信令,应当避免频繁调用
|
|
@@ -252,6 +348,16 @@ export class MainViewProxy {
|
|
|
252
348
|
this.ensureMainViewSize();
|
|
253
349
|
};
|
|
254
350
|
|
|
351
|
+
private onCameraUpdated = (camera: Camera) => {
|
|
352
|
+
this.cameraUpdatedLocalConsole.log(JSON.stringify(camera));
|
|
353
|
+
this.handleCameraOrSizeUpdated();
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
private onSizeUpdated = (size: Size) => {
|
|
357
|
+
this.sizeUpdatedLocalConsole.log(JSON.stringify(size));
|
|
358
|
+
this.handleCameraOrSizeUpdated();
|
|
359
|
+
};
|
|
360
|
+
|
|
255
361
|
private ensureMainViewSize() {
|
|
256
362
|
if (
|
|
257
363
|
(!this.mainViewSize ||
|
|
@@ -309,6 +415,11 @@ export class MainViewProxy {
|
|
|
309
415
|
};
|
|
310
416
|
|
|
311
417
|
public destroy() {
|
|
418
|
+
console.log("[window-manager] destroy ");
|
|
419
|
+
if (this.wrapperRectWorkaroundFrame) {
|
|
420
|
+
cancelAnimationFrame(this.wrapperRectWorkaroundFrame);
|
|
421
|
+
this.wrapperRectWorkaroundFrame = 0;
|
|
422
|
+
}
|
|
312
423
|
this.removeMainViewListener();
|
|
313
424
|
this.stop();
|
|
314
425
|
this.sideEffectManager.flushAll();
|