@netless/app-slide 0.0.27 → 0.0.28

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,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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/app-slide",
3
- "version": "0.0.27",
3
+ "version": "0.0.28",
4
4
  "main": "dist/main.cjs.js",
5
5
  "module": "dist/main.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "devDependencies": {
13
13
  "@netless/app-shared": "^0.1.1",
14
- "@netless/slide": "^0.1.32",
14
+ "@netless/slide": "^0.1.33",
15
15
  "@types/color-string": "^1.5.0",
16
16
  "color-string": "^1.6.0",
17
17
  "side-effect-manager": "^0.1.5",
@@ -76,6 +76,16 @@ export class SlideController {
76
76
  this.initialize();
77
77
  }
78
78
 
79
+ public ready = false;
80
+ private resolveReady!: () => void;
81
+ public readonly readyPromise = new Promise<void>(resolve => {
82
+ this.resolveReady = () =>
83
+ setTimeout(() => {
84
+ this.ready = true;
85
+ resolve();
86
+ }, 1000);
87
+ });
88
+
79
89
  public jumpToPage(page: number) {
80
90
  if (this.ready) {
81
91
  page = clamp(page, 1, this.pageCount);
@@ -150,6 +160,8 @@ export class SlideController {
150
160
  slide.on(SLIDE_EVENTS.renderError, this.onError);
151
161
  slide.on(SLIDE_EVENTS.stateChange, this.onStateChange);
152
162
  slide.on(SLIDE_EVENTS.syncDispatch, this.onSyncDispatch);
163
+
164
+ slide.on(SLIDE_EVENTS.renderEnd, this.resolveReady);
153
165
  }
154
166
 
155
167
  private onSyncDispatch = (event: SyncEvent) => {
@@ -204,15 +216,14 @@ export class SlideController {
204
216
  }
205
217
  };
206
218
 
207
- private resolveReady!: () => void;
208
- public readonly readyPromise = new Promise<void>(resolve => {
209
- this.resolveReady = resolve;
210
- });
211
-
212
219
  private pollCount = 0;
213
220
  private pollReadyState = () => {
214
221
  if (this.ready) {
215
- this.resolveReady();
222
+ if (this._toFreeze === 1) {
223
+ this.freeze();
224
+ } else if (this._toFreeze === -1) {
225
+ this.unfreeze();
226
+ }
216
227
  } else if (this.pollCount < MaxPollCount) {
217
228
  this.pollCount++;
218
229
  setTimeout(this.pollReadyState, 500);
@@ -225,12 +236,13 @@ export class SlideController {
225
236
  }
226
237
  };
227
238
 
228
- public get ready() {
229
- return this.slide.slideCount > 0;
230
- }
231
-
239
+ // cache `slideCount`, because once the slide is frozen,
240
+ // the `slideCount` will be 0
241
+ private _pageCount = 0;
232
242
  public get pageCount() {
233
- return this.slide.slideCount;
243
+ if (this._pageCount > 0) return this._pageCount;
244
+ this._pageCount = this.slide.slideCount;
245
+ return this._pageCount;
234
246
  }
235
247
 
236
248
  public get page() {
@@ -283,4 +295,56 @@ export class SlideController {
283
295
  return Date.now();
284
296
  }
285
297
  };
298
+
299
+ public isFrozen = false;
300
+ private _toFreeze: -1 | 0 | 1 = 0; // -1: unfreeze, 0: no change, 1: freeze
301
+ private freezePromise: Promise<void> | null = null;
302
+
303
+ private afterFreeze(from: -1 | 1) {
304
+ if (from === 1) {
305
+ this.isFrozen = true;
306
+ this.freezePromise = null;
307
+ if (this._toFreeze === -1) {
308
+ this.unfreeze();
309
+ }
310
+ } else if (from === -1) {
311
+ this.isFrozen = false;
312
+ this.freezePromise = null;
313
+ if (this._toFreeze === 1) {
314
+ this.freeze();
315
+ }
316
+ }
317
+ }
318
+
319
+ public freeze = () => {
320
+ if (this.ready) {
321
+ if (this.debug) {
322
+ console.log("[Slide] freeze", this.context.appId);
323
+ }
324
+ if (this.freezePromise) {
325
+ this._toFreeze = 1;
326
+ } else if (!this.isFrozen) {
327
+ this._toFreeze = 0;
328
+ this.freezePromise = this.slide.frozen().then(this.afterFreeze.bind(this, 1));
329
+ }
330
+ } else {
331
+ this._toFreeze = 1;
332
+ }
333
+ };
334
+
335
+ public unfreeze = async () => {
336
+ if (this.ready) {
337
+ if (this.debug) {
338
+ console.log("[Slide] unfreeze", this.context.appId);
339
+ }
340
+ if (this.freezePromise) {
341
+ this._toFreeze = -1;
342
+ } else if (this.isFrozen) {
343
+ this._toFreeze = 0;
344
+ this.freezePromise = this.slide.release().then(this.afterFreeze.bind(this, -1));
345
+ }
346
+ } else {
347
+ this._toFreeze = -1;
348
+ }
349
+ };
286
350
  }
@@ -161,17 +161,19 @@ 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
- });
171
- if (!this.isViewMounted && width && height) {
172
- this.isViewMounted = true;
173
- console.log("[Slide] mount whiteboard view");
174
- this.mountWhiteboard(this.$whiteboardView);
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
+ }
175
177
  }
176
178
  }
177
179
  };
@@ -6,6 +6,6 @@ $namespace: "netless-app-slide";
6
6
  left: 0;
7
7
  width: 100%;
8
8
  height: 100%;
9
- z-index: 100;
9
+ z-index: 1001;
10
10
  overflow: hidden;
11
11
  }
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,12 @@ import {
12
13
  SlideController,
13
14
  } from "./SlideController";
14
15
  import { SlideDocsViewer } from "./SlideDocsViewer";
16
+ import { apps, FreezerLength, addHooks } from "./utils/freezer";
15
17
  import styles from "./style.scss?inline";
16
18
 
17
- export type { Attributes };
19
+ export type { Attributes, AddHooks, FreezableSlide };
18
20
 
19
- export { DefaultUrl };
21
+ export { DefaultUrl, apps, FreezerLength, addHooks };
20
22
 
21
23
  const SlideApp: NetlessApp<Attributes> = {
22
24
  kind: "Slide",
@@ -48,6 +50,9 @@ const SlideApp: NetlessApp<Attributes> = {
48
50
  const room = context.getRoom();
49
51
  if (docsViewer && docsViewer.slideController && room && context.getIsWritable()) {
50
52
  syncSceneWithSlide(room, context, docsViewer.slideController.slide, baseScenePath);
53
+ if (context.getAppOptions()?.debug || import.meta.env.DEV) {
54
+ console.log("[Slide] page to", page);
55
+ }
51
56
  docsViewer.viewer.setPageIndex(page - 1);
52
57
  }
53
58
  };
@@ -58,6 +63,7 @@ const SlideApp: NetlessApp<Attributes> = {
58
63
  ...options,
59
64
  onPageChanged,
60
65
  });
66
+ apps.set(context.appId, slideController);
61
67
  if (import.meta.env.DEV) {
62
68
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
69
  (window as any).slideController = slideController;
@@ -95,6 +101,7 @@ const SlideApp: NetlessApp<Attributes> = {
95
101
 
96
102
  context.emitter.on("destroy", () => {
97
103
  console.log("[Slide]: destroy");
104
+ apps.delete(context.appId);
98
105
  sideEffect.flushAll();
99
106
  if (docsViewer) {
100
107
  docsViewer.destroy();
@@ -0,0 +1,78 @@
1
+ import type { RegisterParams } from "@netless/window-manager";
2
+
3
+ export interface FreezableSlide {
4
+ freeze: () => void;
5
+ unfreeze: () => void;
6
+ }
7
+
8
+ export const FreezerLength = 2;
9
+
10
+ export const apps = {
11
+ map: new Map<string, FreezableSlide>(),
12
+ queue: [] as string[],
13
+ validateQueue() {
14
+ if (import.meta.env.DEV) {
15
+ console.log("[Slide] freezer: validate", this.queue);
16
+ }
17
+ while (this.queue.length > FreezerLength) {
18
+ const appId = this.queue.pop() as string;
19
+ const slide = this.map.get(appId);
20
+ if (slide) {
21
+ if (import.meta.env.DEV) {
22
+ console.log("[Slide] freezer: validate-freeze", appId, this.queue);
23
+ }
24
+ slide.freeze();
25
+ }
26
+ }
27
+ },
28
+ set(appId: string, slide: FreezableSlide) {
29
+ if (import.meta.env.DEV) {
30
+ console.log("[Slide] freezer: add", appId, this.queue);
31
+ }
32
+ this.map.set(appId, slide);
33
+ if (!this.queue.includes(appId)) {
34
+ this.queue.unshift(appId);
35
+ }
36
+ this.validateQueue();
37
+ },
38
+ delete(appId: string) {
39
+ if (import.meta.env.DEV) {
40
+ console.log("[Slide] freezer: delete", appId, this.queue);
41
+ }
42
+ this.map.delete(appId);
43
+ this.queue = this.queue.filter(id => id !== appId);
44
+ },
45
+ focus(appId: string) {
46
+ const slide = this.map.get(appId);
47
+ const index = this.queue.indexOf(appId);
48
+ if (index > -1) {
49
+ this.queue.splice(index, 1);
50
+ }
51
+ this.queue.unshift(appId);
52
+ this.validateQueue();
53
+ if (import.meta.env.DEV) {
54
+ console.log("[Slide] freezer: focus", appId, this.queue);
55
+ }
56
+ if (slide) {
57
+ slide.unfreeze();
58
+ }
59
+ },
60
+ };
61
+
62
+ if (import.meta.env.DEV) {
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ (window as any).freezer = apps;
65
+ }
66
+
67
+ export type AddHooks = NonNullable<RegisterParams["addHooks"]>;
68
+
69
+ /**
70
+ * Put this function in your register code:
71
+ * `WindowManager.register({ addHooks })`
72
+ * So that it will automatically freeze the app when it is not in focus.
73
+ */
74
+ export const addHooks: AddHooks = emitter => {
75
+ emitter.on("focus", ({ appId }) => {
76
+ apps.focus(appId);
77
+ });
78
+ };