@netless/window-manager 1.0.0-canary.9 → 1.0.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.
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} +9480 -6984
  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 +124 -113
  12. package/docs/app-context.md +248 -209
  13. package/docs/basic.md +25 -26
  14. package/docs/camera.md +19 -20
  15. package/docs/cn/advanced.md +137 -0
  16. package/docs/cn/api.md +309 -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,66 @@
1
+ import type { MemberState } from "white-web-sdk";
2
+ import { ApplianceNames } from "white-web-sdk";
3
+
4
+ type Color = string;
5
+
6
+ const staticCircle = `data:image/svg+xml,%3Csvg width='24' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Ccircle cx='12' cy='12' r='2.5' stroke='%23000' stroke-linejoin='square'/%3E%3Ccircle cx='12' cy='12' r='3.5' stroke='%23FFF'/%3E%3C/g%3E%3C/svg%3E`;
7
+
8
+ function circleUrl(color: Color): string {
9
+ return `data:image/svg+xml,%3Csvg width='24' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Ccircle cx='12' cy='12' r='2.5' stroke='%23${color}' stroke-linejoin='square'/%3E%3Ccircle cx='12' cy='12' r='3.5' stroke='%23${color}'/%3E%3C/g%3E%3C/svg%3E`;
10
+ }
11
+
12
+ function crossUrl(color: Color): string {
13
+ return `data:image/svg+xml,%3Csvg width='24' height='24' xmlns='http://www.w3.org/2000/svg' fill='none'%3E%3Cpath d='M5 12H19' stroke='%23${color}' stroke-linejoin='round'/%3E%3Cpath d='M12 5V19' stroke='%23${color}' stroke-linejoin='round'/%3E%3C/svg%3E`;
14
+ }
15
+
16
+ function cssCursor(url: string): string {
17
+ return `url("${url}") 12 12, auto`;
18
+ }
19
+
20
+ function makeStyleContent(config: { [cursor: string]: string }): string {
21
+ let result = "";
22
+ for (const cursor in config) {
23
+ result += `.netless-whiteboard.${cursor} {cursor: ${config[cursor]}}\n`;
24
+ }
25
+ return result;
26
+ }
27
+
28
+ const $style = document.createElement("style");
29
+
30
+ export function enableLocal(memberState: MemberState): () => void {
31
+ const [r, g, b] = memberState.strokeColor;
32
+ const hex = ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
33
+ $style.textContent = makeStyleContent({
34
+ "cursor-pencil": cssCursor(circleUrl(hex)),
35
+ "cursor-eraser": cssCursor(staticCircle),
36
+ "cursor-rectangle": cssCursor(crossUrl(hex)),
37
+ "cursor-ellipse": cssCursor(crossUrl(hex)),
38
+ "cursor-straight": cssCursor(crossUrl(hex)),
39
+ "cursor-arrow": cssCursor(crossUrl(hex)),
40
+ "cursor-shape": cssCursor(crossUrl(hex)),
41
+ });
42
+ document.head.appendChild($style);
43
+
44
+ return () => {
45
+ if ($style.parentNode == null) return;
46
+ document.head.removeChild($style);
47
+ };
48
+ }
49
+
50
+ const shapeAppliances: Set<ApplianceNames> = new Set([
51
+ ApplianceNames.rectangle,
52
+ ApplianceNames.ellipse,
53
+ ApplianceNames.straight,
54
+ ApplianceNames.arrow,
55
+ ApplianceNames.shape,
56
+ ]);
57
+
58
+ export function remoteIcon(applianceName: ApplianceNames, hex: string): string | undefined {
59
+ if (applianceName === ApplianceNames.pencil) {
60
+ return circleUrl(hex);
61
+ } else if (applianceName === ApplianceNames.eraser) {
62
+ return staticCircle;
63
+ } else if (shapeAppliances.has(applianceName)) {
64
+ return crossUrl(hex);
65
+ }
66
+ }
@@ -1,15 +1,16 @@
1
- import { ApplianceNames } from "white-web-sdk";
1
+ import { ApplianceNames, isRoom } from "white-web-sdk";
2
2
  import { Cursor } from "./Cursor";
3
3
  import { CursorState, Events } from "../constants";
4
- import { emitter } from "../InternalEmitter";
4
+ import { internalEmitter } from "../InternalEmitter";
5
5
  import { SideEffectManager } from "side-effect-manager";
6
- import { throttle } from "lodash";
7
6
  import { WindowManager } from "../index";
8
- import type { CursorMovePayload , ApplianceIcons} from "../index";
7
+ import type { CursorMovePayload, ApplianceIcons, CursorOptions } from "../index";
9
8
  import type { PositionType } from "../AttributesDelegate";
10
- import type { Point, RoomMember, View } from "white-web-sdk";
9
+ import type { Point, Room, RoomMember, RoomState, View } from "white-web-sdk";
11
10
  import type { AppManager } from "../AppManager";
12
11
  import { ApplianceMap } from "./icons";
12
+ import { findMemberByUid } from "../Helper";
13
+ import { enableLocal } from "./icons2";
13
14
 
14
15
  export type EventType = {
15
16
  type: PositionType;
@@ -22,31 +23,73 @@ export type MoveCursorParams = {
22
23
  y: number;
23
24
  };
24
25
 
26
+ const LocalCursorSideEffectId = "local-cursor";
27
+
25
28
  export class CursorManager {
26
29
  public containerRect?: DOMRect;
27
30
  public wrapperRect?: DOMRect;
28
31
  public cursorInstances: Map<string, Cursor> = new Map();
29
32
  public roomMembers?: readonly RoomMember[];
33
+ public userApplianceIcons: ApplianceIcons = {};
34
+
30
35
  private mainViewElement?: HTMLDivElement;
31
36
  private sideEffectManager = new SideEffectManager();
32
37
  private store = this.manager.store;
33
- public applianceIcons: ApplianceIcons = ApplianceMap;
38
+ private leaveFlag = true;
39
+ private _style: CursorOptions["style"] & string = "default";
34
40
 
35
- constructor(private manager: AppManager, private enableCursor: boolean, applianceIcons?: ApplianceIcons) {
41
+ constructor(
42
+ private manager: AppManager,
43
+ private enableCursor: boolean,
44
+ cursorOptions?: CursorOptions,
45
+ applianceIcons?: ApplianceIcons
46
+ ) {
36
47
  this.roomMembers = this.manager.room?.state.roomMembers;
37
- const playground = WindowManager.playground;
38
- if (playground) {
39
- this.setupWrapper(playground);
48
+ const wrapper = WindowManager.wrapper;
49
+ if (wrapper) {
50
+ this.setupWrapper(wrapper);
40
51
  }
41
52
  this.sideEffectManager.add(() => {
42
- return emitter.on("cursorMove", this.onCursorMove);
53
+ return internalEmitter.on("cursorMove", this.onCursorMove);
43
54
  });
44
-
45
55
  this.sideEffectManager.add(() => {
46
- return emitter.on("playgroundSizeChange", () => this.updateContainerRect());
56
+ return internalEmitter.on("playgroundSizeChange", () => this.updateContainerRect());
47
57
  });
58
+ const room = this.manager.room;
59
+ if (room) {
60
+ this.sideEffectManager.add(() => {
61
+ const update = (state: RoomState) => {
62
+ if (this.style === "custom" && state.memberState) this.enableCustomCursor();
63
+ };
64
+ room.callbacks.on("onRoomStateChanged", update);
65
+ return () => room.callbacks.off("onRoomStateChanged", update);
66
+ });
67
+ }
48
68
  if (applianceIcons) {
49
- this.applianceIcons = { ...ApplianceMap, ...applianceIcons };
69
+ this.userApplianceIcons = applianceIcons;
70
+ }
71
+ this.style = cursorOptions?.style || "default";
72
+ }
73
+
74
+ public get applianceIcons(): ApplianceIcons {
75
+ return { ...ApplianceMap, ...this.userApplianceIcons };
76
+ }
77
+
78
+ public get style() {
79
+ return this._style;
80
+ }
81
+
82
+ public set style(value) {
83
+ if (this._style !== value) {
84
+ this._style = value;
85
+ this.cursorInstances.forEach(cursor => {
86
+ cursor.setStyle(value);
87
+ });
88
+ if (value === "custom") {
89
+ this.enableCustomCursor();
90
+ } else {
91
+ this.sideEffectManager.flush(LocalCursorSideEffectId);
92
+ }
50
93
  }
51
94
  }
52
95
 
@@ -65,12 +108,19 @@ export class CursorManager {
65
108
  private initCursorInstance = (uid: string) => {
66
109
  let cursorInstance = this.cursorInstances.get(uid);
67
110
  if (!cursorInstance) {
68
- cursorInstance = new Cursor(this.manager, uid, this, WindowManager.playground);
111
+ cursorInstance = new Cursor(this.manager, uid, this, WindowManager.wrapper);
69
112
  this.cursorInstances.set(uid, cursorInstance);
70
113
  }
71
114
  return cursorInstance;
72
115
  };
73
116
 
117
+ private enableCustomCursor() {
118
+ this.sideEffectManager.add(
119
+ () => enableLocal(this.manager.getMemberState()),
120
+ LocalCursorSideEffectId
121
+ );
122
+ }
123
+
74
124
  private canMoveCursor(member: RoomMember | undefined) {
75
125
  const isLaserPointer =
76
126
  member?.memberState.currentApplianceName === ApplianceNames.laserPointer;
@@ -105,15 +155,70 @@ export class CursorManager {
105
155
  return this.manager.focusApp?.view;
106
156
  }
107
157
 
108
- private mouseMoveListener = throttle((event: PointerEvent) => {
109
- if (event.pointerType === "touch") {
110
- if (!event.isPrimary) return;
158
+ private mouseMoveListener_ = (event: PointerEvent, isTouch: boolean) => {
159
+ const type = this.getType(event);
160
+ this.updateCursor(type, event.clientX, event.clientY);
161
+ isTouch && this.showPencilEraserIfNeeded(type, event.clientX, event.clientY);
162
+ };
163
+
164
+ private mouseMoveTimer = 0;
165
+ private mouseMoveListener = (event: PointerEvent) => {
166
+ const isTouch = event.pointerType === "touch";
167
+ if (isTouch && !event.isPrimary) return;
168
+ const now = Date.now();
169
+ if (now - this.mouseMoveTimer > 48) {
170
+ this.mouseMoveTimer = now;
171
+ if (
172
+ WindowManager.supportAppliancePlugin &&
173
+ isRoom(WindowManager.displayer) &&
174
+ (WindowManager.displayer as Room).disableDeviceInputs
175
+ ) {
176
+ if (this.leaveFlag) {
177
+ this.manager.dispatchInternalEvent(Events.CursorMove, {
178
+ uid: this.manager.uid,
179
+ state: CursorState.Leave,
180
+ } as CursorMovePayload);
181
+ this.leaveFlag = false;
182
+ }
183
+ return;
184
+ }
185
+ this.mouseMoveListener_(event, isTouch);
186
+ this.leaveFlag = true;
187
+ }
188
+ };
189
+
190
+ private mouseLeaveListener = () => {
191
+ this.hideCursor(this.manager.uid);
192
+ };
193
+
194
+ private showPencilEraserIfNeeded(event: EventType, clientX: number, clientY: number) {
195
+ const self = findMemberByUid(this.manager.room, this.manager.uid);
196
+ const isPencilEraser =
197
+ self?.memberState.currentApplianceName === ApplianceNames.pencilEraser;
198
+ if (
199
+ this.wrapperRect &&
200
+ this.manager.canOperate &&
201
+ this.canMoveCursor(self) &&
202
+ isPencilEraser
203
+ ) {
204
+ const view = event.type === "main" ? this.manager.mainView : this.focusView;
205
+ const point = this.getPoint(view, clientX, clientY);
206
+ if (point) {
207
+ this.onCursorMove({
208
+ uid: this.manager.uid,
209
+ position: {
210
+ x: point.x,
211
+ y: point.y,
212
+ type: event.type,
213
+ },
214
+ });
215
+ }
111
216
  }
112
- this.updateCursor(this.getType(event), event.clientX, event.clientY);
113
- }, 16);
217
+ }
114
218
 
115
219
  private updateCursor(event: EventType, clientX: number, clientY: number) {
116
- if (this.wrapperRect && this.manager.canOperate) {
220
+ const self = findMemberByUid(this.manager.room, this.manager.uid);
221
+ if (this.wrapperRect && this.manager.canOperate && this.canMoveCursor(self)) {
117
222
  const view = event.type === "main" ? this.manager.mainView : this.focusView;
118
223
  const point = this.getPoint(view, clientX, clientY);
119
224
  if (point) {
@@ -163,13 +268,9 @@ export class CursorManager {
163
268
  }
164
269
  };
165
270
 
166
- private mouseLeaveListener = () => {
167
- this.hideCursor(this.manager.uid);
168
- };
169
-
170
271
  public updateContainerRect() {
171
272
  this.containerRect = WindowManager.container?.getBoundingClientRect();
172
- this.wrapperRect = WindowManager.playground?.getBoundingClientRect();
273
+ this.wrapperRect = WindowManager.wrapper?.getBoundingClientRect();
173
274
  }
174
275
 
175
276
  public deleteCursor(uid: string) {
package/src/Helper.ts CHANGED
@@ -1,24 +1,39 @@
1
- import { getVersionNumber } from "./Utils/Common";
2
- import { REQUIRE_VERSION } from "./constants";
1
+ import pRetry from "p-retry";
2
+ import type { Room, RoomMember } from "white-web-sdk";
3
3
  import { WhiteVersion } from "white-web-sdk";
4
+ import { REQUIRE_VERSION } from "./constants";
5
+ import { WindowManager } from "./index";
6
+ import { getVersionNumber } from "./Utils/Common";
4
7
  import { WhiteWebSDKInvalidError } from "./Utils/error";
5
- import type { Room , RoomMember} from "white-web-sdk";
8
+ import { log } from "./Utils/log";
6
9
 
7
10
  export const setupWrapper = (
8
11
  root: HTMLElement
9
12
  ): {
10
13
  playground: HTMLDivElement;
14
+ wrapper: HTMLDivElement;
15
+ sizer: HTMLDivElement;
11
16
  mainViewElement: HTMLDivElement;
12
17
  } => {
13
18
  const playground = document.createElement("div");
14
19
  playground.className = "netless-window-manager-playground";
15
20
 
21
+ const sizer = document.createElement("div");
22
+ sizer.className = "netless-window-manager-sizer";
23
+
24
+ const wrapper = document.createElement("div");
25
+ wrapper.className = "netless-window-manager-wrapper";
26
+
16
27
  const mainViewElement = document.createElement("div");
17
28
  mainViewElement.className = "netless-window-manager-main-view";
18
- playground.appendChild(mainViewElement);
29
+
30
+ playground.appendChild(sizer);
31
+ sizer.appendChild(wrapper);
32
+ wrapper.appendChild(mainViewElement);
19
33
  root.appendChild(playground);
34
+ WindowManager.wrapper = wrapper;
20
35
 
21
- return { playground, mainViewElement };
36
+ return { playground, wrapper, sizer, mainViewElement };
22
37
  };
23
38
 
24
39
  export const checkVersion = () => {
@@ -29,15 +44,80 @@ export const checkVersion = () => {
29
44
  };
30
45
 
31
46
  export const findMemberByUid = (room: Room | undefined, uid: string) => {
32
- const roomMembers = room?.state.roomMembers;
33
- return roomMembers?.find(member => member.payload?.uid === uid);
47
+ const roomMembers = room?.state.roomMembers || [];
48
+ let maxMemberId = -1; // 第一个进入房间的用户 memberId 是 0
49
+ let result: RoomMember | undefined = undefined;
50
+ for (const member of roomMembers) {
51
+ if (member.payload?.uid === uid && maxMemberId < member.memberId) {
52
+ maxMemberId = member.memberId;
53
+ result = member;
54
+ }
55
+ }
56
+ return result;
34
57
  };
35
58
 
36
- export type Member = RoomMember & { uid: string };
59
+ export const createInvisiblePlugin = async (room: Room): Promise<WindowManager> => {
60
+ let manager = room.getInvisiblePlugin(WindowManager.kind) as WindowManager;
61
+ if (manager) return manager;
37
62
 
38
- export const serializeRoomMembers = (members: readonly RoomMember[]) => {
39
- return members.map(member => ({
40
- uid: member.payload?.uid || "",
41
- ...member,
42
- }));
43
- }
63
+ let resolve!: (manager: WindowManager) => void;
64
+ const promise = new Promise<WindowManager>(r => {
65
+ // @ts-expect-error Set private property.
66
+ WindowManager._resolve = resolve = r;
67
+ });
68
+
69
+ let wasReadonly = false;
70
+ const canOperate = isRoomTokenWritable(room);
71
+ if (!room.isWritable && canOperate) {
72
+ wasReadonly = true;
73
+ await pRetry(
74
+ async count => {
75
+ log(`switching to writable (x${count})`);
76
+ await room.setWritable(true);
77
+ },
78
+ { retries: 10, maxTimeout: 5000 }
79
+ );
80
+ }
81
+ if (room.isWritable) {
82
+ log("creating InvisiblePlugin...");
83
+ room.createInvisiblePlugin(WindowManager, {}).catch(console.warn);
84
+ } else {
85
+ if (canOperate) console.warn("[WindowManager]: failed to switch to writable");
86
+ console.warn("[WindowManager]: waiting for others to create the plugin...");
87
+ }
88
+
89
+ const timeout = setTimeout(() => {
90
+ console.warn("[WindowManager]: no one called createInvisiblePlugin() after 20 seconds");
91
+ }, 20_000);
92
+
93
+ const abort = setTimeout(() => {
94
+ throw new Error("[WindowManager]: no one called createInvisiblePlugin() after 60 seconds");
95
+ }, 60_000);
96
+
97
+ const interval = setInterval(() => {
98
+ manager = room.getInvisiblePlugin(WindowManager.kind) as WindowManager;
99
+ if (manager) {
100
+ clearTimeout(abort);
101
+ clearTimeout(timeout);
102
+ clearInterval(interval);
103
+ resolve(manager);
104
+ if (wasReadonly && room.isWritable) {
105
+ setTimeout(() => room.setWritable(false).catch(console.warn), 500);
106
+ }
107
+ }
108
+ }, 200);
109
+
110
+ return promise;
111
+ };
112
+
113
+ const isRoomTokenWritable = (room: Room) => {
114
+ try {
115
+ const str = atob(room.roomToken.slice("NETLESSROOM_".length));
116
+ const index = str.indexOf("&role=");
117
+ const role = +str[index + "&role=".length];
118
+ return role < 2;
119
+ } catch (error) {
120
+ console.error(error);
121
+ return false;
122
+ }
123
+ };
@@ -1,8 +1,5 @@
1
1
  import Emittery from "emittery";
2
- import type { TeleBoxRect } from "@netless/telebox-insider";
3
2
  import type { AppInitState, CursorMovePayload } from "./index";
4
- import type { Member } from "./Helper";
5
- import type { MemberState } from "white-web-sdk";
6
3
 
7
4
  export type RemoveSceneParams = {
8
5
  scenePath: string;
@@ -18,7 +15,7 @@ export type EmitterEvent = {
18
15
  mainViewMounted: undefined;
19
16
  observerIdChange: number;
20
17
  boxStateChange: string;
21
- playgroundSizeChange: TeleBoxRect;
18
+ playgroundSizeChange: DOMRect;
22
19
  startReconnect: undefined;
23
20
  onReconnected: undefined;
24
21
  removeScenes: RemoveSceneParams;
@@ -31,9 +28,7 @@ export type EmitterEvent = {
31
28
  changePageState: undefined;
32
29
  writableChange: boolean;
33
30
  containerSizeRatioUpdate: number;
34
- roomMembersChange: Member[];
35
- memberStateChange: MemberState;
36
31
  };
37
32
 
38
33
  export type EmitterType = Emittery<EmitterEvent>;
39
- export const emitter: EmitterType = new Emittery();
34
+ export const internalEmitter: EmitterType = new Emittery();
package/src/Page/index.ts CHANGED
@@ -15,4 +15,4 @@ export const calculateNextIndex = (index: number, pageState: PageState) => {
15
15
  nextIndex = pageState.index;
16
16
  }
17
17
  return nextIndex;
18
- }
18
+ };
package/src/PageState.ts CHANGED
@@ -1,21 +1,22 @@
1
- import { callbacks } from "./callback";
2
- import { emitter } from "./InternalEmitter";
3
1
  import type { AppManager } from "./AppManager";
4
2
  import type { PageState } from "./Page";
5
3
 
4
+ import { internalEmitter } from "./InternalEmitter";
5
+ import { callbacks } from "./callback";
6
+
6
7
  export class PageStateImpl {
7
8
  constructor(private manager: AppManager) {
8
- emitter.on("changePageState", () => {
9
+ internalEmitter.on("changePageState", () => {
9
10
  callbacks.emit("pageStateChange", this.toObject());
10
11
  });
11
12
  }
12
13
 
13
14
  public get index(): number {
14
- return this.manager?.store.getMainViewSceneIndex() || 0;
15
+ return this.manager.store.getMainViewSceneIndex() || 0;
15
16
  }
16
17
 
17
18
  public get length(): number {
18
- return this.manager?.mainViewScenesLength || 0;
19
+ return this.manager.mainViewScenesLength || 0;
19
20
  }
20
21
 
21
22
  public toObject(): PageState {
@@ -4,6 +4,7 @@ import { RoomPhase } from "white-web-sdk";
4
4
  import type { Room } from "white-web-sdk";
5
5
  import type { EmitterType } from "./InternalEmitter";
6
6
  import { EnsureReconnectEvent } from "./constants";
7
+ import { wait } from "./Utils/Common";
7
8
 
8
9
  export type ReconnectRefresherContext = {
9
10
  emitter: EmitterType;
@@ -41,19 +42,24 @@ export class ReconnectRefresher {
41
42
  this.ctx = ctx;
42
43
  }
43
44
 
44
- private onPhaseChanged = (phase: RoomPhase) => {
45
+ private onPhaseChanged = async (phase: RoomPhase) => {
45
46
  if (phase === RoomPhase.Reconnecting) {
46
47
  this.ctx.emitter.emit("startReconnect");
47
48
  }
48
49
  if (phase === RoomPhase.Connected && this.phase === RoomPhase.Reconnecting) {
49
- this.room?.dispatchMagixEvent(EnsureReconnectEvent, {});
50
+ if (this.room?.isWritable) {
51
+ this.room?.dispatchMagixEvent(EnsureReconnectEvent, {});
52
+ } else {
53
+ await wait(500);
54
+ this.onReconnected();
55
+ }
50
56
  }
51
57
  this.phase = phase;
52
58
  };
53
59
 
54
60
  private onReconnected = debounce(() => {
55
61
  this._onReconnected();
56
- }, 3000);
62
+ }, 1000);
57
63
 
58
64
  private _onReconnected = () => {
59
65
  log("onReconnected refresh reactors");
@@ -88,7 +94,6 @@ export class ReconnectRefresher {
88
94
  this.reactors.set(id, func);
89
95
  this.disposers.set(id, func());
90
96
  }
91
- return () => this.remove(id);
92
97
  }
93
98
 
94
99
  public remove(id: string) {
package/src/RedoUndo.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { callbacks } from "./callback";
2
- import { emitter } from "./InternalEmitter";
2
+ import { internalEmitter } from "./InternalEmitter";
3
3
  import type { View } from "white-web-sdk";
4
4
  import type { AppProxy } from "./App";
5
5
 
@@ -11,13 +11,13 @@ export type RedoUndoContext = {
11
11
 
12
12
  export class RedoUndo {
13
13
  constructor(private context: RedoUndoContext) {
14
- emitter.on("focusedChange", changed => {
14
+ internalEmitter.on("focusedChange", changed => {
15
15
  this.disposePrevFocusViewRedoUndoListeners(changed.prev);
16
16
  setTimeout(() => {
17
17
  this.addRedoUndoListeners(changed.focused);
18
18
  }, 0);
19
19
  });
20
- emitter.on("rootDirRemoved", () => {
20
+ internalEmitter.on("rootDirRemoved", () => {
21
21
  this.disposePrevFocusViewRedoUndoListeners(context.focus());
22
22
  this.addRedoUndoListeners(context.focus());
23
23
  });
@@ -8,7 +8,7 @@ export type LoadAppEvent = {
8
8
  reason?: string;
9
9
  };
10
10
 
11
- export type SyncRegisterAppPayload = { kind: string, src: string, name: string | undefined };
11
+ export type SyncRegisterAppPayload = { kind: string; src: string; name: string | undefined };
12
12
  export type SyncRegisterApp = (payload: SyncRegisterAppPayload) => void;
13
13
 
14
14
  class AppRegister {
@@ -25,38 +25,43 @@ class AppRegister {
25
25
 
26
26
  public onSyncRegisterAppChange = (payload: SyncRegisterAppPayload) => {
27
27
  this.register({ kind: payload.kind, src: payload.src });
28
- }
28
+ };
29
29
 
30
30
  public async register(params: RegisterParams): Promise<void> {
31
31
  this.appClassesCache.delete(params.kind);
32
32
  this.registered.set(params.kind, params);
33
33
 
34
- const srcOrAppOrFunction = params.src;
34
+ const paramSrc = params.src;
35
35
  let downloadApp: () => Promise<NetlessApp>;
36
36
 
37
- if (typeof srcOrAppOrFunction === "string") {
37
+ if (typeof paramSrc === "string") {
38
38
  downloadApp = async () => {
39
- let appClass = (await loadApp(srcOrAppOrFunction, params.kind, params.name)) as any;
39
+ const result = (await loadApp(paramSrc, params.kind, params.name)) as any;
40
+ if (result.__esModule) {
41
+ return result.default;
42
+ }
43
+ return result;
44
+ };
45
+ if (this.syncRegisterApp) {
46
+ this.syncRegisterApp({ kind: params.kind, src: paramSrc, name: params.name });
47
+ }
48
+ }
49
+ if (typeof paramSrc === "function") {
50
+ downloadApp = async () => {
51
+ let appClass = (await paramSrc()) as any;
40
52
  if (appClass) {
41
- if (appClass.__esModule) {
53
+ if (appClass.__esModule || appClass.default) {
42
54
  appClass = appClass.default;
43
55
  }
44
56
  return appClass;
45
57
  } else {
46
- throw new Error(
47
- `[WindowManager]: load remote script failed, ${srcOrAppOrFunction}`
48
- );
58
+ throw new Error(`[WindowManager]: load remote script failed, ${paramSrc}`);
49
59
  }
50
60
  };
51
- if (this.syncRegisterApp) {
52
- this.syncRegisterApp({ kind: params.kind, src: srcOrAppOrFunction, name: params.name });
53
- }
54
- } else if (typeof srcOrAppOrFunction === "function") {
55
- downloadApp = srcOrAppOrFunction;
56
- } else {
57
- downloadApp = async () => srcOrAppOrFunction;
58
61
  }
59
-
62
+ if (typeof paramSrc === "object") {
63
+ downloadApp = async () => paramSrc;
64
+ }
60
65
  this.appClasses.set(params.kind, async () => {
61
66
  let app = this.appClassesCache.get(params.kind);
62
67
  if (!app) {