@netless/window-manager 0.4.0-canary.10 → 0.4.0-canary.14

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/dist/typings.d.ts CHANGED
@@ -2,7 +2,7 @@ import type Emittery from "emittery";
2
2
  import type { AnimationMode, Displayer, DisplayerState, Player, Room, SceneDefinition, SceneState, View } from "white-web-sdk";
3
3
  import type { AppContext } from "./AppContext";
4
4
  import type { ReadonlyTeleBox, TeleBoxRect } from "@netless/telebox-insider";
5
- export interface NetlessApp<Attributes = any, SetupResult = any, AppOptions = any> {
5
+ export interface NetlessApp<Attributes = any, MagixEventPayloads = any, AppOptions = any, SetupResult = any> {
6
6
  kind: string;
7
7
  config?: {
8
8
  /** Box width relative to whiteboard. 0~1. Default 0.5. */
@@ -16,7 +16,7 @@ export interface NetlessApp<Attributes = any, SetupResult = any, AppOptions = an
16
16
  /** App only single instance. */
17
17
  singleton?: boolean;
18
18
  };
19
- setup: (context: AppContext<Attributes, AppOptions>) => SetupResult;
19
+ setup: (context: AppContext<Attributes, MagixEventPayloads, AppOptions>) => SetupResult;
20
20
  }
21
21
  export declare type AppEmitterEvent<T = any> = {
22
22
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/window-manager",
3
- "version": "0.4.0-canary.10",
3
+ "version": "0.4.0-canary.14",
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",
@@ -0,0 +1,68 @@
1
+ import type {
2
+ MagixEventListenerOptions as WhiteMagixListenerOptions,
3
+ Event as WhiteEvent,
4
+ EventPhase as WhiteEventPhase,
5
+ Scope as WhiteScope,
6
+ } from "white-web-sdk";
7
+
8
+ export interface MagixEventListenerOptions extends WhiteMagixListenerOptions {
9
+ /**
10
+ * Rapid emitted callbacks will be slowed down to this interval (in ms).
11
+ */
12
+ fireInterval?: number;
13
+ /**
14
+ * If `true`, sent events will reach self-listeners after committed to server.
15
+ * Otherwise the events will reach self-listeners immediately.
16
+ */
17
+ fireSelfEventAfterCommit?: boolean;
18
+ }
19
+
20
+ export interface MagixEventMessage<
21
+ TPayloads = any,
22
+ TEvent extends MagixEventTypes<TPayloads> = MagixEventTypes<TPayloads>
23
+ > extends Omit<WhiteEvent, "scope" | "phase"> {
24
+ /** Event name */
25
+ event: TEvent;
26
+ /** Event Payload */
27
+ payload: TPayloads[TEvent];
28
+ /** Whiteboard ID of the client who dispatched the event. It will be AdminObserverId for system events. */
29
+ authorId: number;
30
+ scope: `${WhiteScope}`;
31
+ phase: `${WhiteEventPhase}`;
32
+ }
33
+
34
+ export type MagixEventTypes<TPayloads = any> = Extract<keyof TPayloads, string>;
35
+
36
+ export type MagixEventPayload<
37
+ TPayloads = any,
38
+ TEvent extends MagixEventTypes<TPayloads> = MagixEventTypes<TPayloads>
39
+ > = TPayloads[TEvent];
40
+
41
+ export type MagixEventDispatcher<TPayloads = any> = <
42
+ TEvent extends MagixEventTypes<TPayloads> = MagixEventTypes<TPayloads>
43
+ >(
44
+ event: TEvent,
45
+ payload: TPayloads[TEvent]
46
+ ) => void;
47
+
48
+ export type MagixEventHandler<
49
+ TPayloads = any,
50
+ TEvent extends MagixEventTypes<TPayloads> = MagixEventTypes<TPayloads>
51
+ > = (message: MagixEventMessage<TPayloads, TEvent>) => void;
52
+
53
+ export type MagixEventListenerDisposer = () => void
54
+
55
+ export type MagixEventAddListener<TPayloads = any> = <
56
+ TEvent extends MagixEventTypes<TPayloads> = MagixEventTypes<TPayloads>
57
+ >(
58
+ event: TEvent,
59
+ handler: MagixEventHandler<TPayloads, TEvent>,
60
+ options?: MagixEventListenerOptions | undefined
61
+ ) => MagixEventListenerDisposer;
62
+
63
+ export type MagixEventRemoveListener<TPayloads = any> = <
64
+ TEvent extends MagixEventTypes<TPayloads> = MagixEventTypes<TPayloads>
65
+ >(
66
+ event: TEvent,
67
+ handler?: MagixEventHandler<TPayloads, TEvent>
68
+ ) => void;
@@ -1,20 +1,20 @@
1
1
  import type { AkkoObjectUpdatedProperty } from "white-web-sdk";
2
- import { get, has, isObject } from "lodash";
2
+ import { get, has, mapValues, isObject, size, noop } from "lodash";
3
3
  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';
11
11
 
12
- const STORAGE_NS = "_WM-STORAGE_";
12
+ export const STORAGE_NS = "_WM-STORAGE_";
13
13
 
14
- export class Storage<TState = any> implements Storage<TState> {
15
- readonly id: string;
14
+ export class Storage<TState extends Record<string, any> = any> implements Storage<TState> {
15
+ readonly id: string | null;
16
16
 
17
- private readonly _context: AppContext<{ [STORAGE_NS]: TState }>;
17
+ private readonly _context: AppContext;
18
18
  private readonly _sideEffect = new SideEffectManager();
19
19
  private _state: TState;
20
20
  private _destroyed = false;
@@ -26,54 +26,52 @@ export class Storage<TState = any> implements Storage<TState> {
26
26
  */
27
27
  private _lastValue = new Map<string | number | symbol, TState[Extract<keyof TState, string>]>();
28
28
 
29
- constructor(context: AppContext<any>, id: string, defaultState?: TState) {
30
- if (id == null) {
31
- throw new Error("Cannot create Storage with empty id.");
32
- }
33
-
29
+ constructor(context: AppContext, id?: string, defaultState?: TState) {
34
30
  if (defaultState && !isObject(defaultState)) {
35
31
  throw new Error(`Default state for Storage ${id} is not an object.`);
36
32
  }
37
33
 
38
34
  this._context = context;
39
- this.id = id;
35
+ this.id = id || null;
40
36
 
41
- const attrs = context.getAttributes();
42
37
  this._state = {} as TState;
43
- const rawState = get<TState>(attrs, [STORAGE_NS, id], this._state);
38
+ const rawState = this._getRawState(this._state);
44
39
 
45
- if (this._context.getIsWritable()) {
46
- if (!isObject(rawState) || rawState === this._state) {
47
- if (!attrs[STORAGE_NS]) {
40
+ if (this.id !== null && this._context.getIsWritable()) {
41
+ if (rawState === this._state || !isObject(rawState)) {
42
+ if (!get(this._context.getAttributes(), [STORAGE_NS])) {
48
43
  this._context.updateAttributes([STORAGE_NS], {});
49
44
  }
50
45
  this._context.updateAttributes([STORAGE_NS, this.id], this._state);
51
- if (defaultState) {
52
- this.setState(defaultState);
53
- }
54
- } else {
55
- // strip mobx
56
- plainObjectKeys(rawState).forEach(key => {
57
- try {
58
- const rawValue = isObject(rawState[key]) ? JSON.parse(JSON.stringify(rawState[key])) : rawState[key];
59
- if (isRef<TState[Extract<keyof TState, string>]>(rawValue)) {
60
- this._state[key] = rawValue.v;
61
- if (isObject(rawValue.v)) {
62
- this._refMap.set(rawValue.v, rawValue);
63
- }
64
- } else {
65
- this._state[key] = rawValue;
66
- }
67
- } catch (e) {
68
- console.error(e);
69
- }
70
- });
46
+ }
47
+ if (defaultState) {
48
+ this.setState(defaultState);
71
49
  }
72
50
  }
73
51
 
52
+ // strip mobx
53
+ plainObjectKeys(rawState).forEach(key => {
54
+ if (this.id === null && key === STORAGE_NS) {
55
+ return;
56
+ }
57
+ try {
58
+ const rawValue = isObject(rawState[key]) ? JSON.parse(JSON.stringify(rawState[key])) : rawState[key];
59
+ if (isRef<TState[Extract<keyof TState, string>]>(rawValue)) {
60
+ this._state[key] = rawValue.v;
61
+ if (isObject(rawValue.v)) {
62
+ this._refMap.set(rawValue.v, rawValue);
63
+ }
64
+ } else {
65
+ this._state[key] = rawValue;
66
+ }
67
+ } catch (e) {
68
+ console.error(e);
69
+ }
70
+ });
71
+
74
72
  this._sideEffect.addDisposer(
75
73
  safeListenPropsUpdated(
76
- () => get(context.getAttributes(), [STORAGE_NS, this.id]),
74
+ () => this.id === null ? context.getAttributes() : get(context.getAttributes(), [STORAGE_NS, this.id]),
77
75
  this._updateProperties.bind(this),
78
76
  this.destroy.bind(this)
79
77
  )
@@ -88,6 +86,11 @@ export class Storage<TState = any> implements Storage<TState> {
88
86
  }
89
87
 
90
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
+ }
91
94
 
92
95
  ensureState(state: Partial<TState>): void {
93
96
  return this.setState(
@@ -122,7 +125,7 @@ export class Storage<TState = any> implements Storage<TState> {
122
125
  if (value === void 0) {
123
126
  this._lastValue.set(key, this._state[key]);
124
127
  delete this._state[key];
125
- this._context.updateAttributes([STORAGE_NS, this.id, key], value);
128
+ this._setRawState(key, value);
126
129
  } else {
127
130
  this._lastValue.set(key, this._state[key]);
128
131
  this._state[key] = value as TState[Extract<keyof TState, string>];
@@ -137,13 +140,20 @@ export class Storage<TState = any> implements Storage<TState> {
137
140
  payload = refValue;
138
141
  }
139
142
 
140
- this._context.updateAttributes([STORAGE_NS, this.id, key], payload);
143
+ this._setRawState(key, payload)
141
144
  }
142
145
  });
143
146
  }
144
147
  }
145
148
 
146
- emptyStore(): void {
149
+ /**
150
+ * Empty storage data.
151
+ */
152
+ emptyStorage(): void {
153
+ if (size(this._state) <= 0) {
154
+ return;
155
+ }
156
+
147
157
  if (this._destroyed) {
148
158
  console.error(new Error(`Cannot empty destroyed Storage "${this.id}".`));
149
159
  return;
@@ -154,10 +164,17 @@ export class Storage<TState = any> implements Storage<TState> {
154
164
  return;
155
165
  }
156
166
 
157
- this._context.updateAttributes([STORAGE_NS, this.id], {});
167
+ this.setState(mapValues(this._state, noop as () => undefined));
158
168
  }
159
169
 
160
- deleteStore(): void {
170
+ /**
171
+ * Delete storage index with all of its data and destroy the Storage instance.
172
+ */
173
+ deleteStorage(): void {
174
+ if (this.id === null) {
175
+ throw new Error(`Cannot delete main Storage`);
176
+ }
177
+
161
178
  if (!this._context.getIsWritable()) {
162
179
  console.error(new Error(`Cannot delete Storage "${this.id}" without writable access.`));
163
180
  return;
@@ -172,11 +189,35 @@ export class Storage<TState = any> implements Storage<TState> {
172
189
  return this._destroyed;
173
190
  }
174
191
 
192
+ /**
193
+ * Destroy the Storage instance. The data will be kept.
194
+ */
175
195
  destroy() {
176
196
  this._destroyed = true;
177
197
  this._sideEffect.flushAll();
178
198
  }
179
199
 
200
+ private _getRawState(): TState | undefined
201
+ private _getRawState(defaultValue: TState): TState
202
+ private _getRawState(defaultValue?: TState): TState | undefined {
203
+ if (this.id === null) {
204
+ return get(this._context.getAttributes(), [], defaultValue);
205
+ } else {
206
+ return get(this._context.getAttributes(), [STORAGE_NS, this.id], defaultValue);
207
+ }
208
+ }
209
+
210
+ private _setRawState(key: string, value: any): void {
211
+ if (this.id === null) {
212
+ if (key === STORAGE_NS) {
213
+ throw new Error(`Cannot set attribute internal filed "${STORAGE_NS}"`)
214
+ }
215
+ return this._context.updateAttributes([key], value);
216
+ } else {
217
+ return this._context.updateAttributes([STORAGE_NS, this.id, key], value);
218
+ }
219
+ }
220
+
180
221
  private _updateProperties(actions: ReadonlyArray<AkkoObjectUpdatedProperty<TState, string>>): void {
181
222
  if (this._destroyed) {
182
223
  console.error(new Error(`Cannot call _updateProperties on destroyed Storage "${this.id}".`));
@@ -190,6 +231,11 @@ export class Storage<TState = any> implements Storage<TState> {
190
231
  try {
191
232
  const action = actions[i]
192
233
  const key = action.key as Extract<keyof TState, string>;
234
+
235
+ if (this.id === null && key === STORAGE_NS) {
236
+ continue
237
+ }
238
+
193
239
  const value = isObject(action.value) ? JSON.parse(JSON.stringify(action.value)) : action.value;
194
240
  let oldValue: TState[Extract<keyof TState, string>] | undefined;
195
241
  if (this._lastValue.has(key)) {
@@ -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
@@ -6,9 +6,9 @@ import {
6
6
  unlistenDisposed,
7
7
  unlistenUpdated,
8
8
  toJS
9
- } from 'white-web-sdk';
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";
@@ -16,9 +16,10 @@ import type { AppEmitterEvent } from "./index";
16
16
  import type { AppManager } from "./AppManager";
17
17
  import type { AppProxy } from "./AppProxy";
18
18
  import { Storage } from './App/Storage';
19
+ import type { MagixEventAddListener, MagixEventDispatcher, MagixEventRemoveListener } from './App/MagixEvent';
19
20
 
20
- export class AppContext<TAttrs extends Record<string, any> = any, AppOptions = any> {
21
- public readonly emitter: Emittery<AppEmitterEvent<TAttrs>>;
21
+ export class AppContext<TAttributes = any, TMagixEventPayloads = any, TAppOptions = any> {
22
+ public readonly emitter: Emittery<AppEmitterEvent<TAttributes>>;
22
23
  public readonly mobxUtils = {
23
24
  autorun,
24
25
  reaction,
@@ -40,21 +41,22 @@ export class AppContext<TAttrs extends Record<string, any> = any, AppOptions = a
40
41
  private boxManager: BoxManager,
41
42
  public appId: string,
42
43
  private appProxy: AppProxy,
43
- private appOptions?: AppOptions | (() => AppOptions),
44
+ private appOptions?: TAppOptions | (() => TAppOptions),
44
45
  ) {
45
46
  this.emitter = appProxy.appEmitter;
46
47
  this.isAddApp = appProxy.isAddApp;
47
48
  }
48
49
 
49
- public getDisplayer() {
50
+ public getDisplayer = () => {
50
51
  return this.manager.displayer;
51
52
  }
52
53
 
53
- public getAttributes(): TAttrs | undefined {
54
+ /** @deprecated Use context.storage.state instead. */
55
+ public getAttributes = (): TAttributes | undefined => {
54
56
  return this.appProxy.attributes;
55
57
  }
56
58
 
57
- public getScenes(): SceneDefinition[] | undefined {
59
+ public getScenes = (): SceneDefinition[] | undefined => {
58
60
  const appAttr = this.store.getAppAttributes(this.appId);
59
61
  if (appAttr?.isDynamicPPT) {
60
62
  const appProxy = this.manager.appProxies.get(this.appId);
@@ -66,19 +68,21 @@ export class AppContext<TAttrs extends Record<string, any> = any, AppOptions = a
66
68
  }
67
69
  }
68
70
 
69
- public getView(): View | undefined {
71
+ public getView = (): View | undefined => {
70
72
  return this.appProxy.view;
71
73
  }
72
74
 
73
- public getInitScenePath() {
75
+ public getInitScenePath = () => {
74
76
  return this.manager.getAppInitPath(this.appId);
75
77
  }
76
78
 
77
- public getIsWritable(): boolean {
79
+ /** Get App writable status. */
80
+ public getIsWritable = (): boolean => {
78
81
  return this.manager.canOperate;
79
82
  }
80
83
 
81
- public getBox(): ReadonlyTeleBox {
84
+ /** Get the App Window UI box. */
85
+ public getBox = (): ReadonlyTeleBox => {
82
86
  const box = this.boxManager.getBox(this.appId);
83
87
  if (box) {
84
88
  return box;
@@ -87,26 +91,30 @@ export class AppContext<TAttrs extends Record<string, any> = any, AppOptions = a
87
91
  }
88
92
  }
89
93
 
90
- public getRoom(): Room | undefined {
94
+ public getRoom = (): Room | undefined => {
91
95
  return this.manager.room;
92
96
  }
93
97
 
94
- public setAttributes(attributes: TAttrs) {
98
+ /** @deprecated Use context.storage.setState instead. */
99
+ public setAttributes = (attributes: TAttributes) => {
95
100
  this.manager.safeSetAttributes({ [this.appId]: attributes });
96
101
  }
97
102
 
98
- public updateAttributes(keys: string[], value: any) {
103
+ /** @deprecated Use context.storage.setState instead. */
104
+ public updateAttributes = (keys: string[], value: any) => {
99
105
  if (this.manager.attributes[this.appId]) {
100
106
  this.manager.safeUpdateAttributes([this.appId, ...keys], value);
101
107
  }
102
108
  }
103
109
 
104
- public async setScenePath(scenePath: string): Promise<void> {
110
+ public setScenePath = async (scenePath: string): Promise<void> => {
105
111
  if (!this.appProxy.box) return;
106
112
  this.appProxy.setFullPath(scenePath);
113
+ // 兼容 15 版本 SDK 的切页
114
+ this.getRoom()?.setScenePath(scenePath);
107
115
  }
108
116
 
109
- public mountView(dom: HTMLDivElement): void {
117
+ public mountView = (dom: HTMLDivElement): void => {
110
118
  const view = this.getView();
111
119
  if (view) {
112
120
  view.divElement = dom;
@@ -117,15 +125,44 @@ export class AppContext<TAttrs extends Record<string, any> = any, AppOptions = a
117
125
  }
118
126
  }
119
127
 
120
- public getAppOptions(): AppOptions | undefined {
121
- return typeof this.appOptions === 'function' ? (this.appOptions as () => AppOptions)() : this.appOptions
128
+ /** Get the local App options. */
129
+ public getAppOptions = (): TAppOptions | undefined => {
130
+ return typeof this.appOptions === 'function' ? (this.appOptions as () => TAppOptions)() : this.appOptions
122
131
  }
123
132
 
124
- public createStorage<TState>(storeId: string, defaultState?: TState): Storage<TState> {
133
+ private _storage?: Storage<TAttributes>
134
+
135
+ /** Main Storage for attributes. */
136
+ public get storage(): Storage<TAttributes> {
137
+ if (!this._storage) {
138
+ this._storage = new Storage(this);
139
+ }
140
+ return this._storage;
141
+ }
142
+
143
+ /**
144
+ * Create separated storages for flexible state management.
145
+ * @param storeId Namespace for the storage. Storages of the same namespace share the same data.
146
+ * @param defaultState Default state for initial storage creation.
147
+ * @returns
148
+ */
149
+ public createStorage = <TState>(storeId: string, defaultState?: TState): Storage<TState> => {
125
150
  const storage = new Storage(this, storeId, defaultState);
126
151
  this.emitter.on("destroy", () => {
127
152
  storage.destroy();
128
153
  });
129
154
  return storage;
130
155
  }
156
+
157
+ /** Dispatch events to other clients (and self). */
158
+ public dispatchMagixEvent: MagixEventDispatcher<TMagixEventPayloads> = (this.manager.displayer as Room).dispatchMagixEvent.bind(this.manager.displayer)
159
+
160
+ /** Listen to events from others clients (and self messages). */
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
+ }
165
+
166
+ /** Remove a Magix event listener. */
167
+ public removeMagixEventListener = this.manager.displayer.removeMagixEventListener.bind(this.manager.displayer) as MagixEventRemoveListener<TMagixEventPayloads>
131
168
  }
package/src/AppManager.ts CHANGED
@@ -30,6 +30,7 @@ export class AppManager {
30
30
  public boxManager?: BoxManager;
31
31
 
32
32
  private _prevSceneIndex: number | undefined;
33
+ private _prevFocused: string | undefined;
33
34
 
34
35
  constructor(public windowManger: WindowManager) {
35
36
  this.displayer = windowManger.displayer;
@@ -106,6 +107,15 @@ export class AppManager {
106
107
  }
107
108
  });
108
109
  });
110
+ this.refresher?.add("focusedChange", () => {
111
+ return autorun(() => {
112
+ const focused = get(this.attributes, "focus");
113
+ if (this._prevFocused !== focused) {
114
+ callbacks.emit("focusedChange", focused);
115
+ this._prevFocused = focused;
116
+ }
117
+ });
118
+ })
109
119
  if (!this.attributes.apps || Object.keys(this.attributes.apps).length === 0) {
110
120
  const mainScenePath = this.store.getMainViewScenePath();
111
121
  if (!mainScenePath) return;
@@ -116,6 +126,7 @@ export class AppManager {
116
126
  }
117
127
  this.displayerWritableListener(!this.room?.isWritable);
118
128
  this.displayer.callbacks.on("onEnableWriteNowChanged", this.displayerWritableListener);
129
+ this._prevFocused = this.attributes.focus;
119
130
  }
120
131
 
121
132
  /**
@@ -307,6 +318,9 @@ export class AppManager {
307
318
  });
308
319
  if (isWritable === true) {
309
320
  this.mainView.disableCameraTransform = false;
321
+ if (this.room && this.room.disableSerialization === true) {
322
+ this.room.disableSerialization = false;
323
+ }
310
324
  } else {
311
325
  this.mainView.disableCameraTransform = true;
312
326
  }
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> {
@@ -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
@@ -151,6 +151,7 @@ export type PublicEvent = {
151
151
  cameraStateChange: CameraState;
152
152
  mainViewScenePathChange: string;
153
153
  mainViewSceneIndexChange: number;
154
+ focusedChange: string | undefined;
154
155
  };
155
156
 
156
157
  export type MountParams = {
@@ -219,7 +220,7 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
219
220
  if (room.phase !== RoomPhase.Connected) {
220
221
  throw new Error("[WindowManager]: Room only Connected can be mount");
221
222
  }
222
- if (room.phase === RoomPhase.Connected) {
223
+ if (room.phase === RoomPhase.Connected && room.isWritable) {
223
224
  // redo undo 需要设置这个属性
224
225
  room.disableSerialization = false;
225
226
  }
@@ -570,6 +571,10 @@ export class WindowManager extends InvisiblePlugin<WindowMangerAttributes> {
570
571
  return this.attributes.focus;
571
572
  }
572
573
 
574
+ public get mainViewSceneIndex(): number {
575
+ return this.appManager?.store.getMainViewSceneIndex();
576
+ }
577
+
573
578
  /**
574
579
  * 查询所有的 App
575
580
  */