@netless/app-slide 0.3.0-canary.2 → 0.3.0-canary.21
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/README-zh.md +17 -7
- package/README.md +4 -7
- package/dist/Logger.d.ts +12 -0
- package/dist/Refrigerator.d.ts +3 -1
- package/dist/SlideViewer/footer.d.ts +12 -0
- package/dist/{SlideViewer.d.ts → SlideViewer/index.d.ts} +33 -20
- package/dist/SlideViewer/sidebar.d.ts +12 -0
- package/dist/SlideViewer/typings.d.ts +18 -0
- package/dist/SlideViewer/utils.d.ts +7 -0
- package/dist/connect.d.ts +14 -0
- package/dist/constants.d.ts +1 -1
- package/dist/icons/index.d.ts +5 -0
- package/dist/icons/save.d.ts +1 -0
- package/dist/icons/spinner.d.ts +1 -0
- package/dist/index.d.ts +7 -6
- package/dist/main.js +3 -0
- package/dist/main.js.map +1 -0
- package/dist/main.mjs +1406 -0
- package/dist/main.mjs.map +1 -0
- package/dist/preview.d.ts +5 -0
- package/dist/typings.d.ts +9 -18
- package/dist/utils.d.ts +12 -0
- package/package.json +12 -8
- package/src/Logger.ts +38 -0
- package/src/Refrigerator.ts +11 -1
- package/src/SlideViewer/footer.ts +169 -0
- package/src/SlideViewer/index.ts +329 -0
- package/src/SlideViewer/sidebar.ts +140 -0
- package/src/SlideViewer/typings.ts +21 -0
- package/src/SlideViewer/utils.ts +37 -0
- package/src/connect.ts +152 -0
- package/src/constants.ts +1 -1
- package/src/icons/index.ts +5 -0
- package/src/icons/save.ts +18 -0
- package/src/icons/spinner.ts +37 -0
- package/src/index.ts +64 -192
- package/src/preview.ts +21 -0
- package/src/style.scss +67 -16
- package/src/typings.ts +18 -16
- package/src/utils.ts +90 -0
- package/dist/main.cjs.js +0 -6363
- package/dist/main.cjs.js.map +0 -1
- package/dist/main.es.js +0 -38214
- package/dist/main.es.js.map +0 -1
- package/dist/main.iife.js +0 -6363
- package/dist/main.iife.js.map +0 -1
- package/src/SlideViewer.ts +0 -557
package/src/connect.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { AppContext, Storage, WhiteBoardView } from "@netless/window-manager";
|
|
2
|
+
import type { SideEffectManager } from "side-effect-manager";
|
|
3
|
+
import type { SlideViewer } from "./SlideViewer";
|
|
4
|
+
import type { Attributes } from "./typings";
|
|
5
|
+
|
|
6
|
+
import { get, wrap_class } from "./utils";
|
|
7
|
+
|
|
8
|
+
export interface ConnectParams {
|
|
9
|
+
context: AppContext;
|
|
10
|
+
storage: Storage<Attributes>;
|
|
11
|
+
viewer: SlideViewer;
|
|
12
|
+
sideEffect: SideEffectManager;
|
|
13
|
+
logger: { info: (msg: string) => void };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function connect({ context, storage, viewer, sideEffect, logger }: ConnectParams) {
|
|
17
|
+
const log = logger.info.bind(logger);
|
|
18
|
+
|
|
19
|
+
let whiteboard: WhiteBoardView | null = null;
|
|
20
|
+
|
|
21
|
+
viewer.prepare(({ width, height, slideCount }) => {
|
|
22
|
+
if (context.destroyed) return;
|
|
23
|
+
width && context.box.setStageRatio(height / width);
|
|
24
|
+
whiteboard = context.createWhiteBoardView({ size: slideCount });
|
|
25
|
+
whiteboard.setBaseRect({ width, height });
|
|
26
|
+
|
|
27
|
+
viewer._slideTitle = context.box.title;
|
|
28
|
+
viewer._getWhiteSnapshot = (
|
|
29
|
+
index: number,
|
|
30
|
+
canvas: HTMLCanvasElement,
|
|
31
|
+
ctx: CanvasRenderingContext2D
|
|
32
|
+
) => {
|
|
33
|
+
if (!whiteboard) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const scenePath = whiteboard.pageState.pages[index - 1];
|
|
37
|
+
const { width, height } = whiteboard.view.size;
|
|
38
|
+
canvas.width = width;
|
|
39
|
+
canvas.height = height;
|
|
40
|
+
whiteboard.view.screenshotToCanvas(ctx, scenePath, width, height, whiteboard.view.camera);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
kick_start();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const { slide } = viewer;
|
|
47
|
+
|
|
48
|
+
function dump_state(state: unknown = storage.state.state) {
|
|
49
|
+
return JSON.stringify(state);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function kick_start() {
|
|
53
|
+
if (storage.state.state) {
|
|
54
|
+
log("[Slide] restore " + dump_state());
|
|
55
|
+
slide.setSlideState(storage.state.state);
|
|
56
|
+
} else if (context.isAddApp) {
|
|
57
|
+
log("[Slide] kick start");
|
|
58
|
+
slide.renderSlide(1);
|
|
59
|
+
} else {
|
|
60
|
+
log("[Slide] wait for restore");
|
|
61
|
+
const timeoutID = sideEffect.add(() => {
|
|
62
|
+
const timer = setInterval(() => {
|
|
63
|
+
if (!storage.state.state) {
|
|
64
|
+
log("[Slide] timeout");
|
|
65
|
+
slide.renderSlide(1);
|
|
66
|
+
}
|
|
67
|
+
}, 15 * 1000);
|
|
68
|
+
return () => clearInterval(timer);
|
|
69
|
+
});
|
|
70
|
+
const disposerID = sideEffect.push(
|
|
71
|
+
storage.on("stateChanged", () => {
|
|
72
|
+
if (storage.state.state) {
|
|
73
|
+
log("[Slide] restore " + dump_state());
|
|
74
|
+
slide.setSlideState(storage.state.state);
|
|
75
|
+
sideEffect.flush(disposerID);
|
|
76
|
+
sideEffect.flush(timeoutID);
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// setup sync event
|
|
84
|
+
slide.on("stateChange", () => {
|
|
85
|
+
if (storage.isWritable) {
|
|
86
|
+
// log("[Slide] save state " + dump_state(slide.slideState));
|
|
87
|
+
storage.setState({ state: slide.slideState });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
slide.on("syncDispatch", payload => {
|
|
91
|
+
if (context.isWritable) {
|
|
92
|
+
log("[Slide] dispatch " + dump_state(get(payload, "uuid")));
|
|
93
|
+
context.dispatchMagixEvent("syncDispatch", payload);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
sideEffect.push(
|
|
97
|
+
context.addMagixEventListener(
|
|
98
|
+
"syncDispatch",
|
|
99
|
+
({ payload }) => {
|
|
100
|
+
log("[Slide] receive " + dump_state(get(payload, "uuid")));
|
|
101
|
+
slide.emit("syncReceive", payload);
|
|
102
|
+
},
|
|
103
|
+
{ fireSelfEventAfterCommit: true }
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// save state
|
|
108
|
+
slide.on("slideChange", page => {
|
|
109
|
+
if (context.isWritable && whiteboard) {
|
|
110
|
+
log("[Slide] page to " + page);
|
|
111
|
+
whiteboard.jumpPage(page - 1);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// handle ui events
|
|
116
|
+
function disable_whiteboard() {
|
|
117
|
+
if (whiteboard && whiteboard.view.divElement) {
|
|
118
|
+
whiteboard.view.divElement.classList.add(wrap_class("wb-view-hidden"));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function enable_whiteboard() {
|
|
123
|
+
if (whiteboard && whiteboard.view.divElement) {
|
|
124
|
+
whiteboard.view.divElement.classList.remove(wrap_class("wb-view-hidden"));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
viewer.setReadonly(!context.isWritable);
|
|
129
|
+
sideEffect.push([
|
|
130
|
+
context.emitter.on("writableChange", () => viewer.setReadonly(!context.isWritable)),
|
|
131
|
+
viewer.events.on("renderStart", disable_whiteboard),
|
|
132
|
+
viewer.events.on("renderEnd", enable_whiteboard),
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
const isEditable = (el: EventTarget | null) => {
|
|
136
|
+
if (!el) return false;
|
|
137
|
+
const tagName = (el as HTMLElement).tagName;
|
|
138
|
+
return tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT";
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
sideEffect.addEventListener(window, "keydown", ev => {
|
|
142
|
+
if (!ev || (ev.target as HTMLElement).className === "telebox-quarantine-outer") return;
|
|
143
|
+
if (context.box.focus && !context.box.readonly && !isEditable(ev.target)) {
|
|
144
|
+
if (ev.key === "ArrowUp" || ev.key === "ArrowLeft") {
|
|
145
|
+
viewer.slide.prevStep();
|
|
146
|
+
}
|
|
147
|
+
if (ev.key === "ArrowRight" || ev.key === "ArrowDown") {
|
|
148
|
+
viewer.slide.nextStep();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
package/src/constants.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function saveSVG(namespace: string): SVGElement {
|
|
2
|
+
const NS = "http://www.w3.org/2000/svg";
|
|
3
|
+
|
|
4
|
+
const $svg = document.createElementNS(NS, "svg");
|
|
5
|
+
$svg.setAttribute("class", `${namespace}-footer-icon-save`);
|
|
6
|
+
$svg.setAttribute("viewBox", "0 0 448 512");
|
|
7
|
+
|
|
8
|
+
const $path = document.createElementNS(NS, "path");
|
|
9
|
+
$path.setAttribute("fill", "currentColor");
|
|
10
|
+
$path.setAttribute(
|
|
11
|
+
"d",
|
|
12
|
+
"M224 256c-35.2 0-64 28.8-64 64c0 35.2 28.8 64 64 64c35.2 0 64-28.8 64-64C288 284.8 259.2 256 224 256zM433.1 129.1l-83.9-83.9C341.1 37.06 328.8 32 316.1 32H64C28.65 32 0 60.65 0 96v320c0 35.35 28.65 64 64 64h320c35.35 0 64-28.65 64-64V163.9C448 151.2 442.9 138.9 433.1 129.1zM128 80h144V160H128V80zM400 416c0 8.836-7.164 16-16 16H64c-8.836 0-16-7.164-16-16V96c0-8.838 7.164-16 16-16h16v104c0 13.25 10.75 24 24 24h192C309.3 208 320 197.3 320 184V83.88l78.25 78.25C399.4 163.2 400 164.8 400 166.3V416z"
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
$svg.appendChild($path);
|
|
16
|
+
|
|
17
|
+
return $svg;
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function spinnerSVG(namespace: string): SVGElement {
|
|
2
|
+
const NS = "http://www.w3.org/2000/svg";
|
|
3
|
+
|
|
4
|
+
const $svg = document.createElementNS(NS, "svg");
|
|
5
|
+
$svg.setAttribute("class", `${namespace}-footer-icon-spinner`);
|
|
6
|
+
$svg.setAttribute("viewBox", "0 0 512 512");
|
|
7
|
+
|
|
8
|
+
const $path = document.createElementNS(NS, "path");
|
|
9
|
+
$path.setAttribute("fill", "currentColor");
|
|
10
|
+
$path.setAttribute(
|
|
11
|
+
"d",
|
|
12
|
+
"M304 48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zm0 416c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM48 304c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48zm464-48c0-26.5-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48s48-21.5 48-48zM142.9 437c18.7-18.7 18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zm0-294.2c18.7-18.7 18.7-49.1 0-67.9S93.7 56.2 75 75s-18.7 49.1 0 67.9s49.1 18.7 67.9 0zM369.1 437c18.7 18.7 49.1 18.7 67.9 0s18.7-49.1 0-67.9s-49.1-18.7-67.9 0s-18.7 49.1 0 67.9z"
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const $animate = document.createElementNS(NS, "animateTransform");
|
|
16
|
+
$animate.setAttribute("attributeName", "transform");
|
|
17
|
+
$animate.setAttribute("attributeType", "xml");
|
|
18
|
+
$animate.setAttribute("type", "rotate");
|
|
19
|
+
$animate.setAttribute("from", "0 256 256");
|
|
20
|
+
$animate.setAttribute("to", "360 256 256");
|
|
21
|
+
$animate.setAttribute("begin", "0s");
|
|
22
|
+
$animate.setAttribute("dur", "2s");
|
|
23
|
+
$animate.setAttribute("fill", "freeze");
|
|
24
|
+
$animate.setAttribute("repeatCount", "indefinite");
|
|
25
|
+
$path.appendChild($animate);
|
|
26
|
+
|
|
27
|
+
const $progress = document.createElementNS(NS, "text");
|
|
28
|
+
$progress.setAttribute("x", "125");
|
|
29
|
+
$progress.setAttribute("y", "345");
|
|
30
|
+
$progress.setAttribute("data-id", "progress");
|
|
31
|
+
$progress.style.fontSize = "246px";
|
|
32
|
+
$progress.textContent = "20";
|
|
33
|
+
$svg.appendChild($progress);
|
|
34
|
+
$svg.appendChild($path);
|
|
35
|
+
|
|
36
|
+
return $svg;
|
|
37
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,31 +1,45 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { NetlessApp } from "@netless/window-manager";
|
|
2
|
+
import type { ISlideConfig } from "@netless/slide";
|
|
3
3
|
import type { SlideViewerOptions } from "./SlideViewer";
|
|
4
|
+
import type { AddHooks, AppOptions, Attributes, MagixEvents, SlideState } from "./typings";
|
|
4
5
|
|
|
5
6
|
export type { SlideState, AddHooks, AppOptions, Attributes, MagixEvents, SlideViewerOptions };
|
|
6
7
|
|
|
7
|
-
import
|
|
8
|
+
import { Slide } from "@netless/slide";
|
|
8
9
|
import { SideEffectManager } from "side-effect-manager";
|
|
10
|
+
|
|
11
|
+
import { DefaultUrl } from "./constants";
|
|
12
|
+
import { addHooks, refrigerator } from "./Refrigerator";
|
|
9
13
|
import { SlideViewer } from "./SlideViewer";
|
|
10
|
-
import {
|
|
14
|
+
import { Logger } from "./Logger";
|
|
15
|
+
import { connect } from "./connect";
|
|
16
|
+
import { previewSlide } from "./preview";
|
|
17
|
+
import { make_bg_color, make_timestamp } from "./utils";
|
|
11
18
|
|
|
12
|
-
export { SlideViewer, refrigerator, addHooks };
|
|
19
|
+
export { Slide, SlideViewer, refrigerator, addHooks, DefaultUrl, previewSlide };
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
export const usePlugin = /* @__PURE__ */ Slide.usePlugin.bind(Slide) as (plugin: any) => void;
|
|
13
23
|
|
|
14
24
|
export const version = __APP_VERSION__;
|
|
15
25
|
|
|
16
26
|
const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, void> = {
|
|
17
27
|
kind: "Slide",
|
|
28
|
+
config: { enableShadowDOM: false },
|
|
18
29
|
setup(context) {
|
|
19
|
-
console.log("[Slide] setup @ " + __APP_VERSION__);
|
|
30
|
+
console.log("[Slide] setup @ " + __APP_VERSION__ + " (" + context.appId + ")");
|
|
31
|
+
|
|
32
|
+
const logger = new Logger(context);
|
|
33
|
+
logger.info("[Slide] setup @ " + __APP_VERSION__);
|
|
20
34
|
|
|
21
|
-
if (!context.
|
|
35
|
+
if (!context.attributes.taskId) {
|
|
22
36
|
throw new Error("[Slide] no taskId");
|
|
23
37
|
}
|
|
24
38
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
39
|
+
const storage = context.createStorage("slide", context.attributes);
|
|
40
|
+
|
|
41
|
+
const appOptions = context.getAppOptions() || {};
|
|
42
|
+
const renderOptions = appOptions.renderOptions || {};
|
|
29
43
|
|
|
30
44
|
const sideEffect = new SideEffectManager();
|
|
31
45
|
|
|
@@ -33,213 +47,71 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, void> = {
|
|
|
33
47
|
const viewer = new SlideViewer({
|
|
34
48
|
interactive: true,
|
|
35
49
|
mode: "interactive",
|
|
36
|
-
resize: true,
|
|
37
50
|
enableGlobalClick: true,
|
|
38
|
-
controller: debug,
|
|
39
51
|
timestamp: make_timestamp(context),
|
|
40
|
-
logger,
|
|
52
|
+
logger: appOptions.logger || logger,
|
|
53
|
+
whiteTracker: (context.displayer as unknown as { tracker: ISlideConfig["whiteTracker"] }).tracker,
|
|
41
54
|
renderOptions: {
|
|
42
|
-
minFPS:
|
|
43
|
-
maxFPS:
|
|
44
|
-
autoFPS:
|
|
45
|
-
autoResolution:
|
|
46
|
-
resolution:
|
|
47
|
-
|
|
55
|
+
minFPS: renderOptions.minFPS || 25,
|
|
56
|
+
maxFPS: renderOptions.maxFPS || 30,
|
|
57
|
+
autoFPS: renderOptions.autoFPS ?? true,
|
|
58
|
+
autoResolution: renderOptions.autoResolution ?? true,
|
|
59
|
+
resolution: renderOptions.resolution,
|
|
60
|
+
maxResolutionLevel: renderOptions.maxResolutionLevel,
|
|
61
|
+
transactionBgColor: renderOptions.transactionBgColor || make_bg_color(context.box.$content),
|
|
48
62
|
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
rtcAudio: appOptions.rtcAudio,
|
|
64
|
+
useLocalCache: appOptions.useLocalCache,
|
|
65
|
+
resourceTimeout: appOptions.resourceTimeout,
|
|
66
|
+
loaderDelegate: appOptions.loaderDelegate,
|
|
67
|
+
navigatorDelegate: appOptions.navigatorDelegate,
|
|
68
|
+
fixedFrameSize: appOptions.fixedFrameSize,
|
|
69
|
+
taskId: storage.state.taskId,
|
|
70
|
+
url: storage.state.url,
|
|
52
71
|
});
|
|
53
72
|
|
|
54
73
|
// For debugging.
|
|
55
|
-
|
|
56
|
-
(viewer as any).context = context;
|
|
74
|
+
Object.assign(viewer, { context, logger });
|
|
57
75
|
|
|
58
|
-
sideEffect.
|
|
76
|
+
sideEffect.push(refrigerator.set(context.appId, viewer, context.box));
|
|
59
77
|
|
|
60
78
|
if (import.meta.env.DEV) {
|
|
61
79
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
62
80
|
(window as any).slideViewer = viewer;
|
|
63
81
|
}
|
|
64
82
|
|
|
83
|
+
// Log more
|
|
84
|
+
sideEffect.push(viewer.events.on("freeze", () => logger.info("[Slide] freeze")));
|
|
85
|
+
sideEffect.push(viewer.events.on("unfreeze", () => logger.info("[Slide] unfreeze")));
|
|
86
|
+
sideEffect.push(
|
|
87
|
+
viewer.events.on("prepareError", error =>
|
|
88
|
+
logger.info("[Slide] fetch slide info failed: " + error.message)
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
sideEffect.push(
|
|
92
|
+
viewer.events.on("renderError", ({ error, index }) =>
|
|
93
|
+
logger.info("[Slide] render failed " + `[${index}]: ${error.message}`)
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
|
|
65
97
|
// Mount UI
|
|
66
98
|
context.box.mountStyles(SlideViewer.styles);
|
|
67
99
|
context.box.mountStage(viewer.$slide);
|
|
68
100
|
context.box.mountFooter(viewer.$footer);
|
|
69
101
|
context.box.mountContent(viewer.$content);
|
|
70
|
-
sideEffect.
|
|
102
|
+
sideEffect.push(() => viewer.destroy());
|
|
71
103
|
|
|
72
104
|
// Connect UI with Netless App
|
|
73
|
-
connect({ context, viewer, sideEffect, logger });
|
|
105
|
+
connect({ context, storage, viewer, sideEffect, logger });
|
|
74
106
|
|
|
75
|
-
|
|
107
|
+
context.emitter.on("destroy", () => {
|
|
76
108
|
logger.info("[Slide] destroy");
|
|
109
|
+
logger.destroy();
|
|
77
110
|
sideEffect.flushAll();
|
|
78
111
|
});
|
|
112
|
+
|
|
113
|
+
return viewer;
|
|
79
114
|
},
|
|
80
115
|
};
|
|
81
116
|
|
|
82
117
|
export default SlideApp;
|
|
83
|
-
|
|
84
|
-
export function previewSlide(options: SlideViewerOptions & { container: HTMLElement }) {
|
|
85
|
-
const { container } = options;
|
|
86
|
-
container.style.cssText = "display:flex;flex-direction:column";
|
|
87
|
-
const viewer = new SlideViewer(options);
|
|
88
|
-
container.appendChild(document.createElement("style")).textContent = SlideViewer.styles;
|
|
89
|
-
container.appendChild(viewer.$content);
|
|
90
|
-
container.appendChild(viewer.$footer);
|
|
91
|
-
viewer.prepare(() => viewer.slide.renderSlide(1));
|
|
92
|
-
return viewer;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function make_timestamp(context: AppContext): () => number {
|
|
96
|
-
const room = context.room;
|
|
97
|
-
const player = context.displayer as Player;
|
|
98
|
-
if (room) {
|
|
99
|
-
return () => room.calibrationTimestamp;
|
|
100
|
-
} else if (player) {
|
|
101
|
-
return () => player.beginTimestamp + player.progressTime;
|
|
102
|
-
} else {
|
|
103
|
-
return () => Date.now();
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function make_logger(context: AppContext): { info: (msg: string) => void } {
|
|
108
|
-
if (import.meta.env.DEV) {
|
|
109
|
-
return { info: console.debug.bind(console) };
|
|
110
|
-
}
|
|
111
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
112
|
-
return (context.displayer as any).logger;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function make_bg_color(el: HTMLElement): string {
|
|
116
|
-
try {
|
|
117
|
-
let bg = window.getComputedStyle(el).backgroundColor;
|
|
118
|
-
if (bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
|
119
|
-
bg = hex(bg);
|
|
120
|
-
console.log("[Slide] guess bg color:", bg);
|
|
121
|
-
return bg;
|
|
122
|
-
}
|
|
123
|
-
if (el.parentElement) {
|
|
124
|
-
return make_bg_color(el.parentElement);
|
|
125
|
-
}
|
|
126
|
-
} catch {
|
|
127
|
-
// ignored
|
|
128
|
-
}
|
|
129
|
-
return "#ffffff";
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// https://github.com/Qix-/color-convert/blob/8dfdbbc6b46fa6a305bf394d942cc1b08e23fca5/conversions.js#L616
|
|
133
|
-
function hex(color: string): string {
|
|
134
|
-
const result = ColorString.get(color);
|
|
135
|
-
if (result && result.model === "rgb") {
|
|
136
|
-
const args = result.value;
|
|
137
|
-
|
|
138
|
-
const integer =
|
|
139
|
-
((Math.round(args[0]) & 0xff) << 16) +
|
|
140
|
-
((Math.round(args[1]) & 0xff) << 8) +
|
|
141
|
-
(Math.round(args[2]) & 0xff);
|
|
142
|
-
|
|
143
|
-
const string = integer.toString(16);
|
|
144
|
-
return "#" + "000000".substring(string.length) + string;
|
|
145
|
-
} else {
|
|
146
|
-
return color;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function onUnmount(context: AppContext, callback: () => void) {
|
|
151
|
-
context.emitter.on("destroy", callback);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
interface ConnectParams {
|
|
155
|
-
context: AppContext;
|
|
156
|
-
viewer: SlideViewer;
|
|
157
|
-
sideEffect: SideEffectManager;
|
|
158
|
-
logger: { info: (msg: string) => void };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function connect({ context, viewer, sideEffect, logger }: ConnectParams) {
|
|
162
|
-
const log = import.meta.env.DEV ? console.log.bind(console) : logger.info.bind(logger);
|
|
163
|
-
|
|
164
|
-
let whiteboard: WhiteBoardView | null = null;
|
|
165
|
-
|
|
166
|
-
viewer.prepare(({ width, height, slideCount }) => {
|
|
167
|
-
if (context.destroyed) return;
|
|
168
|
-
width && context.box.setStageRatio(height / width);
|
|
169
|
-
whiteboard = context.createWhiteBoardView(slideCount);
|
|
170
|
-
kick_start();
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const { slide } = viewer;
|
|
174
|
-
|
|
175
|
-
function kick_start() {
|
|
176
|
-
if (context.storage.state.state) {
|
|
177
|
-
log("[Slide] restore", { ...context.storage.state.state });
|
|
178
|
-
slide.setSlideState(context.storage.state.state);
|
|
179
|
-
} else if (context.isAddApp) {
|
|
180
|
-
log("[Slide] kick start");
|
|
181
|
-
slide.renderSlide(1);
|
|
182
|
-
} else {
|
|
183
|
-
log("[Slide] wait for restore");
|
|
184
|
-
const timeoutID = sideEffect.add(() => {
|
|
185
|
-
const timer = setInterval(() => {
|
|
186
|
-
if (!context.storage.state.state) {
|
|
187
|
-
log("[Slide] timeout");
|
|
188
|
-
slide.renderSlide(1);
|
|
189
|
-
}
|
|
190
|
-
}, 15 * 1000);
|
|
191
|
-
return () => clearInterval(timer);
|
|
192
|
-
});
|
|
193
|
-
const disposerID = sideEffect.addDisposer(
|
|
194
|
-
context.storage.addStateChangedListener(() => {
|
|
195
|
-
if (context.storage.state.state) {
|
|
196
|
-
log("[Slide] restore", { ...context.storage.state.state });
|
|
197
|
-
slide.setSlideState(context.storage.state.state);
|
|
198
|
-
sideEffect.flush(disposerID);
|
|
199
|
-
sideEffect.flush(timeoutID);
|
|
200
|
-
}
|
|
201
|
-
})
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// setup sync event
|
|
207
|
-
slide.on("stateChange", () => {
|
|
208
|
-
if (context.isWritable) {
|
|
209
|
-
log("[Slide] save state", { ...slide.slideState });
|
|
210
|
-
context.storage.setState({ state: slide.slideState });
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
slide.on("syncDispatch", payload => {
|
|
214
|
-
if (context.isWritable) {
|
|
215
|
-
log("[Slide] dispatch", { ...payload });
|
|
216
|
-
context.dispatchMagixEvent("syncDispatch", payload);
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
sideEffect.addDisposer(
|
|
220
|
-
context.addMagixEventListener(
|
|
221
|
-
"syncDispatch",
|
|
222
|
-
({ payload }) => {
|
|
223
|
-
log("[Slide] receive", { ...payload });
|
|
224
|
-
slide.emit("syncReceive", payload);
|
|
225
|
-
},
|
|
226
|
-
{ fireSelfEventAfterCommit: true }
|
|
227
|
-
)
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
// save state
|
|
231
|
-
slide.on("slideChange", page => {
|
|
232
|
-
if (context.isWritable && whiteboard) {
|
|
233
|
-
log("[Slide] page to", page);
|
|
234
|
-
whiteboard.jumpPage(page - 1);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// handle ui events
|
|
239
|
-
viewer.setReadonly(!context.isWritable);
|
|
240
|
-
sideEffect.addDisposer(
|
|
241
|
-
context.emitter.on("writableChange", w => {
|
|
242
|
-
viewer.setReadonly(!w);
|
|
243
|
-
})
|
|
244
|
-
);
|
|
245
|
-
}
|
package/src/preview.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SlideViewerOptions } from "./SlideViewer";
|
|
2
|
+
|
|
3
|
+
import { SlideViewer } from "./SlideViewer";
|
|
4
|
+
import { hc } from "./utils";
|
|
5
|
+
|
|
6
|
+
export function previewSlide(options: SlideViewerOptions & { container: HTMLElement }) {
|
|
7
|
+
const { container, ...slideOptions } = options;
|
|
8
|
+
const wrapper = hc("div", "preview-wrapper");
|
|
9
|
+
const viewer = new SlideViewer(slideOptions);
|
|
10
|
+
wrapper.appendChild(document.createElement("style")).textContent = SlideViewer.styles;
|
|
11
|
+
wrapper.appendChild(viewer.$content);
|
|
12
|
+
wrapper.appendChild(viewer.$footer);
|
|
13
|
+
container.appendChild(wrapper);
|
|
14
|
+
viewer.prepare(() => viewer.slide.renderSlide(1));
|
|
15
|
+
return viewer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (import.meta.env.DEV) {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
(window as any).previewSlide = previewSlide;
|
|
21
|
+
}
|