@netless/app-slide 0.3.0-canary.13 → 0.3.0-canary.14

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/typings.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ISlideConfig, Slide, SLIDE_EVENTS, SyncEvent } from "@netless/slide";
2
2
  import type { RegisterParams } from "@netless/window-manager";
3
- export declare type SlideState = Slide["slideState"];
4
- export declare type AddHooks = NonNullable<RegisterParams["addHooks"]>;
3
+ export type SlideState = Slide["slideState"];
4
+ export type AddHooks = NonNullable<RegisterParams["addHooks"]>;
5
5
  export interface Attributes {
6
6
  /** convert task id */
7
7
  taskId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/app-slide",
3
- "version": "0.3.0-canary.13",
3
+ "version": "0.3.0-canary.14",
4
4
  "main": "dist/main.js",
5
5
  "module": "dist/main.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -10,7 +10,8 @@
10
10
  "README-zh.md"
11
11
  ],
12
12
  "dependencies": {
13
- "@netless/slide": "^0.7.4"
13
+ "@netless/slide": "^0.9.2",
14
+ "jspdf": "2.5.1"
14
15
  },
15
16
  "devDependencies": {
16
17
  "@types/color-string": "^1.5.2",
@@ -1,3 +1,4 @@
1
+ import type { ReadonlyTeleBox } from "@netless/window-manager";
1
2
  import type { SlideViewer } from "./SlideViewer";
2
3
  import type { AddHooks } from "./typings";
3
4
 
@@ -5,10 +6,12 @@ export class Refrigerator {
5
6
  threshold = 2;
6
7
 
7
8
  private _map = new Map<string, SlideViewer>();
9
+ private _box = new Map<string, ReadonlyTeleBox>();
8
10
  private _queue: string[] = [];
9
11
 
10
- set(appId: string, viewer: SlideViewer) {
12
+ set(appId: string, viewer: SlideViewer, box: ReadonlyTeleBox) {
11
13
  this._map.set(appId, viewer);
14
+ this._box.set(appId, box);
12
15
  if (!this._queue.includes(appId)) {
13
16
  this._queue.unshift(appId);
14
17
  }
@@ -20,6 +23,7 @@ export class Refrigerator {
20
23
 
21
24
  delete(appId: string) {
22
25
  this._map.delete(appId);
26
+ this._box.delete(appId);
23
27
  this._queue = this._queue.filter(id => id !== appId);
24
28
  this._refresh();
25
29
  }
@@ -35,6 +39,12 @@ export class Refrigerator {
35
39
 
36
40
  private _refresh() {
37
41
  let viewer: SlideViewer | undefined;
42
+ // queue = [5, 4, 3, 2, 1]
43
+ this._queue.sort((a, b) => {
44
+ const za = this._box.get(a)?.zIndex ?? 0;
45
+ const zb = this._box.get(b)?.zIndex ?? 0;
46
+ return -(za - zb);
47
+ });
38
48
  this._queue.forEach((appId, i) => {
39
49
  if (!(viewer = this._map.get(appId))) return;
40
50
  if (i < this.threshold) {
@@ -1,6 +1,8 @@
1
1
  import { class_name } from "../constants";
2
2
  import { arrowLeftSVG, arrowRightSVG, pauseSVG, playSVG, sidebarSVG } from "../icons";
3
- import { h, noop, set_class, wrap_class } from "../utils";
3
+ import { hc, noop, wrap_class } from "../utils";
4
+ import { saveSVG } from "../icons/save";
5
+ import { spinnerSVG } from "../icons/spinner";
4
6
 
5
7
  export function create_footer() {
6
8
  type NewPageIndexCallback = (newPageIndex: number) => void;
@@ -24,11 +26,9 @@ export function create_footer() {
24
26
  let page_index = 0;
25
27
  let page_count = 0;
26
28
 
27
- const $footer = h("div");
28
- set_class($footer, "footer");
29
+ const $footer = hc("div", "footer");
29
30
 
30
- const $btnSidebar = h("button");
31
- set_class($btnSidebar, "btn-sidebar");
31
+ const $btnSidebar = hc("button", "btn-sidebar");
32
32
  $btnSidebar.classList.add(wrap_class("footer-btn"));
33
33
  $btnSidebar.appendChild(sidebarSVG(class_name));
34
34
  $btnSidebar.onclick = function on_click_btn_sidebar() {
@@ -38,11 +38,9 @@ export function create_footer() {
38
38
  $btnSidebar.style.display = "none";
39
39
  $footer.appendChild($btnSidebar);
40
40
 
41
- const $pageJumps = h("div");
42
- set_class($pageJumps, "page-jumps");
41
+ const $pageJumps = hc("div", "page-jumps");
43
42
 
44
- const $btnPageBack = h("button");
45
- set_class($btnPageBack, "btn-page-back");
43
+ const $btnPageBack = hc("button", "btn-page-back");
46
44
  $btnPageBack.classList.add(wrap_class("footer-btn"));
47
45
  $btnPageBack.appendChild(arrowLeftSVG(class_name));
48
46
  $btnPageBack.onclick = function on_click_btn_page_back() {
@@ -51,8 +49,7 @@ export function create_footer() {
51
49
  };
52
50
  $pageJumps.appendChild($btnPageBack);
53
51
 
54
- const $btnPlay = h("button");
55
- set_class($btnPlay, "btn-page-play");
52
+ const $btnPlay = hc("button", "btn-page-play");
56
53
  $btnPlay.classList.add(wrap_class("footer-btn"));
57
54
  $btnPlay.appendChild(playSVG(class_name));
58
55
  $btnPlay.appendChild(pauseSVG(class_name));
@@ -68,8 +65,7 @@ export function create_footer() {
68
65
  };
69
66
  $pageJumps.appendChild($btnPlay);
70
67
 
71
- const $btnPageNext = h("button");
72
- set_class($btnPageNext, "btn-page-next");
68
+ const $btnPageNext = hc("button", "btn-page-next");
73
69
  $btnPageNext.classList.add(wrap_class("footer-btn"));
74
70
  $btnPageNext.appendChild(arrowRightSVG(class_name));
75
71
  $btnPageNext.onclick = function on_click_btn_page_next() {
@@ -78,11 +74,9 @@ export function create_footer() {
78
74
  };
79
75
  $pageJumps.appendChild($btnPageNext);
80
76
 
81
- const $pageNumber = h("div");
82
- set_class($pageNumber, "page-number");
77
+ const $pageNumber = hc("div", "page-number");
83
78
 
84
- const $pageNumberInput = h("input");
85
- set_class($pageNumberInput, "page-number-input");
79
+ const $pageNumberInput = hc("input", "page-number-input");
86
80
  $pageNumberInput.value = String(page_index + 1);
87
81
  $pageNumberInput.onchange = function on_change_page_number_input() {
88
82
  if (readonly) return;
@@ -91,8 +85,7 @@ export function create_footer() {
91
85
  }
92
86
  };
93
87
 
94
- const $totalPage = h("span");
95
- set_class($totalPage, "total-page");
88
+ const $totalPage = hc("span", "total-page");
96
89
  $totalPage.textContent = " / ";
97
90
 
98
91
  $pageNumber.appendChild($pageNumberInput);
@@ -103,6 +96,35 @@ export function create_footer() {
103
96
 
104
97
  const class_readonly = wrap_class("readonly");
105
98
 
99
+ let saveToPdf: (progressCallback: (p: number) => void) => void = noop;
100
+ const on_save_to_pdf = (callback: (progressCallback: (p: number) => void) => void) => {
101
+ saveToPdf = callback;
102
+ };
103
+ const progressCallback = (p: number) => {
104
+ if (p < 99) {
105
+ spinnerIcon.style.display = "block";
106
+ saveIcon.style.display = "none";
107
+ const progress = spinnerIcon.querySelector("[data-id]");
108
+ if (progress) {
109
+ progress.textContent = p.toString().padStart(2, "0");
110
+ }
111
+ } else {
112
+ spinnerIcon.style.display = "none";
113
+ saveIcon.style.display = "block";
114
+ }
115
+ };
116
+ const saveIcon = saveSVG(class_name);
117
+ const spinnerIcon = spinnerSVG(class_name);
118
+ spinnerIcon.style.display = "none";
119
+ const $saveBtn = hc("button", "footer-btn");
120
+ $saveBtn.appendChild(saveIcon);
121
+ $saveBtn.onclick = function () {
122
+ saveToPdf(progressCallback);
123
+ };
124
+ $saveBtn.appendChild(spinnerIcon);
125
+
126
+ $footer.appendChild($saveBtn);
127
+
106
128
  function set_readonly(readonly_: boolean) {
107
129
  if (readonly !== readonly_) {
108
130
  readonly = readonly_;
@@ -142,5 +164,6 @@ export function create_footer() {
142
164
  on_new_page_index,
143
165
  on_toggle_preview,
144
166
  on_play,
167
+ on_save_to_pdf,
145
168
  };
146
169
  }
@@ -1,6 +1,7 @@
1
1
  import type { ISlideConfig } from "@netless/slide";
2
2
  import type { SlideAttributes } from "./typings";
3
3
 
4
+ import { jsPDF } from "jspdf";
4
5
  import { Slide } from "@netless/slide";
5
6
  import { SideEffectManager } from "side-effect-manager";
6
7
  import { Remitter } from "remitter";
@@ -49,6 +50,10 @@ export class SlideViewer {
49
50
 
50
51
  readonly slide: Slide;
51
52
 
53
+ private _getWhiteSnapshot:
54
+ | ((index: number, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => void)
55
+ | null = null;
56
+ private _slideTitle = "";
52
57
  private readonly _readyPromise: Promise<void>;
53
58
  private readonly _infoPromise: Promise<SlideViewerInfoResponse>;
54
59
  private _ready = false;
@@ -69,6 +74,7 @@ export class SlideViewer {
69
74
  this.footer.on_new_page_index(this.onNewPageIndex);
70
75
  this.footer.on_toggle_preview(this.onTogglePreview);
71
76
  this.footer.on_play(this.onPlay);
77
+ this.footer.on_save_to_pdf(this.toPdf);
72
78
 
73
79
  // remove dom on cleanup
74
80
  this.sideEffect.push(this.sidebar.destroy);
@@ -181,6 +187,16 @@ export class SlideViewer {
181
187
  return this._infoPromise.then(callback);
182
188
  }
183
189
 
190
+ attachWhiteSnapshot(
191
+ snapshot: (index: number, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => void
192
+ ) {
193
+ this._getWhiteSnapshot = snapshot;
194
+ }
195
+
196
+ setSlideTitle(title: string) {
197
+ this._slideTitle = title;
198
+ }
199
+
184
200
  ready(callback: () => void) {
185
201
  return this._readyPromise.then(callback);
186
202
  }
@@ -232,4 +248,79 @@ export class SlideViewer {
232
248
  this.slide.release();
233
249
  this.events.emit("unfreeze");
234
250
  }
251
+
252
+ toPdf = async (progressCallback: (progress: number) => void) => {
253
+ const MAX = 1920;
254
+ const resizeCanvas = document.createElement("canvas");
255
+ const resizeCtx = resizeCanvas.getContext("2d");
256
+ const { slideCount, width, height } = this.slide;
257
+ let pdfWidth = Math.floor(width);
258
+ let pdfHeight = Math.floor(height);
259
+ if (pdfWidth > MAX) {
260
+ pdfWidth = MAX;
261
+ pdfHeight = Math.floor((height * pdfWidth) / width);
262
+ }
263
+ if (pdfHeight > MAX) {
264
+ pdfHeight = MAX;
265
+ pdfWidth = Math.floor((width * pdfHeight) / height);
266
+ }
267
+ resizeCanvas.width = pdfWidth;
268
+ resizeCanvas.height = pdfHeight;
269
+
270
+ const whiteSnapshotCanvas = document.createElement("canvas");
271
+ whiteSnapshotCanvas.width = width;
272
+ whiteSnapshotCanvas.height = height;
273
+ const whiteCtx = whiteSnapshotCanvas.getContext("2d");
274
+ if (!whiteCtx || !this._getWhiteSnapshot || !resizeCtx) {
275
+ return null;
276
+ }
277
+
278
+ const orientation = pdfWidth > pdfHeight ? "l" : "p";
279
+ const pdf = new jsPDF({
280
+ format: [pdfWidth, pdfHeight],
281
+ orientation,
282
+ compress: true,
283
+ });
284
+
285
+ for (let i = 1; i <= slideCount; i++) {
286
+ const slideSnapshot = await this.slide.snapshotWithTimingEnd(i);
287
+
288
+ if (slideSnapshot) {
289
+ const img = document.createElement("img");
290
+ img.src = slideSnapshot;
291
+ await new Promise(resolve => (img.onload = resolve));
292
+ resizeCtx.drawImage(img, 0, 0, pdfWidth, pdfHeight);
293
+ }
294
+ whiteCtx.clearRect(0, 0, width, height);
295
+ this._getWhiteSnapshot(i, whiteSnapshotCanvas, whiteCtx);
296
+ try {
297
+ const whiteSnapshot = whiteSnapshotCanvas.toDataURL("image/png");
298
+ const whiteImg = document.createElement("img");
299
+ whiteImg.src = whiteSnapshot;
300
+ await new Promise(resolve => (whiteImg.onload = resolve));
301
+ resizeCtx.drawImage(whiteImg, 0, 0, pdfWidth, pdfHeight);
302
+ } catch (e) {
303
+ // ignore
304
+ }
305
+
306
+ const outputDataUrl = resizeCanvas.toDataURL("image/jpeg", 0.6);
307
+ if (i > 1) {
308
+ pdf.addPage();
309
+ }
310
+ pdf.addImage(outputDataUrl, "JPEG", 0, 0, pdfWidth, pdfHeight, "", "FAST");
311
+ resizeCtx.clearRect(0, 0, pdfWidth, pdfHeight);
312
+ progressCallback(Math.ceil((i / slideCount) * 100));
313
+ }
314
+ const dataUrl = pdf.output("arraybuffer");
315
+ const blob = new Blob([dataUrl]);
316
+ const downloadUrl = URL.createObjectURL(blob);
317
+ const element = document.createElement("a");
318
+ element.setAttribute("href", downloadUrl);
319
+ element.setAttribute("download", `${this._slideTitle || "snapshot"}.pdf`);
320
+ element.style.display = "none";
321
+ document.body.appendChild(element);
322
+ element.click();
323
+ document.body.removeChild(element);
324
+ window.postMessage({ type: "@app-slide/_download_pdf_", buf: dataUrl });
325
+ };
235
326
  }
@@ -1,7 +1,8 @@
1
+ import type { ILazyLoadInstance } from "vanilla-lazyload";
1
2
  import type { PreviewPage } from "./typings";
2
3
 
3
4
  import LazyLoad from "vanilla-lazyload";
4
- import { append, h, noop, set_class, wrap_class } from "../utils";
5
+ import { append, h, hc, noop, wrap_class } from "../utils";
5
6
 
6
7
  export function create_sidebar() {
7
8
  type NewPageIndexCallback = (newPageIndex: number) => void;
@@ -19,11 +20,9 @@ export function create_sidebar() {
19
20
  const class_readonly = wrap_class("readonly");
20
21
  const class_preview_active = wrap_class("preview-active");
21
22
 
22
- const $content = h("div");
23
- set_class($content, "content");
23
+ const $content = hc("div", "content");
24
24
 
25
- const $previewMask = h("div");
26
- set_class($previewMask, "preview-mask");
25
+ const $previewMask = hc("div", "preview-mask");
27
26
  $previewMask.onclick = function on_click_preview_mask(ev) {
28
27
  if (readonly) return;
29
28
  if (ev.target !== $previewMask) return;
@@ -31,8 +30,7 @@ export function create_sidebar() {
31
30
  };
32
31
  append($content, $previewMask);
33
32
 
34
- const $preview = h("div");
35
- set_class($preview, "preview");
33
+ const $preview = hc("div", "preview");
36
34
  $preview.classList.add(wrap_class("tele-fancy-scrollbar"));
37
35
  $preview.onclick = function on_click_preview(ev) {
38
36
  if (readonly) return;
@@ -47,10 +45,7 @@ export function create_sidebar() {
47
45
  };
48
46
  append($content, $preview);
49
47
 
50
- const lazyload = new LazyLoad({
51
- container: $preview,
52
- elements_selector: `.${wrap_class("preview-page>img")}`,
53
- });
48
+ let lazyload: ILazyLoadInstance | undefined;
54
49
 
55
50
  function set_readonly(readonly_: boolean) {
56
51
  if (readonly !== readonly_) {
@@ -63,6 +58,12 @@ export function create_sidebar() {
63
58
  if (active !== active_) {
64
59
  active = active_;
65
60
  $content.classList.toggle(class_preview_active, active);
61
+ if (active && !lazyload) {
62
+ lazyload = new LazyLoad({
63
+ container: $preview,
64
+ elements_selector: `.${wrap_class("preview-page>img")}`,
65
+ });
66
+ }
66
67
  }
67
68
  }
68
69
 
@@ -89,8 +90,8 @@ export function create_sidebar() {
89
90
  const $img = h("img");
90
91
  $img.width = page.width;
91
92
  $img.height = page.height;
92
- $img.src = page.thumbnail;
93
93
  $img.alt = `page-${i}`;
94
+ $img.dataset.src = page.thumbnail;
94
95
  $img.dataset.pageIndex = pageIndex;
95
96
 
96
97
  append($page, $img);
@@ -99,7 +100,7 @@ export function create_sidebar() {
99
100
  });
100
101
 
101
102
  if (!destroyed) {
102
- lazyload.update();
103
+ lazyload?.update();
103
104
  }
104
105
  }
105
106
  }
@@ -118,7 +119,7 @@ export function create_sidebar() {
118
119
 
119
120
  function destroy() {
120
121
  destroyed = true;
121
- lazyload.destroy();
122
+ lazyload?.destroy();
122
123
  }
123
124
 
124
125
  function get_active() {
package/src/connect.ts CHANGED
@@ -23,6 +23,30 @@ export function connect({ context, storage, viewer, sideEffect, logger }: Connec
23
23
  width && context.box.setStageRatio(height / width);
24
24
  whiteboard = context.createWhiteBoardView({ size: slideCount });
25
25
  whiteboard.setBaseRect({ width, height });
26
+
27
+ viewer.setSlideTitle(context.box.title);
28
+ viewer.attachWhiteSnapshot(
29
+ (index: number, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => {
30
+ if (!whiteboard) {
31
+ return null;
32
+ }
33
+ const scenePath = whiteboard.pageState.pages[index - 1];
34
+ canvas.width = whiteboard.view.size.width;
35
+ canvas.height = whiteboard.view.size.height;
36
+ whiteboard.view.screenshotToCanvas(
37
+ ctx,
38
+ scenePath,
39
+ whiteboard.view.size.width,
40
+ whiteboard.view.size.height,
41
+ {
42
+ centerX: 0,
43
+ centerY: 0,
44
+ scale: whiteboard.view.camera.scale,
45
+ }
46
+ );
47
+ }
48
+ );
49
+
26
50
  kick_start();
27
51
  });
28
52
 
@@ -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
@@ -70,7 +70,7 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, void> = {
70
70
  // For debugging.
71
71
  Object.assign(viewer, { context, logger });
72
72
 
73
- sideEffect.push(refrigerator.set(context.appId, viewer));
73
+ sideEffect.push(refrigerator.set(context.appId, viewer, context.box));
74
74
 
75
75
  if (import.meta.env.DEV) {
76
76
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
package/src/preview.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import type { SlideViewerOptions } from "./SlideViewer";
2
2
 
3
3
  import { SlideViewer } from "./SlideViewer";
4
+ import { hc } from "./utils";
4
5
 
5
6
  export function previewSlide(options: SlideViewerOptions & { container: HTMLElement }) {
6
7
  const { container, ...slideOptions } = options;
7
- const wrapper = document.createElement("div");
8
- wrapper.className = "netless-app-slide-preview-wrapper";
8
+ const wrapper = hc("div", "preview-wrapper");
9
9
  const viewer = new SlideViewer(slideOptions);
10
10
  wrapper.appendChild(document.createElement("style")).textContent = SlideViewer.styles;
11
11
  wrapper.appendChild(viewer.$content);