@netless/window-manager 0.4.0-canary.29 → 0.4.0-canary.32

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.
@@ -0,0 +1,39 @@
1
+ ## 进阶使用
2
+
3
+ - 目录
4
+ - [撤销重做]
5
+
6
+
7
+
8
+ <h3>撤销重做</h3>
9
+
10
+ > 以下事件和属性都会根据 `focus` 的窗口来进行自动切换应用对象
11
+
12
+
13
+ #### 获取可以撤销/重做的步数
14
+
15
+ ```ts
16
+ manager.canUndoSteps
17
+ manager.canRedoSteps
18
+ ```
19
+
20
+ #### 监听可以撤销/重做的步数的变化
21
+
22
+ `canRedoStepsChange` 和 `canUndoStepsChange` 会在切换窗口时重新触发
23
+
24
+ ```ts
25
+ manager.emitter.on("canUndoStepsChange", (steps: number) => {
26
+ // 可以撤销的步数更新
27
+ })
28
+ manager.emitter.on("canRedoStepsChange", (steps: number) => {
29
+ // 可以重做的步数更新
30
+ })
31
+ ```
32
+
33
+ #### 撤销/重做
34
+
35
+ ```ts
36
+ manager.undo() //撤销
37
+ manager.redo() // 重做
38
+ ```
39
+
package/docs/api.md CHANGED
@@ -12,6 +12,8 @@
12
12
  - [`setMainViewSceneIndex`](#setMainViewSceneIndex)
13
13
  - [`setBoxState`](#setBoxState)
14
14
  - [`cleanCurrentScene`](#cleanCurrentScene)
15
+ - [`redo`](#redo)
16
+ - [`undo`](#undo)
15
17
  - [实例属性](#prototypes)
16
18
  - [事件回调](#events)
17
19
 
@@ -137,19 +139,37 @@ manager.setBoxState("normal") // boxState: normal | maximized | minimized
137
139
  manager.cleanCurrentScene()
138
140
  ```
139
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
+
140
158
  <br>
141
159
 
142
160
  <h2 id="prototypes">实例属性</h2>
143
161
 
144
- | name | type | default | desc |
145
- | ------------------ | ------- | ------- | ----------------- |
146
- | mainView | View | | 主白板 |
147
- | mainViewSceneIndex | number | | 当前主白板的 SceneIndex |
162
+ | name | type | default | desc |
163
+ | ------------------ | ------- | ------- | ----------------- |
164
+ | mainView | View | | 主白板 |
165
+ | mainViewSceneIndex | number | | 当前主白板的 SceneIndex |
148
166
  | mainViewScenesLength | number | | mainView 的 scenes 长度 |
149
- | boxState | string | | 当前窗口状态 |
150
- | darkMode | boolean | | 黑夜模式 |
151
- | prefersColorScheme | string | | 颜色主题 |
152
- | 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 可以撤销的步数 |
153
173
 
154
174
  <br>
155
175
 
@@ -168,4 +188,6 @@ manager.callbacks.on(events, listener)
168
188
  | prefersColorSchemeChange | string | | auto,light,dark |
169
189
  | cameraStateChange | CameraState | | |
170
190
  | focusedChange | string, undefined | | 当前 focus 的 appId,主白板时为 undefined |
171
- | 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.29",
3
+ "version": "0.4.0-canary.32",
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.3"
61
+ "white-web-sdk": "^2.16.5"
62
62
  }
63
63
  }
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
  *
@@ -591,6 +647,8 @@ export class AppManager {
591
647
  callbacks.clearListeners();
592
648
  this.callbacksNode?.dispose();
593
649
  this.appCreateQueue.destroy();
650
+ this.disposePrevFocusViewRedoUndoListeners(this._prevFocused);
651
+ this._prevFocused = undefined;
594
652
  this._prevSceneIndex = undefined;
595
653
  }
596
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) {
@@ -41,9 +41,9 @@ export const replaceRoomFunction = (room: Room | Player, manager: WindowManager)
41
41
  room.fillSceneSnapshot = (...args) => manager.mainView.fillSceneSnapshot(...args);
42
42
  room.generateScreenshot = (...args) => manager.mainView.generateScreenshot(...args);
43
43
  room.setMemberState = (...args) => manager.mainView.setMemberState(...args);
44
- room.redo = () => manager.mainView.redo();
45
- room.undo = () => manager.mainView.undo();
46
- room.cleanCurrentScene = () => manager.mainView.cleanCurrentScene();
44
+ room.redo = () => manager.redo();
45
+ room.undo = () => manager.undo();
46
+ room.cleanCurrentScene = () => manager.cleanCurrentScene();
47
47
  delegateRemoveScenes(room);
48
48
  }
49
49
  };
@@ -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
@@ -150,6 +150,8 @@ export type PublicEvent = {
150
150
  mainViewSceneIndexChange: number;
151
151
  focusedChange: string | undefined;
152
152
  mainViewScenesLengthChange: number;
153
+ canRedoStepsChange: number;
154
+ canUndoStepsChange: number;
153
155
  };
154
156
 
155
157
  export type MountParams = {
@@ -261,10 +263,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
261
263
  await manager.ensureAttributes();
262
264
 
263
265
  manager.appManager = new AppManager(manager);
264
-
265
- if (cursor) {
266
- manager.cursorManager = new CursorManager(manager.appManager);
267
- }
266
+ manager.cursorManager = new CursorManager(manager.appManager, Boolean(cursor));
268
267
 
269
268
  if (params.container) {
270
269
  manager.bindContainer(params.container);
@@ -370,6 +369,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
370
369
  this.appManager?.refresh();
371
370
  this.appManager?.resetMaximized();
372
371
  this.appManager?.resetMinimized();
372
+ this.appManager?.displayerWritableListener(!this.room.isWritable);
373
373
  WindowManager.container = container;
374
374
  }
375
375
 
@@ -632,6 +632,24 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
632
632
  return this.appManager?.mainViewScenesLength || 0;
633
633
  }
634
634
 
635
+ public get canRedoSteps(): number {
636
+ const focused = this.focused;
637
+ if (focused) {
638
+ return this.appManager?.focusApp?.view?.canRedoSteps || 0;
639
+ } else {
640
+ return this.mainView.canRedoSteps;
641
+ }
642
+ }
643
+
644
+ public get canUndoSteps(): number {
645
+ const focused = this.focused;
646
+ if (focused) {
647
+ return this.appManager?.focusApp?.view?.canUndoSteps || 0;
648
+ } else {
649
+ return this.mainView.canUndoSteps;
650
+ }
651
+ }
652
+
635
653
  /**
636
654
  * 查询所有的 App
637
655
  */
@@ -749,6 +767,24 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
749
767
  }
750
768
  }
751
769
 
770
+ public redo(): number {
771
+ const focused = this.focused;
772
+ if (focused) {
773
+ return this.appManager?.focusApp?.view?.redo() || 0;
774
+ } else {
775
+ return this.mainView.redo();
776
+ }
777
+ }
778
+
779
+ public undo(): number {
780
+ const focused = this.focused;
781
+ if (focused) {
782
+ return this.appManager?.focusApp?.view?.undo() || 0;
783
+ } else {
784
+ return this.mainView.undo();
785
+ }
786
+ }
787
+
752
788
  private isDynamicPPT(scenes: SceneDefinition[]) {
753
789
  const sceneSrc = scenes[0]?.ppt?.src;
754
790
  return sceneSrc?.startsWith("pptx://");