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

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
@@ -136,6 +136,7 @@ manager.setBoxState("normal") // boxState: normal | maximized | minimized
136
136
  | ------------------ | ------- | ------- | ----------------- |
137
137
  | mainView | View | | 主白板 |
138
138
  | mainViewSceneIndex | number | | 当前主白板的 SceneIndex |
139
+ | mainViewScenesLength | number | | mainView 的 scenes 长度 |
139
140
  | boxState | string | | 当前窗口状态 |
140
141
  | darkMode | boolean | | 黑夜模式 |
141
142
  | prefersColorScheme | string | | 颜色主题 |
@@ -158,3 +159,4 @@ manager.callbacks.on(events, listener)
158
159
  | prefersColorSchemeChange | string | | auto,light,dark |
159
160
  | cameraStateChange | CameraState | | |
160
161
  | focusedChange | string, undefined | | 当前 focus 的 appId,主白板时为 undefined |
162
+ | mainViewScenesLengthChange | number | | mainView scenes 添加或删除时触发 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/window-manager",
3
- "version": "0.4.0-canary.23",
3
+ "version": "0.4.0-canary.27",
4
4
  "description": "",
5
5
  "main": "dist/index.es.js",
6
6
  "module": "dist/index.es.js",
@@ -33,7 +33,7 @@
33
33
  "devDependencies": {
34
34
  "@netless/app-docs-viewer": "^0.2.6",
35
35
  "@netless/app-media-player": "0.1.0-beta.5",
36
- "@netless/telebox-insider": "0.2.21",
36
+ "@netless/telebox-insider": "0.2.22",
37
37
  "@rollup/plugin-commonjs": "^20.0.0",
38
38
  "@rollup/plugin-node-resolve": "^13.0.4",
39
39
  "@rollup/plugin-url": "^6.1.0",
package/src/AppManager.ts CHANGED
@@ -1,9 +1,15 @@
1
- import pRetry from "p-retry";
2
- import { AppAttributes, AppStatus, Events, MagixEventName } from "./constants";
1
+ import { AppAttributes, AppStatus, Events, MagixEventName, ROOT_DIR } from "./constants";
3
2
  import { AppListeners } from "./AppListener";
4
3
  import { AppProxy } from "./AppProxy";
4
+ import { appRegister } from "./Register";
5
5
  import { autorun, isPlayer, isRoom, ScenePathType } from "white-web-sdk";
6
- import { callbacks, emitter, WindowManager, reconnectRefresher } from "./index";
6
+ import { callbacks, emitter, reconnectRefresher, WindowManager } from "./index";
7
+ import { get, isInteger, orderBy } from "lodash";
8
+ import { log } from "./Utils/log";
9
+ import { MainViewProxy } from "./View/MainView";
10
+ import { onObjectRemoved, safeListenPropsUpdated } from "./Utils/Reactive";
11
+ import { store } from "./AttributesDelegate";
12
+ import { ViewManager } from "./View/ViewManager";
7
13
  import {
8
14
  entireScenes,
9
15
  genAppId,
@@ -12,17 +18,11 @@ import {
12
18
  setScenePath,
13
19
  setViewFocusScenePath,
14
20
  } from "./Utils/Common";
15
- import { log } from "./Utils/log";
16
- import { MainViewProxy } from "./View/MainView";
17
- import { onObjectRemoved, safeListenPropsUpdated } from "./Utils/Reactive";
18
- import { get, isInteger, sortBy } from "lodash";
19
- import { store } from "./AttributesDelegate";
20
- import { ViewManager } from "./View/ViewManager";
21
21
  import type { ReconnectRefresher } from "./ReconnectRefresher";
22
22
  import type { BoxManager } from "./BoxManager";
23
- import type { Displayer, DisplayerState, Room } from "white-web-sdk";
23
+ import type { Displayer, DisplayerState, Room, ScenesCallbacksNode } from "white-web-sdk";
24
24
  import type { AddAppParams, BaseInsertParams, TeleBoxRect, EmitterEvent } from "./index";
25
- import { appRegister } from "./Register";
25
+ import { AppCreateQueue } from "./Utils/AppCreateQueue";
26
26
 
27
27
  export class AppManager {
28
28
  public displayer: Displayer;
@@ -33,12 +33,15 @@ export class AppManager {
33
33
  public mainViewProxy: MainViewProxy;
34
34
  public refresher?: ReconnectRefresher;
35
35
  public isReplay = this.windowManger.isReplay;
36
+ public mainViewScenesLength = 0;
36
37
 
37
38
  private appListeners: AppListeners;
38
39
  public boxManager?: BoxManager;
39
40
 
40
41
  private _prevSceneIndex: number | undefined;
41
42
  private _prevFocused: string | undefined;
43
+ private callbacksNode: ScenesCallbacksNode | null;
44
+ private appCreateQueue = new AppCreateQueue();
42
45
 
43
46
  constructor(public windowManger: WindowManager) {
44
47
  this.displayer = windowManger.displayer;
@@ -69,17 +72,30 @@ export class AppManager {
69
72
  });
70
73
  }
71
74
  emitter.on("removeScenes", scenePath => {
72
- if (scenePath === "/") {
73
- this.setMainViewScenePath("/");
75
+ if (scenePath === ROOT_DIR) {
76
+ this.setMainViewScenePath(ROOT_DIR);
74
77
  return;
75
78
  }
76
79
  const mainViewScenePath = this.store.getMainViewScenePath();
77
80
  if (this.room && mainViewScenePath) {
78
81
  if (mainViewScenePath === scenePath) {
79
- this.setMainViewScenePath("/");
82
+ this.setMainViewScenePath(ROOT_DIR);
80
83
  }
81
84
  }
82
85
  });
86
+ this.callbacksNode = this.displayer.createScenesCallback(ROOT_DIR, {
87
+ onAddScene: scenesCallback => {
88
+ this.mainViewScenesLength = scenesCallback.scenes.length;
89
+ callbacks.emit("mainViewScenesLengthChange", this.mainViewScenesLength);
90
+ },
91
+ onRemoveScene: scenesCallback => {
92
+ this.mainViewScenesLength = scenesCallback.scenes.length;
93
+ callbacks.emit("mainViewScenesLengthChange", this.mainViewScenesLength);
94
+ },
95
+ });
96
+ if (this.callbacksNode) {
97
+ this.mainViewScenesLength = this.callbacksNode.scenes.length;
98
+ }
83
99
  }
84
100
 
85
101
  private get eventName() {
@@ -112,6 +128,15 @@ export class AppManager {
112
128
  return this.room?.uid || "";
113
129
  }
114
130
 
131
+ public getMainViewSceneDir() {
132
+ const scenePath = this.store.getMainViewScenePath();
133
+ if (scenePath) {
134
+ return parseSceneDir(scenePath);
135
+ } else {
136
+ throw new Error("[WindowManager]: mainViewSceneDir not found");
137
+ }
138
+ }
139
+
115
140
  private async onCreated() {
116
141
  await this.attributesUpdateCallback(this.attributes.apps);
117
142
  this.boxManager?.updateManagerRect();
@@ -161,9 +186,18 @@ export class AppManager {
161
186
  return autorun(() => {
162
187
  const focused = get(this.attributes, "focus");
163
188
  if (this._prevFocused !== focused) {
164
- this.boxManager?.focusBox({ appId: focused });
165
189
  callbacks.emit("focusedChange", focused);
166
190
  this._prevFocused = focused;
191
+ if (focused !== undefined) {
192
+ this.boxManager?.focusBox({ appId: focused });
193
+ // 确保 focus 修改的时候, appProxy 已经创建
194
+ setTimeout(() => {
195
+ const appProxy = this.appProxies.get(focused);
196
+ if (appProxy) {
197
+ appRegister.notifyApp(appProxy.kind, "focus", { appId: focused });
198
+ }
199
+ }, 0);
200
+ }
167
201
  }
168
202
  });
169
203
  });
@@ -195,19 +229,18 @@ export class AppManager {
195
229
  createdAt: apps[appId].createdAt,
196
230
  };
197
231
  });
198
- for (const { id } of sortBy(appsWithCreatedAt, "createdAt")) {
232
+ for (const { id } of orderBy(appsWithCreatedAt, "createdAt", "asc")) {
199
233
  if (!this.appProxies.has(id) && !this.appStatus.has(id)) {
200
234
  const app = apps[id];
201
235
 
202
- pRetry(
203
- async () => {
204
- this.appStatus.set(id, AppStatus.StartCreate);
205
- // 防御 appAttributes 有可能为 undefined 的情况,这里做一个重试
206
- const appAttributes = this.attributes[id];
207
- if (!appAttributes) {
208
- throw new Error("appAttributes is undefined");
209
- }
210
- await this.baseInsertApp(
236
+ this.appStatus.set(id, AppStatus.StartCreate);
237
+ try {
238
+ const appAttributes = this.attributes[id];
239
+ if (!appAttributes) {
240
+ throw new Error("appAttributes is undefined");
241
+ }
242
+ this.appCreateQueue.push(() => {
243
+ return this.baseInsertApp(
211
244
  {
212
245
  kind: app.kind,
213
246
  options: app.options,
@@ -216,13 +249,11 @@ export class AppManager {
216
249
  id,
217
250
  false
218
251
  );
219
- this.focusByAttributes(apps);
220
- },
221
- { retries: 3 }
222
- ).catch(err => {
223
- console.warn(`[WindowManager]: Insert App Error`, err);
224
- this.appStatus.delete(id);
225
- });
252
+ });
253
+ this.focusByAttributes(apps);
254
+ } catch (error) {
255
+ console.warn(`[WindowManager]: Insert App Error`, error);
256
+ }
226
257
  }
227
258
  }
228
259
  }
@@ -385,6 +416,10 @@ export class AppManager {
385
416
  public async setMainViewScenePath(scenePath: string) {
386
417
  if (this.room) {
387
418
  const scenePathType = this.displayer.scenePathType(scenePath);
419
+ const sceneDir = parseSceneDir(scenePath);
420
+ if (sceneDir !== ROOT_DIR) {
421
+ throw new Error(`[WindowManager]: main view scenePath must in root dir "/"`);
422
+ }
388
423
  if (scenePathType === ScenePathType.None) {
389
424
  throw new Error(`[WindowManager]: ${scenePath} not valid scene`);
390
425
  } else if (scenePathType === ScenePathType.Page) {
@@ -470,10 +505,6 @@ export class AppManager {
470
505
  }
471
506
  case "focus": {
472
507
  this.windowManger.safeSetAttributes({ focus: payload.appId });
473
- const appProxy = this.appProxies.get(payload.appId);
474
- if (appProxy) {
475
- appRegister.notifyApp(appProxy.kind, "focus", { appId: payload.appId });
476
- }
477
508
  break;
478
509
  }
479
510
  case "resize": {
@@ -554,6 +585,8 @@ export class AppManager {
554
585
  this.refresher?.destroy();
555
586
  this.mainViewProxy.destroy();
556
587
  callbacks.clearListeners();
588
+ this.callbacksNode?.dispose();
589
+ this.appCreateQueue.destroy();
557
590
  this._prevSceneIndex = undefined;
558
591
  }
559
592
  }
package/src/AppProxy.ts CHANGED
@@ -7,7 +7,7 @@ import { BoxManagerNotFoundError } from "./Utils/error";
7
7
  import { debounce, get } from "lodash";
8
8
  import { emitter } from "./index";
9
9
  import { Fields } from "./AttributesDelegate";
10
- import { getScenePath, removeScenes, setScenePath, setViewFocusScenePath } from "./Utils/Common";
10
+ import { entireScenes, getScenePath, removeScenes, setScenePath, setViewFocusScenePath } from "./Utils/Common";
11
11
  import { log } from "./Utils/log";
12
12
  import type {
13
13
  AppEmitterEvent,
@@ -67,7 +67,7 @@ export class AppProxy {
67
67
  if (options) {
68
68
  this.scenePath = options.scenePath;
69
69
  if (this.appAttributes?.isDynamicPPT && this.scenePath) {
70
- this.scenes = this.manager.displayer.entireScenes()[this.scenePath];
70
+ this.scenes = entireScenes(this.manager.displayer)[this.scenePath];
71
71
  } else {
72
72
  this.scenes = options.scenes;
73
73
  }
@@ -134,19 +134,10 @@ export class AppProxy {
134
134
  };
135
135
  }
136
136
 
137
- private focusApp() {
138
- this.focusBox();
139
- this.store.setMainViewFocusPath(this.manager.mainView);
140
- }
141
-
142
137
  public get box(): ReadonlyTeleBox | undefined {
143
138
  return this.boxManager?.getBox(this.id);
144
139
  }
145
140
 
146
- public focusBox() {
147
- this.boxManager?.focusBox({ appId: this.id });
148
- }
149
-
150
141
  private async setupApp(
151
142
  appId: string,
152
143
  skipUpdate: boolean,
@@ -174,7 +165,7 @@ export class AppProxy {
174
165
  // 延迟执行 setup, 防止初始化的属性没有更新成功
175
166
  const result = await app.setup(context);
176
167
  this.appResult = result;
177
- appRegister.notifyApp(app.kind, "created", { appId, result });
168
+ appRegister.notifyApp(this.kind, "created", { appId, result });
178
169
  this.afterSetupApp(boxInitState);
179
170
  this.fixMobileSize();
180
171
  }, 50);
@@ -188,8 +179,7 @@ export class AppProxy {
188
179
  });
189
180
  if (this.isAddApp && this.box) {
190
181
  this.store.updateAppState(appId, AppAttributes.ZIndex, this.box.zIndex);
191
- this.store.setAppFocus(appId, true);
192
- this.focusBox();
182
+ this.boxManager.focusBox({ appId }, false);
193
183
  }
194
184
  } catch (error: any) {
195
185
  console.error(error);
@@ -42,6 +42,7 @@ export class CursorManager {
42
42
  if (payload.state === CursorState.Leave) {
43
43
  cursorInstance.leave();
44
44
  } else {
45
+ cursorInstance.setMember();
45
46
  cursorInstance.move(payload.position);
46
47
  }
47
48
  });
@@ -0,0 +1,54 @@
1
+ import type { AppProxy } from "../AppProxy";
2
+
3
+ export type Invoker = () => Promise<AppProxy | undefined>;
4
+
5
+ export class AppCreateQueue {
6
+ private list: Invoker[] = [];
7
+ private currentInvoker: Invoker | undefined;
8
+ private timer: number | undefined;
9
+
10
+ private initInterval() {
11
+ return setInterval(() => {
12
+ this.invoke();
13
+ }, 50);
14
+ }
15
+
16
+ public push(item: Invoker) {
17
+ this.list.push(item);
18
+ this.invoke();
19
+ if (this.timer === undefined && this.list.length > 0) {
20
+ this.timer = this.initInterval();
21
+ }
22
+ }
23
+
24
+ public invoke() {
25
+ if (this.list.length === 0) {
26
+ return;
27
+ }
28
+ if (this.currentInvoker !== undefined) {
29
+ return;
30
+ }
31
+
32
+ const item = this.list.shift();
33
+ if (item) {
34
+ this.currentInvoker = item;
35
+ item()
36
+ .then(() => {
37
+ this.currentInvoker = undefined;
38
+ if (this.list.length === 0) {
39
+ clearInterval(this.timer);
40
+ }
41
+ })
42
+ .catch(error => {
43
+ console.error(`[WindowManager]: create app error: ${error.message}`);
44
+ clearInterval(this.timer);
45
+ });
46
+ }
47
+ }
48
+
49
+ public destroy() {
50
+ if (this.timer) {
51
+ clearInterval(this.timer);
52
+ }
53
+ }
54
+ }
@@ -6,6 +6,7 @@ import { v4 } from "uuid";
6
6
  import type { PublicEvent } from "../index";
7
7
  import type { Displayer, ViewVisionMode, Room, View } from "white-web-sdk";
8
8
  import type Emittery from "emittery";
9
+ import { ROOT_DIR } from "../constants";
9
10
 
10
11
  export const genAppId = async (kind: string) => {
11
12
  const impl = await appRegister.appClasses.get(kind)?.();
@@ -91,7 +92,7 @@ export const makeValidScenePath = (displayer: Displayer, scenePath: string, inde
91
92
  const scene = scenes[index];
92
93
  if (!scene) return;
93
94
  const firstSceneName = scene.name;
94
- if (scenePath === "/") {
95
+ if (scenePath === ROOT_DIR) {
95
96
  return `/${firstSceneName}`;
96
97
  } else {
97
98
  return `${scenePath}/${firstSceneName}`;
@@ -102,6 +103,7 @@ export const entireScenes = (displayer: Displayer) => {
102
103
  return displayer.entireScenes();
103
104
  };
104
105
 
106
+
105
107
  export const isValidScenePath = (scenePath: string) => {
106
108
  return scenePath.startsWith("/");
107
109
  };
@@ -4,11 +4,12 @@ import type { WindowManager } from "../index";
4
4
  import type { Camera, Room, Player, PlayerSeekingResult } from "white-web-sdk";
5
5
 
6
6
  // 修改多窗口状态下一些失效的方法实现到 manager 的 mainview 上, 降低迁移成本
7
- export const replaceRoomFunction = (room: Room, manager: WindowManager) => {
7
+ export const replaceRoomFunction = (room: Room | Player, manager: WindowManager) => {
8
8
  if (isPlayer(room)) {
9
9
  const player = room as unknown as Player;
10
10
  delegateSeekToProgressTime(player);
11
11
  } else {
12
+ room = room as unknown as Room;
12
13
  const descriptor = Object.getOwnPropertyDescriptor(room, "disableCameraTransform");
13
14
  if (descriptor) return;
14
15
  Object.defineProperty(room, "disableCameraTransform", {
package/src/constants.ts CHANGED
@@ -46,3 +46,5 @@ export const MIN_HEIGHT = 340 / 720;
46
46
  export const SET_SCENEPATH_DELAY = 100; // 设置 scenePath 的延迟事件
47
47
 
48
48
  export const DEFAULT_CONTAINER_RATIO = 9 / 16;
49
+
50
+ export const ROOT_DIR = "/";
package/src/index.ts CHANGED
@@ -19,9 +19,9 @@ import "@netless/telebox-insider/dist/style.css";
19
19
  import {
20
20
  addEmitterOnceListener,
21
21
  ensureValidScenePath,
22
+ entireScenes,
22
23
  getVersionNumber,
23
24
  isValidScenePath,
24
- parseSceneDir,
25
25
  wait,
26
26
  } from "./Utils/Common";
27
27
  import type { TELE_BOX_STATE, BoxManager } from "./BoxManager";
@@ -54,6 +54,7 @@ import type {
54
54
  Rectangle,
55
55
  ViewVisionMode,
56
56
  CameraState,
57
+ Player,
57
58
  } from "white-web-sdk";
58
59
  import type { AppListeners } from "./AppListener";
59
60
  import type { NetlessApp, RegisterParams } from "./typings";
@@ -157,10 +158,11 @@ export type PublicEvent = {
157
158
  mainViewScenePathChange: string;
158
159
  mainViewSceneIndexChange: number;
159
160
  focusedChange: string | undefined;
161
+ mainViewScenesLengthChange: number;
160
162
  };
161
163
 
162
164
  export type MountParams = {
163
- room: Room;
165
+ room: Room | Player;
164
166
  container?: HTMLElement;
165
167
  /** 白板高宽比例, 默认为 9 / 16 */
166
168
  containerSizeRatio?: number;
@@ -223,6 +225,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
223
225
  WindowManager.params = params;
224
226
 
225
227
  this.checkVersion();
228
+ let manager: WindowManager | undefined = undefined;
226
229
  if (isRoom(room)) {
227
230
  if (room.phase !== RoomPhase.Connected) {
228
231
  throw new Error("[WindowManager]: Room only Connected can be mount");
@@ -231,11 +234,12 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
231
234
  // redo undo 需要设置这个属性
232
235
  room.disableSerialization = false;
233
236
  }
237
+ manager = await this.initManager(room);
234
238
  }
235
239
  if (WindowManager.isCreated) {
236
240
  throw new Error("[WindowManager]: Already created cannot be created again");
237
241
  }
238
- let manager = await this.initManager(room);
242
+
239
243
  this.debug = Boolean(debug);
240
244
  log("Already insert room", manager);
241
245
 
@@ -246,7 +250,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
246
250
  } else {
247
251
  await pRetry(
248
252
  async count => {
249
- manager = await this.initManager(room);
253
+ manager = (await room.getInvisiblePlugin(WindowManager.kind)) as WindowManager;
250
254
  if (!manager) {
251
255
  log(`manager is empty. retrying ${count}`);
252
256
  throw new Error();
@@ -256,6 +260,10 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
256
260
  );
257
261
  }
258
262
 
263
+ if (!manager) {
264
+ throw new Error("[WindowManager]: create manager failed");
265
+ }
266
+
259
267
  if (containerSizeRatio) {
260
268
  WindowManager.containerSizeRatio = containerSizeRatio;
261
269
  }
@@ -447,11 +455,11 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
447
455
  if (scenePath && scenes && scenes.length > 0) {
448
456
  if (this.isDynamicPPT(scenes)) {
449
457
  isDynamicPPT = true;
450
- if (!this.displayer.entireScenes()[scenePath]) {
458
+ if (!entireScenes(this.displayer)[scenePath]) {
451
459
  this.room?.putScenes(scenePath, scenes);
452
460
  }
453
461
  } else {
454
- if (!this.displayer.entireScenes()[scenePath]) {
462
+ if (!entireScenes(this.displayer)[scenePath]) {
455
463
  this.room?.putScenes(scenePath, [{ name: scenes[0].name }]);
456
464
  }
457
465
  }
@@ -618,11 +626,10 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
618
626
  }
619
627
 
620
628
  public get mainViewSceneDir(): string {
621
- const scenePath = this.appManager?.store.getMainViewScenePath();
622
- if (scenePath) {
623
- return parseSceneDir(scenePath);
629
+ if (this.appManager) {
630
+ return this.appManager?.getMainViewSceneDir();
624
631
  } else {
625
- throw new Error("[WindowManager]: mainViewSceneDir not found");
632
+ throw new AppManagerNotInitError();
626
633
  }
627
634
  }
628
635
 
@@ -630,6 +637,10 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
630
637
  return this.boxManager?.getTopBox()?.id;
631
638
  }
632
639
 
640
+ public get mainViewScenesLength(): number {
641
+ return this.appManager?.mainViewScenesLength || 0;
642
+ }
643
+
633
644
  /**
634
645
  * 查询所有的 App
635
646
  */
@@ -770,10 +781,6 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
770
781
  }
771
782
  }
772
783
  }
773
-
774
- private _removeScenes = (scenePath: string) => {
775
- this.room.removeScenes(scenePath);
776
- };
777
784
  }
778
785
 
779
786
  setupBuiltin();