@netless/app-slide 0.0.26 → 0.0.30

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.
@@ -0,0 +1,3 @@
1
+ export declare function guessBgColor(el: HTMLElement): string;
2
+ export declare function toHex(color: string): string;
3
+ export declare function cachedGetBgColor(el: HTMLElement): string;
@@ -0,0 +1,21 @@
1
+ import type { RegisterParams } from "@netless/window-manager";
2
+ export interface FreezableSlide {
3
+ freeze: () => void;
4
+ unfreeze: () => void;
5
+ }
6
+ export declare const FreezerLength = 2;
7
+ export declare const apps: {
8
+ map: Map<string, FreezableSlide>;
9
+ queue: string[];
10
+ validateQueue(): void;
11
+ set(appId: string, slide: FreezableSlide): void;
12
+ delete(appId: string): void;
13
+ focus(appId: string): void;
14
+ };
15
+ export declare type AddHooks = NonNullable<RegisterParams["addHooks"]>;
16
+ /**
17
+ * Put this function in your register code:
18
+ * `WindowManager.register({ addHooks })`
19
+ * So that it will automatically freeze the app when it is not in focus.
20
+ */
21
+ export declare const addHooks: AddHooks;
@@ -0,0 +1,20 @@
1
+ import type { SlideController } from "../SlideController";
2
+ declare class Logger {
3
+ enable: boolean;
4
+ apps: Record<string, {
5
+ context?: any;
6
+ controller?: SlideController;
7
+ }>;
8
+ level: "debug" | "verbose";
9
+ constructor(enable: boolean);
10
+ log(...args: Parameters<Console["log"]>): void;
11
+ verbose(...args: Parameters<Console["log"]>): void;
12
+ dispose(appId: string): void;
13
+ _onMessage: (ev: MessageEvent<{
14
+ slide: boolean | "__instance";
15
+ }>) => void;
16
+ }
17
+ export declare const logger: Logger;
18
+ export declare const log: (...args: Parameters<Console["log"]>) => void;
19
+ export declare const verbose: (...args: Parameters<Console["log"]>) => void;
20
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/app-slide",
3
- "version": "0.0.26",
3
+ "version": "0.0.30",
4
4
  "main": "dist/main.cjs.js",
5
5
  "module": "dist/main.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,9 @@
11
11
  ],
12
12
  "devDependencies": {
13
13
  "@netless/app-shared": "^0.1.1",
14
- "@netless/slide": "^0.1.32",
14
+ "@netless/slide": "^0.1.35",
15
+ "@types/color-string": "^1.5.1",
16
+ "color-string": "^1.7.4",
15
17
  "side-effect-manager": "^0.1.5",
16
18
  "vanilla-lazyload": "^17.5.0"
17
19
  },
@@ -16,6 +16,8 @@ import type { Attributes, MagixPayload, SlideState } from "../typings";
16
16
  import { SideEffectManager } from "side-effect-manager";
17
17
  import { Slide, SLIDE_EVENTS } from "@netless/slide";
18
18
  import { clamp, deepClone, isObj } from "../utils/helpers";
19
+ import { cachedGetBgColor } from "../utils/bgcolor";
20
+ import { logger, log, verbose } from "../utils/logger";
19
21
  export { syncSceneWithSlide, createDocsViewerPages } from "./helpers";
20
22
 
21
23
  export const DefaultUrl = "https://convertcdn.netless.link/dynamicConvert";
@@ -38,7 +40,6 @@ export interface SlideControllerOptions {
38
40
  export class SlideController {
39
41
  public readonly context: SlideControllerOptions["context"];
40
42
  public readonly slide: Slide;
41
- public readonly debug: boolean;
42
43
 
43
44
  private readonly channel: string;
44
45
  private readonly room?: Room;
@@ -68,13 +69,22 @@ export class SlideController {
68
69
  this.channel = `channel-${context.appId}`;
69
70
  this.room = context.getRoom();
70
71
  this.player = this.room ? undefined : (context.getDisplayer() as Player);
71
- this.debug = import.meta.env.DEV || !!context.getAppOptions()?.debug;
72
72
  this.slide = this.createSlide(anchor);
73
73
  // the adder does not need to sync state
74
74
  this.syncStateOnceFlag = !this.context.isAddApp;
75
75
  this.initialize();
76
76
  }
77
77
 
78
+ public ready = false;
79
+ private resolveReady!: () => void;
80
+ public readonly readyPromise = new Promise<void>(resolve => {
81
+ this.resolveReady = () =>
82
+ setTimeout(() => {
83
+ this.ready = true;
84
+ resolve();
85
+ }, 1000);
86
+ });
87
+
78
88
  public jumpToPage(page: number) {
79
89
  if (this.ready) {
80
90
  page = clamp(page, 1, this.pageCount);
@@ -93,16 +103,12 @@ export class SlideController {
93
103
  slide.setResource(taskId, url || DefaultUrl);
94
104
  if (state) {
95
105
  // if we already have state, try restore from it
96
- if (this.debug) {
97
- console.log("[Slide] init with state", deepClone(state));
98
- }
106
+ log("[Slide] init with state", deepClone(state));
99
107
  this.syncStateOnceFlag = false;
100
108
  slide.setSlideState(deepClone(state));
101
109
  } else if (context.isAddApp) {
102
110
  // otherwise, maybe this slide is just added, let the adder kick start first render
103
- if (this.debug) {
104
- console.log("[Slide] init by renderSlide", 1);
105
- }
111
+ log("[Slide] init by renderSlide", 1);
106
112
  slide.renderSlide(1);
107
113
  }
108
114
  // there's still some risk that the adder is left and no first render
@@ -131,6 +137,11 @@ export class SlideController {
131
137
  return disposer;
132
138
  });
133
139
 
140
+ this.sideEffect.add(() => {
141
+ context.emitter.on("seek", this.onSeeked);
142
+ return () => context.emitter.off("seek", this.onSeeked);
143
+ });
144
+
134
145
  this.sideEffect.add(() => {
135
146
  const displayer = context.getDisplayer();
136
147
  displayer.addMagixEventListener(this.channel, this.magixEventListener, {
@@ -149,6 +160,8 @@ export class SlideController {
149
160
  slide.on(SLIDE_EVENTS.renderError, this.onError);
150
161
  slide.on(SLIDE_EVENTS.stateChange, this.onStateChange);
151
162
  slide.on(SLIDE_EVENTS.syncDispatch, this.onSyncDispatch);
163
+
164
+ slide.on(SLIDE_EVENTS.renderEnd, this.resolveReady);
152
165
  }
153
166
 
154
167
  private onSyncDispatch = (event: SyncEvent) => {
@@ -157,9 +170,7 @@ export class SlideController {
157
170
  type: SLIDE_EVENTS.syncDispatch,
158
171
  payload: event,
159
172
  };
160
- if (this.debug) {
161
- console.log("[Slide] dispatch", event);
162
- }
173
+ log("[Slide] dispatch", event);
163
174
  this.room.dispatchMagixEvent(this.channel, payload);
164
175
  }
165
176
  };
@@ -169,9 +180,7 @@ export class SlideController {
169
180
  const { type, payload } = ev.payload as MagixPayload;
170
181
  if (type === SLIDE_EVENTS.syncDispatch) {
171
182
  this.syncStateOnce();
172
- if (this.debug) {
173
- console.log("[Slide] receive", payload);
174
- }
183
+ log("[Slide] receive", payload);
175
184
  // only emit the event if the slide state is sync-ed
176
185
  if (!this.syncStateOnceFlag) {
177
186
  this.slide.emit(SLIDE_EVENTS.syncReceive, payload);
@@ -185,9 +194,7 @@ export class SlideController {
185
194
  if (this.syncStateOnceFlag) {
186
195
  const { state } = { ...EmptyAttributes, ...this.context.getAttributes() };
187
196
  if (state) {
188
- if (this.debug) {
189
- console.log("[Slide] sync with state (once)", deepClone(state));
190
- }
197
+ log("[Slide] sync with state (once)", deepClone(state));
191
198
  this.slide.setSlideState(deepClone(state));
192
199
  this.syncStateOnceFlag = false;
193
200
  }
@@ -196,40 +203,44 @@ export class SlideController {
196
203
 
197
204
  private onStateChange = (state: SlideState) => {
198
205
  if (this.context.getIsWritable()) {
199
- if (import.meta.env.DEV) {
200
- console.log("[Slide] state change", JSON.stringify(state, null, 2));
201
- }
206
+ verbose("[Slide] state change", JSON.stringify(state, null, 2));
202
207
  this.context.updateAttributes(["state"], state);
203
208
  }
204
209
  };
205
210
 
206
- private resolveReady!: () => void;
207
- public readonly readyPromise = new Promise<void>(resolve => {
208
- this.resolveReady = resolve;
209
- });
211
+ private onSeeked = () => {
212
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
213
+ const state = this.context.getAttributes()!.state;
214
+ if (state) {
215
+ this.slide.setSlideState(deepClone(state));
216
+ }
217
+ };
210
218
 
211
219
  private pollCount = 0;
212
220
  private pollReadyState = () => {
213
221
  if (this.ready) {
214
- this.resolveReady();
222
+ if (this._toFreeze === 1) {
223
+ this.freeze();
224
+ } else if (this._toFreeze === -1) {
225
+ this.unfreeze();
226
+ }
215
227
  } else if (this.pollCount < MaxPollCount) {
216
228
  this.pollCount++;
217
229
  setTimeout(this.pollReadyState, 500);
218
230
  } else {
219
231
  this.pollCount = 0;
220
- if (this.debug) {
221
- console.log("[Slide] renderSlide (retry after timeout)", 1);
222
- }
232
+ log("[Slide] renderSlide (retry after timeout)", 1);
223
233
  this.slide.renderSlide(1);
224
234
  }
225
235
  };
226
236
 
227
- public get ready() {
228
- return this.slide.slideCount > 0;
229
- }
230
-
237
+ // cache `slideCount`, because once the slide is frozen,
238
+ // the `slideCount` will be 0
239
+ private _pageCount = 0;
231
240
  public get pageCount() {
232
- return this.slide.slideCount;
241
+ if (this._pageCount > 0) return this._pageCount;
242
+ this._pageCount = this.slide.slideCount;
243
+ return this._pageCount;
233
244
  }
234
245
 
235
246
  public get page() {
@@ -242,18 +253,18 @@ export class SlideController {
242
253
  interactive: true,
243
254
  mode: "interactive",
244
255
  resize: true,
245
- controller: this.debug,
256
+ controller: logger.enable,
246
257
  renderOptions: {
247
258
  minFPS: 25,
248
259
  maxFPS: 30,
249
260
  autoFPS: true,
250
261
  autoResolution: true,
251
262
  resolution: this.context.getAppOptions()?.resolution,
252
- transactionBgColor: this.context.getAppOptions()?.bgColor || "#f9f9fc",
263
+ transactionBgColor: this.context.getAppOptions()?.bgColor || cachedGetBgColor(anchor),
253
264
  },
254
265
  timestamp: this.timestamp,
255
266
  });
256
- if (this.debug) {
267
+ if (import.meta.env.DEV) {
257
268
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
258
269
  (window as any).slide = slide;
259
270
  }
@@ -265,9 +276,7 @@ export class SlideController {
265
276
  public destroy() {
266
277
  this.sideEffect.flushAll();
267
278
  if (!this.destroyed) {
268
- if (this.debug) {
269
- console.log("[Slide] destroy slide (once)");
270
- }
279
+ log("[Slide] destroy slide (once)");
271
280
  this.slide.destroy();
272
281
  this.destroyed = true;
273
282
  }
@@ -282,4 +291,52 @@ export class SlideController {
282
291
  return Date.now();
283
292
  }
284
293
  };
294
+
295
+ public isFrozen = false;
296
+ private _toFreeze: -1 | 0 | 1 = 0; // -1: unfreeze, 0: no change, 1: freeze
297
+ private freezePromise: Promise<void> | null = null;
298
+
299
+ private afterFreeze(from: -1 | 1) {
300
+ if (from === 1) {
301
+ this.isFrozen = true;
302
+ this.freezePromise = null;
303
+ if (this._toFreeze === -1) {
304
+ this.unfreeze();
305
+ }
306
+ } else if (from === -1) {
307
+ this.isFrozen = false;
308
+ this.freezePromise = null;
309
+ if (this._toFreeze === 1) {
310
+ this.freeze();
311
+ }
312
+ }
313
+ }
314
+
315
+ public freeze = () => {
316
+ if (this.ready) {
317
+ log("[Slide] freeze", this.context.appId);
318
+ if (this.freezePromise) {
319
+ this._toFreeze = 1;
320
+ } else if (!this.isFrozen) {
321
+ this._toFreeze = 0;
322
+ this.freezePromise = this.slide.frozen().then(this.afterFreeze.bind(this, 1));
323
+ }
324
+ } else {
325
+ this._toFreeze = 1;
326
+ }
327
+ };
328
+
329
+ public unfreeze = async () => {
330
+ if (this.ready) {
331
+ log("[Slide] unfreeze", this.context.appId);
332
+ if (this.freezePromise) {
333
+ this._toFreeze = -1;
334
+ } else if (this.isFrozen) {
335
+ this._toFreeze = 0;
336
+ this.freezePromise = this.slide.release().then(this.afterFreeze.bind(this, -1));
337
+ }
338
+ } else {
339
+ this._toFreeze = -1;
340
+ }
341
+ };
285
342
  }
@@ -5,7 +5,7 @@ import { SideEffectManager } from "side-effect-manager";
5
5
  import { createDocsViewerPages } from "../SlideController";
6
6
  import { DocsViewer } from "../DocsViewer";
7
7
 
8
- export const ClickThroughAppliances = new Set(["clicker", "selector"]);
8
+ export const ClickThroughAppliances = new Set(["clicker"]);
9
9
 
10
10
  export type MountSlideOptions = Omit<SlideControllerOptions, "context" | "onPageChanged"> & {
11
11
  onReady: () => void;
@@ -26,6 +26,7 @@ export class SlideDocsViewer {
26
26
  protected readonly whiteboardView: SlideDocsViewerConfig["view"];
27
27
  protected readonly mountSlideController: SlideDocsViewerConfig["mountSlideController"];
28
28
  protected readonly mountWhiteboard: SlideDocsViewerConfig["mountWhiteboard"];
29
+ private isViewMounted = false;
29
30
 
30
31
  public constructor({ box, view, mountSlideController, mountWhiteboard }: SlideDocsViewerConfig) {
31
32
  this.box = box;
@@ -92,7 +93,6 @@ export class SlideDocsViewer {
92
93
  if (!this.$whiteboardView) {
93
94
  this.$whiteboardView = document.createElement("div");
94
95
  this.$whiteboardView.className = this.wrapClassName("wb-view");
95
- this.mountWhiteboard(this.$whiteboardView);
96
96
  }
97
97
  return this.$whiteboardView;
98
98
  }
@@ -161,13 +161,20 @@ export class SlideDocsViewer {
161
161
  protected scaleDocsToFit = () => {
162
162
  if (this.slideController) {
163
163
  const { width, height } = this.slideController.slide;
164
- this.whiteboardView.moveCameraToContain({
165
- originX: -width / 2,
166
- originY: -height / 2,
167
- width,
168
- height,
169
- animationMode: "immediately" as AnimationMode.Immediately,
170
- });
164
+ if (width && height) {
165
+ this.whiteboardView.moveCameraToContain({
166
+ originX: -width / 2,
167
+ originY: -height / 2,
168
+ width,
169
+ height,
170
+ animationMode: "immediately" as AnimationMode.Immediately,
171
+ });
172
+ if (!this.isViewMounted) {
173
+ this.isViewMounted = true;
174
+ console.log("[Slide] mount whiteboard view");
175
+ this.mountWhiteboard(this.$whiteboardView);
176
+ }
177
+ }
171
178
  }
172
179
  };
173
180
 
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ import type { NetlessApp } from "@netless/window-manager";
2
2
  import type { RoomState } from "white-web-sdk";
3
3
  import type { MountSlideOptions } from "./SlideDocsViewer";
4
4
  import type { Attributes } from "./typings";
5
+ import type { AddHooks, FreezableSlide } from "./utils/freezer";
5
6
 
6
7
  import { SideEffectManager } from "side-effect-manager";
7
8
  import { ensureAttributes } from "@netless/app-shared";
@@ -12,11 +13,13 @@ import {
12
13
  SlideController,
13
14
  } from "./SlideController";
14
15
  import { SlideDocsViewer } from "./SlideDocsViewer";
16
+ import { apps, FreezerLength, addHooks } from "./utils/freezer";
17
+ import { log, logger } from "./utils/logger";
15
18
  import styles from "./style.scss?inline";
16
19
 
17
- export type { Attributes };
20
+ export type { Attributes, AddHooks, FreezableSlide };
18
21
 
19
- export { DefaultUrl };
22
+ export { DefaultUrl, apps, FreezerLength, addHooks };
20
23
 
21
24
  const SlideApp: NetlessApp<Attributes> = {
22
25
  kind: "Slide",
@@ -46,8 +49,13 @@ const SlideApp: NetlessApp<Attributes> = {
46
49
 
47
50
  const onPageChanged = (page: number) => {
48
51
  const room = context.getRoom();
49
- if (docsViewer && docsViewer.slideController && room && context.getIsWritable()) {
50
- syncSceneWithSlide(room, context, docsViewer.slideController.slide, baseScenePath);
52
+ if (docsViewer && docsViewer.slideController) {
53
+ let synced = false;
54
+ if (room && context.getIsWritable()) {
55
+ syncSceneWithSlide(room, context, docsViewer.slideController.slide, baseScenePath);
56
+ synced = true;
57
+ }
58
+ log("[Slide] page to", page, synced);
51
59
  docsViewer.viewer.setPageIndex(page - 1);
52
60
  }
53
61
  };
@@ -58,6 +66,8 @@ const SlideApp: NetlessApp<Attributes> = {
58
66
  ...options,
59
67
  onPageChanged,
60
68
  });
69
+ apps.set(context.appId, slideController);
70
+ (logger.apps[context.appId] ||= {}).controller = slideController;
61
71
  if (import.meta.env.DEV) {
62
72
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
73
  (window as any).slideController = slideController;
@@ -80,6 +90,14 @@ const SlideApp: NetlessApp<Attributes> = {
80
90
 
81
91
  const room = context.getRoom();
82
92
  const sideEffect = new SideEffectManager();
93
+
94
+ sideEffect.add(() => {
95
+ (logger.apps[context.appId] ||= {}).context = context;
96
+ logger.enable = context.getAppOptions()?.debug || import.meta.env.DEV;
97
+ logger.level = import.meta.env.DEV ? "verbose" : "debug";
98
+ return () => logger.dispose(context.appId);
99
+ });
100
+
83
101
  if (room) {
84
102
  docsViewer.toggleClickThrough(room.state.memberState.currentApplianceName);
85
103
  sideEffect.add(() => {
@@ -94,7 +112,8 @@ const SlideApp: NetlessApp<Attributes> = {
94
112
  }
95
113
 
96
114
  context.emitter.on("destroy", () => {
97
- console.log("[Slide]: destroy");
115
+ log("[Slide]: destroy");
116
+ apps.delete(context.appId);
98
117
  sideEffect.flushAll();
99
118
  if (docsViewer) {
100
119
  docsViewer.destroy();
@@ -0,0 +1,45 @@
1
+ import ColorString from "color-string";
2
+ import { log } from "./logger";
3
+
4
+ export function guessBgColor(el: HTMLElement): string {
5
+ try {
6
+ const bg = window.getComputedStyle(el).backgroundColor;
7
+ if (bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
8
+ return bg;
9
+ }
10
+ if (el.parentElement) {
11
+ return guessBgColor(el.parentElement);
12
+ }
13
+ } catch {
14
+ // ignore any error
15
+ }
16
+ return "#ffffff";
17
+ }
18
+
19
+ export function toHex(color: string): string {
20
+ const result = ColorString.get(color);
21
+ if (result && result.model === "rgb") {
22
+ const args = result.value;
23
+
24
+ // https://github.com/Qix-/color-convert/blob/8dfdbbc6b46fa6a305bf394d942cc1b08e23fca5/conversions.js#L616
25
+ const integer =
26
+ ((Math.round(args[0]) & 0xff) << 16) +
27
+ ((Math.round(args[1]) & 0xff) << 8) +
28
+ (Math.round(args[2]) & 0xff);
29
+
30
+ const string = integer.toString(16);
31
+ return "#" + "000000".substring(string.length) + string;
32
+ } else {
33
+ return color;
34
+ }
35
+ }
36
+
37
+ let cachedBgColor = "";
38
+
39
+ export function cachedGetBgColor(el: HTMLElement): string {
40
+ if (!cachedBgColor) {
41
+ cachedBgColor = toHex(guessBgColor(el));
42
+ log("[Slide] guess bg color", cachedBgColor);
43
+ }
44
+ return cachedBgColor;
45
+ }
@@ -0,0 +1,69 @@
1
+ import type { RegisterParams } from "@netless/window-manager";
2
+ import { log } from "./logger";
3
+
4
+ export interface FreezableSlide {
5
+ freeze: () => void;
6
+ unfreeze: () => void;
7
+ }
8
+
9
+ export const FreezerLength = 2;
10
+
11
+ export const apps = {
12
+ map: new Map<string, FreezableSlide>(),
13
+ queue: [] as string[],
14
+ validateQueue() {
15
+ log("[Slide] freezer: validate", this.queue);
16
+ while (this.queue.length > FreezerLength) {
17
+ const appId = this.queue.pop() as string;
18
+ const slide = this.map.get(appId);
19
+ if (slide) {
20
+ log("[Slide] freezer: validate-freeze", appId, this.queue);
21
+ slide.freeze();
22
+ }
23
+ }
24
+ },
25
+ set(appId: string, slide: FreezableSlide) {
26
+ log("[Slide] freezer: add", appId, this.queue);
27
+ this.map.set(appId, slide);
28
+ if (!this.queue.includes(appId)) {
29
+ this.queue.unshift(appId);
30
+ }
31
+ this.validateQueue();
32
+ },
33
+ delete(appId: string) {
34
+ log("[Slide] freezer: delete", appId, this.queue);
35
+ this.map.delete(appId);
36
+ this.queue = this.queue.filter(id => id !== appId);
37
+ },
38
+ focus(appId: string) {
39
+ const slide = this.map.get(appId);
40
+ const index = this.queue.indexOf(appId);
41
+ if (index > -1) {
42
+ this.queue.splice(index, 1);
43
+ }
44
+ this.queue.unshift(appId);
45
+ this.validateQueue();
46
+ log("[Slide] freezer: focus", appId, this.queue);
47
+ if (slide) {
48
+ slide.unfreeze();
49
+ }
50
+ },
51
+ };
52
+
53
+ if (import.meta.env.DEV) {
54
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
+ (window as any).freezer = apps;
56
+ }
57
+
58
+ export type AddHooks = NonNullable<RegisterParams["addHooks"]>;
59
+
60
+ /**
61
+ * Put this function in your register code:
62
+ * `WindowManager.register({ addHooks })`
63
+ * So that it will automatically freeze the app when it is not in focus.
64
+ */
65
+ export const addHooks: AddHooks = emitter => {
66
+ emitter.on("focus", ({ appId }) => {
67
+ apps.focus(appId);
68
+ });
69
+ };
@@ -0,0 +1,39 @@
1
+ import type { SlideController } from "../SlideController";
2
+ import { isObj } from "./helpers";
3
+
4
+ class Logger {
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ public apps: Record<string, { context?: any; controller?: SlideController }> = {};
7
+ public level: "debug" | "verbose" = import.meta.env.DEV ? "verbose" : "debug";
8
+ constructor(public enable: boolean) {
9
+ window.addEventListener("message", this._onMessage);
10
+ }
11
+ log(...args: Parameters<Console["log"]>) {
12
+ if (this.enable) {
13
+ console.log(...args);
14
+ }
15
+ }
16
+ verbose(...args: Parameters<Console["log"]>) {
17
+ if (this.enable && this.level === "verbose") {
18
+ console.log(...args);
19
+ }
20
+ }
21
+ dispose(appId: string) {
22
+ this.enable = false;
23
+ delete this.apps[appId];
24
+ window.removeEventListener("message", this._onMessage);
25
+ }
26
+ _onMessage = (ev: MessageEvent<{ slide: boolean | "__instance" }>) => {
27
+ if (isObj(ev.data)) {
28
+ if (typeof ev.data.slide === "boolean") {
29
+ this.enable = ev.data.slide;
30
+ } else if (ev.data.slide === "__instance") {
31
+ console.log(this);
32
+ }
33
+ }
34
+ };
35
+ }
36
+
37
+ export const logger = new Logger(import.meta.env.DEV);
38
+ export const log = logger.log.bind(logger);
39
+ export const verbose = logger.verbose.bind(logger);