@netless/window-manager 0.4.9-canary.2 → 0.4.11-canary.0

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.
@@ -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-canary.2",
3
+ "version": "0.4.11-canary.0",
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(ROOT_DIR);
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(ROOT_DIR);
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) {
@@ -236,14 +256,7 @@ export class AppManager {
236
256
  this.refresher?.add("minimized", () => {
237
257
  return autorun(() => {
238
258
  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
- }
259
+ this.onMinimized(minimized);
247
260
  });
248
261
  });
249
262
  this.refresher?.add("mainViewIndex", () => {
@@ -278,6 +291,12 @@ export class AppManager {
278
291
  }
279
292
  });
280
293
  });
294
+ this.refresher?.add("registeredChange", () => {
295
+ return autorun(() => {
296
+ const registered = get(this.attributes, Fields.Registered);
297
+ this.onRegisteredChange(registered);
298
+ });
299
+ });
281
300
  if (!this.attributes.apps || Object.keys(this.attributes.apps).length === 0) {
282
301
  const mainScenePath = this.store.getMainViewScenePath();
283
302
  if (!mainScenePath) return;
@@ -348,6 +367,30 @@ export class AppManager {
348
367
  }
349
368
  }
350
369
 
370
+ private onRegisteredChange = (registered: Record<string, SyncRegisterAppPayload>) => {
371
+ if (!registered) return;
372
+ Object.entries(registered).forEach(([kind, payload]) => {
373
+ if (!appRegister.appClasses.has(kind)) {
374
+ appRegister.register({
375
+ kind,
376
+ src: payload.src,
377
+ name: payload.name,
378
+ });
379
+ }
380
+ });
381
+ };
382
+
383
+ private onMinimized = (minimized: boolean | undefined) => {
384
+ if (this.boxManager?.minimized !== minimized) {
385
+ if (minimized === true) {
386
+ this.boxManager?.blurAllBox();
387
+ }
388
+ setTimeout(() => {
389
+ this.boxManager?.setMinimized(Boolean(minimized));
390
+ }, 0);
391
+ }
392
+ }
393
+
351
394
  public refresh() {
352
395
  this.attributesUpdateCallback(this.attributes.apps);
353
396
  }
package/src/AppProxy.ts CHANGED
@@ -92,7 +92,7 @@ export class AppProxy {
92
92
 
93
93
  public getFullScenePath(): string | undefined {
94
94
  if (this.scenePath) {
95
- return get(this.appAttributes, [Fields.FullPath], this.getFullScenePathFromScenes());
95
+ return get(this.appAttributes, [Fields.FullPath]) || this.getFullScenePathFromScenes();
96
96
  }
97
97
  }
98
98
 
@@ -347,6 +347,7 @@ export class AppProxy {
347
347
  if (fullPath && this.view) {
348
348
  setViewFocusScenePath(this.view, fullPath);
349
349
  }
350
+ return fullPath;
350
351
  }
351
352
 
352
353
  private async createView(): Promise<View> {
@@ -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,7 @@ export type EmitterEvent = {
21
21
  updateManagerRect: undefined;
22
22
  focusedChange: { focused: string | undefined; prev: string | undefined };
23
23
  rootDirRemoved: undefined;
24
+ setReadonly: boolean;
24
25
  };
25
26
 
26
27
  export type EmitterType = Emittery<EmitterEvent>;
@@ -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,
package/src/index.ts CHANGED
@@ -368,6 +368,13 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
368
368
  return appRegister.register(params);
369
369
  }
370
370
 
371
+ /**
372
+ * 注销插件
373
+ */
374
+ public static unregister(kind: string) {
375
+ return appRegister.unregister(kind);
376
+ }
377
+
371
378
  /**
372
379
  * 创建一个 app 至白板
373
380
  */
@@ -517,6 +524,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
517
524
  public setReadonly(readonly: boolean): void {
518
525
  this.readonly = readonly;
519
526
  this.boxManager?.setReadonly(readonly);
527
+ emitter.emit("setReadonly", readonly);
520
528
  }
521
529
 
522
530
  /**
@@ -855,6 +863,9 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
855
863
  if (!this.attributes["_mainSceneIndex"]) {
856
864
  this.safeSetAttributes({ _mainSceneIndex: 0 });
857
865
  }
866
+ if (!this.attributes[Fields.Registered]) {
867
+ this.safeSetAttributes({ [Fields.Registered]: {} });
868
+ }
858
869
  }
859
870
  }
860
871
  }
@@ -864,3 +875,4 @@ setupBuiltin();
864
875
  export * from "./typings";
865
876
 
866
877
  export { BuiltinApps } from "./BuiltinApps";
878
+ export type { PublicEvent } from "./callback";