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

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
@@ -107,12 +107,13 @@ manager.closeApp(appId)
107
107
 
108
108
  <h2 id="prototypes">实例属性</h2>
109
109
 
110
- | name | type | default | desc |
111
- | ------------------ | ------- | ------- | ------ |
112
- | mainView | View | | 主白板 |
113
- | boxState | string | | 当前窗口状态 |
114
- | darkMode | boolean | | 黑夜模式 |
115
- | prefersColorScheme | string | | 颜色主题 |
110
+ | name | type | default | desc |
111
+ | ------------------ | ------- | ------- | ----------------- |
112
+ | mainView | View | | 主白板 |
113
+ | mainViewSceneIndex | number | | 当前主白板的 SceneIndex |
114
+ | boxState | string | | 当前窗口状态 |
115
+ | darkMode | boolean | | 黑夜模式 |
116
+ | prefersColorScheme | string | | 颜色主题 |
116
117
 
117
118
 
118
119
  <h2 id="events">事件回调</h2>
@@ -124,6 +125,7 @@ manager.callbacks.on(events, listener)
124
125
  | name | type | default | desc |
125
126
  | ------------------------ | -------------- | ------- | -------------------------- |
126
127
  | mainViewModeChange | ViewVisionMode | | |
128
+ | mainViewSceneIndexChange | index: number | | |
127
129
  | boxStateChange | string | | normal,minimized,maximized |
128
130
  | darkModeChange | boolean | | |
129
131
  | prefersColorSchemeChange | string | | auto,light,dark |
@@ -0,0 +1,4 @@
1
+ # 概念
2
+
3
+ ## 同步区域
4
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/window-manager",
3
- "version": "0.4.0-canary.11",
3
+ "version": "0.4.0-canary.15",
4
4
  "description": "",
5
5
  "main": "dist/index.es.js",
6
6
  "module": "dist/index.es.js",
@@ -19,7 +19,7 @@
19
19
  "license": "ISC",
20
20
  "peerDependencies": {
21
21
  "video.js": ">=7",
22
- "white-web-sdk": "^2.13.16"
22
+ "white-web-sdk": "^2.16.0"
23
23
  },
24
24
  "dependencies": {
25
25
  "@juggle/resize-observer": "^3.3.1",
@@ -50,13 +50,15 @@ export type MagixEventHandler<
50
50
  TEvent extends MagixEventTypes<TPayloads> = MagixEventTypes<TPayloads>
51
51
  > = (message: MagixEventMessage<TPayloads, TEvent>) => void;
52
52
 
53
+ export type MagixEventListenerDisposer = () => void
54
+
53
55
  export type MagixEventAddListener<TPayloads = any> = <
54
56
  TEvent extends MagixEventTypes<TPayloads> = MagixEventTypes<TPayloads>
55
57
  >(
56
58
  event: TEvent,
57
59
  handler: MagixEventHandler<TPayloads, TEvent>,
58
60
  options?: MagixEventListenerOptions | undefined
59
- ) => void;
61
+ ) => MagixEventListenerDisposer;
60
62
 
61
63
  export type MagixEventRemoveListener<TPayloads = any> = <
62
64
  TEvent extends MagixEventTypes<TPayloads> = MagixEventTypes<TPayloads>
@@ -4,7 +4,7 @@ import { SideEffectManager } from "side-effect-manager";
4
4
  import type { AppContext } from "../../AppContext";
5
5
  import { safeListenPropsUpdated } from "../../Utils/Reactive";
6
6
  import { isRef, makeRef, plainObjectKeys } from "./utils";
7
- import type { Diff, MaybeRefValue, RefValue, StorageStateChangedEvent } from "./typings";
7
+ import type { Diff, MaybeRefValue, RefValue, StorageStateChangedEvent, StorageStateChangedListener, StorageStateChangedListenerDisposer } from "./typings";
8
8
  import { StorageEvent } from "./StorageEvent";
9
9
 
10
10
  export * from './typings';
@@ -86,6 +86,11 @@ export class Storage<TState extends Record<string, any> = any> implements Storag
86
86
  }
87
87
 
88
88
  readonly onStateChanged = new StorageEvent<StorageStateChangedEvent<TState>>();
89
+
90
+ addStateChangedListener(handler: StorageStateChangedListener<TState>): StorageStateChangedListenerDisposer {
91
+ this.onStateChanged.addListener(handler);
92
+ return () => this.onStateChanged.removeListener(handler);
93
+ }
89
94
 
90
95
  ensureState(state: Partial<TState>): void {
91
96
  return this.setState(
@@ -16,6 +16,8 @@ export type StorageOnSetStatePayload<TState = unknown> = {
16
16
  [K in keyof TState]?: MaybeRefValue<TState[K]>;
17
17
  };
18
18
 
19
- export type StorageStateChangedEvent<TState = any> = Diff<TState>
19
+ export type StorageStateChangedEvent<TState = any> = Diff<TState>;
20
20
 
21
- export type StorageStateChangedListener<TState = any> = StorageEventListener<StorageStateChangedEvent<TState>>
21
+ export type StorageStateChangedListener<TState = any> = StorageEventListener<StorageStateChangedEvent<TState>>;
22
+
23
+ export type StorageStateChangedListenerDisposer = () => void;
package/src/AppContext.ts CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  toJS
9
9
  } from 'white-web-sdk';
10
10
  import { BoxNotCreatedError } from './Utils/error';
11
- import type { Room, SceneDefinition, View } from "white-web-sdk";
11
+ import type { Room, SceneDefinition, View, EventListener as WhiteEventListener } from "white-web-sdk";
12
12
  import type { ReadonlyTeleBox } from "@netless/telebox-insider";
13
13
  import type Emittery from "emittery";
14
14
  import type { BoxManager } from "./BoxManager";
@@ -110,6 +110,8 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
110
110
  public setScenePath = async (scenePath: string): Promise<void> => {
111
111
  if (!this.appProxy.box) return;
112
112
  this.appProxy.setFullPath(scenePath);
113
+ // 兼容 15 版本 SDK 的切页
114
+ this.getRoom()?.setScenePath(scenePath);
113
115
  }
114
116
 
115
117
  public mountView = (dom: HTMLDivElement): void => {
@@ -156,7 +158,10 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
156
158
  public dispatchMagixEvent: MagixEventDispatcher<TMagixEventPayloads> = (this.manager.displayer as Room).dispatchMagixEvent.bind(this.manager.displayer)
157
159
 
158
160
  /** Listen to events from others clients (and self messages). */
159
- public addMagixEventListener: MagixEventAddListener<TMagixEventPayloads> = this.manager.displayer.addMagixEventListener.bind(this.manager.displayer)
161
+ public addMagixEventListener: MagixEventAddListener<TMagixEventPayloads> = (event, handler, options) => {
162
+ this.manager.displayer.addMagixEventListener(event, handler as WhiteEventListener, options);
163
+ return () => this.manager.displayer.removeMagixEventListener(event, handler as WhiteEventListener);
164
+ }
160
165
 
161
166
  /** Remove a Magix event listener. */
162
167
  public removeMagixEventListener = this.manager.displayer.removeMagixEventListener.bind(this.manager.displayer) as MagixEventRemoveListener<TMagixEventPayloads>
package/src/AppManager.ts CHANGED
@@ -4,11 +4,11 @@ import { AppListeners } from "./AppListener";
4
4
  import { AppProxy } from "./AppProxy";
5
5
  import { autorun, isPlayer, isRoom, ScenePathType } from "white-web-sdk";
6
6
  import { callbacks, emitter, WindowManager, reconnectRefresher } from "./index";
7
- import { genAppId, makeValidScenePath, setScenePath, setViewFocusScenePath } from "./Utils/Common";
7
+ import { entireScenes, genAppId, makeValidScenePath, parseSceneDir, setScenePath, setViewFocusScenePath } from "./Utils/Common";
8
8
  import { log } from "./Utils/log";
9
9
  import { MainViewProxy } from "./View/MainView";
10
10
  import { onObjectRemoved, safeListenPropsUpdated } from "./Utils/Reactive";
11
- import { get, sortBy } from "lodash";
11
+ import { get, isInteger, sortBy } from "lodash";
12
12
  import { store } from "./AttributesDelegate";
13
13
  import { ViewManager } from "./View/ViewManager";
14
14
  import type { ReconnectRefresher } from "./ReconnectRefresher";
@@ -212,10 +212,11 @@ export class AppManager {
212
212
  emitter.emit("mainViewMounted");
213
213
  }
214
214
 
215
- public setMainViewFocusPath() {
216
- const scenePath = this.store.getMainViewScenePath();
217
- if (scenePath) {
218
- setViewFocusScenePath(this.mainView, scenePath);
215
+ public setMainViewFocusPath(scenePath?: string) {
216
+ const focusScenePath = scenePath || this.store.getMainViewScenePath();
217
+ if (focusScenePath) {
218
+ const view = setViewFocusScenePath(this.mainView, focusScenePath);
219
+ return view?.focusScenePath === focusScenePath;
219
220
  }
220
221
  }
221
222
 
@@ -318,6 +319,9 @@ export class AppManager {
318
319
  });
319
320
  if (isWritable === true) {
320
321
  this.mainView.disableCameraTransform = false;
322
+ if (this.room && this.room.disableSerialization === true) {
323
+ this.room.disableSerialization = false;
324
+ }
321
325
  } else {
322
326
  this.mainView.disableCameraTransform = true;
323
327
  }
@@ -374,27 +378,44 @@ export class AppManager {
374
378
  }
375
379
 
376
380
  private async _setMainViewScenePath(scenePath: string) {
377
- this.safeSetAttributes({ _mainScenePath: scenePath });
378
- this.setMainViewFocusPath();
379
- this.store.setMainViewFocusPath(this.mainView);
380
- this.dispatchInternalEvent(Events.SetMainViewScenePath, { nextScenePath: scenePath });
381
+ const success = this.setMainViewFocusPath(scenePath);
382
+ if (success) {
383
+ this.safeSetAttributes({ _mainScenePath: scenePath });
384
+ this.store.setMainViewFocusPath(this.mainView);
385
+ this.updateSceneIndex();
386
+ this.dispatchInternalEvent(Events.SetMainViewScenePath, { nextScenePath: scenePath });
387
+ }
388
+ }
389
+
390
+ private updateSceneIndex = () => {
391
+ const scenePath = this.store.getMainViewScenePath() as string;
392
+ const sceneDir = parseSceneDir(scenePath);
393
+ const scenes = entireScenes(this.displayer)[sceneDir];
394
+ if (scenes.length) {
395
+ // "/ppt3/1" -> "1"
396
+ const pageName = scenePath.replace(sceneDir, "").replace("/", "");
397
+ const index = scenes.findIndex(scene => scene.name === pageName);
398
+ if (isInteger(index) && index >= 0) {
399
+ this.safeSetAttributes({ _mainSceneIndex: index });
400
+ }
401
+ }
381
402
  }
382
403
 
383
404
  public async setMainViewSceneIndex(index: number) {
384
405
  if (this.room) {
385
- this.safeSetAttributes({ _mainSceneIndex: index });
406
+ if (this.store.getMainViewSceneIndex() === index) return;
386
407
  const mainViewScenePath = this.store.getMainViewScenePath() as string;
387
408
  if (mainViewScenePath) {
388
- const sceneList = mainViewScenePath.split("/");
389
- sceneList.pop();
390
- let sceneDir = sceneList.join("/");
391
- if (sceneDir === "") {
392
- sceneDir = "/";
393
- }
409
+ const sceneDir = parseSceneDir(mainViewScenePath);
394
410
  const scenePath = makeValidScenePath(this.displayer, sceneDir, index);
395
411
  if (scenePath) {
396
- this.store.setMainViewScenePath(scenePath);
397
- this.setMainViewFocusPath();
412
+ const success = this.setMainViewFocusPath(scenePath);
413
+ if (success) {
414
+ this.store.setMainViewScenePath(scenePath);
415
+ this.safeSetAttributes({ _mainSceneIndex: index });
416
+ }
417
+ } else {
418
+ throw new Error(`[WindowManager]: ${sceneDir}: ${index} not valid index`);
398
419
  }
399
420
  }
400
421
  }
package/src/AppProxy.ts CHANGED
@@ -5,13 +5,9 @@ import { appRegister } from "./Register";
5
5
  import { autorun } from "white-web-sdk";
6
6
  import { emitter } from "./index";
7
7
  import { Fields } from "./AttributesDelegate";
8
- import { get } from "lodash";
8
+ import { debounce, get } from "lodash";
9
9
  import { log } from "./Utils/log";
10
- import {
11
- setScenePath,
12
- setViewFocusScenePath,
13
- getScenePath
14
- } from "./Utils/Common";
10
+ import { setScenePath, setViewFocusScenePath, getScenePath } from "./Utils/Common";
15
11
  import type {
16
12
  AppEmitterEvent,
17
13
  AppInitState,
@@ -113,9 +109,7 @@ export class AppProxy extends Base {
113
109
  this.manager.safeUpdateAttributes(["apps", this.id, Fields.FullPath], path);
114
110
  }
115
111
 
116
- public async baseInsertApp(
117
- skipUpdate = false,
118
- ): Promise<{ appId: string; app: NetlessApp }> {
112
+ public async baseInsertApp(skipUpdate = false): Promise<{ appId: string; app: NetlessApp }> {
119
113
  const params = this.params;
120
114
  if (!params.kind) {
121
115
  throw new Error("[WindowManager]: kind require");
@@ -123,7 +117,13 @@ export class AppProxy extends Base {
123
117
  const appImpl = await appRegister.appClasses.get(params.kind)?.();
124
118
  const appParams = appRegister.registered.get(params.kind);
125
119
  if (appImpl) {
126
- await this.setupApp(this.id, skipUpdate, appImpl, params.options, appParams?.appOptions);
120
+ await this.setupApp(
121
+ this.id,
122
+ skipUpdate,
123
+ appImpl,
124
+ params.options,
125
+ appParams?.appOptions
126
+ );
127
127
  } else {
128
128
  throw new Error(`[WindowManager]: app load failed ${params.kind} ${params.src}`);
129
129
  }
@@ -317,7 +317,7 @@ export class AppProxy extends Base {
317
317
  }
318
318
  });
319
319
  });
320
- this.manager.refresher?.add(this.stateKey,() => {
320
+ this.manager.refresher?.add(this.stateKey, () => {
321
321
  return autorun(() => {
322
322
  const appState = this.appAttributes?.state;
323
323
  if (appState?.zIndex > 0 && appState.zIndex !== this.box?.zIndex) {
@@ -325,8 +325,20 @@ export class AppProxy extends Base {
325
325
  }
326
326
  });
327
327
  });
328
+ this.manager.refresher?.add(`${appId}-fullPath`, () => {
329
+ return autorun(() => {
330
+ const fullPath = this.appAttributes?.fullPath;
331
+ this.setFocusScenePathHandler(fullPath);
332
+ });
333
+ });
328
334
  };
329
335
 
336
+ private setFocusScenePathHandler = debounce((fullPath: string | undefined) => {
337
+ if (this.view && fullPath && fullPath !== this.view?.focusScenePath) {
338
+ setViewFocusScenePath(this.view, fullPath);
339
+ }
340
+ }, 50);
341
+
330
342
  public setScenePath(): void {
331
343
  if (!this.manager.canOperate) return;
332
344
  const fullScenePath = this.getFullScenePath();
@@ -372,6 +384,7 @@ export class AppProxy extends Base {
372
384
  this.manager.appStatus.delete(this.id);
373
385
  this.manager.refresher?.remove(this.id);
374
386
  this.manager.refresher?.remove(this.stateKey);
387
+ this.manager.refresher?.remove(`${this.id}-fullPath`);
375
388
  }
376
389
 
377
390
  public close(): Promise<void> {
@@ -17,9 +17,17 @@ export const genAppId = async (kind: string) => {
17
17
  export const setViewFocusScenePath = (view: View, focusScenePath: string) => {
18
18
  if (view.focusScenePath !== focusScenePath) {
19
19
  view.focusScenePath = focusScenePath;
20
+ return view;
20
21
  }
21
22
  };
22
23
 
24
+ export const setViewSceneIndex = (view: View, index: number) => {
25
+ if (view.focusSceneIndex !== index) {
26
+ view.focusSceneIndex = index;
27
+ return view;
28
+ }
29
+ }
30
+
23
31
  export const setScenePath = (room: Room | undefined, scenePath: string) => {
24
32
  if (room && room.isWritable) {
25
33
  if (room.state.sceneState.scenePath !== scenePath) {
@@ -70,7 +78,9 @@ export const notifyMainViewModeChange = debounce(
70
78
  export const makeValidScenePath = (displayer: Displayer, scenePath: string, index = 0) => {
71
79
  const scenes = entireScenes(displayer)[scenePath];
72
80
  if (!scenes) return;
73
- const firstSceneName = scenes[index].name;
81
+ const scene = scenes[index];
82
+ if (!scene) return;
83
+ const firstSceneName = scene.name;
74
84
  if (scenePath === "/") {
75
85
  return `/${firstSceneName}`;
76
86
  } else {
@@ -86,6 +96,17 @@ export const isValidScenePath = (scenePath: string) => {
86
96
  return scenePath.startsWith("/");
87
97
  };
88
98
 
99
+ export const parseSceneDir = (scenePath: string) => {
100
+ const sceneList = scenePath.split("/");
101
+ sceneList.pop();
102
+ let sceneDir = sceneList.join("/");
103
+ // "/page1" 的 dir 为 "/"
104
+ if (sceneDir === "") {
105
+ sceneDir = "/";
106
+ }
107
+ return sceneDir;
108
+ }
109
+
89
110
  export const ensureValidScenePath = (scenePath: string) => {
90
111
  if (scenePath.endsWith("/")) {
91
112
  return scenePath.slice(0, -1);
@@ -51,6 +51,7 @@ export const replaceRoomFunction = (room: Room, manager: WindowManager) => {
51
51
  room.setMemberState = (...args) => manager.mainView.setMemberState(...args);
52
52
  room.redo = () => manager.mainView.redo();
53
53
  room.undo = () => manager.mainView.undo();
54
+ room.cleanCurrentScene = () => manager.mainView.cleanCurrentScene();
54
55
  }
55
56
 
56
57
  };
@@ -1,4 +1,4 @@
1
- import type { View , Displayer} from "white-web-sdk";
1
+ import type { View, Displayer } from "white-web-sdk";
2
2
 
3
3
  export class ViewManager {
4
4
  public views: Map<string, View> = new Map();
@@ -38,7 +38,6 @@ export class ViewManager {
38
38
  }
39
39
  }
40
40
 
41
-
42
41
  export const createView = (displayer: Displayer): View => {
43
42
  const view = displayer.views.createView();
44
43
  setDefaultCameraBound(view);
package/src/index.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  ensureValidScenePath,
22
22
  getVersionNumber,
23
23
  isValidScenePath,
24
+ parseSceneDir,
24
25
  wait,
25
26
  } from "./Utils/Common";
26
27
  import type { TELE_BOX_STATE, BoxManager } from "./BoxManager";
@@ -220,7 +221,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
220
221
  if (room.phase !== RoomPhase.Connected) {
221
222
  throw new Error("[WindowManager]: Room only Connected can be mount");
222
223
  }
223
- if (room.phase === RoomPhase.Connected) {
224
+ if (room.phase === RoomPhase.Connected && room.isWritable) {
224
225
  // redo undo 需要设置这个属性
225
226
  room.disableSerialization = false;
226
227
  }
@@ -571,6 +572,19 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
571
572
  return this.attributes.focus;
572
573
  }
573
574
 
575
+ public get mainViewSceneIndex(): number {
576
+ return this.appManager?.store.getMainViewSceneIndex();
577
+ }
578
+
579
+ public get mainViewSceneDir(): string {
580
+ const scenePath = this.appManager?.store.getMainViewScenePath();
581
+ if (scenePath) {
582
+ return parseSceneDir(scenePath);
583
+ } else {
584
+ throw new Error("[WindowManager]: mainViewSceneDir not found");
585
+ }
586
+ }
587
+
574
588
  /**
575
589
  * 查询所有的 App
576
590
  */