@netless/window-manager 0.4.0-canary.27 → 0.4.0-canary.30

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/docs/api.md CHANGED
@@ -11,6 +11,9 @@
11
11
  - [`closeApp`](#closeApp)
12
12
  - [`setMainViewSceneIndex`](#setMainViewSceneIndex)
13
13
  - [`setBoxState`](#setBoxState)
14
+ - [`cleanCurrentScene`](#cleanCurrentScene)
15
+ - [`redo`](#redo)
16
+ - [`undo`](#undo)
14
17
  - [实例属性](#prototypes)
15
18
  - [事件回调](#events)
16
19
 
@@ -128,19 +131,45 @@ manager.setMainViewSceneIndex(1)
128
131
  manager.setBoxState("normal") // boxState: normal | maximized | minimized
129
132
  ```
130
133
 
134
+ <h3 id="cleanCurrentScene">cleanCurrentScene</h3>
135
+
136
+ > 清除当前 focus 的 view 的笔迹
137
+
138
+ ```ts
139
+ manager.cleanCurrentScene()
140
+ ```
141
+
142
+ <h3 id="redo">redo</h3>
143
+
144
+ > 在当前 focus 的 view 上重做上一步操作
145
+
146
+ ```ts
147
+ manager.redo()
148
+ ```
149
+
150
+ <h3 id="undo">undo</h3>
151
+
152
+ > 在当前 focus 的 view 上撤消上一步操作
153
+
154
+ ```ts
155
+ manager.undo()
156
+ ```
157
+
131
158
  <br>
132
159
 
133
160
  <h2 id="prototypes">实例属性</h2>
134
161
 
135
- | name | type | default | desc |
136
- | ------------------ | ------- | ------- | ----------------- |
137
- | mainView | View | | 主白板 |
138
- | mainViewSceneIndex | number | | 当前主白板的 SceneIndex |
162
+ | name | type | default | desc |
163
+ | ------------------ | ------- | ------- | ----------------- |
164
+ | mainView | View | | 主白板 |
165
+ | mainViewSceneIndex | number | | 当前主白板的 SceneIndex |
139
166
  | mainViewScenesLength | number | | mainView 的 scenes 长度 |
140
- | boxState | string | | 当前窗口状态 |
141
- | darkMode | boolean | | 黑夜模式 |
142
- | prefersColorScheme | string | | 颜色主题 |
143
- | focused | string | | focus 的 app |
167
+ | boxState | string | | 当前窗口状态 |
168
+ | darkMode | boolean | | 黑夜模式 |
169
+ | prefersColorScheme | string | | 颜色主题 |
170
+ | focused | string | | focus 的 app |
171
+ | canRedoSteps | number | | 当前 focus 的 view 可以重做的步数 |
172
+ | canRedoSteps | number | | 当前 focus 的 view 可以撤销的步数 |
144
173
 
145
174
  <br>
146
175
 
@@ -159,4 +188,6 @@ manager.callbacks.on(events, listener)
159
188
  | prefersColorSchemeChange | string | | auto,light,dark |
160
189
  | cameraStateChange | CameraState | | |
161
190
  | focusedChange | string, undefined | | 当前 focus 的 appId,主白板时为 undefined |
162
- | mainViewScenesLengthChange | number | | mainView scenes 添加或删除时触发 |
191
+ | mainViewScenesLengthChange | number | | mainView scenes 添加或删除时触发 |
192
+ | canRedoStepsChange | number | | 当前 focus 的 view 可重做步数改变 |
193
+ | canUndoStepsChange | number | | 当前 focus 的 view 可撤销步数改变 |
package/docs/replay.md ADDED
@@ -0,0 +1,40 @@
1
+ ## 回放
2
+
3
+ > 注意: 多窗口的回放只支持从创建房间开始就是多窗口的房间
4
+
5
+ > 如果是一开始作为单窗口模式使用,又转变成多窗口模式使用, 则会造成回放渲染空白
6
+
7
+ <br>
8
+
9
+
10
+ ```typescript
11
+ import { WhiteWebSdk } from "white-web-sdk";
12
+ import { WindowManager, BuiltinApps } from "@netless/window-manager";
13
+ import "@netless/window-manager/dist/style.css";
14
+
15
+ const sdk = new WhiteWebSdk({
16
+ appIdentifier: "appIdentifier",
17
+ useMobXState: true // 请确保打开这个选项
18
+ });
19
+
20
+ let manager: WindowManager;
21
+
22
+ sdk.replayRoom({
23
+ uuid: "room uuid",
24
+ roomToken: "room token",
25
+ invisiblePlugins: [WindowManager],
26
+ useMultiViews: true, // 多窗口必须用开启 useMultiViews
27
+ }).then(player => {
28
+ player.callbacks.on("onPhaseChanged", async (phase) => {
29
+ if (phase === PlayerPhase.Playing) {
30
+ if (manager) return;
31
+ manager = await WindowManager.mount({
32
+ room: player,
33
+ container: document.getElementById("container")
34
+ });
35
+ }
36
+ })
37
+ });
38
+
39
+ player.play(); // WindowManager 只有在播放之后才能挂载
40
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/window-manager",
3
- "version": "0.4.0-canary.27",
3
+ "version": "0.4.0-canary.30",
4
4
  "description": "",
5
5
  "main": "dist/index.es.js",
6
6
  "module": "dist/index.es.js",
@@ -58,6 +58,6 @@
58
58
  "typescript": "^4.3.5",
59
59
  "video.js": "^7.14.3",
60
60
  "vite": "^2.5.3",
61
- "white-web-sdk": "^2.16.1"
61
+ "white-web-sdk": "^2.16.4"
62
62
  }
63
63
  }
package/src/AppContext.ts CHANGED
@@ -152,7 +152,10 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
152
152
  }
153
153
 
154
154
  /** Dispatch events to other clients (and self). */
155
- public dispatchMagixEvent: MagixEventDispatcher<TMagixEventPayloads> = (this.manager.displayer as Room).dispatchMagixEvent.bind(this.manager.displayer)
155
+ public dispatchMagixEvent: MagixEventDispatcher<TMagixEventPayloads> = (...args) => {
156
+ // can't dispatch events on replay mode
157
+ return this.manager.room?.dispatchMagixEvent(...args);
158
+ }
156
159
 
157
160
  /** Listen to events from others clients (and self messages). */
158
161
  public addMagixEventListener: MagixEventAddListener<TMagixEventPayloads> = (event, handler, options) => {
package/src/AppManager.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { AppAttributes, AppStatus, Events, MagixEventName, ROOT_DIR } from "./constants";
2
+ import { AppCreateQueue } from "./Utils/AppCreateQueue";
2
3
  import { AppListeners } from "./AppListener";
3
4
  import { AppProxy } from "./AppProxy";
4
5
  import { appRegister } from "./Register";
@@ -20,9 +21,8 @@ import {
20
21
  } from "./Utils/Common";
21
22
  import type { ReconnectRefresher } from "./ReconnectRefresher";
22
23
  import type { BoxManager } from "./BoxManager";
23
- import type { Displayer, DisplayerState, Room, ScenesCallbacksNode } from "white-web-sdk";
24
+ import type { Displayer, DisplayerState, Room, ScenesCallbacksNode, View } from "white-web-sdk";
24
25
  import type { AddAppParams, BaseInsertParams, TeleBoxRect, EmitterEvent } from "./index";
25
- import { AppCreateQueue } from "./Utils/AppCreateQueue";
26
26
 
27
27
  export class AppManager {
28
28
  public displayer: Displayer;
@@ -187,6 +187,10 @@ export class AppManager {
187
187
  const focused = get(this.attributes, "focus");
188
188
  if (this._prevFocused !== focused) {
189
189
  callbacks.emit("focusedChange", focused);
190
+ this.disposePrevFocusViewRedoUndoListeners(this._prevFocused);
191
+ setTimeout(() => {
192
+ this.addRedoUndoListeners(focused);
193
+ }, 0);
190
194
  this._prevFocused = focused;
191
195
  if (focused !== undefined) {
192
196
  this.boxManager?.focusBox({ appId: focused });
@@ -212,8 +216,60 @@ export class AppManager {
212
216
  this.displayerWritableListener(!this.room?.isWritable);
213
217
  this.displayer.callbacks.on("onEnableWriteNowChanged", this.displayerWritableListener);
214
218
  this._prevFocused = this.attributes.focus;
219
+ this.addRedoUndoListeners(this.attributes.focus);
215
220
  }
216
221
 
222
+ private disposePrevFocusViewRedoUndoListeners = (prevFocused: string | undefined) => {
223
+ if (prevFocused === undefined) {
224
+ this.mainView.callbacks.off("onCanRedoStepsUpdate", this.onCanRedoStepsUpdate);
225
+ this.mainView.callbacks.off("onCanUndoStepsUpdate", this.onCanRedoStepsUpdate);
226
+ } else {
227
+ const appProxy = this.appProxies.get(prevFocused);
228
+ if (appProxy) {
229
+ appProxy.view?.callbacks.off("onCanRedoStepsUpdate", this.onCanRedoStepsUpdate);
230
+ appProxy.view?.callbacks.off("onCanUndoStepsUpdate", this.onCanUndoStepsUpdate);
231
+ }
232
+ }
233
+ };
234
+
235
+ private addRedoUndoListeners = (focused: string | undefined) => {
236
+ if (focused === undefined) {
237
+ this.addViewCallbacks(
238
+ this.mainView,
239
+ this.onCanRedoStepsUpdate,
240
+ this.onCanUndoStepsUpdate
241
+ );
242
+ } else {
243
+ const focusApp = this.appProxies.get(focused);
244
+ if (focusApp && focusApp.view) {
245
+ this.addViewCallbacks(
246
+ focusApp.view,
247
+ this.onCanRedoStepsUpdate,
248
+ this.onCanUndoStepsUpdate
249
+ );
250
+ }
251
+ }
252
+ };
253
+
254
+ private addViewCallbacks = (
255
+ view: View,
256
+ redoListener: (steps: number) => void,
257
+ undoListener: (steps: number) => void
258
+ ) => {
259
+ redoListener(view.canRedoSteps);
260
+ undoListener(view.canUndoSteps);
261
+ view.callbacks.on("onCanRedoStepsUpdate", redoListener);
262
+ view.callbacks.on("onCanUndoStepsUpdate", undoListener);
263
+ };
264
+
265
+ private onCanRedoStepsUpdate = (steps: number) => {
266
+ callbacks.emit("canRedoStepsChange", steps);
267
+ };
268
+
269
+ private onCanUndoStepsUpdate = (steps: number) => {
270
+ callbacks.emit("canUndoStepsChange", steps);
271
+ };
272
+
217
273
  /**
218
274
  * 插件更新 attributes 时的回调
219
275
  *
@@ -439,7 +495,7 @@ export class AppManager {
439
495
  this.safeSetAttributes({ _mainScenePath: scenePath });
440
496
  this.store.setMainViewFocusPath(this.mainView);
441
497
  this.updateSceneIndex();
442
- this.dispatchInternalEvent(Events.SetMainViewScenePath, { nextScenePath: scenePath });
498
+ this.dispatchSetMainViewScenePath(scenePath);
443
499
  }
444
500
  }
445
501
 
@@ -469,9 +525,7 @@ export class AppManager {
469
525
  if (success) {
470
526
  this.store.setMainViewScenePath(scenePath);
471
527
  this.safeSetAttributes({ _mainSceneIndex: index });
472
- this.dispatchInternalEvent(Events.SetMainViewScenePath, {
473
- nextScenePath: scenePath,
474
- });
528
+ this.dispatchSetMainViewScenePath(scenePath);
475
529
  }
476
530
  } else {
477
531
  throw new Error(`[WindowManager]: ${sceneDir}: ${index} not valid index`);
@@ -480,6 +534,12 @@ export class AppManager {
480
534
  }
481
535
  }
482
536
 
537
+ private dispatchSetMainViewScenePath(scenePath: string): void {
538
+ this.dispatchInternalEvent(Events.SetMainViewScenePath, { nextScenePath: scenePath });
539
+ // 兼容 15 的 SDK, 需要 room 的当前 ScenePath
540
+ setScenePath(this.room, scenePath);
541
+ }
542
+
483
543
  public getAppInitPath(appId: string): string | undefined {
484
544
  const attrs = this.store.getAppAttributes(appId);
485
545
  if (attrs) {
@@ -587,6 +647,8 @@ export class AppManager {
587
647
  callbacks.clearListeners();
588
648
  this.callbacksNode?.dispose();
589
649
  this.appCreateQueue.destroy();
650
+ this.disposePrevFocusViewRedoUndoListeners(this._prevFocused);
651
+ this._prevFocused = undefined;
590
652
  this._prevSceneIndex = undefined;
591
653
  }
592
654
  }
@@ -1,6 +1,6 @@
1
1
  import { ResizeObserver as ResizeObserverPolyfill } from "@juggle/resize-observer";
2
2
  import { WindowManager } from "./index";
3
- import type { EmitterType} from "./index";
3
+ import type { EmitterType } from "./index";
4
4
 
5
5
  const ResizeObserver = window.ResizeObserver || ResizeObserverPolyfill;
6
6
 
@@ -13,7 +13,7 @@ export class ContainerResizeObserver {
13
13
  container: HTMLElement,
14
14
  sizer: HTMLElement,
15
15
  wrapper: HTMLDivElement,
16
- emitter: EmitterType,
16
+ emitter: EmitterType
17
17
  ) {
18
18
  const containerResizeObserver = new ContainerResizeObserver(emitter);
19
19
  containerResizeObserver.observePlaygroundSize(container, sizer, wrapper);
@@ -31,7 +31,7 @@ export class ContainerResizeObserver {
31
31
  const containerRect = entries[0]?.contentRect;
32
32
  if (containerRect) {
33
33
  this.updateSizer(containerRect, sizer, wrapper);
34
- this.emitter.emit("playgroundSizeChange", containerRect)
34
+ this.emitter.emit("playgroundSizeChange", containerRect);
35
35
  }
36
36
  });
37
37
 
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { isEmpty } from "lodash";
3
+ import { ApplianceNames } from "white-web-sdk";
3
4
 
4
5
  export let cursorName: string;
5
6
  export let tagName: string;
@@ -19,6 +20,7 @@
19
20
  $: hasTagName = !isEmpty(tagName);
20
21
  $: hasAvatar = !isEmpty(avatar);
21
22
  $: display = visible ? "initial" : "none";
23
+ $: isLaserPointer = appliance === ApplianceNames.laserPointer;
22
24
 
23
25
  const computedAvatarStyle = () => {
24
26
  return Object.entries({
@@ -36,28 +38,30 @@
36
38
  <div
37
39
  class="netless-window-manager-cursor-mid"
38
40
  style="transform: translateX({x}px) translateY({y}px);display: {display}"
39
- >
40
- <div class="netless-window-manager-cursor-name">
41
- <div
42
- class={theme}
43
- style="background-color: {backgroundColor};color: {color};opacity: {opacity}"
44
- >
45
- {#if hasAvatar}
46
- <img
47
- class="netless-window-manager-cursor-selector-avatar"
48
- style={computedAvatarStyle()}
49
- src={avatar}
50
- alt="avatar"
51
- />
52
- {/if}
53
- <span style="overflow: hidden;white-space: nowrap;text-overflow: ellipsis;max-width: 80px">{cursorName}</span>
54
- {#if hasTagName}
55
- <span class="netless-window-manager-cursor-tag-name" style="background-color: {cursorTagBackgroundColor}">
56
- {tagName}
57
- </span>
58
- {/if}
41
+ >
42
+ {#if !isLaserPointer}
43
+ <div class="netless-window-manager-cursor-name">
44
+ <div
45
+ class={theme}
46
+ style="background-color: {backgroundColor};color: {color};opacity: {opacity}"
47
+ >
48
+ {#if hasAvatar}
49
+ <img
50
+ class="netless-window-manager-cursor-selector-avatar"
51
+ style={computedAvatarStyle()}
52
+ src={avatar}
53
+ alt="avatar"
54
+ />
55
+ {/if}
56
+ <span style="overflow: hidden;white-space: nowrap;text-overflow: ellipsis;max-width: 80px">{cursorName}</span>
57
+ {#if hasTagName}
58
+ <span class="netless-window-manager-cursor-tag-name" style="background-color: {cursorTagBackgroundColor}">
59
+ {tagName}
60
+ </span>
61
+ {/if}
62
+ </div>
59
63
  </div>
60
- </div>
64
+ {/if}
61
65
  <div class="cursor-image-wrapper">
62
66
  <img class="netless-window-manager-cursor-{appliance}-image" {src} alt={appliance} />
63
67
  </div>
@@ -23,7 +23,7 @@ export class Cursor {
23
23
  private cursorManager: CursorManager,
24
24
  private wrapper?: HTMLElement
25
25
  ) {
26
- this.setMember();
26
+ this.updateMember();
27
27
  this.createCursor();
28
28
  this.autoHidden();
29
29
  }
@@ -158,9 +158,10 @@ export class Cursor {
158
158
  }
159
159
  }
160
160
 
161
- public setMember() {
161
+ public updateMember() {
162
162
  this.member = this.manager.findMemberByUid(this.memberId);
163
163
  this.updateComponent();
164
+ return this.member;
164
165
  }
165
166
 
166
167
  private updateComponent() {
@@ -4,6 +4,7 @@ import selector from "../image/selector-cursor.png";
4
4
  import eraser from "../image/eraser-cursor.png";
5
5
  import shape from "../image/shape-cursor.svg";
6
6
  import text from "../image/text-cursor.svg";
7
+ import laser from "../image/laser-pointer-cursor.svg";
7
8
 
8
9
  export const ApplianceMap: {
9
10
  [key: string]: string;
@@ -13,4 +14,5 @@ export const ApplianceMap: {
13
14
  [ApplianceNames.eraser]: eraser,
14
15
  [ApplianceNames.shape]: shape,
15
16
  [ApplianceNames.text]: text,
17
+ [ApplianceNames.laserPointer]: laser,
16
18
  };
@@ -1,8 +1,9 @@
1
- import { throttle } from "lodash";
1
+ import { ApplianceNames } from "white-web-sdk";
2
2
  import { Cursor } from "./Cursor";
3
3
  import { CursorState, Events } from "../constants";
4
4
  import { emitter, WindowManager } from "../index";
5
5
  import { SideEffectManager } from "side-effect-manager";
6
+ import { throttle } from "lodash";
6
7
  import type { CursorMovePayload } from "../index";
7
8
  import type { PositionType } from "../AttributesDelegate";
8
9
  import type { Point, RoomMember, View } from "white-web-sdk";
@@ -27,7 +28,7 @@ export class CursorManager {
27
28
  private sideEffectManager = new SideEffectManager();
28
29
  private store = this.manager.store;
29
30
 
30
- constructor(private manager: AppManager) {
31
+ constructor(private manager: AppManager, private enableCursor: boolean) {
31
32
  this.roomMembers = this.manager.room?.state.roomMembers;
32
33
  const wrapper = WindowManager.wrapper;
33
34
  if (wrapper) {
@@ -42,8 +43,12 @@ export class CursorManager {
42
43
  if (payload.state === CursorState.Leave) {
43
44
  cursorInstance.leave();
44
45
  } else {
45
- cursorInstance.setMember();
46
- cursorInstance.move(payload.position);
46
+ const member = cursorInstance.updateMember();
47
+ const isLaserPointer =
48
+ member?.memberState.currentApplianceName === ApplianceNames.laserPointer;
49
+ if (this.enableCursor || isLaserPointer) {
50
+ cursorInstance.move(payload.position);
51
+ }
47
52
  }
48
53
  });
49
54
  this.sideEffectManager.add(() => {
@@ -51,7 +56,7 @@ export class CursorManager {
51
56
  this.updateContainerRect();
52
57
  });
53
58
  return unsubscribe;
54
- })
59
+ });
55
60
  }
56
61
 
57
62
  public setupWrapper(wrapper: HTMLElement) {
package/src/Helper.ts CHANGED
@@ -1,3 +1,7 @@
1
+ import { getVersionNumber } from "./Utils/Common";
2
+ import { REQUIRE_VERSION } from "./constants";
3
+ import { WhiteVersion } from "white-web-sdk";
4
+ import { WhiteWebSDKInvalidError } from "./Utils/error";
1
5
  import { WindowManager } from "./index";
2
6
 
3
7
  export const setupWrapper = (
@@ -27,4 +31,11 @@ export const setupWrapper = (
27
31
  WindowManager.wrapper = wrapper;
28
32
 
29
33
  return { playground, wrapper, sizer, mainViewElement };
30
- };
34
+ };
35
+
36
+ export const checkVersion = () => {
37
+ const version = getVersionNumber(WhiteVersion);
38
+ if (version < getVersionNumber(REQUIRE_VERSION)) {
39
+ throw new WhiteWebSDKInvalidError(REQUIRE_VERSION);
40
+ }
41
+ };
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
4
+ <title>编组 2</title>
5
+ <desc>Created with Sketch.</desc>
6
+ <defs>
7
+ <filter x="-120.0%" y="-120.0%" width="340.0%" height="340.0%" filterUnits="objectBoundingBox" id="filter-1">
8
+ <feGaussianBlur stdDeviation="4" in="SourceGraphic"></feGaussianBlur>
9
+ </filter>
10
+ </defs>
11
+ <g id="页面1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
12
+ <g id="编组-2" transform="translate(9.000000, 9.000000)" fill="#FF0100">
13
+ <circle id="椭圆形" filter="url(#filter-1)" cx="5" cy="5" r="5"></circle>
14
+ <path d="M5,8 C6.65685425,8 8,6.65685425 8,5 C8,3.34314575 6.65685425,2 5,2 C3.34314575,2 2,3.34314575 2,5 C2,6.65685425 3.34314575,8 5,8 Z M5,6.28571429 C4.28991961,6.28571429 3.71428571,5.71008039 3.71428571,5 C3.71428571,4.28991961 4.28991961,3.71428571 5,3.71428571 C5.71008039,3.71428571 6.28571429,4.28991961 6.28571429,5 C6.28571429,5.71008039 5.71008039,6.28571429 5,6.28571429 Z" id="椭圆形" fill-rule="nonzero"></path>
15
+ </g>
16
+ </g>
17
+ </svg>
package/src/index.ts CHANGED
@@ -2,25 +2,25 @@ import Emittery from "emittery";
2
2
  import pRetry from "p-retry";
3
3
  import { AppManager } from "./AppManager";
4
4
  import { appRegister } from "./Register";
5
+ import { checkVersion, setupWrapper } from "./Helper";
5
6
  import { ContainerResizeObserver } from "./ContainerResizeObserver";
6
7
  import { createBoxManager } from "./BoxManager";
7
8
  import { CursorManager } from "./Cursor";
8
- import { DEFAULT_CONTAINER_RATIO, Events, REQUIRE_VERSION } from "./constants";
9
+ import { DEFAULT_CONTAINER_RATIO, Events } from "./constants";
9
10
  import { Fields } from "./AttributesDelegate";
10
11
  import { initDb } from "./Register/storage";
12
+ import { InvisiblePlugin, isPlayer, isRoom, RoomPhase, ViewMode } from "white-web-sdk";
11
13
  import { isNull, isObject } from "lodash";
12
14
  import { log } from "./Utils/log";
13
15
  import { ReconnectRefresher } from "./ReconnectRefresher";
14
16
  import { replaceRoomFunction } from "./Utils/RoomHacker";
15
17
  import { setupBuiltin } from "./BuiltinApps";
16
- import { setupWrapper } from "./Helper";
17
18
  import "./style.css";
18
19
  import "@netless/telebox-insider/dist/style.css";
19
20
  import {
20
21
  addEmitterOnceListener,
21
22
  ensureValidScenePath,
22
23
  entireScenes,
23
- getVersionNumber,
24
24
  isValidScenePath,
25
25
  wait,
26
26
  } from "./Utils/Common";
@@ -30,17 +30,8 @@ import {
30
30
  AppManagerNotInitError,
31
31
  InvalidScenePath,
32
32
  ParamsInvalidError,
33
- WhiteWebSDKInvalidError,
34
33
  } from "./Utils/error";
35
34
  import type { Apps, Position } from "./AttributesDelegate";
36
- import {
37
- InvisiblePlugin,
38
- isPlayer,
39
- isRoom,
40
- RoomPhase,
41
- ViewMode,
42
- WhiteVersion,
43
- } from "white-web-sdk";
44
35
  import type {
45
36
  Displayer,
46
37
  SceneDefinition,
@@ -159,6 +150,8 @@ export type PublicEvent = {
159
150
  mainViewSceneIndexChange: number;
160
151
  focusedChange: string | undefined;
161
152
  mainViewScenesLengthChange: number;
153
+ canRedoStepsChange: number;
154
+ canUndoStepsChange: number;
162
155
  };
163
156
 
164
157
  export type MountParams = {
@@ -224,7 +217,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
224
217
  const cursor = params.cursor;
225
218
  WindowManager.params = params;
226
219
 
227
- this.checkVersion();
220
+ checkVersion();
228
221
  let manager: WindowManager | undefined = undefined;
229
222
  if (isRoom(room)) {
230
223
  if (room.phase !== RoomPhase.Connected) {
@@ -270,10 +263,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
270
263
  await manager.ensureAttributes();
271
264
 
272
265
  manager.appManager = new AppManager(manager);
273
-
274
- if (cursor) {
275
- manager.cursorManager = new CursorManager(manager.appManager);
276
- }
266
+ manager.cursorManager = new CursorManager(manager.appManager, Boolean(cursor));
277
267
 
278
268
  if (params.container) {
279
269
  manager.bindContainer(params.container);
@@ -641,6 +631,24 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
641
631
  return this.appManager?.mainViewScenesLength || 0;
642
632
  }
643
633
 
634
+ public get canRedoSteps(): number {
635
+ const focused = this.focused;
636
+ if (focused) {
637
+ return this.appManager?.focusApp?.view?.canRedoSteps || 0;
638
+ } else {
639
+ return this.mainView.canRedoSteps;
640
+ }
641
+ }
642
+
643
+ public get canUndoSteps(): number {
644
+ const focused = this.focused;
645
+ if (focused) {
646
+ return this.appManager?.focusApp?.view?.canUndoSteps || 0;
647
+ } else {
648
+ return this.mainView.canUndoSteps;
649
+ }
650
+ }
651
+
644
652
  /**
645
653
  * 查询所有的 App
646
654
  */
@@ -749,18 +757,38 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
749
757
  this.appManager?.boxManager?.setPrefersColorScheme(scheme);
750
758
  }
751
759
 
752
- private isDynamicPPT(scenes: SceneDefinition[]) {
753
- const sceneSrc = scenes[0]?.ppt?.src;
754
- return sceneSrc?.startsWith("pptx://");
760
+ public cleanCurrentScene(): void {
761
+ const focused = this.focused;
762
+ if (focused) {
763
+ this.appManager?.focusApp?.view?.cleanCurrentScene();
764
+ } else {
765
+ this.mainView.cleanCurrentScene();
766
+ }
755
767
  }
756
768
 
757
- private static checkVersion() {
758
- const version = getVersionNumber(WhiteVersion);
759
- if (version < getVersionNumber(REQUIRE_VERSION)) {
760
- throw new WhiteWebSDKInvalidError(REQUIRE_VERSION);
769
+ public redo(): void {
770
+ const focused = this.focused;
771
+ if (focused) {
772
+ this.appManager?.focusApp?.view?.redo();
773
+ } else {
774
+ this.mainView.redo();
761
775
  }
762
776
  }
763
777
 
778
+ public undo(): void {
779
+ const focused = this.focused;
780
+ if (focused) {
781
+ this.appManager?.focusApp?.view?.undo();
782
+ } else {
783
+ this.mainView.undo();
784
+ }
785
+ }
786
+
787
+ private isDynamicPPT(scenes: SceneDefinition[]) {
788
+ const sceneSrc = scenes[0]?.ppt?.src;
789
+ return sceneSrc?.startsWith("pptx://");
790
+ }
791
+
764
792
  private async ensureAttributes() {
765
793
  if (isNull(this.attributes)) {
766
794
  await wait(50);