@netless/window-manager 0.4.9 → 0.4.11-canary.1

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/advanced.md CHANGED
@@ -4,6 +4,7 @@
4
4
  - [撤销重做](#redo-undo)
5
5
  - [清屏](#clean-current-scene)
6
6
  - [判断是否打开某种 APP](#has-kind)
7
+ - [页面控制器](#page-control)
7
8
 
8
9
 
9
10
  <h3 id="redo-undo">撤销重做</h3>
@@ -64,3 +65,26 @@ manager.emitter.on("ready", () => { // ready 事件在所有 app 创建完成后
64
65
  const hasSlide = apps.some(app => app.kind === "Slide"); // 判断已经打开的 APP 中是否有 Slide
65
66
  });
66
67
  ```
68
+
69
+ <br>
70
+
71
+ <h3 id="page-control">页面控制器</h3>
72
+
73
+ `manager` 提供了一个 `pageState` 来获取当前的 index 和总页数
74
+
75
+ ```ts
76
+ manager.pageState.index // 当前的 index
77
+ manager.pageState.length // 总页数
78
+
79
+ manager.emitter.on("pageStateChange", state => {
80
+ // 当前 index 变化和总页数变化会触发此事件
81
+ });
82
+ ```
83
+
84
+ 上一页/下一页/添加一页
85
+
86
+ ```ts
87
+ manager.nextPage()
88
+ manager.prevPage()
89
+ manager.addPage()
90
+ ```
package/docs/api.md CHANGED
@@ -229,6 +229,7 @@ manager.addPage({ scene: { name: "page2" } }) // 传入 page 信息
229
229
  | canRedoSteps | number | | 当前 focus 的 view 可以重做的步数 |
230
230
  | canRedoSteps | number | | 当前 focus 的 view 可以撤销的步数 |
231
231
  | sceneState | SceneState | | 兼容原本 SDK 的 sceneState 属性, 只对 mainView 生效 |
232
+ | pageState | PageState | | 组合 mainView 的 index 和 scenes 的修改 |
232
233
 
233
234
  <br>
234
235
 
@@ -253,6 +254,7 @@ manager.callbacks.on(events, listener)
253
254
  | loadApp | LoadAppEvent | | 加载远程APP 事件 |
254
255
  | ready | undefined | | 当所有 APP 创建完毕时触发 |
255
256
  | sceneStateChange | SceneState | | 当 sceneState 修改时触发 |
257
+ | pageStateChange | PageState | | |
256
258
 
257
259
  ```ts
258
260
  type LoadAppEvent = {
@@ -260,4 +262,11 @@ type LoadAppEvent = {
260
262
  status: "start" | "success" | "failed";
261
263
  reason?: string;
262
264
  }
265
+ ```
266
+
267
+ ```ts
268
+ type PageState = {
269
+ index: number;
270
+ length: number;
271
+ }
263
272
  ```
@@ -11,28 +11,6 @@ const HelloWorld: NetlessApp = {
11
11
  kind: "HelloWorld",
12
12
  setup: (context: AppContext) => {
13
13
  context.mountView(context.getBox().$content); // 可选: 挂载 View 到 box 上
14
-
15
- const storage = context.createStorage<{ a: number }>("HelloWorld", { a: 1 });
16
- console.log(storage.state === { a: 1 });
17
-
18
- storage.addStateChangedListener(diff => {
19
- if (diff.a) {
20
- console.log(diff.a.oldValue === 1);
21
- console.log(diff.a.newValue === 2);
22
- }
23
- });
24
-
25
- if (context.getIsWritable()) {
26
- // 只有在可写状态才可以调用 setState
27
- storage.setState({ a: 2 });
28
- }
29
-
30
- // magixEvent 事件是房间内范围的, 建议 app 内使用需要添加自己的 prefix
31
- context.addMagixEventListener(`${context.appId}_event1`, message => {
32
- console.log("MagixEvent", message);
33
- });
34
-
35
- context.dispatchMagixEvent(`${context.appId}_event1`, { count: 1 });
36
14
  },
37
15
  };
38
16
 
@@ -48,3 +26,63 @@ manager.addApp({
48
26
  },
49
27
  });
50
28
  ```
29
+
30
+ ## Counter
31
+
32
+ ```ts
33
+ const Counter: NetlessApp<{ count: number }> = {
34
+ kind: "Counter",
35
+ setup: (context) => {
36
+ const storage = context.storage;
37
+ storage.ensureState({ count: 0 });
38
+
39
+ const box = context.getBox(); // box 为这个应用打开的窗口
40
+ const $content = box.$content // 获取窗口的 content
41
+
42
+ const countDom = document.createElement("div");
43
+ countDom.innerText = storage.state.count.toString();
44
+ $content.appendChild(countDom);
45
+
46
+ // state 变化回调
47
+ storage.addStateChangedListener(diff => {
48
+ if (diff.count) {
49
+ // diff 会给出 newValue 和 oldValue
50
+ console.log(diff.count.newValue);
51
+ console.log(diff.count.oldValue);
52
+ countDom.innerText = diff.count.newValue.toString();
53
+ }
54
+ });
55
+
56
+ const incButton = document.createElement("button");
57
+ incButton.innerText = "Inc";
58
+ const incButtonOnClick = () => {
59
+ storage.setState({ count: storage.state.count + 1 });
60
+ }
61
+ incButton.addEventListener("click", incButtonOnClick);
62
+ $content.appendChild(incButton);
63
+
64
+ const decButton = document.createElement("button");
65
+ decButton.innerText = "Dec";
66
+ const decButtonOnClick = () => {
67
+ storage.setState({ count: storage.state.count - 1 });
68
+ }
69
+ decButton.addEventListener("click", decButtonOnClick);
70
+ $content.appendChild(decButton);
71
+
72
+ // 监听事件
73
+ const event1Disposer = context.addMagixEventListener("event1", msg => {
74
+ console.log("event1", msg);
75
+ });
76
+
77
+ // 向打开 app 的其他人发送消息
78
+ context.dispatchMagixEvent("event1", { count: 10 });
79
+
80
+ // 应用销毁时, 注意清理掉监听器
81
+ context.emitter.on("destroy", () => {
82
+ incButton.removeEventListener("click", incButtonOnClick);
83
+ decButton.removeEventListener("click", decButtonOnClick);
84
+ event1Disposer();
85
+ });
86
+ }
87
+ }
88
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/window-manager",
3
- "version": "0.4.9",
3
+ "version": "0.4.11-canary.1",
4
4
  "description": "",
5
5
  "main": "dist/index.es.js",
6
6
  "module": "dist/index.es.js",
@@ -31,7 +31,7 @@
31
31
  "video.js": ">=7"
32
32
  },
33
33
  "devDependencies": {
34
- "@netless/app-docs-viewer": "^0.2.6",
34
+ "@netless/app-docs-viewer": "^0.2.8",
35
35
  "@netless/app-media-player": "0.1.0-beta.5",
36
36
  "@rollup/plugin-commonjs": "^20.0.0",
37
37
  "@rollup/plugin-node-resolve": "^13.0.4",
package/src/AppContext.ts CHANGED
@@ -165,7 +165,8 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
165
165
  /** Dispatch events to other clients (and self). */
166
166
  public dispatchMagixEvent: MagixEventDispatcher<TMagixEventPayloads> = (...args) => {
167
167
  // can't dispatch events on replay mode
168
- return this.manager.room?.dispatchMagixEvent(...args);
168
+ const appScopeEvent = `${this.appId}:${args[0]}`;
169
+ return this.manager.room?.dispatchMagixEvent(appScopeEvent, args[1]);
169
170
  };
170
171
 
171
172
  /** Listen to events from others clients (and self messages). */
@@ -174,9 +175,17 @@ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOption
174
175
  handler,
175
176
  options
176
177
  ) => {
177
- this.manager.displayer.addMagixEventListener(event, handler as WhiteEventListener, options);
178
+ const appScopeEvent = `${this.appId}:${event}`;
179
+ this.manager.displayer.addMagixEventListener(
180
+ appScopeEvent,
181
+ handler as WhiteEventListener,
182
+ options
183
+ );
178
184
  return () =>
179
- this.manager.displayer.removeMagixEventListener(event, handler as WhiteEventListener);
185
+ this.manager.displayer.removeMagixEventListener(
186
+ appScopeEvent,
187
+ handler as WhiteEventListener
188
+ );
180
189
  };
181
190
 
182
191
  /** Remove a Magix event listener. */
package/src/AppManager.ts CHANGED
@@ -6,6 +6,7 @@ import { appRegister } from "./Register";
6
6
  import { autorun, isPlayer, isRoom, ScenePathType } from "white-web-sdk";
7
7
  import { callbacks } from "./callback";
8
8
  import { emitter } from "./InternalEmitter";
9
+ import { Fields, store } from "./AttributesDelegate";
9
10
  import { get, isInteger, orderBy } from "lodash";
10
11
  import { log } from "./Utils/log";
11
12
  import { MainViewProxy } from "./View/MainView";
@@ -13,8 +14,8 @@ import { onObjectRemoved, safeListenPropsUpdated } from "./Utils/Reactive";
13
14
  import { reconnectRefresher, WindowManager } from "./index";
14
15
  import { RedoUndo } from "./RedoUndo";
15
16
  import { SideEffectManager } from "side-effect-manager";
16
- import { store } from "./AttributesDelegate";
17
17
  import { ViewManager } from "./View/ViewManager";
18
+ import type { SyncRegisterAppPayload } from "./Register";
18
19
  import type { EmitterEvent } from "./InternalEmitter";
19
20
  import {
20
21
  entireScenes,
@@ -34,6 +35,7 @@ import type {
34
35
  SceneState,
35
36
  } from "white-web-sdk";
36
37
  import type { AddAppParams, BaseInsertParams, TeleBoxRect } from "./index";
38
+
37
39
  export class AppManager {
38
40
  public displayer: Displayer;
39
41
  public viewManager: ViewManager;
@@ -88,33 +90,37 @@ export class AppManager {
88
90
 
89
91
  emitter.once("onCreated").then(() => this.onCreated());
90
92
  emitter.on("onReconnected", () => this.onReconnected());
93
+
91
94
  if (isPlayer(this.displayer)) {
92
- emitter.on("seek", time => {
93
- this.appProxies.forEach(appProxy => {
94
- appProxy.onSeek(time);
95
- });
96
- this.attributesUpdateCallback(this.attributes.apps);
97
- this.onAppDelete(this.attributes.apps);
98
- });
95
+ emitter.on("seek", this.onPlayerSeek);
99
96
  }
100
- emitter.on("removeScenes", scenePath => {
101
- if (scenePath === ROOT_DIR) {
102
- this.setMainViewScenePath(ROOT_DIR);
103
- this.createRootDirScenesCallback();
104
- this.onRootDirRemoved();
105
- emitter.emit("rootDirRemoved");
106
- return;
107
- }
108
- const mainViewScenePath = this.store.getMainViewScenePath();
109
- if (this.room && mainViewScenePath) {
110
- if (mainViewScenePath === scenePath) {
111
- this.setMainViewScenePath(ROOT_DIR);
112
- }
113
- }
114
- });
97
+
98
+ emitter.on("removeScenes", this.onRemoveScenes);
99
+ emitter.on("setReadonly", this.onReadonlyChanged);
100
+
115
101
  this.createRootDirScenesCallback();
102
+
103
+ appRegister.setSyncRegisterApp(payload => {
104
+ this.safeUpdateAttributes([Fields.Registered, payload.kind], payload);
105
+ });
116
106
  }
117
107
 
108
+ private onRemoveScenes = (scenePath: string) => {
109
+ if (scenePath === ROOT_DIR) {
110
+ this.setMainViewScenePath("");
111
+ this.createRootDirScenesCallback();
112
+ this.onRootDirRemoved();
113
+ emitter.emit("rootDirRemoved");
114
+ return;
115
+ }
116
+ const mainViewScenePath = this.store.getMainViewScenePath();
117
+ if (this.room && mainViewScenePath) {
118
+ if (mainViewScenePath === scenePath) {
119
+ this.setMainViewScenePath("");
120
+ }
121
+ }
122
+ };
123
+
118
124
  /**
119
125
  * 根目录被删除时所有的 scene 都会被删除.
120
126
  * 所以需要关掉所有开启了 view 的 app
@@ -129,6 +135,20 @@ export class AppManager {
129
135
  this.mainViewProxy.rebind();
130
136
  }
131
137
 
138
+ private onReadonlyChanged = () => {
139
+ this.appProxies.forEach(appProxy => {
140
+ appProxy.emitAppIsWritableChange();
141
+ });
142
+ };
143
+
144
+ private onPlayerSeek = (time: number) => {
145
+ this.appProxies.forEach(appProxy => {
146
+ appProxy.onSeek(time);
147
+ });
148
+ this.attributesUpdateCallback(this.attributes.apps);
149
+ this.onAppDelete(this.attributes.apps);
150
+ };
151
+
132
152
  private createRootDirScenesCallback = () => {
133
153
  let isRecreate = false;
134
154
  if (this.callbacksNode) {
@@ -143,7 +163,7 @@ export class AppManager {
143
163
  this.updateSceneState(this.callbacksNode);
144
164
  this.mainViewScenesLength = this.callbacksNode.scenes.length;
145
165
  if (isRecreate) {
146
- callbacks.emit("mainViewScenesLengthChange", this.callbacksNode.scenes.length);
166
+ this.emitMainViewScenesChange(this.callbacksNode.scenes.length);
147
167
  }
148
168
  }
149
169
  };
@@ -151,7 +171,12 @@ export class AppManager {
151
171
  private onSceneChange = (node: ScenesCallbacksNode) => {
152
172
  this.mainViewScenesLength = node.scenes.length;
153
173
  this.updateSceneState(node);
154
- callbacks.emit("mainViewScenesLengthChange", this.mainViewScenesLength);
174
+ this.emitMainViewScenesChange(this.mainViewScenesLength);
175
+ };
176
+
177
+ private emitMainViewScenesChange = (length: number) => {
178
+ callbacks.emit("mainViewScenesLengthChange", length);
179
+ emitter.emit("changePageState");
155
180
  };
156
181
 
157
182
  private updateSceneState = (node: ScenesCallbacksNode) => {
@@ -236,14 +261,7 @@ export class AppManager {
236
261
  this.refresher?.add("minimized", () => {
237
262
  return autorun(() => {
238
263
  const minimized = this.attributes.minimized;
239
- if (this.boxManager?.minimized !== minimized) {
240
- if (minimized === true) {
241
- this.boxManager?.blurAllBox();
242
- }
243
- setTimeout(() => {
244
- this.boxManager?.setMinimized(Boolean(minimized));
245
- }, 0);
246
- }
264
+ this.onMinimized(minimized);
247
265
  });
248
266
  });
249
267
  this.refresher?.add("mainViewIndex", () => {
@@ -251,6 +269,7 @@ export class AppManager {
251
269
  const mainSceneIndex = get(this.attributes, "_mainSceneIndex");
252
270
  if (mainSceneIndex !== undefined && this._prevSceneIndex !== mainSceneIndex) {
253
271
  callbacks.emit("mainViewSceneIndexChange", mainSceneIndex);
272
+ emitter.emit("changePageState");
254
273
  if (this.callbacksNode) {
255
274
  this.updateSceneState(this.callbacksNode);
256
275
  }
@@ -278,13 +297,16 @@ export class AppManager {
278
297
  }
279
298
  });
280
299
  });
300
+ this.refresher?.add("registeredChange", () => {
301
+ return autorun(() => {
302
+ const registered = get(this.attributes, Fields.Registered);
303
+ this.onRegisteredChange(registered);
304
+ });
305
+ });
281
306
  if (!this.attributes.apps || Object.keys(this.attributes.apps).length === 0) {
282
307
  const mainScenePath = this.store.getMainViewScenePath();
283
308
  if (!mainScenePath) return;
284
- const sceneState = this.displayer.state.sceneState;
285
- if (sceneState.scenePath !== mainScenePath) {
286
- setScenePath(this.room, mainScenePath);
287
- }
309
+ this.resetScenePath(mainScenePath);
288
310
  }
289
311
  this.displayerWritableListener(!this.room?.isWritable);
290
312
  this.displayer.callbacks.on("onEnableWriteNowChanged", this.displayerWritableListener);
@@ -348,6 +370,30 @@ export class AppManager {
348
370
  }
349
371
  }
350
372
 
373
+ private onRegisteredChange = (registered: Record<string, SyncRegisterAppPayload>) => {
374
+ if (!registered) return;
375
+ Object.entries(registered).forEach(([kind, payload]) => {
376
+ if (!appRegister.appClasses.has(kind)) {
377
+ appRegister.register({
378
+ kind,
379
+ src: payload.src,
380
+ name: payload.name,
381
+ });
382
+ }
383
+ });
384
+ };
385
+
386
+ private onMinimized = (minimized: boolean | undefined) => {
387
+ if (this.boxManager?.minimized !== minimized) {
388
+ if (minimized === true) {
389
+ this.boxManager?.blurAllBox();
390
+ }
391
+ setTimeout(() => {
392
+ this.boxManager?.setMinimized(Boolean(minimized));
393
+ }, 0);
394
+ }
395
+ };
396
+
351
397
  public refresh() {
352
398
  this.attributesUpdateCallback(this.attributes.apps);
353
399
  }
@@ -391,6 +437,13 @@ export class AppManager {
391
437
  }
392
438
  }
393
439
 
440
+ private resetScenePath(scenePath: string) {
441
+ const sceneState = this.displayer.state.sceneState;
442
+ if (sceneState.scenePath !== scenePath) {
443
+ setScenePath(this.room, scenePath);
444
+ }
445
+ }
446
+
394
447
  public async addApp(params: AddAppParams, isDynamicPPT: boolean): Promise<string | undefined> {
395
448
  log("addApp", params);
396
449
  const { appId, needFocus } = await this.beforeAddApp(params, isDynamicPPT);
@@ -17,6 +17,7 @@ export enum Fields {
17
17
  Position = "position",
18
18
  CursorState = "cursorState",
19
19
  FullPath = "fullPath",
20
+ Registered = "registered",
20
21
  }
21
22
 
22
23
  export type Apps = {
@@ -21,6 +21,8 @@ export type EmitterEvent = {
21
21
  updateManagerRect: undefined;
22
22
  focusedChange: { focused: string | undefined; prev: string | undefined };
23
23
  rootDirRemoved: undefined;
24
+ setReadonly: boolean;
25
+ changePageState: undefined;
24
26
  };
25
27
 
26
28
  export type EmitterType = Emittery<EmitterEvent>;
@@ -0,0 +1,31 @@
1
+ import type { AppManager } from "./AppManager";
2
+ import { callbacks } from "./callback";
3
+ import { emitter } from "./InternalEmitter";
4
+
5
+ export type PageState = {
6
+ index: number;
7
+ length: number;
8
+ }
9
+
10
+ export class PageStateImpl {
11
+ constructor(private manager: AppManager) {
12
+ emitter.on("changePageState", () => {
13
+ callbacks.emit("pageStateChange", this.toObject())
14
+ });
15
+ };
16
+
17
+ public get index(): number {
18
+ return this.manager?.store.getMainViewSceneIndex() || 0;
19
+ }
20
+
21
+ public get length(): number {
22
+ return this.manager?.mainViewScenesLength || 0;
23
+ }
24
+
25
+ public toObject(): PageState {
26
+ return {
27
+ index: this.index,
28
+ length: this.length
29
+ }
30
+ }
31
+ }
@@ -8,12 +8,25 @@ export type LoadAppEvent = {
8
8
  reason?: string;
9
9
  };
10
10
 
11
+ export type SyncRegisterAppPayload = { kind: string, src: string, name: string | undefined };
12
+ export type SyncRegisterApp = (payload: SyncRegisterAppPayload) => void;
13
+
11
14
  class AppRegister {
12
15
  public kindEmitters: Map<string, Emittery<RegisterEvents>> = new Map();
13
16
  public registered: Map<string, RegisterParams> = new Map();
14
17
  public appClassesCache: Map<string, Promise<NetlessApp>> = new Map();
15
18
  public appClasses: Map<string, () => Promise<NetlessApp>> = new Map();
16
19
 
20
+ private syncRegisterApp: SyncRegisterApp | null = null;
21
+
22
+ public setSyncRegisterApp(fn: SyncRegisterApp) {
23
+ this.syncRegisterApp = fn;
24
+ }
25
+
26
+ public onSyncRegisterAppChange = (payload: SyncRegisterAppPayload) => {
27
+ this.register({ kind: payload.kind, src: payload.src });
28
+ }
29
+
17
30
  public async register(params: RegisterParams): Promise<void> {
18
31
  this.appClassesCache.delete(params.kind);
19
32
  this.registered.set(params.kind, params);
@@ -23,7 +36,7 @@ class AppRegister {
23
36
 
24
37
  if (typeof srcOrAppOrFunction === "string") {
25
38
  downloadApp = async () => {
26
- let appClass = (await loadApp(srcOrAppOrFunction, params.kind)) as any;
39
+ let appClass = (await loadApp(srcOrAppOrFunction, params.kind, params.name)) as any;
27
40
  if (appClass) {
28
41
  if (appClass.__esModule) {
29
42
  appClass = appClass.default;
@@ -35,6 +48,9 @@ class AppRegister {
35
48
  );
36
49
  }
37
50
  };
51
+ if (this.syncRegisterApp) {
52
+ this.syncRegisterApp({ kind: params.kind, src: srcOrAppOrFunction, name: params.name });
53
+ }
38
54
  } else if (typeof srcOrAppOrFunction === "function") {
39
55
  downloadApp = srcOrAppOrFunction;
40
56
  } else {
@@ -58,6 +74,17 @@ class AppRegister {
58
74
  }
59
75
  }
60
76
 
77
+ public unregister(kind: string) {
78
+ this.appClasses.delete(kind);
79
+ this.appClassesCache.delete(kind);
80
+ this.registered.delete(kind);
81
+ const kindEmitter = this.kindEmitters.get(kind);
82
+ if (kindEmitter) {
83
+ kindEmitter.clearListeners();
84
+ this.kindEmitters.delete(kind);
85
+ }
86
+ }
87
+
61
88
  public async notifyApp<T extends keyof RegisterEvents>(
62
89
  kind: string,
63
90
  event: T,
@@ -33,7 +33,8 @@ export const setViewSceneIndex = (view: View, index: number) => {
33
33
  export const setScenePath = (room: Room | undefined, scenePath: string) => {
34
34
  if (room && room.isWritable) {
35
35
  if (room.state.sceneState.scenePath !== scenePath) {
36
- room.setScenePath(scenePath);
36
+ const nextScenePath = scenePath === "/" ? "" : scenePath;
37
+ room.setScenePath(nextScenePath);
37
38
  }
38
39
  }
39
40
  };
package/src/callback.ts CHANGED
@@ -2,6 +2,7 @@ import Emittery from "emittery";
2
2
  import type { TeleBoxColorScheme, TELE_BOX_STATE } from "@netless/telebox-insider";
3
3
  import type { CameraState, SceneState, ViewVisionMode } from "white-web-sdk";
4
4
  import type { LoadAppEvent } from "./Register";
5
+ import type { PageState } from "./PageState";
5
6
 
6
7
  export type PublicEvent = {
7
8
  mainViewModeChange: ViewVisionMode;
@@ -18,6 +19,7 @@ export type PublicEvent = {
18
19
  loadApp: LoadAppEvent;
19
20
  ready: undefined; // 所有 APP 创建完毕时触发
20
21
  sceneStateChange: SceneState;
22
+ pageStateChange: PageState;
21
23
  };
22
24
 
23
25
  export type CallbacksType = Emittery<PublicEvent>;
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ import { initDb } from "./Register/storage";
13
13
  import { InvisiblePlugin, isPlayer, isRoom, RoomPhase, ViewMode } from "white-web-sdk";
14
14
  import { isEqual, isNull, isObject, omit } from "lodash";
15
15
  import { log } from "./Utils/log";
16
+ import { PageStateImpl } from "./PageState";
16
17
  import { ReconnectRefresher } from "./ReconnectRefresher";
17
18
  import { replaceRoomFunction } from "./Utils/RoomHacker";
18
19
  import { setupBuiltin } from "./BuiltinApps";
@@ -56,6 +57,7 @@ import type { TeleBoxColorScheme, TeleBoxState } from "@netless/telebox-insider"
56
57
  import type { AppProxy } from "./AppProxy";
57
58
  import type { PublicEvent } from "./Callback";
58
59
  import type Emittery from "emittery";
60
+ import type { PageState } from "./PageState";
59
61
 
60
62
  export type WindowMangerAttributes = {
61
63
  modelValue?: string;
@@ -168,6 +170,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
168
170
  public cursorManager?: CursorManager;
169
171
  public viewMode = ViewMode.Broadcaster;
170
172
  public isReplay = isPlayer(this.displayer);
173
+ private _pageState?: PageStateImpl;
171
174
 
172
175
  private boxManager?: BoxManager;
173
176
  private static params?: MountParams;
@@ -235,6 +238,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
235
238
  await manager.ensureAttributes();
236
239
 
237
240
  manager.appManager = new AppManager(manager);
241
+ manager._pageState = new PageStateImpl(manager.appManager);
238
242
  manager.cursorManager = new CursorManager(manager.appManager, Boolean(cursor));
239
243
 
240
244
  if (params.container) {
@@ -368,6 +372,13 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
368
372
  return appRegister.register(params);
369
373
  }
370
374
 
375
+ /**
376
+ * 注销插件
377
+ */
378
+ public static unregister(kind: string) {
379
+ return appRegister.unregister(kind);
380
+ }
381
+
371
382
  /**
372
383
  * 创建一个 app 至白板
373
384
  */
@@ -517,6 +528,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
517
528
  public setReadonly(readonly: boolean): void {
518
529
  this.readonly = readonly;
519
530
  this.boxManager?.setReadonly(readonly);
531
+ emitter.emit("setReadonly", readonly);
520
532
  }
521
533
 
522
534
  /**
@@ -670,6 +682,14 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
670
682
  }
671
683
  }
672
684
 
685
+ public get pageState(): PageState {
686
+ if (this._pageState) {
687
+ return this._pageState.toObject();
688
+ } else {
689
+ throw new AppManagerNotInitError();
690
+ }
691
+ }
692
+
673
693
  /**
674
694
  * 查询所有的 App
675
695
  */
@@ -855,6 +875,9 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
855
875
  if (!this.attributes["_mainSceneIndex"]) {
856
876
  this.safeSetAttributes({ _mainSceneIndex: 0 });
857
877
  }
878
+ if (!this.attributes[Fields.Registered]) {
879
+ this.safeSetAttributes({ [Fields.Registered]: {} });
880
+ }
858
881
  }
859
882
  }
860
883
  }
@@ -864,3 +887,4 @@ setupBuiltin();
864
887
  export * from "./typings";
865
888
 
866
889
  export { BuiltinApps } from "./BuiltinApps";
890
+ export type { PublicEvent } from "./callback";