@netless/app-slide 0.0.27 → 0.0.32
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/SlideController/index.d.ts +11 -4
- package/dist/index.d.ts +4 -2
- package/dist/main.cjs.js +221 -221
- package/dist/main.cjs.js.map +1 -1
- package/dist/main.es.js +1504 -1288
- package/dist/main.es.js.map +1 -1
- package/dist/main.iife.js +149 -149
- package/dist/main.iife.js.map +1 -1
- package/dist/utils/freezer.d.ts +21 -0
- package/dist/utils/logger.d.ts +20 -0
- package/package.json +4 -4
- package/src/SlideController/index.ts +94 -38
- package/src/SlideDocsViewer/index.ts +14 -12
- package/src/index.ts +24 -5
- package/src/utils/bgcolor.ts +2 -3
- package/src/utils/freezer.ts +69 -0
- package/src/utils/logger.ts +39 -0
|
@@ -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.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"main": "dist/main.cjs.js",
|
|
5
5
|
"module": "dist/main.es.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
],
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@netless/app-shared": "^0.1.1",
|
|
14
|
-
"@netless/slide": "^0.1.
|
|
15
|
-
"@types/color-string": "^1.5.
|
|
16
|
-
"color-string": "^1.
|
|
14
|
+
"@netless/slide": "^0.1.37",
|
|
15
|
+
"@types/color-string": "^1.5.1",
|
|
16
|
+
"color-string": "^1.7.4",
|
|
17
17
|
"side-effect-manager": "^0.1.5",
|
|
18
18
|
"vanilla-lazyload": "^17.5.0"
|
|
19
19
|
},
|
|
@@ -17,6 +17,7 @@ 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
19
|
import { cachedGetBgColor } from "../utils/bgcolor";
|
|
20
|
+
import { logger, log, verbose } from "../utils/logger";
|
|
20
21
|
export { syncSceneWithSlide, createDocsViewerPages } from "./helpers";
|
|
21
22
|
|
|
22
23
|
export const DefaultUrl = "https://convertcdn.netless.link/dynamicConvert";
|
|
@@ -39,7 +40,6 @@ export interface SlideControllerOptions {
|
|
|
39
40
|
export class SlideController {
|
|
40
41
|
public readonly context: SlideControllerOptions["context"];
|
|
41
42
|
public readonly slide: Slide;
|
|
42
|
-
public readonly debug: boolean;
|
|
43
43
|
|
|
44
44
|
private readonly channel: string;
|
|
45
45
|
private readonly room?: Room;
|
|
@@ -69,13 +69,22 @@ export class SlideController {
|
|
|
69
69
|
this.channel = `channel-${context.appId}`;
|
|
70
70
|
this.room = context.getRoom();
|
|
71
71
|
this.player = this.room ? undefined : (context.getDisplayer() as Player);
|
|
72
|
-
this.debug = import.meta.env.DEV || !!context.getAppOptions()?.debug;
|
|
73
72
|
this.slide = this.createSlide(anchor);
|
|
74
73
|
// the adder does not need to sync state
|
|
75
74
|
this.syncStateOnceFlag = !this.context.isAddApp;
|
|
76
75
|
this.initialize();
|
|
77
76
|
}
|
|
78
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
|
+
|
|
79
88
|
public jumpToPage(page: number) {
|
|
80
89
|
if (this.ready) {
|
|
81
90
|
page = clamp(page, 1, this.pageCount);
|
|
@@ -94,16 +103,12 @@ export class SlideController {
|
|
|
94
103
|
slide.setResource(taskId, url || DefaultUrl);
|
|
95
104
|
if (state) {
|
|
96
105
|
// if we already have state, try restore from it
|
|
97
|
-
|
|
98
|
-
console.log("[Slide] init with state", deepClone(state));
|
|
99
|
-
}
|
|
106
|
+
log("[Slide] init with state", deepClone(state));
|
|
100
107
|
this.syncStateOnceFlag = false;
|
|
101
108
|
slide.setSlideState(deepClone(state));
|
|
102
109
|
} else if (context.isAddApp) {
|
|
103
110
|
// otherwise, maybe this slide is just added, let the adder kick start first render
|
|
104
|
-
|
|
105
|
-
console.log("[Slide] init by renderSlide", 1);
|
|
106
|
-
}
|
|
111
|
+
log("[Slide] init by renderSlide", 1);
|
|
107
112
|
slide.renderSlide(1);
|
|
108
113
|
}
|
|
109
114
|
// there's still some risk that the adder is left and no first render
|
|
@@ -132,6 +137,11 @@ export class SlideController {
|
|
|
132
137
|
return disposer;
|
|
133
138
|
});
|
|
134
139
|
|
|
140
|
+
this.sideEffect.add(() => {
|
|
141
|
+
context.emitter.on("seek", this.onSeeked);
|
|
142
|
+
return () => context.emitter.off("seek", this.onSeeked);
|
|
143
|
+
});
|
|
144
|
+
|
|
135
145
|
this.sideEffect.add(() => {
|
|
136
146
|
const displayer = context.getDisplayer();
|
|
137
147
|
displayer.addMagixEventListener(this.channel, this.magixEventListener, {
|
|
@@ -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) => {
|
|
@@ -158,9 +170,7 @@ export class SlideController {
|
|
|
158
170
|
type: SLIDE_EVENTS.syncDispatch,
|
|
159
171
|
payload: event,
|
|
160
172
|
};
|
|
161
|
-
|
|
162
|
-
console.log("[Slide] dispatch", event);
|
|
163
|
-
}
|
|
173
|
+
log("[Slide] dispatch", event);
|
|
164
174
|
this.room.dispatchMagixEvent(this.channel, payload);
|
|
165
175
|
}
|
|
166
176
|
};
|
|
@@ -170,9 +180,7 @@ export class SlideController {
|
|
|
170
180
|
const { type, payload } = ev.payload as MagixPayload;
|
|
171
181
|
if (type === SLIDE_EVENTS.syncDispatch) {
|
|
172
182
|
this.syncStateOnce();
|
|
173
|
-
|
|
174
|
-
console.log("[Slide] receive", payload);
|
|
175
|
-
}
|
|
183
|
+
log("[Slide] receive", payload);
|
|
176
184
|
// only emit the event if the slide state is sync-ed
|
|
177
185
|
if (!this.syncStateOnceFlag) {
|
|
178
186
|
this.slide.emit(SLIDE_EVENTS.syncReceive, payload);
|
|
@@ -186,9 +194,7 @@ export class SlideController {
|
|
|
186
194
|
if (this.syncStateOnceFlag) {
|
|
187
195
|
const { state } = { ...EmptyAttributes, ...this.context.getAttributes() };
|
|
188
196
|
if (state) {
|
|
189
|
-
|
|
190
|
-
console.log("[Slide] sync with state (once)", deepClone(state));
|
|
191
|
-
}
|
|
197
|
+
log("[Slide] sync with state (once)", deepClone(state));
|
|
192
198
|
this.slide.setSlideState(deepClone(state));
|
|
193
199
|
this.syncStateOnceFlag = false;
|
|
194
200
|
}
|
|
@@ -197,40 +203,44 @@ export class SlideController {
|
|
|
197
203
|
|
|
198
204
|
private onStateChange = (state: SlideState) => {
|
|
199
205
|
if (this.context.getIsWritable()) {
|
|
200
|
-
|
|
201
|
-
console.log("[Slide] state change", JSON.stringify(state, null, 2));
|
|
202
|
-
}
|
|
206
|
+
verbose("[Slide] state change", JSON.stringify(state, null, 2));
|
|
203
207
|
this.context.updateAttributes(["state"], state);
|
|
204
208
|
}
|
|
205
209
|
};
|
|
206
210
|
|
|
207
|
-
private
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
+
};
|
|
211
218
|
|
|
212
219
|
private pollCount = 0;
|
|
213
220
|
private pollReadyState = () => {
|
|
214
221
|
if (this.ready) {
|
|
215
|
-
this.
|
|
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);
|
|
219
230
|
} else {
|
|
220
231
|
this.pollCount = 0;
|
|
221
|
-
|
|
222
|
-
console.log("[Slide] renderSlide (retry after timeout)", 1);
|
|
223
|
-
}
|
|
232
|
+
log("[Slide] renderSlide (retry after timeout)", 1);
|
|
224
233
|
this.slide.renderSlide(1);
|
|
225
234
|
}
|
|
226
235
|
};
|
|
227
236
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
237
|
+
// cache `slideCount`, because once the slide is frozen,
|
|
238
|
+
// the `slideCount` will be 0
|
|
239
|
+
private _pageCount = 0;
|
|
232
240
|
public get pageCount() {
|
|
233
|
-
return this.
|
|
241
|
+
if (this._pageCount > 0) return this._pageCount;
|
|
242
|
+
this._pageCount = this.slide.slideCount;
|
|
243
|
+
return this._pageCount;
|
|
234
244
|
}
|
|
235
245
|
|
|
236
246
|
public get page() {
|
|
@@ -243,7 +253,7 @@ export class SlideController {
|
|
|
243
253
|
interactive: true,
|
|
244
254
|
mode: "interactive",
|
|
245
255
|
resize: true,
|
|
246
|
-
controller:
|
|
256
|
+
controller: logger.enable,
|
|
247
257
|
renderOptions: {
|
|
248
258
|
minFPS: 25,
|
|
249
259
|
maxFPS: 30,
|
|
@@ -254,7 +264,7 @@ export class SlideController {
|
|
|
254
264
|
},
|
|
255
265
|
timestamp: this.timestamp,
|
|
256
266
|
});
|
|
257
|
-
if (
|
|
267
|
+
if (import.meta.env.DEV) {
|
|
258
268
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
259
269
|
(window as any).slide = slide;
|
|
260
270
|
}
|
|
@@ -266,9 +276,7 @@ export class SlideController {
|
|
|
266
276
|
public destroy() {
|
|
267
277
|
this.sideEffect.flushAll();
|
|
268
278
|
if (!this.destroyed) {
|
|
269
|
-
|
|
270
|
-
console.log("[Slide] destroy slide (once)");
|
|
271
|
-
}
|
|
279
|
+
log("[Slide] destroy slide (once)");
|
|
272
280
|
this.slide.destroy();
|
|
273
281
|
this.destroyed = true;
|
|
274
282
|
}
|
|
@@ -283,4 +291,52 @@ export class SlideController {
|
|
|
283
291
|
return Date.now();
|
|
284
292
|
}
|
|
285
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
|
+
};
|
|
286
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"
|
|
8
|
+
export const ClickThroughAppliances = new Set(["clicker"]);
|
|
9
9
|
|
|
10
10
|
export type MountSlideOptions = Omit<SlideControllerOptions, "context" | "onPageChanged"> & {
|
|
11
11
|
onReady: () => void;
|
|
@@ -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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
this.isViewMounted
|
|
173
|
-
|
|
174
|
-
|
|
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
|
};
|
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
|
|
50
|
-
|
|
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
|
-
|
|
115
|
+
log("[Slide]: destroy");
|
|
116
|
+
apps.delete(context.appId);
|
|
98
117
|
sideEffect.flushAll();
|
|
99
118
|
if (docsViewer) {
|
|
100
119
|
docsViewer.destroy();
|
package/src/utils/bgcolor.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ColorString from "color-string";
|
|
2
|
+
import { log } from "./logger";
|
|
2
3
|
|
|
3
4
|
export function guessBgColor(el: HTMLElement): string {
|
|
4
5
|
try {
|
|
@@ -38,9 +39,7 @@ let cachedBgColor = "";
|
|
|
38
39
|
export function cachedGetBgColor(el: HTMLElement): string {
|
|
39
40
|
if (!cachedBgColor) {
|
|
40
41
|
cachedBgColor = toHex(guessBgColor(el));
|
|
41
|
-
|
|
42
|
-
console.log("[Slide] guess bg color", cachedBgColor);
|
|
43
|
-
}
|
|
42
|
+
log("[Slide] guess bg color", cachedBgColor);
|
|
44
43
|
}
|
|
45
44
|
return cachedBgColor;
|
|
46
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);
|