@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/dist/typings.d.ts
CHANGED
|
@@ -14,7 +14,6 @@ export interface MagixEvents {
|
|
|
14
14
|
[SLIDE_EVENTS.syncDispatch]: SyncEvent;
|
|
15
15
|
}
|
|
16
16
|
export interface AppOptions extends Pick<ISlideConfig, "renderOptions" | "rtcAudio" | "useLocalCache" | "resourceTimeout" | "loaderDelegate" | "navigatorDelegate" | "fixedFrameSize" | "logger"> {
|
|
17
|
-
debug?: boolean;
|
|
18
17
|
}
|
|
19
18
|
export interface ILogger {
|
|
20
19
|
info?(msg: string): void;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
+
import type { AppContext } from "@netless/window-manager";
|
|
1
2
|
export declare function noop(): void;
|
|
3
|
+
export declare function h<K extends keyof HTMLElementTagNameMap>(tag: K): HTMLElementTagNameMap[K];
|
|
4
|
+
export declare function hc<K extends keyof HTMLElementTagNameMap>(tag: K, name: string): HTMLElementTagNameMap[K];
|
|
5
|
+
export declare function append(parent: HTMLElement, child: HTMLElement): HTMLElement;
|
|
6
|
+
export declare function wrap_class(name: string): string;
|
|
7
|
+
export declare function set_class(el: HTMLElement, name: string): void;
|
|
8
|
+
export declare function trim_slash(s: string): string;
|
|
9
|
+
export declare function get(o: unknown, k: string): unknown;
|
|
10
|
+
export declare function block<T = void>(): [p: Promise<T>, resolve: (t: T) => void];
|
|
11
|
+
export declare function make_timestamp(context: AppContext): () => number;
|
|
12
|
+
export declare function make_bg_color(el: HTMLElement): string;
|
|
13
|
+
export declare function hex(color: string): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netless/app-slide",
|
|
3
|
-
"version": "0.3.0-canary.
|
|
3
|
+
"version": "0.3.0-canary.13",
|
|
4
4
|
"main": "dist/main.js",
|
|
5
5
|
"module": "dist/main.mjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
"README-zh.md"
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@netless/slide": "^0.7.
|
|
13
|
+
"@netless/slide": "^0.7.4"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@types/color-string": "^1.5.2",
|
|
17
17
|
"color-string": "^1.9.1",
|
|
18
|
+
"remitter": "^0.2.9",
|
|
18
19
|
"side-effect-manager": "^1.2.1",
|
|
19
20
|
"value-enhancer": "^1.3.2",
|
|
20
21
|
"vanilla-lazyload": "^17.8.3"
|
package/src/Logger.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AppContext } from "@netless/window-manager";
|
|
2
|
+
|
|
3
|
+
export interface ILogger {
|
|
4
|
+
info(this: void, msg: string): void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function getRoomLogger(context: AppContext) {
|
|
8
|
+
return (context.displayer as unknown as { logger: ILogger }).logger;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class Logger implements ILogger {
|
|
12
|
+
static active = new Set<string>();
|
|
13
|
+
static roomLogger: ILogger | null = null;
|
|
14
|
+
|
|
15
|
+
constructor(readonly context: AppContext | null) {
|
|
16
|
+
if (context) {
|
|
17
|
+
Logger.active.add(context.appId);
|
|
18
|
+
Logger.roomLogger = getRoomLogger(context);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
info = (msg: string) => {
|
|
23
|
+
if (Logger.roomLogger && this.context) {
|
|
24
|
+
Logger.roomLogger.info(msg + ` (${this.context.appId})`);
|
|
25
|
+
} else if (this.context) {
|
|
26
|
+
console.debug(msg + ` (${this.context.appId})`);
|
|
27
|
+
} else {
|
|
28
|
+
console.debug(msg);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
destroy() {
|
|
33
|
+
this.context && Logger.active.delete(this.context.appId);
|
|
34
|
+
if (Logger.active.size === 0) {
|
|
35
|
+
Logger.roomLogger = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { class_name } from "../constants";
|
|
2
|
+
import { arrowLeftSVG, arrowRightSVG, pauseSVG, playSVG, sidebarSVG } from "../icons";
|
|
3
|
+
import { h, noop, set_class, wrap_class } from "../utils";
|
|
4
|
+
|
|
5
|
+
export function create_footer() {
|
|
6
|
+
type NewPageIndexCallback = (newPageIndex: number) => void;
|
|
7
|
+
let new_page_index_callback: NewPageIndexCallback = noop;
|
|
8
|
+
const on_new_page_index = (callback: NewPageIndexCallback) => {
|
|
9
|
+
new_page_index_callback = callback;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let toggle_preview_callback: () => void = noop;
|
|
13
|
+
const on_toggle_preview = (callback: () => void) => {
|
|
14
|
+
toggle_preview_callback = callback;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let play_callback: () => void = noop;
|
|
18
|
+
const on_play = (callback: () => void) => {
|
|
19
|
+
play_callback = callback;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let readonly = false;
|
|
23
|
+
let sidebar = false;
|
|
24
|
+
let page_index = 0;
|
|
25
|
+
let page_count = 0;
|
|
26
|
+
|
|
27
|
+
const $footer = h("div");
|
|
28
|
+
set_class($footer, "footer");
|
|
29
|
+
|
|
30
|
+
const $btnSidebar = h("button");
|
|
31
|
+
set_class($btnSidebar, "btn-sidebar");
|
|
32
|
+
$btnSidebar.classList.add(wrap_class("footer-btn"));
|
|
33
|
+
$btnSidebar.appendChild(sidebarSVG(class_name));
|
|
34
|
+
$btnSidebar.onclick = function on_click_btn_sidebar() {
|
|
35
|
+
if (readonly) return;
|
|
36
|
+
toggle_preview_callback();
|
|
37
|
+
};
|
|
38
|
+
$btnSidebar.style.display = "none";
|
|
39
|
+
$footer.appendChild($btnSidebar);
|
|
40
|
+
|
|
41
|
+
const $pageJumps = h("div");
|
|
42
|
+
set_class($pageJumps, "page-jumps");
|
|
43
|
+
|
|
44
|
+
const $btnPageBack = h("button");
|
|
45
|
+
set_class($btnPageBack, "btn-page-back");
|
|
46
|
+
$btnPageBack.classList.add(wrap_class("footer-btn"));
|
|
47
|
+
$btnPageBack.appendChild(arrowLeftSVG(class_name));
|
|
48
|
+
$btnPageBack.onclick = function on_click_btn_page_back() {
|
|
49
|
+
if (readonly) return;
|
|
50
|
+
new_page_index_callback(page_index - 1);
|
|
51
|
+
};
|
|
52
|
+
$pageJumps.appendChild($btnPageBack);
|
|
53
|
+
|
|
54
|
+
const $btnPlay = h("button");
|
|
55
|
+
set_class($btnPlay, "btn-page-play");
|
|
56
|
+
$btnPlay.classList.add(wrap_class("footer-btn"));
|
|
57
|
+
$btnPlay.appendChild(playSVG(class_name));
|
|
58
|
+
$btnPlay.appendChild(pauseSVG(class_name));
|
|
59
|
+
let returnPlayTimer = 0;
|
|
60
|
+
$btnPlay.onclick = function on_click_btn_play() {
|
|
61
|
+
if (readonly) return;
|
|
62
|
+
$btnPlay.classList.add(wrap_class("footer-btn-playing"));
|
|
63
|
+
play_callback();
|
|
64
|
+
clearTimeout(returnPlayTimer);
|
|
65
|
+
returnPlayTimer = setTimeout(() => {
|
|
66
|
+
$btnPlay.classList.remove(wrap_class("footer-btn-playing"));
|
|
67
|
+
}, 500);
|
|
68
|
+
};
|
|
69
|
+
$pageJumps.appendChild($btnPlay);
|
|
70
|
+
|
|
71
|
+
const $btnPageNext = h("button");
|
|
72
|
+
set_class($btnPageNext, "btn-page-next");
|
|
73
|
+
$btnPageNext.classList.add(wrap_class("footer-btn"));
|
|
74
|
+
$btnPageNext.appendChild(arrowRightSVG(class_name));
|
|
75
|
+
$btnPageNext.onclick = function on_click_btn_page_next() {
|
|
76
|
+
if (readonly) return;
|
|
77
|
+
new_page_index_callback(page_index + 1);
|
|
78
|
+
};
|
|
79
|
+
$pageJumps.appendChild($btnPageNext);
|
|
80
|
+
|
|
81
|
+
const $pageNumber = h("div");
|
|
82
|
+
set_class($pageNumber, "page-number");
|
|
83
|
+
|
|
84
|
+
const $pageNumberInput = h("input");
|
|
85
|
+
set_class($pageNumberInput, "page-number-input");
|
|
86
|
+
$pageNumberInput.value = String(page_index + 1);
|
|
87
|
+
$pageNumberInput.onchange = function on_change_page_number_input() {
|
|
88
|
+
if (readonly) return;
|
|
89
|
+
if ($pageNumberInput.value) {
|
|
90
|
+
new_page_index_callback(Number($pageNumberInput.value) - 1);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const $totalPage = h("span");
|
|
95
|
+
set_class($totalPage, "total-page");
|
|
96
|
+
$totalPage.textContent = " / ";
|
|
97
|
+
|
|
98
|
+
$pageNumber.appendChild($pageNumberInput);
|
|
99
|
+
$pageNumber.appendChild($totalPage);
|
|
100
|
+
|
|
101
|
+
$footer.appendChild($pageJumps);
|
|
102
|
+
$footer.appendChild($pageNumber);
|
|
103
|
+
|
|
104
|
+
const class_readonly = wrap_class("readonly");
|
|
105
|
+
|
|
106
|
+
function set_readonly(readonly_: boolean) {
|
|
107
|
+
if (readonly !== readonly_) {
|
|
108
|
+
readonly = readonly_;
|
|
109
|
+
$footer.classList.toggle(class_readonly, readonly);
|
|
110
|
+
$pageNumberInput.disabled = readonly;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function set_sidebar(sidebar_: boolean) {
|
|
115
|
+
if (sidebar !== sidebar_) {
|
|
116
|
+
sidebar = sidebar_;
|
|
117
|
+
$btnSidebar.style.display = sidebar ? "" : "none";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function set_page_index(page_index_: number, force = false) {
|
|
122
|
+
if (force || page_index !== page_index_) {
|
|
123
|
+
page_index = page_index_;
|
|
124
|
+
$pageNumberInput.value = String(page_index + 1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function set_page_count(page_count_: number) {
|
|
129
|
+
if (page_count !== page_count_) {
|
|
130
|
+
page_count = page_count_;
|
|
131
|
+
$totalPage.textContent = ` / ${page_count}`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
$footer,
|
|
137
|
+
$pageNumberInput,
|
|
138
|
+
set_readonly,
|
|
139
|
+
set_sidebar,
|
|
140
|
+
set_page_index,
|
|
141
|
+
set_page_count,
|
|
142
|
+
on_new_page_index,
|
|
143
|
+
on_toggle_preview,
|
|
144
|
+
on_play,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import type { ISlideConfig } from "@netless/slide";
|
|
2
|
+
import type { SlideAttributes } from "./typings";
|
|
3
|
+
|
|
4
|
+
import { Slide } from "@netless/slide";
|
|
5
|
+
import { SideEffectManager } from "side-effect-manager";
|
|
6
|
+
import { Remitter } from "remitter";
|
|
7
|
+
|
|
8
|
+
import { DefaultUrl } from "../constants";
|
|
9
|
+
import { append, block, hc } from "../utils";
|
|
10
|
+
import { fetch_slide_info, make_prefix, on_visibility_change } from "./utils";
|
|
11
|
+
|
|
12
|
+
import styles from "../style.scss?inline";
|
|
13
|
+
import { create_footer } from "./footer";
|
|
14
|
+
import { create_sidebar } from "./sidebar";
|
|
15
|
+
|
|
16
|
+
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
17
|
+
|
|
18
|
+
export type SlideViewerOptions = MakeOptional<
|
|
19
|
+
Omit<ISlideConfig & SlideAttributes, "anchor">,
|
|
20
|
+
"mode" | "interactive"
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
export interface SlideViewerInfoResponse {
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
slideCount: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SlideViewerEventData {
|
|
30
|
+
freeze: void;
|
|
31
|
+
unfreeze: void;
|
|
32
|
+
renderError: { error: Error; index: number };
|
|
33
|
+
prepareError: Error;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class SlideViewer {
|
|
37
|
+
static styles = styles;
|
|
38
|
+
static fetchSlideInfo = fetch_slide_info;
|
|
39
|
+
|
|
40
|
+
readonly sideEffect = new SideEffectManager();
|
|
41
|
+
readonly events = new Remitter<SlideViewerEventData>();
|
|
42
|
+
|
|
43
|
+
readonly $slide = hc("div", "slide");
|
|
44
|
+
readonly $overlay = hc("div", "overlay");
|
|
45
|
+
readonly sidebar = create_sidebar();
|
|
46
|
+
readonly footer = create_footer();
|
|
47
|
+
readonly $content: HTMLElement;
|
|
48
|
+
readonly $footer: HTMLElement;
|
|
49
|
+
|
|
50
|
+
readonly slide: Slide;
|
|
51
|
+
|
|
52
|
+
private readonly _readyPromise: Promise<void>;
|
|
53
|
+
private readonly _infoPromise: Promise<SlideViewerInfoResponse>;
|
|
54
|
+
private _ready = false;
|
|
55
|
+
private _slideCount = 0;
|
|
56
|
+
private _destroyed = false;
|
|
57
|
+
private _isFrozen = false;
|
|
58
|
+
private _cachedIsFrozen = false;
|
|
59
|
+
|
|
60
|
+
constructor(options: SlideViewerOptions) {
|
|
61
|
+
// user interface
|
|
62
|
+
this.$content = this.sidebar.$content;
|
|
63
|
+
this.$footer = this.footer.$footer;
|
|
64
|
+
append(this.$content, this.$slide);
|
|
65
|
+
append(this.$content, this.$overlay);
|
|
66
|
+
|
|
67
|
+
// bind user actions
|
|
68
|
+
this.sidebar.on_new_page_index(this.onNewPageIndex);
|
|
69
|
+
this.footer.on_new_page_index(this.onNewPageIndex);
|
|
70
|
+
this.footer.on_toggle_preview(this.onTogglePreview);
|
|
71
|
+
this.footer.on_play(this.onPlay);
|
|
72
|
+
|
|
73
|
+
// remove dom on cleanup
|
|
74
|
+
this.sideEffect.push(this.sidebar.destroy);
|
|
75
|
+
this.sideEffect.push(() => this.slide.destroy());
|
|
76
|
+
this.sideEffect.push(() => {
|
|
77
|
+
this.$content.parentElement && this.$content.remove();
|
|
78
|
+
this.$footer.parentElement && this.$footer.remove();
|
|
79
|
+
this.$slide.parentElement && this.$slide.remove();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// auto freeze on lost focus
|
|
83
|
+
this.sideEffect.push(
|
|
84
|
+
on_visibility_change(visible => {
|
|
85
|
+
if (this._destroyed) return;
|
|
86
|
+
if (!visible) {
|
|
87
|
+
this._cachedIsFrozen = this._isFrozen;
|
|
88
|
+
this.freeze();
|
|
89
|
+
} else {
|
|
90
|
+
if (!this._cachedIsFrozen) this.unfreeze();
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// start loading slide
|
|
96
|
+
// prepare(info) -> slide.renderEnd(ready)
|
|
97
|
+
const [infoPromise, resolveInfo] = block<SlideViewerInfoResponse>();
|
|
98
|
+
this._infoPromise = infoPromise;
|
|
99
|
+
|
|
100
|
+
const fetching = SlideViewer.fetchSlideInfo(options).then(({ preview, slide }) => {
|
|
101
|
+
if (this._destroyed) return;
|
|
102
|
+
|
|
103
|
+
this._slideCount = slide.slideCount;
|
|
104
|
+
|
|
105
|
+
if (preview) {
|
|
106
|
+
const { taskId, url } = options;
|
|
107
|
+
const { slideCount } = slide;
|
|
108
|
+
const { width, height } = preview;
|
|
109
|
+
|
|
110
|
+
const prefix = make_prefix(taskId, url);
|
|
111
|
+
const pages = Array.from({ length: slideCount }, (_, i) => ({
|
|
112
|
+
width,
|
|
113
|
+
height,
|
|
114
|
+
thumbnail: `${prefix}/preview/${i + 1}.png`,
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
this.sidebar.set_pages(pages);
|
|
118
|
+
this.footer.set_sidebar(true);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.footer.set_page_count(slide.slideCount);
|
|
122
|
+
resolveInfo(slide);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
fetching.catch(error => {
|
|
126
|
+
console.log("[Slide] fetch slide info error");
|
|
127
|
+
console.error(error);
|
|
128
|
+
this.events.emit("prepareError", error);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const [readyPromise, resolveReady] = block();
|
|
132
|
+
this._readyPromise = readyPromise;
|
|
133
|
+
|
|
134
|
+
this.slide = new Slide({
|
|
135
|
+
anchor: this.$slide,
|
|
136
|
+
mode: "local",
|
|
137
|
+
interactive: true,
|
|
138
|
+
enableGlobalClick: true,
|
|
139
|
+
useLocalCache: true,
|
|
140
|
+
...options,
|
|
141
|
+
});
|
|
142
|
+
this.slide.setResource(options.taskId, options.url || DefaultUrl);
|
|
143
|
+
|
|
144
|
+
this.slide.on("slideChange", page => {
|
|
145
|
+
if (this._destroyed) return;
|
|
146
|
+
this.setPage(page);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
this.slide.on("renderEnd", () => {
|
|
150
|
+
// ready = first render end
|
|
151
|
+
if (!this._ready) {
|
|
152
|
+
setTimeout(() => {
|
|
153
|
+
this._ready = true;
|
|
154
|
+
resolveReady();
|
|
155
|
+
}, 1000);
|
|
156
|
+
} else {
|
|
157
|
+
// otherwise, clear overlay
|
|
158
|
+
this.$overlay.style.opacity = "";
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// show overlay on render error
|
|
163
|
+
this.slide.on("renderError", ev => {
|
|
164
|
+
if (this._destroyed) return;
|
|
165
|
+
if (ev.error) {
|
|
166
|
+
console.error(ev.error);
|
|
167
|
+
this.$overlay.textContent = `Error on slide.index=${ev.index}: ${ev.error.message}`;
|
|
168
|
+
this.$overlay.style.opacity = "1";
|
|
169
|
+
this.events.emit("renderError", ev);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
this.ready(() => {
|
|
174
|
+
if (this._isFrozen) {
|
|
175
|
+
this.freeze();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
prepare(callback: (response: SlideViewerInfoResponse) => void) {
|
|
181
|
+
return this._infoPromise.then(callback);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
ready(callback: () => void) {
|
|
185
|
+
return this._readyPromise.then(callback);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
destroy() {
|
|
189
|
+
this._destroyed = true;
|
|
190
|
+
this.sideEffect.flushAll();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
onPlay = () => {
|
|
194
|
+
this.slide.nextStep();
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
onNewPageIndex = (index: number) => {
|
|
198
|
+
if (this._ready && index >= 0 && index < this._slideCount) {
|
|
199
|
+
this.slide.renderSlide(index + 1);
|
|
200
|
+
} else {
|
|
201
|
+
this.setPage(this.slide.slideState.currentSlideIndex);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
onTogglePreview = () => {
|
|
206
|
+
this.sidebar.set_active(!this.sidebar.get_active());
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
setReadonly(readonly: boolean) {
|
|
210
|
+
this.slide.setInteractive(!readonly);
|
|
211
|
+
this.sidebar.set_readonly(readonly);
|
|
212
|
+
this.footer.set_readonly(readonly);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
setPage(page: number) {
|
|
216
|
+
this.sidebar.set_page_index(page - 1, true);
|
|
217
|
+
this.footer.set_page_index(page - 1, true);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
freeze() {
|
|
221
|
+
if (this._destroyed) return;
|
|
222
|
+
this._isFrozen = true;
|
|
223
|
+
if (!this._ready) return;
|
|
224
|
+
this.slide.frozen();
|
|
225
|
+
this.events.emit("freeze");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
unfreeze() {
|
|
229
|
+
if (this._destroyed) return;
|
|
230
|
+
this._isFrozen = false;
|
|
231
|
+
if (!this._ready) return;
|
|
232
|
+
this.slide.release();
|
|
233
|
+
this.events.emit("unfreeze");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { PreviewPage } from "./typings";
|
|
2
|
+
|
|
3
|
+
import LazyLoad from "vanilla-lazyload";
|
|
4
|
+
import { append, h, noop, set_class, wrap_class } from "../utils";
|
|
5
|
+
|
|
6
|
+
export function create_sidebar() {
|
|
7
|
+
type NewPageIndexCallback = (newPageIndex: number) => void;
|
|
8
|
+
let new_page_index_callback: NewPageIndexCallback = noop;
|
|
9
|
+
const on_new_page_index = (callback: NewPageIndexCallback) => {
|
|
10
|
+
new_page_index_callback = callback;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let destroyed = false;
|
|
14
|
+
let readonly = false;
|
|
15
|
+
let active = false;
|
|
16
|
+
let pages: PreviewPage[] = [];
|
|
17
|
+
let page_index = 0;
|
|
18
|
+
|
|
19
|
+
const class_readonly = wrap_class("readonly");
|
|
20
|
+
const class_preview_active = wrap_class("preview-active");
|
|
21
|
+
|
|
22
|
+
const $content = h("div");
|
|
23
|
+
set_class($content, "content");
|
|
24
|
+
|
|
25
|
+
const $previewMask = h("div");
|
|
26
|
+
set_class($previewMask, "preview-mask");
|
|
27
|
+
$previewMask.onclick = function on_click_preview_mask(ev) {
|
|
28
|
+
if (readonly) return;
|
|
29
|
+
if (ev.target !== $previewMask) return;
|
|
30
|
+
set_active(false);
|
|
31
|
+
};
|
|
32
|
+
append($content, $previewMask);
|
|
33
|
+
|
|
34
|
+
const $preview = h("div");
|
|
35
|
+
set_class($preview, "preview");
|
|
36
|
+
$preview.classList.add(wrap_class("tele-fancy-scrollbar"));
|
|
37
|
+
$preview.onclick = function on_click_preview(ev) {
|
|
38
|
+
if (readonly) return;
|
|
39
|
+
const page_index = (ev.target as HTMLElement).dataset?.pageIndex;
|
|
40
|
+
if (page_index) {
|
|
41
|
+
ev.preventDefault();
|
|
42
|
+
ev.stopPropagation();
|
|
43
|
+
ev.stopImmediatePropagation();
|
|
44
|
+
new_page_index_callback(Number(page_index));
|
|
45
|
+
set_active(false);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
append($content, $preview);
|
|
49
|
+
|
|
50
|
+
const lazyload = new LazyLoad({
|
|
51
|
+
container: $preview,
|
|
52
|
+
elements_selector: `.${wrap_class("preview-page>img")}`,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function set_readonly(readonly_: boolean) {
|
|
56
|
+
if (readonly !== readonly_) {
|
|
57
|
+
readonly = readonly_;
|
|
58
|
+
$content.classList.toggle(class_readonly, readonly);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function set_active(active_: boolean) {
|
|
63
|
+
if (active !== active_) {
|
|
64
|
+
active = active_;
|
|
65
|
+
$content.classList.toggle(class_preview_active, active);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function set_pages(pages_: PreviewPage[]) {
|
|
70
|
+
if (pages !== pages_) {
|
|
71
|
+
pages = pages_;
|
|
72
|
+
|
|
73
|
+
const page_class = wrap_class("preview-page");
|
|
74
|
+
const page_name_class = wrap_class("preview-page-name");
|
|
75
|
+
|
|
76
|
+
pages.forEach((page, i) => {
|
|
77
|
+
const pageIndex = String(i);
|
|
78
|
+
|
|
79
|
+
const $page = h("a");
|
|
80
|
+
$page.className = page_class + " " + wrap_class(`preview-page-${i}`);
|
|
81
|
+
$page.href = "#";
|
|
82
|
+
$page.dataset.pageIndex = pageIndex;
|
|
83
|
+
|
|
84
|
+
const $name = h("span");
|
|
85
|
+
$name.className = page_name_class;
|
|
86
|
+
$name.textContent = String(i + 1);
|
|
87
|
+
$name.dataset.pageIndex = pageIndex;
|
|
88
|
+
|
|
89
|
+
const $img = h("img");
|
|
90
|
+
$img.width = page.width;
|
|
91
|
+
$img.height = page.height;
|
|
92
|
+
$img.src = page.thumbnail;
|
|
93
|
+
$img.alt = `page-${i}`;
|
|
94
|
+
$img.dataset.pageIndex = pageIndex;
|
|
95
|
+
|
|
96
|
+
append($page, $img);
|
|
97
|
+
append($page, $name);
|
|
98
|
+
append($preview, $page);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!destroyed) {
|
|
102
|
+
lazyload.update();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function set_page_index(page_index_: number, force = false) {
|
|
108
|
+
if (force || page_index !== page_index_) {
|
|
109
|
+
page_index = page_index_;
|
|
110
|
+
const $page = $preview.querySelector<HTMLElement>(
|
|
111
|
+
`.${wrap_class(`preview-page-${page_index}`)}`
|
|
112
|
+
);
|
|
113
|
+
if ($page) {
|
|
114
|
+
$preview.scrollTo({ top: $page.offsetTop - 16 });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function destroy() {
|
|
120
|
+
destroyed = true;
|
|
121
|
+
lazyload.destroy();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function get_active() {
|
|
125
|
+
return active;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
$content,
|
|
130
|
+
$preview,
|
|
131
|
+
set_readonly,
|
|
132
|
+
set_active,
|
|
133
|
+
get_active,
|
|
134
|
+
set_pages,
|
|
135
|
+
on_new_page_index,
|
|
136
|
+
set_page_index,
|
|
137
|
+
destroy,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface SlideAttributes {
|
|
2
|
+
taskId: string;
|
|
3
|
+
url?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface PreviewPage {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
thumbnail: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PreviewInfo {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SlideInfo {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
slideCount: number;
|
|
21
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { PreviewInfo, SlideAttributes, SlideInfo } from "./typings";
|
|
2
|
+
|
|
3
|
+
import { DefaultUrl } from "../constants";
|
|
4
|
+
import { noop, trim_slash } from "../utils";
|
|
5
|
+
|
|
6
|
+
export function make_prefix(taskId: string, url?: string) {
|
|
7
|
+
return `${trim_slash(url || DefaultUrl)}/${taskId}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function fetch_slide_info({ taskId, url }: SlideAttributes): Promise<{
|
|
11
|
+
preview: PreviewInfo | null;
|
|
12
|
+
slide: SlideInfo;
|
|
13
|
+
}> {
|
|
14
|
+
const prefix = make_prefix(taskId, url);
|
|
15
|
+
const imgSrc = `${prefix}/preview/1.png`;
|
|
16
|
+
const jsonUrl = `${prefix}/jsonOutput/slide-1.json`;
|
|
17
|
+
|
|
18
|
+
const p1 = new Promise<PreviewInfo | null>(resolve => {
|
|
19
|
+
const image = new Image();
|
|
20
|
+
image.onload = () => resolve(image);
|
|
21
|
+
image.onerror = () => resolve(null);
|
|
22
|
+
image.src = imgSrc;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const p2: Promise<SlideInfo> = fetch(jsonUrl).then(r => r.json());
|
|
26
|
+
|
|
27
|
+
return { preview: await p1, slide: await p2 };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function on_visibility_change(callback: (visible: boolean) => void) {
|
|
31
|
+
if (typeof document !== "undefined") {
|
|
32
|
+
const listener = () => callback(document.visibilityState === "visible");
|
|
33
|
+
document.addEventListener("visibilitychange", listener);
|
|
34
|
+
return () => document.removeEventListener("visibilitychange", listener);
|
|
35
|
+
}
|
|
36
|
+
return noop;
|
|
37
|
+
}
|