@netless/window-manager 1.0.0-canary.9 → 1.0.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.
Files changed (132) hide show
  1. package/LICENSE.txt +21 -0
  2. package/README.md +90 -64
  3. package/README.zh-cn.md +224 -0
  4. package/dist/index.d.ts +1133 -40
  5. package/dist/index.js +62 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/{index.es.js → index.mjs} +7954 -5445
  8. package/dist/index.mjs.map +1 -0
  9. package/dist/style.css +1 -1
  10. package/docs/advanced.md +55 -55
  11. package/docs/api.md +126 -113
  12. package/docs/app-context.md +248 -209
  13. package/docs/basic.md +25 -26
  14. package/docs/camera.md +21 -21
  15. package/docs/cn/advanced.md +137 -0
  16. package/docs/cn/api.md +311 -0
  17. package/docs/cn/app-context.md +369 -0
  18. package/docs/cn/basic.md +64 -0
  19. package/docs/cn/camera.md +53 -0
  20. package/docs/cn/concept.md +9 -0
  21. package/docs/cn/custom-max-bar.md +31 -0
  22. package/docs/cn/develop-app.md +94 -0
  23. package/docs/cn/export-pdf.md +48 -0
  24. package/docs/cn/migrate.md +60 -0
  25. package/docs/cn/replay.md +40 -0
  26. package/docs/concept.md +6 -5
  27. package/docs/custom-max-bar.md +31 -0
  28. package/docs/develop-app.md +22 -19
  29. package/docs/export-pdf.md +48 -0
  30. package/docs/migrate.md +25 -27
  31. package/docs/quickstart.md +50 -0
  32. package/docs/replay.md +20 -20
  33. package/package.json +32 -22
  34. package/src/App/AppContext.ts +105 -73
  35. package/src/App/AppPageStateImpl.ts +6 -25
  36. package/src/App/AppProxy.ts +41 -166
  37. package/src/App/MagixEvent/index.ts +38 -38
  38. package/src/App/Storage/StorageEvent.ts +13 -13
  39. package/src/App/Storage/index.ts +269 -245
  40. package/src/App/Storage/typings.ts +4 -2
  41. package/src/App/Storage/utils.ts +3 -3
  42. package/src/App/index.ts +0 -1
  43. package/src/AppListener.ts +8 -8
  44. package/src/AppManager.ts +88 -77
  45. package/src/AttributesDelegate.ts +42 -22
  46. package/src/BoxEmitter.ts +12 -6
  47. package/src/BoxManager.ts +128 -108
  48. package/src/ContainerResizeObserver.ts +75 -0
  49. package/src/Cursor/Cursor.svelte +16 -5
  50. package/src/Cursor/Cursor.svelte.d.ts +21 -0
  51. package/src/Cursor/Cursor.ts +77 -13
  52. package/src/Cursor/icons.ts +6 -0
  53. package/src/Cursor/icons2.ts +66 -0
  54. package/src/Cursor/index.ts +127 -26
  55. package/src/Helper.ts +94 -14
  56. package/src/InternalEmitter.ts +2 -7
  57. package/src/Page/index.ts +1 -1
  58. package/src/PageState.ts +6 -5
  59. package/src/ReconnectRefresher.ts +9 -4
  60. package/src/RedoUndo.ts +3 -3
  61. package/src/Register/index.ts +22 -17
  62. package/src/Register/loader.ts +26 -22
  63. package/src/Register/storage.ts +13 -13
  64. package/src/Utils/Common.ts +18 -14
  65. package/src/Utils/Reactive.ts +26 -25
  66. package/src/Utils/RoomHacker.ts +4 -4
  67. package/src/Utils/error.ts +0 -1
  68. package/src/View/IframeBridge.ts +680 -0
  69. package/src/View/MainView.ts +127 -53
  70. package/src/callback.ts +21 -1
  71. package/src/constants.ts +0 -2
  72. package/src/image/pencil-eraser-1.svg +3 -0
  73. package/src/image/pencil-eraser-2.svg +3 -0
  74. package/src/image/pencil-eraser-3.svg +3 -0
  75. package/src/index.ts +220 -83
  76. package/src/style.css +27 -10
  77. package/src/typings.ts +20 -10
  78. package/.prettierignore +0 -7
  79. package/.prettierrc.json +0 -9
  80. package/CHANGELOG.md +0 -196
  81. package/__mocks__/white-web-sdk.ts +0 -50
  82. package/dist/App/AppContext.d.ts +0 -76
  83. package/dist/App/AppPageStateImpl.d.ts +0 -21
  84. package/dist/App/AppProxy.d.ts +0 -86
  85. package/dist/App/AppViewSync.d.ts +0 -11
  86. package/dist/App/MagixEvent/index.d.ts +0 -29
  87. package/dist/App/Storage/StorageEvent.d.ts +0 -8
  88. package/dist/App/Storage/index.d.ts +0 -39
  89. package/dist/App/Storage/typings.d.ts +0 -22
  90. package/dist/App/Storage/utils.d.ts +0 -5
  91. package/dist/App/WhiteboardView.d.ts +0 -22
  92. package/dist/App/index.d.ts +0 -3
  93. package/dist/AppListener.d.ts +0 -21
  94. package/dist/AppManager.d.ts +0 -107
  95. package/dist/AttributesDelegate.d.ts +0 -80
  96. package/dist/BoxEmitter.d.ts +0 -34
  97. package/dist/BoxManager.d.ts +0 -99
  98. package/dist/BuiltinApps.d.ts +0 -5
  99. package/dist/Cursor/Cursor.d.ts +0 -39
  100. package/dist/Cursor/icons.d.ts +0 -3
  101. package/dist/Cursor/index.d.ts +0 -46
  102. package/dist/Helper.d.ts +0 -17
  103. package/dist/InternalEmitter.d.ts +0 -39
  104. package/dist/Page/PageController.d.ts +0 -21
  105. package/dist/Page/index.d.ts +0 -3
  106. package/dist/PageState.d.ts +0 -9
  107. package/dist/ReconnectRefresher.d.ts +0 -24
  108. package/dist/RedoUndo.d.ts +0 -18
  109. package/dist/Register/index.d.ts +0 -28
  110. package/dist/Register/loader.d.ts +0 -4
  111. package/dist/Register/storage.d.ts +0 -8
  112. package/dist/Utils/AppCreateQueue.d.ts +0 -15
  113. package/dist/Utils/Common.d.ts +0 -23
  114. package/dist/Utils/Reactive.d.ts +0 -6
  115. package/dist/Utils/RoomHacker.d.ts +0 -3
  116. package/dist/Utils/error.d.ts +0 -27
  117. package/dist/Utils/log.d.ts +0 -1
  118. package/dist/View/CameraSynchronizer.d.ts +0 -16
  119. package/dist/View/MainView.d.ts +0 -47
  120. package/dist/View/ViewManager.d.ts +0 -13
  121. package/dist/callback.d.ts +0 -24
  122. package/dist/constants.d.ts +0 -49
  123. package/dist/index.cjs.js +0 -46
  124. package/dist/index.umd.js +0 -46
  125. package/dist/typings.d.ts +0 -82
  126. package/jest.config.js +0 -27
  127. package/pnpm-lock.yaml +0 -6302
  128. package/src/App/AppViewSync.ts +0 -68
  129. package/src/App/WhiteboardView.ts +0 -83
  130. package/src/View/CameraSynchronizer.ts +0 -56
  131. package/vite.config.js +0 -51
  132. /package/docs/{qickstart.md → cn/quickstart.md} +0 -0
@@ -0,0 +1,680 @@
1
+ import type { Displayer, DisplayerState, Room, RoomState } from "white-web-sdk";
2
+ import type { AppManager } from "../AppManager";
3
+ import type { WindowManager } from "../index";
4
+
5
+ import Emittery from "emittery";
6
+ import { PlayerPhase, AnimationMode, autorun } from "white-web-sdk";
7
+ import { SideEffectManager } from "side-effect-manager";
8
+ import { debounce, noop } from "lodash";
9
+ import { log } from "../Utils/log";
10
+
11
+ // Note: typo below should not be fixed.
12
+ export enum IframeEvents {
13
+ Init = "Init",
14
+ AttributesUpdate = "AttributesUpdate",
15
+ SetAttributes = "SetAttributes",
16
+ RegisterMagixEvent = "RegisterMagixEvent",
17
+ RemoveMagixEvent = "RemoveMagixEvent",
18
+ RemoveAllMagixEvent = "RemoveAllMagixEvent",
19
+ RoomStateChanged = "RoomStateChanged",
20
+ DispatchMagixEvent = "DispatchMagixEvent",
21
+ ReciveMagixEvent = "ReciveMagixEvent",
22
+ NextPage = "NextPage",
23
+ PrevPage = "PrevPage",
24
+ SDKCreate = "SDKCreate",
25
+ OnCreate = "OnCreate",
26
+ SetPage = "SetPage",
27
+ GetAttributes = "GetAttributes",
28
+ Ready = "Ready",
29
+ Destory = "Destory",
30
+ StartCreate = "StartCreate",
31
+ WrapperDidUpdate = "WrapperDidUpdate",
32
+ DispayIframe = "DispayIframe",
33
+ HideIframe = "HideIframe",
34
+ GetRootRect = "GetRootRect",
35
+ ReplayRootRect = "ReplayRootRect",
36
+ PageTo = "PageTo",
37
+ }
38
+
39
+ export enum DomEvents {
40
+ WrapperDidMount = "WrapperDidMount",
41
+ IframeLoad = "IframeLoad",
42
+ }
43
+
44
+ export type IframeBridgeAttributes = {
45
+ readonly url: string;
46
+ readonly width: number;
47
+ readonly height: number;
48
+ readonly displaySceneDir: string;
49
+ readonly lastEvent?: { name: string; payload: any };
50
+ readonly useClicker?: boolean;
51
+ readonly useSelector?: boolean;
52
+ };
53
+
54
+ export type IframeBridgeEvents = {
55
+ created: undefined;
56
+ [IframeEvents.Ready]: undefined;
57
+ [IframeEvents.StartCreate]: undefined;
58
+ [IframeEvents.OnCreate]: IframeBridge;
59
+ [IframeEvents.Destory]: undefined;
60
+ [IframeEvents.GetRootRect]: undefined;
61
+ [IframeEvents.ReplayRootRect]: DOMRect;
62
+ [DomEvents.WrapperDidMount]: undefined;
63
+ [IframeEvents.WrapperDidUpdate]: undefined;
64
+ [DomEvents.IframeLoad]: Event;
65
+ [IframeEvents.HideIframe]: undefined;
66
+ [IframeEvents.DispayIframe]: undefined;
67
+ };
68
+
69
+ export type IframeSize = {
70
+ readonly width: number;
71
+ readonly height: number;
72
+ };
73
+
74
+ type BaseOption = {
75
+ readonly url: string;
76
+ readonly width: number;
77
+ readonly height: number;
78
+ readonly displaySceneDir: string;
79
+ };
80
+
81
+ export type InsertOptions = {
82
+ readonly useClicker?: boolean;
83
+ readonly useSelector?: boolean;
84
+ } & BaseOption;
85
+
86
+ export type OnCreateInsertOption = {
87
+ readonly displayer: Displayer;
88
+ } & BaseOption;
89
+
90
+ const RefreshIDs = {
91
+ Ready: IframeEvents.Ready,
92
+ RootRect: IframeEvents.ReplayRootRect,
93
+ Message: "message",
94
+ ComputeStyle: "computeStyle",
95
+ Load: "load",
96
+ DisplayerState: "displayerState",
97
+ Show: "show",
98
+ Hide: "hide",
99
+ };
100
+
101
+ const times = <T>(number: number, iteratee: (value: number) => T) => {
102
+ return new Array(number).fill(0).map((_, index) => iteratee(index));
103
+ };
104
+
105
+ /**
106
+ * {@link https://github.com/netless-io/netless-iframe-bridge @netless/iframe-bridge}
107
+ */
108
+ export class IframeBridge {
109
+ public static readonly kind = "IframeBridge";
110
+ public static readonly hiddenClass = "netless-iframe-brdige-hidden";
111
+ public static emitter: Emittery<IframeBridgeEvents> = new Emittery();
112
+ private static displayer: Displayer | null = null;
113
+ private static alreadyCreate = false;
114
+
115
+ public displayer: Displayer;
116
+ public iframe: HTMLIFrameElement;
117
+
118
+ private readonly magixEventMap = new Map<string, any>();
119
+ private cssList: string[] = [];
120
+ private allowAppliances: string[] = ["clicker"];
121
+ private bridgeDisposer: () => void = noop;
122
+ private rootRect: DOMRect | null = null;
123
+
124
+ private sideEffectManager = new SideEffectManager();
125
+
126
+ constructor(readonly manager: WindowManager, readonly appManager: AppManager) {
127
+ this.displayer = IframeBridge.displayer = appManager.displayer;
128
+
129
+ this.iframe = this._createIframe();
130
+
131
+ this.sideEffectManager.addDisposer(
132
+ IframeBridge.emitter.on(IframeEvents.ReplayRootRect, rect => {
133
+ this.rootRect = rect;
134
+ }),
135
+ RefreshIDs.RootRect
136
+ );
137
+
138
+ this.sideEffectManager.addDisposer(
139
+ IframeBridge.emitter.on(IframeEvents.HideIframe, () => {
140
+ this.iframe.className = IframeBridge.hiddenClass;
141
+ }),
142
+ RefreshIDs.Hide
143
+ );
144
+
145
+ this.sideEffectManager.addDisposer(
146
+ IframeBridge.emitter.on(IframeEvents.DispayIframe, () => {
147
+ this.iframe.className = "";
148
+ }),
149
+ RefreshIDs.Show
150
+ );
151
+
152
+ this.sideEffectManager.addDisposer(
153
+ IframeBridge.emitter.on("created", () => {
154
+ this.bridgeDisposer();
155
+ this.bridgeDisposer = autorun(() => {
156
+ const attributes = this.attributes;
157
+ if (attributes.url) {
158
+ const iframeSrc = this.iframe?.src;
159
+ if (iframeSrc && iframeSrc !== attributes.url) {
160
+ this.execListenIframe(attributes);
161
+ }
162
+ }
163
+ if (attributes.displaySceneDir) {
164
+ this.computedIframeDisplay(this.displayer.state, attributes);
165
+ }
166
+ if ((attributes.width || attributes.height) && this.iframe) {
167
+ this.iframe.width = `${attributes.width}px`;
168
+ this.iframe.height = `${attributes.height}px`;
169
+ }
170
+ this.postMessage({ kind: IframeEvents.AttributesUpdate, payload: attributes });
171
+ });
172
+ })
173
+ );
174
+
175
+ this.sideEffectManager.addDisposer(
176
+ manager.emitter.on("cameraStateChange", () => {
177
+ this.computedStyle(this.displayer.state);
178
+ })
179
+ );
180
+
181
+ IframeBridge.onCreate(this);
182
+ }
183
+
184
+ public static onCreate(plugin: IframeBridge): void {
185
+ IframeBridge.emitter.emit(IframeEvents.StartCreate);
186
+ IframeBridge.emitter.emit(IframeEvents.OnCreate, plugin);
187
+ IframeBridge.emitter.emit("created");
188
+ }
189
+
190
+ public insert(options: InsertOptions): this {
191
+ const initAttributes: IframeBridgeAttributes = {
192
+ url: options.url,
193
+ width: options.width,
194
+ height: options.height,
195
+ displaySceneDir: options.displaySceneDir,
196
+ useClicker: options.useClicker || false,
197
+ useSelector: options.useSelector,
198
+ };
199
+ this.setAttributes(initAttributes);
200
+
201
+ const wrapperDidMountListener = () => {
202
+ this.getIframe();
203
+ this.listenIframe(this.attributes);
204
+ this.listenDisplayerState();
205
+ IframeBridge.emitter.emit(IframeEvents.GetRootRect);
206
+ };
207
+
208
+ if (this.getIframe()) {
209
+ wrapperDidMountListener();
210
+ }
211
+ // Code below will never be executed, just copying the old code...
212
+ else {
213
+ const didMount = this.sideEffectManager.addDisposer(
214
+ IframeBridge.emitter.on(DomEvents.WrapperDidMount, () => {
215
+ wrapperDidMountListener();
216
+ this.sideEffectManager.flush(didMount);
217
+ })
218
+ );
219
+ const didUpdate = this.sideEffectManager.addDisposer(
220
+ IframeBridge.emitter.on(IframeEvents.WrapperDidUpdate, () => {
221
+ wrapperDidMountListener();
222
+ this.sideEffectManager.flush(didUpdate);
223
+ })
224
+ );
225
+ }
226
+ if (this.attributes.useSelector) {
227
+ this.allowAppliances.push("selector");
228
+ }
229
+
230
+ this.computedStyle(this.displayer.state);
231
+ this.listenDisplayerCallbacks();
232
+ this.getComputedIframeStyle();
233
+ this.sideEffectManager.addEventListener(
234
+ window,
235
+ "message",
236
+ this.messageListener.bind(this),
237
+ void 0,
238
+ RefreshIDs.Message
239
+ );
240
+
241
+ IframeBridge.alreadyCreate = true;
242
+ return this;
243
+ }
244
+
245
+ // 在某些安卓机型中会遇到 iframe 嵌套计算 bug,需要手动延迟触发一下重绘
246
+ private getComputedIframeStyle(): void {
247
+ this.sideEffectManager.setTimeout(
248
+ () => {
249
+ if (this.iframe) {
250
+ getComputedStyle(this.iframe);
251
+ }
252
+ },
253
+ 200,
254
+ RefreshIDs.ComputeStyle
255
+ );
256
+ }
257
+
258
+ public destroy() {
259
+ this.sideEffectManager.flushAll();
260
+ IframeBridge.emitter.emit(IframeEvents.Destory);
261
+ IframeBridge.alreadyCreate = false;
262
+ IframeBridge.emitter.clearListeners();
263
+ }
264
+
265
+ private getIframe(): HTMLIFrameElement {
266
+ this.iframe || (this.iframe = this._createIframe());
267
+ return this.iframe;
268
+ }
269
+
270
+ public setIframeSize(params: IframeSize): void {
271
+ if (this.iframe) {
272
+ this.iframe.width = `${params.width}px`;
273
+ this.iframe.height = `${params.height}px`;
274
+ this.setAttributes({ width: params.width, height: params.height });
275
+ }
276
+ }
277
+
278
+ public get attributes(): Partial<IframeBridgeAttributes> {
279
+ return this.appManager.store.getIframeBridge();
280
+ }
281
+
282
+ public setAttributes(data: Partial<IframeBridgeAttributes>): void {
283
+ this.appManager.store.setIframeBridge(data);
284
+ }
285
+
286
+ private _createIframe() {
287
+ const iframe = document.createElement("iframe");
288
+ iframe.id = "IframeBridge";
289
+ iframe.className = IframeBridge.hiddenClass;
290
+ if (this.appManager.mainView.divElement) {
291
+ this.appManager.mainView.divElement.appendChild(iframe);
292
+ }
293
+ return iframe;
294
+ }
295
+
296
+ public scaleIframeToFit(animationMode: AnimationMode = AnimationMode.Immediately) {
297
+ if (!this.inDisplaySceneDir) {
298
+ return;
299
+ }
300
+ const { width = 1280, height = 720 } = this.attributes;
301
+ const x = width ? -width / 2 : 0;
302
+ const y = height ? -height / 2 : 0;
303
+
304
+ this.manager.moveCameraToContain({
305
+ originX: x,
306
+ originY: y,
307
+ width,
308
+ height,
309
+ animationMode,
310
+ });
311
+ }
312
+
313
+ public get isReplay(): boolean {
314
+ return this.manager.isReplay;
315
+ }
316
+
317
+ private handleSetPage(data: any): void {
318
+ if (this.isReplay || !this.attributes.displaySceneDir) {
319
+ return;
320
+ }
321
+ const page = data.payload;
322
+ const room = this.displayer as Room;
323
+ const scenes = room.entireScenes()[this.attributes.displaySceneDir];
324
+ if (!scenes || scenes.length !== page) {
325
+ const genScenes = times<{ name: string }>(page, (index: number) => ({
326
+ name: String(index + 1),
327
+ }));
328
+ room.putScenes(this.attributes.displaySceneDir, genScenes);
329
+ this.manager.setMainViewScenePath(this.attributes.displaySceneDir);
330
+ }
331
+ }
332
+
333
+ private execListenIframe = debounce((options: Partial<IframeBridgeAttributes>) => {
334
+ this.listenIframe(options);
335
+ }, 50);
336
+
337
+ private src_url_equal_anchor?: HTMLAnchorElement;
338
+ private listenIframe(options: Partial<IframeBridgeAttributes>): void {
339
+ const loadListener = (ev: Event) => {
340
+ this.postMessage({
341
+ kind: IframeEvents.Init,
342
+ payload: {
343
+ attributes: this.attributes,
344
+ roomState: IframeBridge.displayer?.state,
345
+ currentPage: this.currentPage,
346
+ observerId: this.displayer.observerId,
347
+ },
348
+ });
349
+ IframeBridge.emitter.emit(DomEvents.IframeLoad, ev);
350
+ this.sideEffectManager.addDisposer(
351
+ IframeBridge.emitter.on(IframeEvents.Ready, () => {
352
+ this.postMessage(this.attributes.lastEvent?.payload);
353
+ }),
354
+ RefreshIDs.Ready
355
+ );
356
+ this.computedStyleAndIframeDisplay();
357
+ // if ((this.displayer as Room).isWritable) {
358
+ // this.manager.moveCamera({
359
+ // scale: this.manager.camera.scale + 1e-6,
360
+ // animationMode: AnimationMode.Immediately,
361
+ // })
362
+ // }
363
+ };
364
+ if (options.url && this.iframe.src !== options.url) {
365
+ if (!this.src_url_equal_anchor) this.src_url_equal_anchor = document.createElement("a");
366
+ this.src_url_equal_anchor.href = options.url;
367
+ if (this.src_url_equal_anchor.href !== this.iframe.src) {
368
+ this.iframe.src = options.url;
369
+ }
370
+ }
371
+ this.iframe.width = `${options.width}px`;
372
+ this.iframe.height = `${options.height}px`;
373
+ this.sideEffectManager.addEventListener(
374
+ this.iframe,
375
+ "load",
376
+ loadListener,
377
+ void 0,
378
+ RefreshIDs.Load
379
+ );
380
+ }
381
+
382
+ private onPhaseChangedListener = (phase: PlayerPhase) => {
383
+ if (phase === PlayerPhase.Playing) {
384
+ this.computedStyleAndIframeDisplay();
385
+ }
386
+ };
387
+
388
+ private listenDisplayerState(): void {
389
+ if (this.isReplay) {
390
+ if ((this.displayer as any)._phase === PlayerPhase.Playing) {
391
+ this.computedStyleAndIframeDisplay();
392
+ }
393
+ this.sideEffectManager.add(() => {
394
+ this.displayer.callbacks.on("onPhaseChanged", this.onPhaseChangedListener);
395
+ return () =>
396
+ this.displayer.callbacks.off("onPhaseChanged", this.onPhaseChangedListener);
397
+ }, RefreshIDs.DisplayerState);
398
+ }
399
+ this.computedStyleAndIframeDisplay();
400
+ }
401
+
402
+ private computedStyleAndIframeDisplay(): void {
403
+ this.computedStyle(this.displayer.state);
404
+ this.computedIframeDisplay(this.displayer.state, this.attributes);
405
+ }
406
+
407
+ private listenDisplayerCallbacks(): void {
408
+ this.displayer.callbacks.on(this.callbackName as any, this.stateChangeListener);
409
+ }
410
+
411
+ private get callbackName(): string {
412
+ return this.isReplay ? "onPlayerStateChanged" : "onRoomStateChanged";
413
+ }
414
+
415
+ private stateChangeListener = (state: RoomState) => {
416
+ state = { ...state };
417
+ state.cameraState = this.manager.cameraState;
418
+ this.postMessage({ kind: IframeEvents.RoomStateChanged, payload: state });
419
+ if (state.cameraState) {
420
+ IframeBridge.emitter.emit(IframeEvents.GetRootRect);
421
+ this.computedStyle(state);
422
+ }
423
+ if (state.memberState) {
424
+ this.computedZindex();
425
+ this.updateStyle();
426
+ }
427
+ if (state.sceneState) {
428
+ this.computedIframeDisplay(state, this.attributes);
429
+ }
430
+ };
431
+
432
+ private computedStyle(_state: DisplayerState): void {
433
+ const cameraState = this.manager.cameraState;
434
+ const setWidth = this.attributes.width || 1280;
435
+ const setHeight = this.attributes.height || 720;
436
+ if (this.iframe) {
437
+ const { width, height, scale, centerX, centerY } = cameraState;
438
+ const rootRect = this.rootRect || { x: 0, y: 0 };
439
+ const transformOriginX = `${width / 2 + rootRect.x}px`;
440
+ const transformOriginY = `${height / 2 + rootRect.y}px`;
441
+ const transformOrigin = `transform-origin: ${transformOriginX} ${transformOriginY};`;
442
+ const iframeXDiff = ((width - setWidth) / 2) * scale;
443
+ const iframeYDiff = ((height - setHeight) / 2) * scale;
444
+ const x = -(centerX * scale) + iframeXDiff;
445
+ const y = -(centerY * scale) + iframeYDiff;
446
+ const transform = `transform: translate(${x}px,${y}px) scale(${scale}, ${scale});`;
447
+ const position = "position: absolute;";
448
+ // 在某些安卓机型, border-width 不为 0 时,才能正确计算 iframe 里嵌套 iframe 的大小
449
+ const borderWidth = "border: 0.1px solid rgba(0,0,0,0);";
450
+ const left = `left: 0px;`;
451
+ const top = `top: 0px;`;
452
+ const cssList = [position, borderWidth, top, left, transformOrigin, transform];
453
+ this.cssList = cssList;
454
+ this.computedZindex();
455
+ this.updateStyle();
456
+ }
457
+ }
458
+
459
+ private computedIframeDisplay(
460
+ _state: DisplayerState,
461
+ _attributes: Partial<IframeBridgeAttributes>
462
+ ): void {
463
+ if (this.inDisplaySceneDir) {
464
+ IframeBridge.emitter.emit(IframeEvents.DispayIframe);
465
+ } else {
466
+ IframeBridge.emitter.emit(IframeEvents.HideIframe);
467
+ }
468
+ }
469
+
470
+ public computedZindex(): void {
471
+ const zIndexString = "z-index: -1;";
472
+ const index = this.cssList.findIndex(css => css === zIndexString);
473
+ if (index !== -1) {
474
+ this.cssList.splice(index, 1);
475
+ }
476
+ if (!this.isClicker() || this.isDisableInput) {
477
+ this.cssList.push(zIndexString);
478
+ }
479
+ }
480
+
481
+ private updateStyle(): void {
482
+ this.iframe.style.cssText = this.cssList.join(" ");
483
+ }
484
+
485
+ private get iframeOrigin(): string | undefined {
486
+ if (this.iframe) {
487
+ try {
488
+ return new URL(this.iframe.src).origin;
489
+ } catch (err) {
490
+ console.warn(err);
491
+ }
492
+ }
493
+ }
494
+
495
+ private messageListener(event: MessageEvent): void {
496
+ log("<<<", JSON.stringify(event.data));
497
+ if (event.origin !== this.iframeOrigin) {
498
+ return;
499
+ }
500
+ const data = event.data;
501
+ switch (data.kind) {
502
+ case IframeEvents.SetAttributes: {
503
+ this.handleSetAttributes(data);
504
+ break;
505
+ }
506
+ case IframeEvents.RegisterMagixEvent: {
507
+ this.handleRegisterMagixEvent(data);
508
+ break;
509
+ }
510
+ case IframeEvents.RemoveMagixEvent: {
511
+ this.handleRemoveMagixEvent(data);
512
+ break;
513
+ }
514
+ case IframeEvents.DispatchMagixEvent: {
515
+ this.handleDispatchMagixEvent(data);
516
+ break;
517
+ }
518
+ case IframeEvents.RemoveAllMagixEvent: {
519
+ this.handleRemoveAllMagixEvent();
520
+ break;
521
+ }
522
+ case IframeEvents.NextPage: {
523
+ this.handleNextPage();
524
+ break;
525
+ }
526
+ case IframeEvents.PrevPage: {
527
+ this.handlePrevPage();
528
+ break;
529
+ }
530
+ case IframeEvents.SDKCreate: {
531
+ this.handleSDKCreate();
532
+ break;
533
+ }
534
+ case IframeEvents.SetPage: {
535
+ this.handleSetPage(data);
536
+ break;
537
+ }
538
+ case IframeEvents.GetAttributes: {
539
+ this.handleGetAttributes();
540
+ break;
541
+ }
542
+ case IframeEvents.PageTo: {
543
+ this.handlePageTo(data);
544
+ break;
545
+ }
546
+ default: {
547
+ log(`${data.kind} not allow event.`);
548
+ break;
549
+ }
550
+ }
551
+ }
552
+
553
+ private handleSDKCreate(): void {
554
+ this.postMessage({
555
+ kind: IframeEvents.Init,
556
+ payload: {
557
+ attributes: this.attributes,
558
+ roomState: this.displayer.state,
559
+ currentPage: this.currentPage,
560
+ observerId: this.displayer.observerId,
561
+ },
562
+ });
563
+ }
564
+
565
+ private handleDispatchMagixEvent(data: any): void {
566
+ const eventPayload: { event: string; payload: any } = data.payload;
567
+ this.appManager.safeDispatchMagixEvent(eventPayload.event, eventPayload.payload);
568
+ }
569
+
570
+ private handleSetAttributes(data: any): void {
571
+ this.setAttributes(data.payload);
572
+ }
573
+
574
+ private handleRegisterMagixEvent(data: any): void {
575
+ const eventName = data.payload as string;
576
+ const listener = (event: any) => {
577
+ if (event.authorId === this.displayer.observerId) {
578
+ return;
579
+ }
580
+ this.postMessage({ kind: IframeEvents.ReciveMagixEvent, payload: event });
581
+ };
582
+ this.magixEventMap.set(eventName, listener);
583
+ this.displayer.addMagixEventListener(eventName, listener);
584
+ }
585
+
586
+ private handleRemoveMagixEvent(data: any): void {
587
+ const eventName = data.payload as string;
588
+ const listener = this.magixEventMap.get(eventName);
589
+ this.displayer.removeMagixEventListener(eventName, listener);
590
+ }
591
+
592
+ private handleNextPage(): void {
593
+ if (this.manager.canOperate) {
594
+ this.manager.nextPage();
595
+ this.dispatchMagixEvent(IframeEvents.NextPage, {});
596
+ }
597
+ }
598
+
599
+ private handlePrevPage(): void {
600
+ if (this.manager.canOperate) {
601
+ this.manager.prevPage();
602
+ this.dispatchMagixEvent(IframeEvents.PrevPage, {});
603
+ }
604
+ }
605
+
606
+ private handlePageTo(data: any): void {
607
+ if (this.manager.canOperate) {
608
+ const page = data.payload as number;
609
+ if (!Number.isSafeInteger(page) || page <= 0) {
610
+ return;
611
+ }
612
+ this.manager.setMainViewSceneIndex(page - 1);
613
+ this.dispatchMagixEvent(IframeEvents.PageTo, page - 1);
614
+ }
615
+ }
616
+
617
+ private handleRemoveAllMagixEvent(): void {
618
+ this.magixEventMap.forEach((listener, event) => {
619
+ this.displayer.removeMagixEventListener(event, listener);
620
+ });
621
+ this.magixEventMap.clear();
622
+ }
623
+
624
+ private handleGetAttributes(): void {
625
+ this.postMessage({
626
+ kind: IframeEvents.GetAttributes,
627
+ payload: this.attributes,
628
+ });
629
+ }
630
+
631
+ public postMessage(message: any): void {
632
+ if (this.iframe) {
633
+ this.iframe.contentWindow?.postMessage(JSON.parse(JSON.stringify(message)), "*");
634
+ }
635
+ }
636
+
637
+ public dispatchMagixEvent(event: string, payload: any): void {
638
+ if (this.manager.canOperate) {
639
+ this.setAttributes({ lastEvent: { name: event, payload } });
640
+ (this.displayer as Room).dispatchMagixEvent(event, payload);
641
+ }
642
+ }
643
+
644
+ private get currentIndex(): number {
645
+ return this.manager.mainViewSceneIndex;
646
+ }
647
+
648
+ private get currentPage(): number {
649
+ return this.currentIndex + 1;
650
+ }
651
+
652
+ private get totalPage(): number {
653
+ return this.manager.mainViewScenesLength;
654
+ }
655
+
656
+ private get readonly(): boolean {
657
+ return !(this.displayer as any).isWritable;
658
+ }
659
+
660
+ public get inDisplaySceneDir(): boolean {
661
+ return this.manager.mainViewSceneDir === this.attributes.displaySceneDir;
662
+ }
663
+
664
+ private isClicker(): boolean {
665
+ if (this.readonly) {
666
+ return false;
667
+ }
668
+ const currentApplianceName = (this.displayer as Room).state.memberState
669
+ .currentApplianceName;
670
+ return this.allowAppliances.includes(currentApplianceName);
671
+ }
672
+
673
+ private get isDisableInput(): boolean {
674
+ if ("disableDeviceInputs" in this.displayer) {
675
+ return (this.displayer as Room).disableDeviceInputs;
676
+ } else {
677
+ return true;
678
+ }
679
+ }
680
+ }