@netless/window-manager 1.0.13-bate.0 → 1.0.13-bate.1
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 +45 -2
- package/dist/index.js +14 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +302 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/AttributesDelegate.ts +0 -3
- package/src/ContainerResizeObserver.ts +4 -6
- package/src/Utils/Reactive.ts +2 -2
- package/src/Utils/attributesLogStringify.ts +78 -0
- package/src/Utils/log.ts +228 -0
- package/src/View/MainView.ts +50 -17
- package/src/index.ts +14 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netless/window-manager",
|
|
3
|
-
"version": "1.0.13-bate.
|
|
3
|
+
"version": "1.0.13-bate.1",
|
|
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.54"
|
|
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.54"
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -7,7 +7,6 @@ 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";
|
|
11
10
|
|
|
12
11
|
export enum Fields {
|
|
13
12
|
Apps = "apps",
|
|
@@ -55,7 +54,6 @@ export type ISize = Size & { id: string };
|
|
|
55
54
|
|
|
56
55
|
export class AttributesDelegate {
|
|
57
56
|
static readonly kind = "AttributesDelegate";
|
|
58
|
-
private setMainViewCameraConsole = new LocalConsole("setMainViewCamera", 30);
|
|
59
57
|
constructor(private context: StoreContext) {}
|
|
60
58
|
|
|
61
59
|
public setContext(context: StoreContext) {
|
|
@@ -212,7 +210,6 @@ export class AttributesDelegate {
|
|
|
212
210
|
}
|
|
213
211
|
|
|
214
212
|
public setMainViewCamera(camera: ICamera) {
|
|
215
|
-
this.setMainViewCameraConsole.log(JSON.stringify(camera));
|
|
216
213
|
this.context.safeSetAttributes({ [Fields.MainViewCamera]: { ...camera } });
|
|
217
214
|
}
|
|
218
215
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ResizeObserver as ResizeObserverPolyfill } from "@juggle/resize-observer";
|
|
2
|
-
import { isFunction } from "lodash";
|
|
3
2
|
import { WindowManager } from "./index";
|
|
4
3
|
import type { EmitterType } from "./InternalEmitter";
|
|
5
4
|
import type { UnsubscribeFn } from "emittery";
|
|
@@ -11,7 +10,7 @@ export class ContainerResizeObserver {
|
|
|
11
10
|
private containerResizeObserver?: ResizeObserver;
|
|
12
11
|
private disposer?: UnsubscribeFn;
|
|
13
12
|
|
|
14
|
-
private updateSizerLocalConsole = new LocalConsole("updateSizer",
|
|
13
|
+
private updateSizerLocalConsole = new LocalConsole("updateSizer", 100);
|
|
15
14
|
|
|
16
15
|
constructor(private emitter: EmitterType) {}
|
|
17
16
|
|
|
@@ -77,10 +76,9 @@ export class ContainerResizeObserver {
|
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
public disconnect() {
|
|
79
|
+
this.updateSizerLocalConsole.destroy();
|
|
80
80
|
this.containerResizeObserver?.disconnect();
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
this.disposer = undefined;
|
|
84
|
-
}
|
|
81
|
+
this.disposer?.();
|
|
82
|
+
this.disposer = undefined;
|
|
85
83
|
}
|
|
86
84
|
}
|
package/src/Utils/Reactive.ts
CHANGED
|
@@ -30,8 +30,8 @@ export const onObjectByEvent = (event: UpdateEventKind) => {
|
|
|
30
30
|
};
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export const safeListenPropsUpdated = <T
|
|
34
|
-
getProps: () => T,
|
|
33
|
+
export const safeListenPropsUpdated = <T extends Record<string, unknown>>(
|
|
34
|
+
getProps: () => T | null | undefined,
|
|
35
35
|
callback: AkkoObjectUpdatedListener<T>,
|
|
36
36
|
onDestroyed?: (props: unknown) => void
|
|
37
37
|
) => {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** 合法标识符形式的 key 省略引号,形如 `{aaa:undefined}` */
|
|
2
|
+
function formatAttributesLogObjectKey(key: string): string {
|
|
3
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* attributes 调试日志:对象/数组会写成近似 JS 字面量(保留 `undefined`、数组空洞),避免 `[object Object]`;
|
|
8
|
+
* 并处理 BigInt、循环引用等。
|
|
9
|
+
*/
|
|
10
|
+
export function stringifyForAttributesLog(value: unknown, seen?: WeakSet<object>): string {
|
|
11
|
+
if (value === undefined) {
|
|
12
|
+
return "undefined";
|
|
13
|
+
}
|
|
14
|
+
if (value === null) {
|
|
15
|
+
return "null";
|
|
16
|
+
}
|
|
17
|
+
const t = typeof value;
|
|
18
|
+
if (t === "bigint") {
|
|
19
|
+
return `${value}n`;
|
|
20
|
+
}
|
|
21
|
+
if (t === "symbol") {
|
|
22
|
+
return String(value);
|
|
23
|
+
}
|
|
24
|
+
if (t === "function") {
|
|
25
|
+
const fn = value as (...args: unknown[]) => unknown;
|
|
26
|
+
return `[Function ${fn.name || "anonymous"}]`;
|
|
27
|
+
}
|
|
28
|
+
if (t !== "object") {
|
|
29
|
+
return t === "string" ? JSON.stringify(value as string) : String(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const obj = value as object;
|
|
33
|
+
if (seen?.has(obj)) {
|
|
34
|
+
return "[Circular]";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const nextSeen = seen ?? new WeakSet<object>();
|
|
38
|
+
nextSeen.add(obj);
|
|
39
|
+
try {
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
return `[${Array.from(value as unknown[], (item) =>
|
|
42
|
+
stringifyForAttributesLog(item, nextSeen),
|
|
43
|
+
).join(",")}]`;
|
|
44
|
+
}
|
|
45
|
+
if (value instanceof Date) {
|
|
46
|
+
return JSON.stringify(value.toISOString());
|
|
47
|
+
}
|
|
48
|
+
if (value instanceof RegExp) {
|
|
49
|
+
return String(value);
|
|
50
|
+
}
|
|
51
|
+
const keys = Object.keys(value as object);
|
|
52
|
+
const pairs = keys.map((k) => {
|
|
53
|
+
let v: unknown;
|
|
54
|
+
try {
|
|
55
|
+
v = (value as Record<string, unknown>)[k];
|
|
56
|
+
} catch {
|
|
57
|
+
return `${formatAttributesLogObjectKey(k)}:[Threw]`;
|
|
58
|
+
}
|
|
59
|
+
return `${formatAttributesLogObjectKey(k)}:${stringifyForAttributesLog(v, nextSeen)}`;
|
|
60
|
+
});
|
|
61
|
+
return `{${pairs.join(",")}}`;
|
|
62
|
+
} catch {
|
|
63
|
+
return "[Unserializable]";
|
|
64
|
+
} finally {
|
|
65
|
+
nextSeen.delete(obj);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** 仅一层 key 合并:可作为 attributes 片段的「普通对象」(非数组、Date 等) */
|
|
70
|
+
export function isShallowMergeAttributesRecord(value: unknown): value is Record<string, unknown> {
|
|
71
|
+
return (
|
|
72
|
+
value !== null &&
|
|
73
|
+
typeof value === "object" &&
|
|
74
|
+
!Array.isArray(value) &&
|
|
75
|
+
!(value instanceof Date) &&
|
|
76
|
+
!(value instanceof RegExp)
|
|
77
|
+
);
|
|
78
|
+
}
|
package/src/Utils/log.ts
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
|
+
import type { Logger } from "white-web-sdk";
|
|
2
|
+
import { isShallowMergeAttributesRecord, stringifyForAttributesLog } from "./attributesLogStringify";
|
|
1
3
|
import { WindowManager } from "../index";
|
|
2
4
|
|
|
5
|
+
/** ArgusLog 经 `logger.info` 上报的单条字符串上限(含前缀) */
|
|
6
|
+
const ARGUS_LOG_INFO_MAX_LENGTH = 1500;
|
|
7
|
+
|
|
8
|
+
function truncateArgusLogInfoMessage(message: string): string {
|
|
9
|
+
if (message.length <= ARGUS_LOG_INFO_MAX_LENGTH) {
|
|
10
|
+
return message;
|
|
11
|
+
}
|
|
12
|
+
const ellipsis = "…";
|
|
13
|
+
return message.slice(0, ARGUS_LOG_INFO_MAX_LENGTH - ellipsis.length) + ellipsis;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function keysPathEqual(a: string[], b: string[]): boolean {
|
|
17
|
+
if (a.length !== b.length) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
for (let i = 0; i < a.length; i++) {
|
|
21
|
+
if (a[i] !== b[i]) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
3
28
|
export const log = (...args: any[]): void => {
|
|
4
29
|
if (WindowManager.debug) {
|
|
5
30
|
console.log(`[WindowManager]:`, ...args);
|
|
@@ -41,4 +66,207 @@ export class LocalConsole {
|
|
|
41
66
|
}
|
|
42
67
|
console.log(`[window-manager][${this.name}]: ${args.join(", ")}`);
|
|
43
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 销毁:清除 debounce 定时器与未输出的暂存参数。
|
|
72
|
+
* 持有 LocalConsole 的类在销毁时应调用。
|
|
73
|
+
*/
|
|
74
|
+
destroy(): void {
|
|
75
|
+
if (this.flushTimer != null) {
|
|
76
|
+
clearTimeout(this.flushTimer);
|
|
77
|
+
this.flushTimer = null;
|
|
78
|
+
}
|
|
79
|
+
this.pendingArgs = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 按 `[WindowManager][tagName]` 前缀输出。
|
|
85
|
+
* 若传入 `debounceTime`(毫秒):窗口内多次 `log` 不立即输出,只在连续停止调用满 `debounceTime` 后输出**最后一次**的参数(尾部 debounce)。
|
|
86
|
+
*/
|
|
87
|
+
export class ArgusLog {
|
|
88
|
+
private pendingArgs: unknown[] | null = null;
|
|
89
|
+
private flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
90
|
+
|
|
91
|
+
/** debounce 窗口内按一层 key 合并;同 key 后者覆盖;非普通对象则整段待输出被本次值替换 */
|
|
92
|
+
private pendingShallowMerge:
|
|
93
|
+
| { kind: "record"; label: string; data: Record<string, unknown> }
|
|
94
|
+
| { kind: "atom"; label: string; value: unknown }
|
|
95
|
+
| null = null;
|
|
96
|
+
private shallowMergeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
97
|
+
|
|
98
|
+
/** debounce 窗口内 safeUpdateAttributes:同 keys 数组则只更新 value,否则追加一段,flush 时拼成一条 */
|
|
99
|
+
private pendingUpdateSegments: { keys: string[]; value: unknown }[] | null = null;
|
|
100
|
+
private updateMergeTimer: ReturnType<typeof setTimeout> | null = null;
|
|
101
|
+
|
|
102
|
+
constructor(
|
|
103
|
+
private readonly logger: Logger,
|
|
104
|
+
private readonly name: string,
|
|
105
|
+
private readonly debounceTime?: number,
|
|
106
|
+
) {}
|
|
107
|
+
|
|
108
|
+
private emitInfo(message: string): void {
|
|
109
|
+
this.logger.info(truncateArgusLogInfoMessage(message));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private flush(): void {
|
|
113
|
+
this.flushTimer = null;
|
|
114
|
+
const args = this.pendingArgs;
|
|
115
|
+
this.pendingArgs = null;
|
|
116
|
+
if (args === null) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.emitInfo(`[WindowManager][${this.name}]: ${args.join(", ")}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private flushShallowMerge(): void {
|
|
123
|
+
this.shallowMergeTimer = null;
|
|
124
|
+
const p = this.pendingShallowMerge;
|
|
125
|
+
this.pendingShallowMerge = null;
|
|
126
|
+
if (p === null) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const body =
|
|
130
|
+
p.kind === "record" ? stringifyForAttributesLog(p.data) : stringifyForAttributesLog(p.value);
|
|
131
|
+
this.emitInfo(`[WindowManager][${this.name}]: ${p.label} ${body}`);
|
|
132
|
+
// 输出后释放合并对象引用,避免长时间持有 attributes 快照
|
|
133
|
+
if (p.kind === "record") {
|
|
134
|
+
for (const k of Object.keys(p.data)) {
|
|
135
|
+
delete p.data[k];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
log(...args: unknown[]): void {
|
|
141
|
+
const ms = this.debounceTime;
|
|
142
|
+
if (ms != null && ms > 0) {
|
|
143
|
+
this.pendingArgs = args;
|
|
144
|
+
if (this.flushTimer != null) {
|
|
145
|
+
clearTimeout(this.flushTimer);
|
|
146
|
+
}
|
|
147
|
+
this.flushTimer = setTimeout(() => this.flush(), ms);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
this.emitInfo(`[WindowManager][${this.name}]: ${args.join(", ")}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 带 debounce 时:窗口内多次调用会把「一层 key」合并进同一条日志(不同 key 并存,同 key 取最后一次)。
|
|
155
|
+
* `payload` 为普通对象时做浅合并;否则视为原子值,覆盖当前待合并状态(丢弃此前累积的对象 key)。
|
|
156
|
+
* 无 debounce 或时间为 0 时立即输出。
|
|
157
|
+
*/
|
|
158
|
+
logDebouncedShallowMerge(label: string, payload: unknown): void {
|
|
159
|
+
const ms = this.debounceTime;
|
|
160
|
+
const debounced = ms != null && ms > 0;
|
|
161
|
+
|
|
162
|
+
const emit = (text: string): void => {
|
|
163
|
+
this.emitInfo(`[WindowManager][${this.name}]: ${label} ${text}`);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
if (!debounced) {
|
|
167
|
+
emit(stringifyForAttributesLog(payload));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (this.shallowMergeTimer != null) {
|
|
172
|
+
clearTimeout(this.shallowMergeTimer);
|
|
173
|
+
this.shallowMergeTimer = null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (isShallowMergeAttributesRecord(payload)) {
|
|
177
|
+
if (this.pendingShallowMerge?.kind === "record") {
|
|
178
|
+
this.pendingShallowMerge = {
|
|
179
|
+
kind: "record",
|
|
180
|
+
label,
|
|
181
|
+
data: { ...this.pendingShallowMerge.data, ...payload },
|
|
182
|
+
};
|
|
183
|
+
} else {
|
|
184
|
+
this.pendingShallowMerge = { kind: "record", label, data: { ...payload } };
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
this.pendingShallowMerge = { kind: "atom", label, value: payload };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.shallowMergeTimer = setTimeout(() => this.flushShallowMerge(), ms);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private flushUpdateAttributesMerge(): void {
|
|
194
|
+
this.updateMergeTimer = null;
|
|
195
|
+
const segments = this.pendingUpdateSegments;
|
|
196
|
+
this.pendingUpdateSegments = null;
|
|
197
|
+
if (segments === null || segments.length === 0) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const parts = segments.map(
|
|
201
|
+
(s) => `${s.keys.join(", ")} ${stringifyForAttributesLog(s.value)}`,
|
|
202
|
+
);
|
|
203
|
+
this.emitInfo(`[WindowManager][${this.name}]: safeUpdateAttributes ${parts.join(" | ")}`);
|
|
204
|
+
for (const s of segments) {
|
|
205
|
+
s.keys.length = 0;
|
|
206
|
+
s.value = undefined;
|
|
207
|
+
}
|
|
208
|
+
segments.length = 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 带 debounce 时:连续调用若 `keys` 与上一段完全相同则覆盖该段的 `value`;否则追加一段。
|
|
213
|
+
* flush 时输出一条日志,多段用 ` | ` 连接。
|
|
214
|
+
*/
|
|
215
|
+
logDebouncedUpdateAttributes(keys: string[], value: unknown): void {
|
|
216
|
+
const ms = this.debounceTime;
|
|
217
|
+
const debounced = ms != null && ms > 0;
|
|
218
|
+
const keysCopy = [...keys];
|
|
219
|
+
|
|
220
|
+
if (!debounced) {
|
|
221
|
+
this.emitInfo(
|
|
222
|
+
`[WindowManager][${this.name}]: safeUpdateAttributes ${keysCopy.join(", ")} ${stringifyForAttributesLog(value)}`,
|
|
223
|
+
);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (this.updateMergeTimer != null) {
|
|
228
|
+
clearTimeout(this.updateMergeTimer);
|
|
229
|
+
this.updateMergeTimer = null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (this.pendingUpdateSegments === null || this.pendingUpdateSegments.length === 0) {
|
|
233
|
+
this.pendingUpdateSegments = [{ keys: keysCopy, value }];
|
|
234
|
+
} else {
|
|
235
|
+
const last = this.pendingUpdateSegments[this.pendingUpdateSegments.length - 1];
|
|
236
|
+
if (keysPathEqual(last.keys, keysCopy)) {
|
|
237
|
+
last.value = value;
|
|
238
|
+
} else {
|
|
239
|
+
this.pendingUpdateSegments.push({ keys: keysCopy, value });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.updateMergeTimer = setTimeout(() => this.flushUpdateAttributesMerge(), ms);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 销毁:清除所有 `setTimeout` debounce 定时器,并丢弃尚未输出的暂存日志(不补打日志)。
|
|
248
|
+
* WindowManager 销毁时应调用,避免泄漏与销毁后仍触发 `logger.info`。
|
|
249
|
+
*/
|
|
250
|
+
destroy(): void {
|
|
251
|
+
if (this.flushTimer != null) {
|
|
252
|
+
clearTimeout(this.flushTimer);
|
|
253
|
+
this.flushTimer = null;
|
|
254
|
+
}
|
|
255
|
+
if (this.shallowMergeTimer != null) {
|
|
256
|
+
clearTimeout(this.shallowMergeTimer);
|
|
257
|
+
this.shallowMergeTimer = null;
|
|
258
|
+
}
|
|
259
|
+
if (this.updateMergeTimer != null) {
|
|
260
|
+
clearTimeout(this.updateMergeTimer);
|
|
261
|
+
this.updateMergeTimer = null;
|
|
262
|
+
}
|
|
263
|
+
this.pendingArgs = null;
|
|
264
|
+
this.pendingShallowMerge = null;
|
|
265
|
+
this.pendingUpdateSegments = null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** 与 `destroy()` 相同,保留旧名以兼容 */
|
|
269
|
+
dispose(): void {
|
|
270
|
+
this.destroy();
|
|
271
|
+
}
|
|
44
272
|
}
|
package/src/View/MainView.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { callbacks } from "../callback";
|
|
|
3
3
|
import { createView } from "./ViewManager";
|
|
4
4
|
import { debounce, get, isEmpty, isEqual } from "lodash";
|
|
5
5
|
import { internalEmitter } from "../InternalEmitter";
|
|
6
|
-
import { Fields } from "../AttributesDelegate";
|
|
6
|
+
import { Fields, type MainViewCamera } from "../AttributesDelegate";
|
|
7
7
|
import { setViewFocusScenePath } from "../Utils/Common";
|
|
8
8
|
import { SideEffectManager } from "side-effect-manager";
|
|
9
9
|
import type { Camera, Room, Size, View } from "white-web-sdk";
|
|
@@ -11,6 +11,14 @@ import type { AppManager } from "../AppManager";
|
|
|
11
11
|
import { Events } from "../constants";
|
|
12
12
|
import { LocalConsole } from "../Utils/log";
|
|
13
13
|
|
|
14
|
+
type MainViewScreenLike = {
|
|
15
|
+
refreshSize?: (width: number, height: number) => void;
|
|
16
|
+
resizeObserver?: {
|
|
17
|
+
disconnect?: () => void;
|
|
18
|
+
observe?: (target: Element) => void;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
14
22
|
export class MainViewProxy {
|
|
15
23
|
/** Refresh the view's camera in an interval of 1.5s. */
|
|
16
24
|
public polling = false;
|
|
@@ -27,9 +35,10 @@ export class MainViewProxy {
|
|
|
27
35
|
|
|
28
36
|
private sideEffectManager = new SideEffectManager();
|
|
29
37
|
|
|
30
|
-
private playgroundSizeChangeListenerLocalConsole = new LocalConsole("playgroundSizeChangeListener",
|
|
31
|
-
private sizeUpdatedLocalConsole = new LocalConsole("sizeUpdated",
|
|
32
|
-
private cameraUpdatedLocalConsole = new LocalConsole("cameraUpdated",
|
|
38
|
+
private playgroundSizeChangeListenerLocalConsole = new LocalConsole("playgroundSizeChangeListener", 100);
|
|
39
|
+
private sizeUpdatedLocalConsole = new LocalConsole("sizeUpdated", 100);
|
|
40
|
+
private cameraUpdatedLocalConsole = new LocalConsole("cameraUpdated", 100);
|
|
41
|
+
private cameraReactionLocalConsole = new LocalConsole("cameraReaction", 100);
|
|
33
42
|
|
|
34
43
|
constructor(private manager: AppManager) {
|
|
35
44
|
this.mainView = this.createMainView();
|
|
@@ -155,6 +164,7 @@ export class MainViewProxy {
|
|
|
155
164
|
element: HTMLDivElement
|
|
156
165
|
) {
|
|
157
166
|
const { width: viewWidth, height: viewHeight } = this.mainView.size;
|
|
167
|
+
let targetElement = element;
|
|
158
168
|
if (
|
|
159
169
|
Math.abs(viewWidth - observedSize.width) <= 0.5 &&
|
|
160
170
|
Math.abs(viewHeight - observedSize.height) <= 0.5
|
|
@@ -170,17 +180,36 @@ export class MainViewProxy {
|
|
|
170
180
|
return;
|
|
171
181
|
}
|
|
172
182
|
this.isForcingMainViewDivElement = true;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
try {
|
|
184
|
+
const mainView = this.mainView as View & { screen?: MainViewScreenLike };
|
|
185
|
+
const screen = mainView.screen;
|
|
186
|
+
const resizeObserver = screen?.resizeObserver;
|
|
187
|
+
if (typeof screen?.refreshSize === "function") {
|
|
188
|
+
console.log(
|
|
189
|
+
"[window-manager] forceSyncMainViewDivElement observerReset " +
|
|
190
|
+
JSON.stringify({
|
|
191
|
+
reason,
|
|
192
|
+
viewSize: this.mainView.size,
|
|
193
|
+
observedSize,
|
|
194
|
+
})
|
|
195
|
+
);
|
|
196
|
+
// Reset the observer queue so we sync against the current DOM box,
|
|
197
|
+
// not a stale ResizeObserver entry from a rapid resize burst.
|
|
198
|
+
resizeObserver?.disconnect?.();
|
|
199
|
+
screen.refreshSize(observedSize.width, observedSize.height);
|
|
200
|
+
resizeObserver?.observe?.(element);
|
|
201
|
+
}
|
|
202
|
+
} finally {
|
|
203
|
+
queueMicrotask(() => {
|
|
204
|
+
const rect = targetElement.getBoundingClientRect();
|
|
205
|
+
console.log("[window-manager] forceSyncMainViewDivElementResult " + JSON.stringify({
|
|
206
|
+
reason,
|
|
207
|
+
viewSize: this.mainView.size,
|
|
208
|
+
rect: { width: rect.width, height: rect.height },
|
|
209
|
+
}));
|
|
210
|
+
this.isForcingMainViewDivElement = false;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
184
213
|
}
|
|
185
214
|
|
|
186
215
|
public start() {
|
|
@@ -210,11 +239,11 @@ export class MainViewProxy {
|
|
|
210
239
|
private cameraReaction = () => {
|
|
211
240
|
return reaction(
|
|
212
241
|
() => this.mainViewCamera,
|
|
213
|
-
camera => {
|
|
242
|
+
(camera: MainViewCamera | undefined) => {
|
|
214
243
|
if (camera && camera.id !== this.manager.uid) {
|
|
215
|
-
console.log("[window-manager] cameraReaction " + JSON.stringify(camera) + JSON.stringify(this.mainViewSize));
|
|
216
244
|
this.moveCameraToContian(this.mainViewSize);
|
|
217
245
|
this.moveCamera(camera);
|
|
246
|
+
this.cameraReactionLocalConsole.log(`camera: ${JSON.stringify(camera)}, current size: ${JSON.stringify(this.mainViewSize)}`);
|
|
218
247
|
}
|
|
219
248
|
},
|
|
220
249
|
{ fireImmediately: true }
|
|
@@ -420,6 +449,10 @@ export class MainViewProxy {
|
|
|
420
449
|
cancelAnimationFrame(this.wrapperRectWorkaroundFrame);
|
|
421
450
|
this.wrapperRectWorkaroundFrame = 0;
|
|
422
451
|
}
|
|
452
|
+
this.playgroundSizeChangeListenerLocalConsole.destroy();
|
|
453
|
+
this.sizeUpdatedLocalConsole.destroy();
|
|
454
|
+
this.cameraUpdatedLocalConsole.destroy();
|
|
455
|
+
this.cameraReactionLocalConsole.destroy();
|
|
423
456
|
this.removeMainViewListener();
|
|
424
457
|
this.stop();
|
|
425
458
|
this.sideEffectManager.flushAll();
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { Fields } from "./AttributesDelegate";
|
|
|
12
12
|
import { initDb } from "./Register/storage";
|
|
13
13
|
import { InvisiblePlugin, isPlayer, isRoom, RoomPhase, ViewMode } from "white-web-sdk";
|
|
14
14
|
import { isEqual, isNull, isObject, omit, isNumber } from "lodash";
|
|
15
|
-
import { log } from "./Utils/log";
|
|
15
|
+
import { ArgusLog, log } from "./Utils/log";
|
|
16
16
|
import { PageStateImpl } from "./PageState";
|
|
17
17
|
import { ReconnectRefresher } from "./ReconnectRefresher";
|
|
18
18
|
import { replaceRoomFunction } from "./Utils/RoomHacker";
|
|
@@ -245,6 +245,8 @@ export class WindowManager
|
|
|
245
245
|
|
|
246
246
|
private _roomLogger?: Logger;
|
|
247
247
|
|
|
248
|
+
public attributesDeboundceLog?: ArgusLog;
|
|
249
|
+
|
|
248
250
|
get Logger(): Logger | undefined {
|
|
249
251
|
return this._roomLogger;
|
|
250
252
|
}
|
|
@@ -287,6 +289,7 @@ export class WindowManager
|
|
|
287
289
|
manager = await this.initManager(room);
|
|
288
290
|
if (manager) {
|
|
289
291
|
manager._roomLogger = (room as unknown as { logger: Logger }).logger;
|
|
292
|
+
manager.attributesDeboundceLog = new ArgusLog(manager._roomLogger, "attributes", 300);
|
|
290
293
|
if (WindowManager.registered.size > 0) {
|
|
291
294
|
manager._roomLogger.info(
|
|
292
295
|
`[WindowManager] registered apps: ${JSON.stringify(
|
|
@@ -1064,6 +1067,8 @@ export class WindowManager
|
|
|
1064
1067
|
}
|
|
1065
1068
|
|
|
1066
1069
|
private _destroy() {
|
|
1070
|
+
this.attributesDeboundceLog?.destroy();
|
|
1071
|
+
this.attributesDeboundceLog = undefined;
|
|
1067
1072
|
this.containerResizeObserver?.disconnect();
|
|
1068
1073
|
this.appManager?.destroy();
|
|
1069
1074
|
this.cursorManager?.destroy();
|
|
@@ -1106,15 +1111,19 @@ export class WindowManager
|
|
|
1106
1111
|
|
|
1107
1112
|
public safeSetAttributes(attributes: any): void {
|
|
1108
1113
|
if (this.canOperate) {
|
|
1109
|
-
this.Logger && this.Logger.info(`[WindowManager]: safeSetAttributes ${JSON.stringify(attributes)}`);
|
|
1110
1114
|
this.setAttributes(attributes);
|
|
1115
|
+
if (this.attributesDeboundceLog) {
|
|
1116
|
+
this.attributesDeboundceLog.logDebouncedShallowMerge("safeSetAttributes", attributes);
|
|
1117
|
+
}
|
|
1111
1118
|
}
|
|
1112
1119
|
}
|
|
1113
1120
|
|
|
1114
1121
|
public safeUpdateAttributes(keys: string[], value: any): void {
|
|
1115
1122
|
if (this.canOperate) {
|
|
1116
|
-
this.Logger && this.Logger.info(`[WindowManager]: safeUpdateAttributes ${keys.join(", ")} ${value}`);
|
|
1117
1123
|
this.updateAttributes(keys, value);
|
|
1124
|
+
if (this.attributesDeboundceLog) {
|
|
1125
|
+
this.attributesDeboundceLog.logDebouncedUpdateAttributes(keys, value);
|
|
1126
|
+
}
|
|
1118
1127
|
}
|
|
1119
1128
|
}
|
|
1120
1129
|
|
|
@@ -1124,8 +1133,8 @@ export class WindowManager
|
|
|
1124
1133
|
|
|
1125
1134
|
public cleanCurrentScene(): void {
|
|
1126
1135
|
log("clean current scene");
|
|
1127
|
-
this.Logger && this.Logger.info(`[WindowManager]: cleanCurrentScene ${this.focusedView?.focusScenePath}`);
|
|
1128
1136
|
this.focusedView?.cleanCurrentScene();
|
|
1137
|
+
this.Logger && this.Logger.info(`[WindowManager]: cleanCurrentScene ${this.focusedView?.focusScenePath}`);
|
|
1129
1138
|
}
|
|
1130
1139
|
|
|
1131
1140
|
public redo(): number {
|
|
@@ -1137,8 +1146,8 @@ export class WindowManager
|
|
|
1137
1146
|
}
|
|
1138
1147
|
|
|
1139
1148
|
public delete(): void {
|
|
1140
|
-
this.Logger && this.Logger.info(`[WindowManager]: delete ${this.focusedView?.focusScenePath}`);
|
|
1141
1149
|
this.focusedView?.delete();
|
|
1150
|
+
this.Logger && this.Logger.info(`[WindowManager]: delete ${this.focusedView?.focusScenePath}`);
|
|
1142
1151
|
}
|
|
1143
1152
|
|
|
1144
1153
|
public copy(): void {
|