@netless/window-manager 0.3.18 → 0.4.0-canary.11

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 (75) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +4 -43
  3. package/dist/App/MagixEvent/index.d.ts +28 -0
  4. package/dist/App/Storage/StorageEvent.d.ts +8 -0
  5. package/dist/App/Storage/index.d.ts +38 -0
  6. package/dist/App/Storage/typings.d.ts +21 -0
  7. package/dist/App/Storage/utils.d.ts +5 -0
  8. package/dist/AppContext.d.ts +42 -17
  9. package/dist/AppListener.d.ts +2 -2
  10. package/dist/AppManager.d.ts +18 -14
  11. package/dist/AppProxy.d.ts +5 -3
  12. package/dist/AttributesDelegate.d.ts +19 -11
  13. package/dist/Base/Context.d.ts +0 -1
  14. package/dist/Base/index.d.ts +1 -2
  15. package/dist/BoxManager.d.ts +24 -7
  16. package/dist/BuiltinApps.d.ts +6 -0
  17. package/dist/ContainerResizeObserver.d.ts +10 -0
  18. package/dist/Cursor/Cursor.d.ts +2 -3
  19. package/dist/Cursor/index.d.ts +9 -5
  20. package/dist/Helper.d.ts +6 -0
  21. package/dist/ReconnectRefresher.d.ts +9 -3
  22. package/dist/Utils/Common.d.ts +3 -1
  23. package/dist/Utils/Reactive.d.ts +1 -1
  24. package/dist/Utils/RoomHacker.d.ts +2 -2
  25. package/dist/Utils/error.d.ts +3 -0
  26. package/dist/{MainView.d.ts → View/MainView.d.ts} +3 -4
  27. package/dist/View/ViewManager.d.ts +13 -0
  28. package/dist/constants.d.ts +3 -7
  29. package/dist/index.d.ts +25 -27
  30. package/dist/index.es.js +1 -1
  31. package/dist/index.es.js.map +1 -1
  32. package/dist/index.umd.js +1 -1
  33. package/dist/index.umd.js.map +1 -1
  34. package/dist/style.css +1 -1
  35. package/dist/typings.d.ts +3 -2
  36. package/docs/api.md +17 -0
  37. package/docs/migrate.md +42 -0
  38. package/package.json +6 -4
  39. package/src/App/MagixEvent/index.ts +66 -0
  40. package/src/App/Storage/StorageEvent.ts +21 -0
  41. package/src/App/Storage/index.ts +284 -0
  42. package/src/App/Storage/typings.ts +21 -0
  43. package/src/App/Storage/utils.ts +17 -0
  44. package/src/AppContext.ts +61 -21
  45. package/src/AppListener.ts +15 -11
  46. package/src/AppManager.ts +141 -95
  47. package/src/AppProxy.ts +49 -52
  48. package/src/AttributesDelegate.ts +76 -49
  49. package/src/Base/Context.ts +2 -6
  50. package/src/Base/index.ts +2 -2
  51. package/src/BoxManager.ts +89 -31
  52. package/src/BuiltinApps.ts +24 -0
  53. package/src/ContainerResizeObserver.ts +62 -0
  54. package/src/Cursor/Cursor.ts +35 -39
  55. package/src/Cursor/index.ts +79 -43
  56. package/src/Helper.ts +30 -0
  57. package/src/ReconnectRefresher.ts +25 -10
  58. package/src/Utils/Common.ts +35 -13
  59. package/src/Utils/Reactive.ts +9 -3
  60. package/src/Utils/RoomHacker.ts +20 -5
  61. package/src/Utils/error.ts +6 -1
  62. package/src/{MainView.ts → View/MainView.ts} +19 -27
  63. package/src/View/ViewManager.ts +53 -0
  64. package/src/constants.ts +2 -3
  65. package/src/index.ts +144 -171
  66. package/src/shim.d.ts +4 -0
  67. package/src/style.css +7 -1
  68. package/src/typings.ts +3 -2
  69. package/vite.config.js +4 -1
  70. package/dist/Utils/CameraStore.d.ts +0 -15
  71. package/dist/ViewManager.d.ts +0 -29
  72. package/dist/sdk.d.ts +0 -14
  73. package/src/Utils/CameraStore.ts +0 -72
  74. package/src/sdk.ts +0 -39
  75. package/src/viewManager.ts +0 -177
package/src/BoxManager.ts CHANGED
@@ -1,22 +1,22 @@
1
- import { callbacks, emitter, WindowManager } from "./index";
1
+ import { AppAttributes, Events, MIN_HEIGHT, MIN_WIDTH } from "./constants";
2
2
  import { debounce, maxBy } from "lodash";
3
- import { AppAttributes, DEFAULT_COLLECTOR_STYLE, Events, MIN_HEIGHT, MIN_WIDTH } from "./constants";
4
3
  import {
5
4
  TELE_BOX_MANAGER_EVENT,
6
5
  TELE_BOX_STATE,
7
6
  TeleBoxCollector,
8
7
  TeleBoxManager,
9
8
  } from "@netless/telebox-insider";
10
- import type { AddAppOptions, AppInitState } from "./index";
9
+ import { emitter, WindowManager } from "./index";
10
+ import type { AddAppOptions, AppInitState, EmitterType, CallbacksType } from "./index";
11
11
  import type {
12
12
  TeleBoxManagerUpdateConfig,
13
13
  TeleBoxManagerCreateConfig,
14
14
  ReadonlyTeleBox,
15
15
  TeleBoxManagerConfig,
16
16
  TeleBoxColorScheme,
17
+ TeleBoxRect,
17
18
  } from "@netless/telebox-insider";
18
19
  import type Emittery from "emittery";
19
- import type { AppManager } from "./AppManager";
20
20
  import type { NetlessApp } from "./typings";
21
21
  import type { View } from "white-web-sdk";
22
22
 
@@ -48,31 +48,63 @@ export type CreateTeleBoxManagerConfig = {
48
48
  prefersColorScheme?: TeleBoxColorScheme;
49
49
  };
50
50
 
51
+ export type BoxManagerContext = {
52
+ safeSetAttributes: (attributes: any) => void;
53
+ getMainView: () => View;
54
+ updateAppState: (appId: string, field: AppAttributes, value: any) => void;
55
+ emitter: EmitterType;
56
+ callbacks: CallbacksType;
57
+ canOperate: () => boolean;
58
+ notifyContainerRectUpdate: (rect: TeleBoxRect) => void;
59
+ cleanFocus: () => void;
60
+ };
61
+
62
+ export const createBoxManager = (
63
+ manager: WindowManager,
64
+ callbacks: CallbacksType,
65
+ emitter: EmitterType,
66
+ options: CreateTeleBoxManagerConfig
67
+ ) => {
68
+ return new BoxManager(
69
+ {
70
+ safeSetAttributes: (attributes: any) => manager.safeSetAttributes(attributes),
71
+ getMainView: () => manager.mainView,
72
+ updateAppState: (...args) => manager.appManager?.store.updateAppState(...args),
73
+ canOperate: () => manager.canOperate,
74
+ notifyContainerRectUpdate: (rect: TeleBoxRect) =>
75
+ manager.appManager?.notifyContainerRectUpdate(rect),
76
+ cleanFocus: () => manager.appManager?.store.cleanFocus(),
77
+ callbacks,
78
+ emitter,
79
+ },
80
+ options
81
+ );
82
+ };
83
+
51
84
  export class BoxManager {
52
85
  public teleBoxManager: TeleBoxManager;
53
- public appBoxMap: Map<string, string> = new Map();
54
- private mainView = this.manager.mainView;
55
86
 
56
87
  constructor(
57
- private manager: AppManager,
58
- createTeleBoxManagerConfig?: CreateTeleBoxManagerConfig
88
+ private context: BoxManagerContext,
89
+ private createTeleBoxManagerConfig?: CreateTeleBoxManagerConfig
59
90
  ) {
91
+ const { emitter, callbacks } = context;
60
92
  this.teleBoxManager = this.setupBoxManager(createTeleBoxManagerConfig);
61
93
  this.teleBoxManager.events.on(TELE_BOX_MANAGER_EVENT.State, state => {
62
94
  if (state) {
63
- callbacks.emit("boxStateChange", state);
64
- emitter.emit("boxStateChange", state);
95
+ this.context.callbacks.emit("boxStateChange", state);
96
+ this.context.emitter.emit("boxStateChange", state);
65
97
  }
66
98
  });
67
99
  this.teleBoxManager.events.on("minimized", minimized => {
68
- this.manager.safeSetAttributes({ minimized });
100
+ this.context.safeSetAttributes({ minimized });
69
101
  if (minimized) {
70
- this.manager.store.cleanFocus();
102
+ this.context.cleanFocus();
71
103
  this.blurAllBox();
72
104
  }
73
105
  });
74
106
  this.teleBoxManager.events.on("maximized", maximized => {
75
- this.manager.safeSetAttributes({ maximized });
107
+ this.context.safeSetAttributes({ maximized });
76
108
  });
77
109
  this.teleBoxManager.events.on("removed", boxes => {
78
110
  boxes.forEach(box => {
@@ -97,7 +129,7 @@ export class BoxManager {
97
129
  );
98
130
  this.teleBoxManager.events.on("focused", box => {
99
131
  if (box) {
100
- if (this.manager.canOperate) {
132
+ if (this.canOperate) {
101
133
  emitter.emit("focus", { appId: box.id });
102
134
  } else {
103
135
  this.teleBoxManager.blurBox(box.id);
@@ -111,8 +143,21 @@ export class BoxManager {
111
143
  callbacks.emit("prefersColorSchemeChange", colorScheme);
112
144
  });
113
145
  this.teleBoxManager.events.on("z_index", box => {
114
- this.manager.store.updateAppState(box.id, AppAttributes.ZIndex, box.zIndex);
146
+ this.context.updateAppState(box.id, AppAttributes.ZIndex, box.zIndex);
115
147
  });
148
+ emitter.on("playgroundSizeChange", this.playgroundSizeChangeListener);
149
+ }
150
+
151
+ private playgroundSizeChangeListener = () => {
152
+ this.updateManagerRect();
153
+ }
154
+
155
+ private get mainView() {
156
+ return this.context.getMainView();
157
+ }
158
+
159
+ private get canOperate() {
160
+ return this.context.canOperate();
116
161
  }
117
162
 
118
163
  public get boxState() {
@@ -135,6 +180,10 @@ export class BoxManager {
135
180
  return this.teleBoxManager.prefersColorScheme;
136
181
  }
137
182
 
183
+ public get boxSize() {
184
+ return this.teleBoxManager.boxes.length;
185
+ }
186
+
138
187
  public createBox(params: CreateBoxParams): void {
139
188
  if (!this.teleBoxManager) return;
140
189
  let { minwidth = MIN_WIDTH, minheight = MIN_HEIGHT } = params.app.config ?? {};
@@ -159,14 +208,14 @@ export class BoxManager {
159
208
  id: params.appId,
160
209
  };
161
210
  this.teleBoxManager.create(createBoxConfig, params.smartPosition);
162
- emitter.emit(`${params.appId}${Events.WindowCreated}` as any);
211
+ this.context.emitter.emit(`${params.appId}${Events.WindowCreated}` as any);
163
212
  }
164
213
 
165
214
  public setBoxInitState(appId: string): void {
166
215
  const box = this.teleBoxManager.queryOne({ id: appId });
167
216
  if (box) {
168
217
  if (box.state === TELE_BOX_STATE.Maximized) {
169
- emitter.emit("resize", {
218
+ this.context.emitter.emit("resize", {
170
219
  appId: appId,
171
220
  x: box.x,
172
221
  y: box.y,
@@ -193,24 +242,26 @@ export class BoxManager {
193
242
  fence: false,
194
243
  prefersColorScheme: createTeleBoxManagerConfig?.prefersColorScheme,
195
244
  };
196
- const container = createTeleBoxManagerConfig?.collectorContainer || WindowManager.wrapper;
197
- const styles = {
198
- ...DEFAULT_COLLECTOR_STYLE,
199
- ...createTeleBoxManagerConfig?.collectorStyles,
200
- };
201
- const teleBoxCollector = new TeleBoxCollector({
202
- styles: styles,
203
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
204
- }).mount(container!);
205
- initManagerState.collector = teleBoxCollector;
245
+
206
246
  const manager = new TeleBoxManager(initManagerState);
207
247
  if (this.teleBoxManager) {
208
248
  this.teleBoxManager.destroy();
209
249
  }
210
250
  this.teleBoxManager = manager;
251
+ const container = createTeleBoxManagerConfig?.collectorContainer || WindowManager.wrapper;
252
+ if (container) {
253
+ this.setCollectorContainer(container);
254
+ }
211
255
  return manager;
212
256
  }
213
257
 
258
+ public setCollectorContainer(container: HTMLElement) {
259
+ const collector = new TeleBoxCollector({
260
+ styles: this.createTeleBoxManagerConfig?.collectorStyles,
261
+ }).mount(container);
262
+ this.teleBoxManager.setCollector(collector);
263
+ }
264
+
214
265
  public getBox(appId: string): ReadonlyTeleBox | undefined {
215
266
  return this.teleBoxManager.queryOne({ id: appId });
216
267
  }
@@ -251,7 +302,7 @@ export class BoxManager {
251
302
  );
252
303
  setTimeout(() => {
253
304
  if (state.focus) {
254
- this.teleBoxManager.focusBox(box.id, true)
305
+ this.teleBoxManager.focusBox(box.id, true);
255
306
  }
256
307
  if (state.maximized != null) {
257
308
  this.teleBoxManager.setMaximized(Boolean(state.maximized), true);
@@ -260,7 +311,7 @@ export class BoxManager {
260
311
  this.teleBoxManager.setMinimized(Boolean(state.minimized), true);
261
312
  }
262
313
  }, 50);
263
- callbacks.emit("boxStateChange", this.teleBoxManager.state);
314
+ this.context.callbacks.emit("boxStateChange", this.teleBoxManager.state);
264
315
  }
265
316
  }
266
317
 
@@ -269,7 +320,7 @@ export class BoxManager {
269
320
  if (rect && rect.width > 0 && rect.height > 0) {
270
321
  const containerRect = { x: 0, y: 0, width: rect.width, height: rect.height };
271
322
  this.teleBoxManager.setContainerRect(containerRect);
272
- this.manager.notifyContainerRectUpdate(this.teleBoxManager.containerRect);
323
+ this.context.notifyContainerRectUpdate(this.teleBoxManager.containerRect);
273
324
  }
274
325
  }
275
326
 
@@ -309,7 +360,9 @@ export class BoxManager {
309
360
  }
310
361
 
311
362
  public setMaximized(maximized: boolean) {
312
- this.teleBoxManager.setMaximized(maximized, true);
363
+ if (maximized !== this.maximized) {
364
+ this.teleBoxManager.setMaximized(maximized, true);
365
+ }
313
366
  }
314
367
 
315
368
  public setMinimized(minimized: boolean, skipUpdate = true) {
@@ -334,7 +387,12 @@ export class BoxManager {
334
387
  this.teleBoxManager.setPrefersColorScheme(colorScheme);
335
388
  }
336
389
 
390
+ public setZIndex(id: string, zIndex: number, skipUpdate = true) {
391
+ this.teleBoxManager.update(id, { zIndex }, skipUpdate);
392
+ }
393
+
337
394
  public destroy() {
395
+ emitter.off("playgroundSizeChange", this.playgroundSizeChangeListener);
338
396
  this.teleBoxManager.destroy();
339
397
  }
340
398
  }
@@ -0,0 +1,24 @@
1
+ import AppDocsViewer from "@netless/app-docs-viewer";
2
+ import AppMediaPlayer, { setOptions } from "@netless/app-media-player";
3
+ import { WindowManager } from "./index";
4
+ import "@netless/app-docs-viewer/dist/style.css";
5
+
6
+ export const setupBuiltin = () => {
7
+ if (WindowManager.debug) {
8
+ setOptions({ verbose: true });
9
+ }
10
+
11
+ WindowManager.register({
12
+ kind: AppDocsViewer.kind,
13
+ src: AppDocsViewer,
14
+ });
15
+ WindowManager.register({
16
+ kind: AppMediaPlayer.kind,
17
+ src: AppMediaPlayer,
18
+ });
19
+ };
20
+
21
+ export const BuiltinApps = {
22
+ DocsViewer: AppDocsViewer.kind as string,
23
+ MediaPlayer: AppMediaPlayer.kind as string,
24
+ };
@@ -0,0 +1,62 @@
1
+ import { ResizeObserver as ResizeObserverPolyfill } from "@juggle/resize-observer";
2
+ import { WindowManager } from "./index";
3
+ import type { EmitterType} from "./index";
4
+
5
+ const ResizeObserver = window.ResizeObserver || ResizeObserverPolyfill;
6
+
7
+ export class ContainerResizeObserver {
8
+ private containerResizeObserver?: ResizeObserver;
9
+
10
+ constructor(private emitter: EmitterType) {}
11
+
12
+ public static create(
13
+ container: HTMLElement,
14
+ sizer: HTMLElement,
15
+ wrapper: HTMLDivElement,
16
+ emitter: EmitterType,
17
+ ) {
18
+ const containerResizeObserver = new ContainerResizeObserver(emitter);
19
+ containerResizeObserver.observePlaygroundSize(container, sizer, wrapper);
20
+ return containerResizeObserver;
21
+ }
22
+
23
+ public observePlaygroundSize(
24
+ container: HTMLElement,
25
+ sizer: HTMLElement,
26
+ wrapper: HTMLDivElement
27
+ ) {
28
+ this.updateSizer(container.getBoundingClientRect(), sizer, wrapper);
29
+
30
+ this.containerResizeObserver = new ResizeObserver(entries => {
31
+ const containerRect = entries[0]?.contentRect;
32
+ if (containerRect) {
33
+ this.updateSizer(containerRect, sizer, wrapper);
34
+ this.emitter.emit("playgroundSizeChange", containerRect)
35
+ }
36
+ });
37
+
38
+ this.containerResizeObserver.observe(container);
39
+ }
40
+
41
+ private updateSizer(
42
+ { width, height }: DOMRectReadOnly,
43
+ sizer: HTMLElement,
44
+ wrapper: HTMLDivElement
45
+ ) {
46
+ if (width && height) {
47
+ if (height / width > WindowManager.containerSizeRatio) {
48
+ height = width * WindowManager.containerSizeRatio;
49
+ sizer.classList.toggle("netless-window-manager-sizer-horizontal", true);
50
+ } else {
51
+ width = height / WindowManager.containerSizeRatio;
52
+ sizer.classList.toggle("netless-window-manager-sizer-horizontal", false);
53
+ }
54
+ wrapper.style.width = `${width}px`;
55
+ wrapper.style.height = `${height}px`;
56
+ }
57
+ }
58
+
59
+ public disconnect() {
60
+ this.containerResizeObserver?.disconnect();
61
+ }
62
+ }
@@ -1,7 +1,6 @@
1
1
  import App from './Cursor.svelte';
2
- import pRetry from 'p-retry';
3
2
  import { ApplianceMap } from './icons';
4
- import { ApplianceNames, autorun } from 'white-web-sdk';
3
+ import { ApplianceNames } from 'white-web-sdk';
5
4
  import { CursorState } from '../constants';
6
5
  import { Fields } from '../AttributesDelegate';
7
6
  import { get, omit } from 'lodash';
@@ -19,64 +18,61 @@ export type Payload = {
19
18
 
20
19
  export class Cursor extends Base {
21
20
  private member?: RoomMember;
22
- private disposer: any;
23
21
  private timer?: number;
24
22
  private component?: SvelteComponent;
25
23
 
26
24
  constructor(
27
25
  manager: AppManager,
26
+ addCursorChangeListener: (uid: string, callback: (position: Position, state: CursorState) => void) => void,
28
27
  private cursors: any,
29
28
  private memberId: string,
30
29
  private cursorManager: CursorManager,
31
- private wrapper?: HTMLElement
30
+ private wrapper?: HTMLElement,
32
31
  ) {
33
32
  super(manager);
34
33
  this.setMember();
35
34
  this.createCursor();
36
- pRetry(() => {
37
- this.disposer && this.disposer();
38
- if (!this.cursorPosition) {
39
- console.warn(`${memberId} not exist`);
40
- }
41
- this.startReaction();
42
- }, { retries: 3 });
35
+ addCursorChangeListener(this.memberId, this.onCursorChange);
43
36
  this.autoHidden();
44
37
  }
45
38
 
46
- private startReaction() {
47
- this.disposer = autorun(() => {
48
- const cursor = this.cursorPosition;
49
- const state = this.cursorState;
50
- if (!cursor) return;
51
- if (cursor.type === "main") {
52
- const rect = this.cursorManager.wrapperRect;
53
- if (this.component && rect) {
54
- this.autoHidden();
55
- this.moveCursor(cursor, rect, this.manager.mainView);
56
- }
57
- } else {
58
- const focusView = this.cursorManager.focusView;
59
- const viewRect = focusView?.divElement?.getBoundingClientRect();
60
- const viewCamera = focusView?.camera;
61
- if (focusView && viewRect && viewCamera && this.component) {
62
- this.autoHidden();
63
- this.moveCursor(cursor, viewRect, focusView);
64
- }
39
+ private onCursorChange = (position: Position, state: CursorState) => {
40
+ if (position.type === "main") {
41
+ const rect = this.cursorManager.wrapperRect;
42
+ if (this.component && rect) {
43
+ this.autoHidden();
44
+ this.moveCursor(position, rect, this.manager.mainView);
65
45
  }
66
- if (state && state === CursorState.Leave) {
67
- this.hide();
46
+ } else {
47
+ const focusView = this.cursorManager.focusView;
48
+ // TODO 可以存一个当前 focusView 的 Rect 这样只有 focus 切换的时候才调用 getBoundingClientRect
49
+ const viewRect = focusView?.divElement?.getBoundingClientRect();
50
+ const viewCamera = focusView?.camera;
51
+ if (focusView && viewRect && viewCamera && this.component) {
52
+ this.autoHidden();
53
+ this.moveCursor(position, viewRect, focusView);
68
54
  }
69
- });
55
+ }
56
+ if (state && state === CursorState.Leave) {
57
+ this.hide();
58
+ }
70
59
  }
71
60
 
72
61
  private moveCursor(cursor: Position, rect: DOMRect, view: any) {
73
- const { x, y } = cursor;
62
+ const { x, y, type } = cursor;
74
63
  const point = view?.screen.convertPointToScreen(x, y);
75
64
  if (point) {
76
- const translateX = point.x + rect.x - 2;
77
- const translateY = point.y + rect.y - 18;
65
+ let translateX = point.x - 2;
66
+ let translateY = point.y - 18;
67
+ if (type === "app") {
68
+ const wrapperRect = this.cursorManager.wrapperRect;
69
+ if (wrapperRect) {
70
+ translateX = translateX + rect.x - wrapperRect.x;
71
+ translateY = translateY + rect.y - wrapperRect.y;
72
+ }
73
+ }
78
74
  if (point.x < 0 || point.x > rect.width || point.y < 0 || point.y > rect.height) {
79
- this.component?.$set({ visible: false });
75
+ this.component?.$set({ visible: false, x: translateX, y: translateY });
80
76
  } else {
81
77
  this.component?.$set({ visible: true, x: translateX, y: translateY });
82
78
  }
@@ -149,7 +145,7 @@ export class Cursor extends Base {
149
145
  private async createCursor() {
150
146
  if (this.member && this.wrapper) {
151
147
  this.component = new App({
152
- target: document.documentElement,
148
+ target: this.wrapper,
153
149
  props: this.initProps(),
154
150
  });
155
151
  }
@@ -189,10 +185,10 @@ export class Cursor extends Base {
189
185
  }
190
186
 
191
187
  public destroy() {
192
- this.disposer && this.disposer();
193
188
  if (this.component) {
194
189
  this.component.$destroy();
195
190
  }
191
+ this.manager.refresher?.remove(this.memberId);
196
192
  this.cursorManager.cursorInstances.delete(this.memberId);
197
193
  }
198
194
 
@@ -1,11 +1,13 @@
1
- import { Base } from '../Base';
2
- import { Cursor } from './Cursor';
3
- import { CursorState } from '../constants';
4
- import { compact, debounce, uniq } from 'lodash';
5
- import { Fields } from '../AttributesDelegate';
6
- import { onObjectInserted } from '../Utils/Reactive';
7
- import { WindowManager } from '../index';
8
- import type { PositionType } from "../AttributesDelegate";
1
+ import { autorun } from "white-web-sdk";
2
+ import { Base } from "../Base";
3
+ import { compact, debounce, get, uniq } from "lodash";
4
+ import { Cursor } from "./Cursor";
5
+ import { CursorState } from "../constants";
6
+ import { emitter, WindowManager } from "../index";
7
+ import { Fields } from "../AttributesDelegate";
8
+ import { onObjectInserted } from "../Utils/Reactive";
9
+ import { SideEffectManager } from "side-effect-manager";
10
+ import type { PositionType, Position } from "../AttributesDelegate";
9
11
  import type { Point, RoomMember, View } from "white-web-sdk";
10
12
  import type { AppManager } from "../AppManager";
11
13
 
@@ -18,28 +20,45 @@ export type MoveCursorParams = {
18
20
  uid: string;
19
21
  x: number;
20
22
  y: number;
21
- }
23
+ };
22
24
  export class CursorManager extends Base {
23
25
  public containerRect?: DOMRect;
24
26
  public wrapperRect?: DOMRect;
25
27
  public cursorInstances: Map<string, Cursor> = new Map();
26
28
  public roomMembers?: readonly RoomMember[];
27
29
  private mainViewElement?: HTMLDivElement;
30
+ private sideEffectManager = new SideEffectManager();
28
31
 
29
32
  constructor(private appManager: AppManager) {
30
33
  super(appManager);
31
34
  this.roomMembers = this.appManager.room?.state.roomMembers;
32
35
  const wrapper = WindowManager.wrapper;
33
36
  if (wrapper) {
34
- wrapper.addEventListener("mousemove", this.mouseMoveListener);
35
- wrapper.addEventListener("touchstart", this.touchMoveListener);
36
- wrapper.addEventListener("touchmove", this.touchMoveListener);
37
- wrapper.addEventListener("mouseleave", this.mouseLeaveListener);
38
- wrapper.addEventListener("touchend", this.mouseLeaveListener);
39
- this.initCursorAttributes();
40
- this.wrapperRect = wrapper.getBoundingClientRect();
41
- this.startReaction(wrapper);
37
+ this.setupWrapper(wrapper);
38
+ }
39
+ emitter.on("onReconnected", () => {
40
+ this.onReconnect();
41
+ });
42
+ }
43
+
44
+ public setupWrapper(wrapper: HTMLElement) {
45
+ if (this.manager.refresher?.hasReactor("cursors")) {
46
+ this.destroy();
42
47
  }
48
+ this.sideEffectManager.add(() => {
49
+ wrapper.addEventListener("pointerenter", this.mouseMoveListener);
50
+ wrapper.addEventListener("pointermove", this.mouseMoveListener);
51
+ wrapper.addEventListener("pointerleave", this.mouseLeaveListener);
52
+ return () => {
53
+ wrapper.removeEventListener("pointerenter", this.mouseMoveListener);
54
+ wrapper.removeEventListener("pointermove", this.mouseMoveListener);
55
+ wrapper.removeEventListener("pointerleave", this.mouseLeaveListener);
56
+ };
57
+ });
58
+
59
+ this.initCursorAttributes();
60
+ this.wrapperRect = wrapper.getBoundingClientRect();
61
+ this.startReaction(wrapper);
43
62
  }
44
63
 
45
64
  public setMainViewDivElement(div: HTMLDivElement) {
@@ -51,27 +70,25 @@ export class CursorManager extends Base {
51
70
  return onObjectInserted(this.cursors, () => {
52
71
  this.handleRoomMembersChange(wrapper);
53
72
  });
54
- })
73
+ });
55
74
  }
56
75
 
57
76
  private getUids = (members: readonly RoomMember[] | undefined) => {
58
77
  return compact(uniq(members?.map(member => member.payload?.uid)));
59
- }
78
+ };
60
79
 
61
80
  private handleRoomMembersChange = debounce((wrapper: HTMLElement) => {
62
81
  const uids = this.getUids(this.roomMembers);
63
82
  const cursors = Object.keys(this.cursors);
64
83
  if (uids?.length) {
65
84
  cursors.map(uid => {
66
- if (
67
- uids.includes(uid) &&
68
- !this.cursorInstances.has(uid)
69
- ) {
85
+ if (uids.includes(uid) && !this.cursorInstances.has(uid)) {
70
86
  if (uid === this.context.uid) {
71
87
  return;
72
88
  }
73
89
  const component = new Cursor(
74
90
  this.appManager,
91
+ this.addCursorChangeListener,
75
92
  this.cursors,
76
93
  uid,
77
94
  this,
@@ -79,7 +96,7 @@ export class CursorManager extends Base {
79
96
  );
80
97
  this.cursorInstances.set(uid, component);
81
98
  }
82
- })
99
+ });
83
100
  }
84
101
  }, 100);
85
102
 
@@ -99,13 +116,6 @@ export class CursorManager extends Base {
99
116
  this.updateCursor(this.getType(event), event.clientX, event.clientY);
100
117
  }, 5);
101
118
 
102
- private touchMoveListener = debounce((event: TouchEvent) => {
103
- if (event.touches.length === 1) {
104
- const touchEvent = event.touches[0];
105
- this.updateCursor(this.getType(touchEvent), touchEvent.clientX, touchEvent.clientY);
106
- }
107
- }, 5);
108
-
109
119
  private updateCursor(event: EventType, clientX: number, clientY: number) {
110
120
  if (this.wrapperRect && this.manager.canOperate) {
111
121
  const view = event.type === "main" ? this.appManager.mainView : this.focusView;
@@ -121,7 +131,11 @@ export class CursorManager extends Base {
121
131
  }
122
132
  }
123
133
 
124
- private getPoint = (view: View | undefined, clientX: number, clientY: number): Point | undefined => {
134
+ private getPoint = (
135
+ view: View | undefined,
136
+ clientX: number,
137
+ clientY: number
138
+ ): Point | undefined => {
125
139
  const rect = view?.divElement?.getBoundingClientRect();
126
140
  if (rect) {
127
141
  const point = view?.convertToPointInWorld({
@@ -130,7 +144,7 @@ export class CursorManager extends Base {
130
144
  });
131
145
  return point;
132
146
  }
133
- }
147
+ };
134
148
 
135
149
  /**
136
150
  * 因为窗口内框在不同分辨率下的大小不一样,所以这里通过来鼠标事件的 target 来判断是在主白板还是在 APP 中
@@ -140,7 +154,7 @@ export class CursorManager extends Base {
140
154
  const focusApp = this.appManager.focusApp;
141
155
  switch (target.parentElement) {
142
156
  case this.mainViewElement: {
143
- return { type: "main" };
157
+ return { type: "main" };
144
158
  }
145
159
  case focusApp?.view?.divElement: {
146
160
  return { type: "app" };
@@ -217,19 +231,41 @@ export class CursorManager extends Base {
217
231
  });
218
232
  }
219
233
 
220
- public destroy() {
221
- const wrapper = WindowManager.wrapper;
222
- if (wrapper) {
223
- wrapper.removeEventListener("mousemove", this.mouseMoveListener);
224
- wrapper.removeEventListener("touchstart", this.touchMoveListener);
225
- wrapper.removeEventListener("touchmove", this.touchMoveListener);
226
- wrapper.removeEventListener("mouseleave", this.mouseLeaveListener);
227
- wrapper.removeEventListener("touchend", this.mouseLeaveListener);
228
- }
234
+ public onReconnect() {
229
235
  if (this.cursorInstances.size) {
230
236
  this.cursorInstances.forEach(cursor => cursor.destroy());
231
237
  this.cursorInstances.clear();
232
238
  }
239
+ this.roomMembers = this.appManager.room?.state.roomMembers;
240
+ if (WindowManager.wrapper) {
241
+ this.handleRoomMembersChange(WindowManager.wrapper);
242
+ }
243
+ }
244
+
245
+ public addCursorChangeListener = (
246
+ uid: string,
247
+ callback: (position: Position, state: CursorState) => void
248
+ ) => {
249
+ this.manager.refresher?.add(uid, () => {
250
+ const disposer = autorun(() => {
251
+ const position = get(this.cursors, [uid, Fields.Position]);
252
+ const state = get(this.cursors, [uid, Fields.CursorState]);
253
+ if (position) {
254
+ callback(position, state);
255
+ }
256
+ });
257
+ return disposer;
258
+ });
259
+ };
260
+
261
+ public destroy() {
262
+ this.sideEffectManager.flushAll();
263
+ if (this.cursorInstances.size) {
264
+ this.cursorInstances.forEach(cursor => {
265
+ cursor.destroy();
266
+ });
267
+ this.cursorInstances.clear();
268
+ }
233
269
  this.manager.refresher?.remove("cursors");
234
270
  }
235
271
  }