@netless/window-manager 0.4.71-beta.0 → 0.4.71

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/window-manager",
3
- "version": "0.4.71-beta.0",
3
+ "version": "0.4.71",
4
4
  "description": "Multi-window mode for Netless Whiteboard",
5
5
  "author": "l1shen <lishen1635@gmail.com> (https://github.com/l1shen)",
6
6
  "license": "MIT",
package/src/AppManager.ts CHANGED
@@ -31,7 +31,14 @@ import {
31
31
  } from "./Utils/Common";
32
32
  import type { ReconnectRefresher } from "./ReconnectRefresher";
33
33
  import type { BoxManager } from "./BoxManager";
34
- import type { Displayer, Room, ScenesCallbacksNode, SceneState, RoomState } from "white-web-sdk";
34
+ import type {
35
+ Displayer,
36
+ Room,
37
+ ScenesCallbacksNode,
38
+ SceneState,
39
+ RoomState,
40
+ MemberState,
41
+ } from "white-web-sdk";
35
42
  import type { AddAppParams, BaseInsertParams, TeleBoxRect } from "./index";
36
43
  import type {
37
44
  BoxClosePayload,
@@ -111,6 +118,10 @@ export class AppManager {
111
118
  });
112
119
  }
113
120
 
121
+ public getMemberState(): MemberState {
122
+ return this.room?.state.memberState || ({ strokeColor: [0, 0, 0] } as MemberState);
123
+ }
124
+
114
125
  private onRemoveScenes = async (params: RemoveSceneParams) => {
115
126
  const { scenePath } = params;
116
127
  // 如果移除根目录就把 scenePath 设置为初始值
@@ -15,7 +15,8 @@
15
15
  export let color: string;
16
16
  export let cursorTagBackgroundColor: string;
17
17
  export let opacity: number;
18
- export let pencilEraserSize: number;
18
+ export let pencilEraserSize: number | undefined;
19
+ export let custom: boolean | undefined;
19
20
 
20
21
  $: hasName = !isEmpty(cursorName);
21
22
  $: hasTagName = !isEmpty(tagName);
@@ -40,9 +41,9 @@
40
41
  </script>
41
42
 
42
43
  <div
43
- class="netless-window-manager-cursor-mid"
44
+ class={"netless-window-manager-cursor-mid" + (custom ? " netless-window-manager-cursor-custom" : "")}
44
45
  style="transform: translateX({x}px) translateY({y}px);display: {display}"
45
- >
46
+ >
46
47
  {#if !isLaserPointer}
47
48
  <div class="netless-window-manager-cursor-name {offset} {pencilEraserSize3ImageOffset}">
48
49
  <div
@@ -67,6 +68,10 @@
67
68
  </div>
68
69
  {/if}
69
70
  <div class="cursor-image-wrapper">
70
- <img class="netless-window-manager-cursor-{appliance}-image {pencilEraserSize3ImageOffset}" {src} alt={appliance} />
71
+ <img
72
+ class="netless-window-manager-cursor-{appliance}-image {pencilEraserSize3ImageOffset}"
73
+ {src}
74
+ alt={appliance}
75
+ />
71
76
  </div>
72
77
  </div>
@@ -0,0 +1,21 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+
3
+ declare class Cursor extends SvelteComponentTyped<{
4
+ readonly cursorName: string;
5
+ readonly tagName?: string;
6
+ readonly backgroundColor: string;
7
+ readonly appliance: string;
8
+ readonly x: number;
9
+ readonly y: number;
10
+ readonly src?: string;
11
+ readonly visible: boolean;
12
+ readonly avatar: string;
13
+ readonly theme: string;
14
+ readonly color: string;
15
+ readonly cursorTagBackgroundColor: string;
16
+ readonly opacity: number;
17
+ readonly pencilEraserSize?: number;
18
+ readonly custom?: boolean;
19
+ }> {}
20
+
21
+ export default Cursor;
@@ -1,12 +1,15 @@
1
- import App from "./Cursor.svelte";
2
- import { ApplianceNames } from "white-web-sdk";
3
- import { findMemberByUid } from "../Helper";
4
- import { omit } from "lodash";
5
- import type { Position } from "../AttributesDelegate";
6
1
  import type { RoomMember } from "white-web-sdk";
7
- import type { CursorManager } from "./index";
8
- import type { SvelteComponent } from "svelte";
2
+ import type { CursorOptions } from "../index";
9
3
  import type { AppManager } from "../AppManager";
4
+ import type { Position } from "../AttributesDelegate";
5
+ import type { CursorManager } from "./index";
6
+
7
+ import { omit } from "lodash";
8
+ import { ApplianceNames } from "white-web-sdk";
9
+
10
+ import { findMemberByUid } from "../Helper";
11
+ import App from "./Cursor.svelte";
12
+ import { remoteIcon } from "./icons2";
10
13
 
11
14
  export type Payload = {
12
15
  [key: string]: any;
@@ -15,7 +18,8 @@ export type Payload = {
15
18
  export class Cursor {
16
19
  private member?: RoomMember;
17
20
  private timer?: number;
18
- private component?: SvelteComponent;
21
+ private component?: App;
22
+ private style: CursorOptions["style"] & string = "default";
19
23
 
20
24
  constructor(
21
25
  private manager: AppManager,
@@ -26,6 +30,7 @@ export class Cursor {
26
30
  this.updateMember();
27
31
  this.createCursor();
28
32
  this.autoHidden();
33
+ this.setStyle(cursorManager.style);
29
34
  }
30
35
 
31
36
  public move = (position: Position) => {
@@ -46,6 +51,16 @@ export class Cursor {
46
51
  }
47
52
  };
48
53
 
54
+ public setStyle = (style: typeof this.style) => {
55
+ this.style = style;
56
+ if (this.component) {
57
+ this.component.$set({
58
+ src: this.getIcon(),
59
+ custom: this.isCustomIcon(),
60
+ });
61
+ }
62
+ };
63
+
49
64
  public leave = () => {
50
65
  this.hide();
51
66
  };
@@ -56,6 +71,10 @@ export class Cursor {
56
71
  if (point) {
57
72
  let translateX = point.x - 2;
58
73
  let translateY = point.y - 18;
74
+ if (this.isCustomIcon()) {
75
+ translateX -= 11;
76
+ translateY += 4;
77
+ }
59
78
  if (type === "app") {
60
79
  const wrapperRect = this.cursorManager.wrapperRect;
61
80
  if (wrapperRect) {
@@ -80,6 +99,11 @@ export class Cursor {
80
99
  return `rgb(${rgb})`;
81
100
  }
82
101
 
102
+ public get memberColorHex(): string {
103
+ const [r, g, b] = this.member?.memberState?.strokeColor || [236, 52, 85];
104
+ return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
105
+ }
106
+
83
107
  private get payload(): Payload | undefined {
84
108
  return this.member?.payload;
85
109
  }
@@ -145,6 +169,7 @@ export class Cursor {
145
169
  appliance: this.memberApplianceName as string,
146
170
  avatar: this.memberAvatar,
147
171
  src: this.getIcon(),
172
+ custom: this.isCustomIcon(),
148
173
  visible: false,
149
174
  backgroundColor: this.memberColor,
150
175
  cursorName: this.memberCursorName,
@@ -157,18 +182,49 @@ export class Cursor {
157
182
  };
158
183
  }
159
184
 
160
- private getIcon() {
161
- if (this.member) {
162
- const icons = this.cursorManager.applianceIcons;
163
- let applianceSrc = icons[ApplianceNames.shape];
164
- if (this.memberApplianceName === ApplianceNames.pencilEraser) {
165
- const size = this.member?.memberState.pencilEraserSize || 1;
166
- applianceSrc = icons[`${this.memberApplianceName}${size}`];
167
- } else {
168
- applianceSrc = icons[this.memberApplianceName || ApplianceNames.shape];
169
- }
170
- return applianceSrc || icons[ApplianceNames.shape];
185
+ private getIcon(): string | undefined {
186
+ if (!this.member) return;
187
+
188
+ const { memberApplianceName, memberColorHex } = this;
189
+ const { userApplianceIcons, applianceIcons } = this.cursorManager;
190
+
191
+ let iconsKey: string | undefined = this.memberApplianceName;
192
+ if (iconsKey === ApplianceNames.pencilEraser) {
193
+ iconsKey = `${iconsKey}${this.member?.memberState.pencilEraserSize || 1}`;
171
194
  }
195
+
196
+ const userApplianceSrc = iconsKey && userApplianceIcons[iconsKey];
197
+ if (userApplianceSrc) return userApplianceSrc;
198
+
199
+ if (this.style === "custom" && memberApplianceName) {
200
+ const customApplianceSrc = remoteIcon(memberApplianceName, memberColorHex);
201
+ if (customApplianceSrc) return customApplianceSrc;
202
+ }
203
+
204
+ const applianceSrc = applianceIcons[iconsKey || ApplianceNames.shape];
205
+ return applianceSrc || applianceIcons[ApplianceNames.shape];
206
+ }
207
+
208
+ private isCustomIcon(): boolean {
209
+ if (!this.member) return false;
210
+
211
+ const { memberApplianceName, memberColorHex } = this;
212
+ const { userApplianceIcons } = this.cursorManager;
213
+
214
+ let iconsKey: string | undefined = this.memberApplianceName;
215
+ if (iconsKey === ApplianceNames.pencilEraser) {
216
+ iconsKey = `${iconsKey}${this.member?.memberState.pencilEraserSize || 1}`;
217
+ }
218
+
219
+ const userApplianceSrc = iconsKey && userApplianceIcons[iconsKey];
220
+ if (userApplianceSrc) return false;
221
+
222
+ if (this.style === "custom" && memberApplianceName) {
223
+ const customApplianceSrc = remoteIcon(memberApplianceName, memberColorHex);
224
+ if (customApplianceSrc) return true;
225
+ }
226
+
227
+ return false;
172
228
  }
173
229
 
174
230
  public updateMember() {
@@ -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
+ }
@@ -4,12 +4,13 @@ import { CursorState, Events } from "../constants";
4
4
  import { internalEmitter } from "../InternalEmitter";
5
5
  import { SideEffectManager } from "side-effect-manager";
6
6
  import { WindowManager } from "../index";
7
- import type { CursorMovePayload, ApplianceIcons } from "../index";
7
+ import type { CursorMovePayload, ApplianceIcons, CursorOptions } from "../index";
8
8
  import type { PositionType } from "../AttributesDelegate";
9
- import type { Point, Room, RoomMember, View } from "white-web-sdk";
9
+ import type { Point, Room, RoomMember, RoomState, View } from "white-web-sdk";
10
10
  import type { AppManager } from "../AppManager";
11
11
  import { ApplianceMap } from "./icons";
12
12
  import { findMemberByUid } from "../Helper";
13
+ import { enableLocal } from "./icons2";
13
14
 
14
15
  export type EventType = {
15
16
  type: PositionType;
@@ -22,19 +23,27 @@ 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;
34
- private onceCount = true;
38
+ private leaveFlag = true;
39
+ private _style: CursorOptions["style"] & string = "default";
35
40
 
36
-
37
- 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
+ ) {
38
47
  this.roomMembers = this.manager.room?.state.roomMembers;
39
48
  const wrapper = WindowManager.wrapper;
40
49
  if (wrapper) {
@@ -43,12 +52,44 @@ export class CursorManager {
43
52
  this.sideEffectManager.add(() => {
44
53
  return internalEmitter.on("cursorMove", this.onCursorMove);
45
54
  });
46
-
47
55
  this.sideEffectManager.add(() => {
48
56
  return internalEmitter.on("playgroundSizeChange", () => this.updateContainerRect());
49
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
+ }
50
68
  if (applianceIcons) {
51
- 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
+ }
52
93
  }
53
94
  }
54
95
 
@@ -73,6 +114,13 @@ export class CursorManager {
73
114
  return cursorInstance;
74
115
  };
75
116
 
117
+ private enableCustomCursor() {
118
+ this.sideEffectManager.add(
119
+ () => enableLocal(this.manager.getMemberState()),
120
+ LocalCursorSideEffectId
121
+ );
122
+ }
123
+
76
124
  private canMoveCursor(member: RoomMember | undefined) {
77
125
  const isLaserPointer =
78
126
  member?.memberState.currentApplianceName === ApplianceNames.laserPointer;
@@ -111,38 +159,48 @@ export class CursorManager {
111
159
  const type = this.getType(event);
112
160
  this.updateCursor(type, event.clientX, event.clientY);
113
161
  isTouch && this.showPencilEraserIfNeeded(type, event.clientX, event.clientY);
114
- }
162
+ };
115
163
 
116
164
  private mouseMoveTimer = 0;
117
165
  private mouseMoveListener = (event: PointerEvent) => {
118
166
  const isTouch = event.pointerType === "touch";
119
167
  if (isTouch && !event.isPrimary) return;
120
- const now = Date.now()
168
+ const now = Date.now();
121
169
  if (now - this.mouseMoveTimer > 48) {
122
170
  this.mouseMoveTimer = now;
123
- if (WindowManager.supportTeachingAidsPlugin && isRoom(WindowManager.displayer) && (WindowManager.displayer as Room).disableDeviceInputs) {
124
- if(this.onceCount){
171
+ if (
172
+ WindowManager.supportTeachingAidsPlugin &&
173
+ isRoom(WindowManager.displayer) &&
174
+ (WindowManager.displayer as Room).disableDeviceInputs
175
+ ) {
176
+ if (this.leaveFlag) {
125
177
  this.manager.dispatchInternalEvent(Events.CursorMove, {
126
178
  uid: this.manager.uid,
127
- state: CursorState.Leave
179
+ state: CursorState.Leave,
128
180
  } as CursorMovePayload);
129
- this.onceCount = false;
181
+ this.leaveFlag = false;
130
182
  }
131
- return ;
183
+ return;
132
184
  }
133
185
  this.mouseMoveListener_(event, isTouch);
134
- this.onceCount = true;
186
+ this.leaveFlag = true;
135
187
  }
136
- }
137
-
188
+ };
189
+
138
190
  private mouseLeaveListener = () => {
139
191
  this.hideCursor(this.manager.uid);
140
- }
192
+ };
141
193
 
142
194
  private showPencilEraserIfNeeded(event: EventType, clientX: number, clientY: number) {
143
195
  const self = findMemberByUid(this.manager.room, this.manager.uid);
144
- const isPencilEraser = self?.memberState.currentApplianceName === ApplianceNames.pencilEraser;
145
- if (this.wrapperRect && this.manager.canOperate && this.canMoveCursor(self) && isPencilEraser) {
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
+ ) {
146
204
  const view = event.type === "main" ? this.manager.mainView : this.focusView;
147
205
  const point = this.getPoint(view, clientX, clientY);
148
206
  if (point) {
package/src/index.ts CHANGED
@@ -126,6 +126,15 @@ export type AppInitState = {
126
126
 
127
127
  export type CursorMovePayload = { uid: string; state?: "leave"; position: Position };
128
128
 
129
+ export type CursorOptions = {
130
+ /**
131
+ * If `"custom"`, it will render the pencil / eraser cursor as a circle and shapes cursor as a cross.
132
+ *
133
+ * @default "default"
134
+ */
135
+ style?: "default" | "custom";
136
+ };
137
+
129
138
  export type MountParams = {
130
139
  room: Room | Player;
131
140
  container?: HTMLElement;
@@ -137,6 +146,7 @@ export type MountParams = {
137
146
  collectorStyles?: Partial<CSSStyleDeclaration>;
138
147
  overwriteStyles?: string;
139
148
  cursor?: boolean;
149
+ cursorOptions?: CursorOptions;
140
150
  debug?: boolean;
141
151
  disableCameraTransform?: boolean;
142
152
  prefersColorScheme?: TeleBoxColorScheme;
@@ -148,7 +158,10 @@ export type MountParams = {
148
158
 
149
159
  export const reconnectRefresher = new ReconnectRefresher({ emitter: internalEmitter });
150
160
 
151
- export class WindowManager extends InvisiblePlugin<WindowMangerAttributes, any> implements PageController {
161
+ export class WindowManager
162
+ extends InvisiblePlugin<WindowMangerAttributes, any>
163
+ implements PageController
164
+ {
152
165
  public static kind = "WindowManager";
153
166
  public static displayer: Displayer;
154
167
  public static wrapper?: HTMLElement;
@@ -249,7 +262,12 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes, any>
249
262
  manager.appManager = new AppManager(manager);
250
263
  manager.appManager.polling = params.polling || false;
251
264
  manager._pageState = new PageStateImpl(manager.appManager);
252
- manager.cursorManager = new CursorManager(manager.appManager, Boolean(cursor), params.applianceIcons);
265
+ manager.cursorManager = new CursorManager(
266
+ manager.appManager,
267
+ Boolean(cursor),
268
+ params.cursorOptions,
269
+ params.applianceIcons
270
+ );
253
271
  if (containerSizeRatio) {
254
272
  manager.containerSizeRatio = containerSizeRatio;
255
273
  }
@@ -296,16 +314,12 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes, any>
296
314
  manager: WindowManager,
297
315
  container: HTMLElement,
298
316
  params: {
299
- chessboard?: boolean,
300
- overwriteStyles?: string,
301
- fullscreen?: boolean,
317
+ chessboard?: boolean;
318
+ overwriteStyles?: string;
319
+ fullscreen?: boolean;
302
320
  }
303
321
  ) {
304
- const {
305
- chessboard,
306
- overwriteStyles,
307
- fullscreen,
308
- } = params;
322
+ const { chessboard, overwriteStyles, fullscreen } = params;
309
323
  if (!WindowManager.container) {
310
324
  WindowManager.container = container;
311
325
  }
@@ -370,7 +384,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes, any>
370
384
  this.appManager?.resetMaximized();
371
385
  this.appManager?.resetMinimized();
372
386
  this.appManager?.displayerWritableListener(!this.room.isWritable);
373
- WindowManager.container = container;
387
+ WindowManager.container = container;
374
388
  }
375
389
 
376
390
  public bindCollectorContainer(container: HTMLElement) {
@@ -581,7 +595,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes, any>
581
595
  console.warn(`[WindowManager]: index ${index} out of range`);
582
596
  return false;
583
597
  }
584
- return this.appManager.removeSceneByIndex(needRemoveIndex);;
598
+ return this.appManager.removeSceneByIndex(needRemoveIndex);
585
599
  } else {
586
600
  return false;
587
601
  }
@@ -629,7 +643,10 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes, any>
629
643
  *
630
644
  * 返回一个用于撤销此监听的函数
631
645
  */
632
- public onAppEvent(kind: string, listener: (args: { kind: string, appId: string, type: string, value: any }) => void): () => void {
646
+ public onAppEvent(
647
+ kind: string,
648
+ listener: (args: { kind: string; appId: string; type: string; value: any }) => void
649
+ ): () => void {
633
650
  return internalEmitter.on(`custom-${kind}` as any, listener);
634
651
  }
635
652
 
@@ -753,6 +770,17 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes, any>
753
770
  }
754
771
  }
755
772
 
773
+ public get cursorStyle(): "default" | "custom" {
774
+ return this.cursorManager?.style || "default";
775
+ }
776
+
777
+ public set cursorStyle(value: "default" | "custom") {
778
+ if (!this.cursorManager) {
779
+ throw new Error("[WindowManager]: cursor is not enabled, please set { cursor: true }.");
780
+ }
781
+ this.cursorManager.style = value;
782
+ }
783
+
756
784
  public get mainViewSceneIndex(): number {
757
785
  return this._pageState?.index || 0;
758
786
  }
@@ -982,8 +1010,8 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes, any>
982
1010
  }
983
1011
 
984
1012
  public refresh() {
985
- this._refresh();
986
- this.appManager?.dispatchInternalEvent(Events.Refresh);
1013
+ this._refresh();
1014
+ this.appManager?.dispatchInternalEvent(Events.Refresh);
987
1015
  }
988
1016
 
989
1017
  /** @inner */
@@ -997,7 +1025,9 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes, any>
997
1025
 
998
1026
  public setContainerSizeRatio(ratio: number) {
999
1027
  if (!isNumber(ratio) || !(ratio > 0)) {
1000
- throw new Error(`[WindowManager]: updateContainerSizeRatio error, ratio must be a positive number. but got ${ratio}`);
1028
+ throw new Error(
1029
+ `[WindowManager]: updateContainerSizeRatio error, ratio must be a positive number. but got ${ratio}`
1030
+ );
1001
1031
  }
1002
1032
  WindowManager.containerSizeRatio = ratio;
1003
1033
  this.containerSizeRatio = ratio;
package/src/style.css CHANGED
@@ -53,7 +53,6 @@
53
53
  height: 100%;
54
54
  }
55
55
 
56
-
57
56
  .netless-window-manager-cursor-pencil-image {
58
57
  width: 26px;
59
58
  height: 26px;