@netless/app-slide 0.3.0-canary.11 → 0.3.0-canary.13
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/Logger.d.ts +12 -0
- package/dist/SlideViewer/footer.d.ts +11 -0
- package/dist/{SlideViewer.d.ts → SlideViewer/index.d.ts} +24 -31
- 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/index.d.ts +5 -7
- package/dist/main.iife.js +2 -2
- package/dist/main.iife.js.map +1 -1
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/dist/main.mjs +834 -664
- package/dist/main.mjs.map +1 -1
- package/dist/preview.d.ts +5 -0
- package/dist/typings.d.ts +0 -1
- package/dist/utils.d.ts +12 -0
- package/package.json +3 -2
- package/src/Logger.ts +38 -0
- package/src/SlideViewer/footer.ts +146 -0
- package/src/SlideViewer/index.ts +235 -0
- package/src/SlideViewer/sidebar.ts +139 -0
- package/src/SlideViewer/typings.ts +21 -0
- package/src/SlideViewer/utils.ts +37 -0
- package/src/connect.ts +116 -0
- package/src/constants.ts +1 -1
- package/src/icons/index.ts +5 -0
- package/src/index.ts +48 -211
- package/src/preview.ts +21 -0
- package/src/typings.ts +1 -3
- package/src/utils.ts +90 -0
- package/src/SlideViewer.ts +0 -576
package/src/connect.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
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 } 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
|
+
kick_start();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const { slide } = viewer;
|
|
30
|
+
|
|
31
|
+
function dump_state(state: unknown = storage.state.state) {
|
|
32
|
+
return JSON.stringify(state);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function kick_start() {
|
|
36
|
+
if (storage.state.state) {
|
|
37
|
+
log("[Slide] restore " + dump_state());
|
|
38
|
+
slide.setSlideState(storage.state.state);
|
|
39
|
+
} else if (context.isAddApp) {
|
|
40
|
+
log("[Slide] kick start");
|
|
41
|
+
slide.renderSlide(1);
|
|
42
|
+
} else {
|
|
43
|
+
log("[Slide] wait for restore");
|
|
44
|
+
const timeoutID = sideEffect.add(() => {
|
|
45
|
+
const timer = setInterval(() => {
|
|
46
|
+
if (!storage.state.state) {
|
|
47
|
+
log("[Slide] timeout");
|
|
48
|
+
slide.renderSlide(1);
|
|
49
|
+
}
|
|
50
|
+
}, 15 * 1000);
|
|
51
|
+
return () => clearInterval(timer);
|
|
52
|
+
});
|
|
53
|
+
const disposerID = sideEffect.push(
|
|
54
|
+
storage.on("stateChanged", () => {
|
|
55
|
+
if (storage.state.state) {
|
|
56
|
+
log("[Slide] restore " + dump_state());
|
|
57
|
+
slide.setSlideState(storage.state.state);
|
|
58
|
+
sideEffect.flush(disposerID);
|
|
59
|
+
sideEffect.flush(timeoutID);
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// setup sync event
|
|
67
|
+
slide.on("stateChange", () => {
|
|
68
|
+
if (context.isWritable) {
|
|
69
|
+
// log("[Slide] save state " + dump_state(slide.slideState));
|
|
70
|
+
storage.setState({ state: slide.slideState });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
slide.on("syncDispatch", payload => {
|
|
74
|
+
if (context.isWritable) {
|
|
75
|
+
log("[Slide] dispatch " + dump_state(get(payload, "uuid")));
|
|
76
|
+
context.dispatchMagixEvent("syncDispatch", payload);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
sideEffect.push(
|
|
80
|
+
context.addMagixEventListener(
|
|
81
|
+
"syncDispatch",
|
|
82
|
+
({ payload }) => {
|
|
83
|
+
log("[Slide] receive " + dump_state(get(payload, "uuid")));
|
|
84
|
+
slide.emit("syncReceive", payload);
|
|
85
|
+
},
|
|
86
|
+
{ fireSelfEventAfterCommit: true }
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// save state
|
|
91
|
+
slide.on("slideChange", page => {
|
|
92
|
+
if (context.isWritable && whiteboard) {
|
|
93
|
+
log("[Slide] page to " + page);
|
|
94
|
+
whiteboard.jumpPage(page - 1);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// handle ui events
|
|
99
|
+
viewer.setReadonly(!context.isWritable);
|
|
100
|
+
sideEffect.push(
|
|
101
|
+
context.emitter.on("writableChange", w => {
|
|
102
|
+
viewer.setReadonly(!w);
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
sideEffect.addEventListener(window, "keydown", ev => {
|
|
107
|
+
if (context.box.focus) {
|
|
108
|
+
if (ev.key === "ArrowUp" || ev.key === "ArrowLeft") {
|
|
109
|
+
viewer.slide.prevStep();
|
|
110
|
+
}
|
|
111
|
+
if (ev.key === "ArrowRight" || ev.key === "ArrowDown") {
|
|
112
|
+
viewer.slide.nextStep();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
package/src/constants.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { AddHooks, AppOptions, Attributes, MagixEvents, SlideState } from "./typings";
|
|
1
|
+
import type { NetlessApp } from "@netless/window-manager";
|
|
3
2
|
import type { SlideViewerOptions } from "./SlideViewer";
|
|
3
|
+
import type { AddHooks, AppOptions, Attributes, MagixEvents, SlideState } from "./typings";
|
|
4
4
|
|
|
5
5
|
export type { SlideState, AddHooks, AppOptions, Attributes, MagixEvents, SlideViewerOptions };
|
|
6
6
|
|
|
7
|
-
import ColorString from "color-string";
|
|
8
|
-
import { SideEffectManager } from "side-effect-manager";
|
|
9
7
|
import { Slide } from "@netless/slide";
|
|
10
|
-
import {
|
|
11
|
-
|
|
8
|
+
import { SideEffectManager } from "side-effect-manager";
|
|
9
|
+
|
|
12
10
|
import { DefaultUrl } from "./constants";
|
|
11
|
+
import { addHooks, refrigerator } from "./Refrigerator";
|
|
12
|
+
import { SlideViewer } from "./SlideViewer";
|
|
13
|
+
import { Logger } from "./Logger";
|
|
14
|
+
import { connect } from "./connect";
|
|
15
|
+
import { previewSlide } from "./preview";
|
|
16
|
+
import { make_bg_color, make_timestamp } from "./utils";
|
|
13
17
|
|
|
14
|
-
export { Slide, SlideViewer, refrigerator, addHooks, DefaultUrl };
|
|
18
|
+
export { Slide, SlideViewer, refrigerator, addHooks, DefaultUrl, previewSlide };
|
|
15
19
|
|
|
16
20
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
21
|
export const usePlugin = /* @__PURE__ */ Slide.usePlugin.bind(Slide) as (plugin: any) => void;
|
|
@@ -21,16 +25,19 @@ export const version = __APP_VERSION__;
|
|
|
21
25
|
const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, void> = {
|
|
22
26
|
kind: "Slide",
|
|
23
27
|
setup(context) {
|
|
24
|
-
console.log("[Slide] setup @ " + __APP_VERSION__);
|
|
28
|
+
console.log("[Slide] setup @ " + __APP_VERSION__ + " (" + context.appId + ")");
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
const logger = new Logger(context);
|
|
31
|
+
logger.info("[Slide] setup @ " + __APP_VERSION__);
|
|
32
|
+
|
|
33
|
+
if (!context.attributes.taskId) {
|
|
27
34
|
throw new Error("[Slide] no taskId");
|
|
28
35
|
}
|
|
29
36
|
|
|
37
|
+
const storage = context.createStorage("slide", context.attributes);
|
|
38
|
+
|
|
30
39
|
const appOptions = context.getAppOptions() || {};
|
|
31
|
-
const
|
|
32
|
-
const logger = make_logger(context, appOptions.debug);
|
|
33
|
-
logger.info("[Slide] setup @ " + __APP_VERSION__);
|
|
40
|
+
const renderOptions = appOptions.renderOptions || {};
|
|
34
41
|
|
|
35
42
|
const sideEffect = new SideEffectManager();
|
|
36
43
|
|
|
@@ -38,18 +45,17 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, void> = {
|
|
|
38
45
|
const viewer = new SlideViewer({
|
|
39
46
|
interactive: true,
|
|
40
47
|
mode: "interactive",
|
|
41
|
-
resize: true,
|
|
42
48
|
enableGlobalClick: true,
|
|
43
49
|
timestamp: make_timestamp(context),
|
|
44
50
|
logger: appOptions.logger || logger,
|
|
45
51
|
renderOptions: {
|
|
46
|
-
minFPS:
|
|
47
|
-
maxFPS:
|
|
48
|
-
autoFPS:
|
|
49
|
-
autoResolution:
|
|
50
|
-
resolution:
|
|
51
|
-
maxResolutionLevel:
|
|
52
|
-
transactionBgColor:
|
|
52
|
+
minFPS: renderOptions.minFPS || 25,
|
|
53
|
+
maxFPS: renderOptions.maxFPS || 30,
|
|
54
|
+
autoFPS: renderOptions.autoFPS ?? true,
|
|
55
|
+
autoResolution: renderOptions.autoResolution ?? true,
|
|
56
|
+
resolution: renderOptions.resolution,
|
|
57
|
+
maxResolutionLevel: renderOptions.maxResolutionLevel,
|
|
58
|
+
transactionBgColor: renderOptions.transactionBgColor || make_bg_color(context.box.$content),
|
|
53
59
|
},
|
|
54
60
|
rtcAudio: appOptions.rtcAudio,
|
|
55
61
|
useLocalCache: appOptions.useLocalCache,
|
|
@@ -57,33 +63,47 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, void> = {
|
|
|
57
63
|
loaderDelegate: appOptions.loaderDelegate,
|
|
58
64
|
navigatorDelegate: appOptions.navigatorDelegate,
|
|
59
65
|
fixedFrameSize: appOptions.fixedFrameSize,
|
|
60
|
-
taskId:
|
|
61
|
-
url:
|
|
66
|
+
taskId: storage.state.taskId,
|
|
67
|
+
url: storage.state.url,
|
|
62
68
|
});
|
|
63
69
|
|
|
64
70
|
// For debugging.
|
|
65
|
-
|
|
66
|
-
(viewer as any).context = context;
|
|
71
|
+
Object.assign(viewer, { context, logger });
|
|
67
72
|
|
|
68
|
-
sideEffect.
|
|
73
|
+
sideEffect.push(refrigerator.set(context.appId, viewer));
|
|
69
74
|
|
|
70
75
|
if (import.meta.env.DEV) {
|
|
71
76
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
77
|
(window as any).slideViewer = viewer;
|
|
73
78
|
}
|
|
74
79
|
|
|
80
|
+
// Log more
|
|
81
|
+
sideEffect.push(viewer.events.on("freeze", () => logger.info("[Slide] freeze")));
|
|
82
|
+
sideEffect.push(viewer.events.on("unfreeze", () => logger.info("[Slide] unfreeze")));
|
|
83
|
+
sideEffect.push(
|
|
84
|
+
viewer.events.on("prepareError", error =>
|
|
85
|
+
logger.info("[Slide] fetch slide info failed: " + error.message)
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
sideEffect.push(
|
|
89
|
+
viewer.events.on("renderError", ({ error, index }) =>
|
|
90
|
+
logger.info("[Slide] render failed " + `[${index}]: ${error.message}`)
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
|
|
75
94
|
// Mount UI
|
|
76
95
|
context.box.mountStyles(SlideViewer.styles);
|
|
77
96
|
context.box.mountStage(viewer.$slide);
|
|
78
97
|
context.box.mountFooter(viewer.$footer);
|
|
79
98
|
context.box.mountContent(viewer.$content);
|
|
80
|
-
sideEffect.
|
|
99
|
+
sideEffect.push(() => viewer.destroy());
|
|
81
100
|
|
|
82
101
|
// Connect UI with Netless App
|
|
83
|
-
connect({ context, viewer, sideEffect, logger });
|
|
102
|
+
connect({ context, storage, viewer, sideEffect, logger });
|
|
84
103
|
|
|
85
|
-
|
|
104
|
+
context.emitter.on("destroy", () => {
|
|
86
105
|
logger.info("[Slide] destroy");
|
|
106
|
+
logger.destroy();
|
|
87
107
|
sideEffect.flushAll();
|
|
88
108
|
});
|
|
89
109
|
|
|
@@ -92,186 +112,3 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, void> = {
|
|
|
92
112
|
};
|
|
93
113
|
|
|
94
114
|
export default SlideApp;
|
|
95
|
-
|
|
96
|
-
export function previewSlide(options: SlideViewerOptions & { container: HTMLElement }) {
|
|
97
|
-
const { container, ...slideOptions } = options;
|
|
98
|
-
const wrapper = document.createElement("div");
|
|
99
|
-
wrapper.className = "netless-app-slide-preview-wrapper";
|
|
100
|
-
const viewer = new SlideViewer(slideOptions);
|
|
101
|
-
wrapper.appendChild(document.createElement("style")).textContent = SlideViewer.styles;
|
|
102
|
-
wrapper.appendChild(viewer.$content);
|
|
103
|
-
wrapper.appendChild(viewer.$footer);
|
|
104
|
-
container.appendChild(wrapper);
|
|
105
|
-
viewer.prepare(() => viewer.slide.renderSlide(1));
|
|
106
|
-
return viewer;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (import.meta.env.DEV) {
|
|
110
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
-
(window as any).previewSlide = previewSlide;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function make_timestamp(context: AppContext): () => number {
|
|
115
|
-
const room = context.room;
|
|
116
|
-
const player = context.displayer as Player;
|
|
117
|
-
if (room) {
|
|
118
|
-
return () => room.calibrationTimestamp;
|
|
119
|
-
} else if (player) {
|
|
120
|
-
return () => player.beginTimestamp + player.progressTime;
|
|
121
|
-
} else {
|
|
122
|
-
return () => Date.now();
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function make_logger(context: AppContext, debug?: boolean): { info: (msg: string) => void } {
|
|
127
|
-
void debug; // not used for now
|
|
128
|
-
if (import.meta.env.DEV) {
|
|
129
|
-
return { info: console.debug.bind(console) };
|
|
130
|
-
}
|
|
131
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
132
|
-
return (context.displayer as any).logger;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function make_bg_color(el: HTMLElement): string {
|
|
136
|
-
try {
|
|
137
|
-
let bg = window.getComputedStyle(el).backgroundColor;
|
|
138
|
-
if (bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
|
139
|
-
bg = hex(bg);
|
|
140
|
-
console.log("[Slide] guess bg color:", bg);
|
|
141
|
-
return bg;
|
|
142
|
-
}
|
|
143
|
-
if (el.parentElement) {
|
|
144
|
-
return make_bg_color(el.parentElement);
|
|
145
|
-
}
|
|
146
|
-
} catch {
|
|
147
|
-
// ignored
|
|
148
|
-
}
|
|
149
|
-
return "#ffffff";
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// https://github.com/Qix-/color-convert/blob/8dfdbbc6b46fa6a305bf394d942cc1b08e23fca5/conversions.js#L616
|
|
153
|
-
function hex(color: string): string {
|
|
154
|
-
const result = ColorString.get(color);
|
|
155
|
-
if (result && result.model === "rgb") {
|
|
156
|
-
const args = result.value;
|
|
157
|
-
|
|
158
|
-
const integer =
|
|
159
|
-
((Math.round(args[0]) & 0xff) << 16) +
|
|
160
|
-
((Math.round(args[1]) & 0xff) << 8) +
|
|
161
|
-
(Math.round(args[2]) & 0xff);
|
|
162
|
-
|
|
163
|
-
const string = integer.toString(16);
|
|
164
|
-
return "#" + "000000".substring(string.length) + string;
|
|
165
|
-
} else {
|
|
166
|
-
return color;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function onUnmount(context: AppContext, callback: () => void) {
|
|
171
|
-
context.emitter.on("destroy", callback);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
interface ConnectParams {
|
|
175
|
-
context: AppContext;
|
|
176
|
-
viewer: SlideViewer;
|
|
177
|
-
sideEffect: SideEffectManager;
|
|
178
|
-
logger: { info: (msg: string) => void };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function connect({ context, viewer, sideEffect, logger }: ConnectParams) {
|
|
182
|
-
const log = import.meta.env.DEV ? console.log.bind(console) : logger.info.bind(logger);
|
|
183
|
-
|
|
184
|
-
let whiteboard: WhiteBoardView | null = null;
|
|
185
|
-
|
|
186
|
-
viewer.prepare(({ width, height, slideCount }) => {
|
|
187
|
-
if (context.destroyed) return;
|
|
188
|
-
width && context.box.setStageRatio(height / width);
|
|
189
|
-
whiteboard = context.createWhiteBoardView({ size: slideCount });
|
|
190
|
-
whiteboard.setBaseRect({ width, height });
|
|
191
|
-
kick_start();
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
const { slide } = viewer;
|
|
195
|
-
|
|
196
|
-
function kick_start() {
|
|
197
|
-
if (context.storage.state.state) {
|
|
198
|
-
log("[Slide] restore", { ...context.storage.state.state });
|
|
199
|
-
slide.setSlideState(context.storage.state.state);
|
|
200
|
-
} else if (context.isAddApp) {
|
|
201
|
-
log("[Slide] kick start");
|
|
202
|
-
slide.renderSlide(1);
|
|
203
|
-
} else {
|
|
204
|
-
log("[Slide] wait for restore");
|
|
205
|
-
const timeoutID = sideEffect.add(() => {
|
|
206
|
-
const timer = setInterval(() => {
|
|
207
|
-
if (!context.storage.state.state) {
|
|
208
|
-
log("[Slide] timeout");
|
|
209
|
-
slide.renderSlide(1);
|
|
210
|
-
}
|
|
211
|
-
}, 15 * 1000);
|
|
212
|
-
return () => clearInterval(timer);
|
|
213
|
-
});
|
|
214
|
-
const disposerID = sideEffect.addDisposer(
|
|
215
|
-
context.storage.addStateChangedListener(() => {
|
|
216
|
-
if (context.storage.state.state) {
|
|
217
|
-
log("[Slide] restore", { ...context.storage.state.state });
|
|
218
|
-
slide.setSlideState(context.storage.state.state);
|
|
219
|
-
sideEffect.flush(disposerID);
|
|
220
|
-
sideEffect.flush(timeoutID);
|
|
221
|
-
}
|
|
222
|
-
})
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// setup sync event
|
|
228
|
-
slide.on("stateChange", () => {
|
|
229
|
-
if (context.isWritable) {
|
|
230
|
-
log("[Slide] save state", { ...slide.slideState });
|
|
231
|
-
context.storage.setState({ state: slide.slideState });
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
slide.on("syncDispatch", payload => {
|
|
235
|
-
if (context.isWritable) {
|
|
236
|
-
log("[Slide] dispatch", { ...payload });
|
|
237
|
-
context.dispatchMagixEvent("syncDispatch", payload);
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
sideEffect.addDisposer(
|
|
241
|
-
context.addMagixEventListener(
|
|
242
|
-
"syncDispatch",
|
|
243
|
-
({ payload }) => {
|
|
244
|
-
log("[Slide] receive", { ...payload });
|
|
245
|
-
slide.emit("syncReceive", payload);
|
|
246
|
-
},
|
|
247
|
-
{ fireSelfEventAfterCommit: true }
|
|
248
|
-
)
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
// save state
|
|
252
|
-
slide.on("slideChange", page => {
|
|
253
|
-
if (context.isWritable && whiteboard) {
|
|
254
|
-
log("[Slide] page to", page);
|
|
255
|
-
whiteboard.jumpPage(page - 1);
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
// handle ui events
|
|
260
|
-
viewer.setReadonly(!context.isWritable);
|
|
261
|
-
sideEffect.addDisposer(
|
|
262
|
-
context.emitter.on("writableChange", w => {
|
|
263
|
-
viewer.setReadonly(!w);
|
|
264
|
-
})
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
sideEffect.addEventListener(window, "keydown", ev => {
|
|
268
|
-
if (context.box.focus) {
|
|
269
|
-
if (ev.key === "ArrowUp" || ev.key === "ArrowLeft") {
|
|
270
|
-
viewer.slide.prevStep();
|
|
271
|
-
}
|
|
272
|
-
if (ev.key === "ArrowRight" || ev.key === "ArrowDown") {
|
|
273
|
-
viewer.slide.nextStep();
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
}
|
package/src/preview.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SlideViewerOptions } from "./SlideViewer";
|
|
2
|
+
|
|
3
|
+
import { SlideViewer } from "./SlideViewer";
|
|
4
|
+
|
|
5
|
+
export function previewSlide(options: SlideViewerOptions & { container: HTMLElement }) {
|
|
6
|
+
const { container, ...slideOptions } = options;
|
|
7
|
+
const wrapper = document.createElement("div");
|
|
8
|
+
wrapper.className = "netless-app-slide-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
|
+
}
|
package/src/typings.ts
CHANGED
package/src/utils.ts
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
|
+
import type { AppContext, Player } from "@netless/window-manager";
|
|
2
|
+
|
|
3
|
+
import ColorString from "color-string";
|
|
4
|
+
import { class_name } from "./constants";
|
|
5
|
+
|
|
1
6
|
export function noop() {
|
|
2
7
|
return;
|
|
3
8
|
}
|
|
9
|
+
|
|
10
|
+
export function h<K extends keyof HTMLElementTagNameMap>(tag: K) {
|
|
11
|
+
return document.createElement(tag);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function hc<K extends keyof HTMLElementTagNameMap>(tag: K, name: string) {
|
|
15
|
+
const el = h(tag);
|
|
16
|
+
set_class(el, name);
|
|
17
|
+
return el;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function append(parent: HTMLElement, child: HTMLElement) {
|
|
21
|
+
return parent.appendChild(child);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function wrap_class(name: string) {
|
|
25
|
+
return `${class_name}-${name}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function set_class(el: HTMLElement, name: string) {
|
|
29
|
+
el.className = wrap_class(name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function trim_slash(s: string) {
|
|
33
|
+
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function get(o: unknown, k: string): unknown {
|
|
37
|
+
if (typeof o === "object" && o !== null) return (o as Record<string, unknown>)[k];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function block<T = void>(): [p: Promise<T>, resolve: (t: T) => void] {
|
|
41
|
+
let resolve!: (t: T) => void;
|
|
42
|
+
const p = new Promise<T>(r => {
|
|
43
|
+
resolve = r;
|
|
44
|
+
});
|
|
45
|
+
return [p, resolve];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function make_timestamp(context: AppContext): () => number {
|
|
49
|
+
const room = context.room;
|
|
50
|
+
const player = context.displayer as Player;
|
|
51
|
+
if (room) {
|
|
52
|
+
return () => room.calibrationTimestamp;
|
|
53
|
+
} else if (player) {
|
|
54
|
+
return () => player.beginTimestamp + player.progressTime;
|
|
55
|
+
} else {
|
|
56
|
+
return () => Date.now();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function make_bg_color(el: HTMLElement): string {
|
|
61
|
+
try {
|
|
62
|
+
let bg = window.getComputedStyle(el).backgroundColor;
|
|
63
|
+
if (bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
|
64
|
+
bg = hex(bg);
|
|
65
|
+
console.log("[Slide] guess bg color:", bg);
|
|
66
|
+
return bg;
|
|
67
|
+
}
|
|
68
|
+
if (el.parentElement) {
|
|
69
|
+
return make_bg_color(el.parentElement);
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// ignored
|
|
73
|
+
}
|
|
74
|
+
return "#ffffff";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// https://github.com/Qix-/color-convert/blob/8dfdbbc6b46fa6a305bf394d942cc1b08e23fca5/conversions.js#L616
|
|
78
|
+
export function hex(color: string): string {
|
|
79
|
+
const result = ColorString.get(color);
|
|
80
|
+
if (result && result.model === "rgb") {
|
|
81
|
+
const args = result.value;
|
|
82
|
+
|
|
83
|
+
const integer =
|
|
84
|
+
((Math.round(args[0]) & 0xff) << 16) +
|
|
85
|
+
((Math.round(args[1]) & 0xff) << 8) +
|
|
86
|
+
(Math.round(args[2]) & 0xff);
|
|
87
|
+
|
|
88
|
+
const string = integer.toString(16);
|
|
89
|
+
return "#" + "000000".substring(string.length) + string;
|
|
90
|
+
} else {
|
|
91
|
+
return color;
|
|
92
|
+
}
|
|
93
|
+
}
|