@netless/app-presentation 0.1.0 → 0.1.2

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/index.d.ts CHANGED
@@ -218,6 +218,7 @@ interface PresentationPage {
218
218
  interface PresentationConfig {
219
219
  readonly pages: PresentationPage[];
220
220
  readonly readonly?: boolean;
221
+ readonly thumbnail?: (src: string) => string;
221
222
  }
222
223
  /**
223
224
  * Standalone presentation slide viewer.
@@ -280,21 +281,40 @@ declare class Presentation implements IDisposable<void> {
280
281
  onNewPageIndex(index: number, _origin: "navigation" | "keydown" | "input" | "preview"): void;
281
282
  page(): PresentationPage | undefined;
282
283
  updateImage(): void;
284
+ /** Returns a modified URL that points to the thumbnail image. */
285
+ thumbnail(src: string): string;
283
286
  private c;
284
287
  private isEditable;
285
- private x_oss_process;
286
288
  }
287
289
 
288
290
  declare const __inline: string;
289
291
 
290
292
  type Logger = (...data: any[]) => void;
293
+ interface Viewport {
294
+ readonly x: number;
295
+ readonly y: number;
296
+ readonly width: number;
297
+ readonly height: number;
298
+ }
291
299
  interface PresentationAppOptions {
292
300
  /** Disables user move / scale the image and whiteboard. */
293
301
  disableCameraTransform?: boolean;
294
302
  /** Max scale = `maxCameraScale` * default scale. Not working when `disableCameraTransform` is true. Default: 3 */
295
303
  maxCameraScale?: number;
296
- /** Custom logger. Default: `console.log` */
304
+ /** Custom logger. Default: a logger that reports to the whiteboard server. */
297
305
  log?: Logger;
306
+ /** Custom thumbnail generator. Default is appending `"?x-oss-process=image/resize,l_50"` to `src`. */
307
+ thumbnail?: (src: string) => string;
308
+ /**
309
+ * Custom viewport to set on the first time the presentation was added. Default is full page.
310
+ * Numbers range in 0 to 1 is considered a ratio to multiply the real page size.
311
+ * Example settings:
312
+ *
313
+ * - Full page: `{ x: 0, y: 0, width: 1, height: 1 }`
314
+ * - Half page: `{ x: 0, y: 0, width: 1, height: 0.5 }`
315
+ * - Absolute top-left area of the page: `{ x: 0, y: 0, width: 100, height: 100 }`
316
+ */
317
+ viewport?: Viewport | ((page: PresentationPage) => Viewport);
298
318
  }
299
319
  interface PresentationController {
300
320
  readonly app: Presentation;
@@ -318,7 +338,7 @@ interface PresentationController {
318
338
  log: Logger;
319
339
  }
320
340
 
321
- declare const NetlessAppPresentation: NetlessApp<{}, unknown, PresentationAppOptions, PresentationController>;
341
+ declare const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions, PresentationController>;
322
342
  type RegisterFn = typeof WindowManager["register"];
323
343
  interface InstallOptions {
324
344
  /**
@@ -328,6 +348,10 @@ interface InstallOptions {
328
348
  * @example "DocsViewer"
329
349
  */
330
350
  as?: string;
351
+ /**
352
+ * Options to customize the local app (not synced to others).
353
+ */
354
+ appOptions?: PresentationAppOptions;
331
355
  }
332
356
  /**
333
357
  * Call `register({ kind: "Presentation", src: NetlessAppPresentation })` to register this app.
@@ -1,14 +1,16 @@
1
1
  var NetlessAppPresentation = (function (exports) {
2
2
  'use strict';
3
3
 
4
- // node_modules/.pnpm/@wopjs+disposable@0.1.4/node_modules/@wopjs/disposable/dist/index.mjs
4
+ // node_modules/.pnpm/@wopjs+disposable@0.1.5/node_modules/@wopjs/disposable/dist/index.mjs
5
5
  var isAbortable = (disposable) => disposable && disposable.abortable;
6
- var invokeDispose = (disposable) => {
6
+ var dispose = (disposable) => {
7
7
  try {
8
- if (disposable.dispose) {
9
- disposable.dispose();
10
- } else {
11
- disposable();
8
+ if (disposable) {
9
+ if (disposable.dispose) {
10
+ disposable.dispose();
11
+ } else if (disposable.constructor && disposable.call && disposable.apply) {
12
+ disposable();
13
+ }
12
14
  }
13
15
  } catch (e) {
14
16
  console.error(e);
@@ -45,7 +47,7 @@ var NetlessAppPresentation = (function (exports) {
45
47
  flush(disposable) {
46
48
  if (disposable) {
47
49
  if (this.remove(disposable)) {
48
- invokeDispose(disposable);
50
+ dispose(disposable);
49
51
  }
50
52
  } else {
51
53
  this.dispose();
@@ -54,7 +56,7 @@ var NetlessAppPresentation = (function (exports) {
54
56
  };
55
57
  var disposableStore = (disposables) => {
56
58
  const disposableStore2 = () => {
57
- disposableStore2._d.forEach(invokeDispose);
59
+ disposableStore2._d.forEach(dispose);
58
60
  disposableStore2._d.clear();
59
61
  };
60
62
  disposableStore2._d = /* @__PURE__ */ new Set();
@@ -73,11 +75,9 @@ var NetlessAppPresentation = (function (exports) {
73
75
  }
74
76
  var listen = addEventListener;
75
77
 
76
- // node_modules/.pnpm/vanilla-lazyload@17.8.5/node_modules/vanilla-lazyload/dist/lazyload.esm.js
78
+ // node_modules/.pnpm/vanilla-lazyload@18.0.0/node_modules/vanilla-lazyload/dist/lazyload.esm.js
77
79
  var runningOnBrowser = typeof window !== "undefined";
78
80
  var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent);
79
- var supportsIntersectionObserver = runningOnBrowser && "IntersectionObserver" in window;
80
- var supportsClassList = runningOnBrowser && "classList" in document.createElement("p");
81
81
  var isHiDpi = runningOnBrowser && window.devicePixelRatio > 1;
82
82
  var defaultSettings = {
83
83
  elements_selector: ".lazy",
@@ -189,18 +189,22 @@ var NetlessAppPresentation = (function (exports) {
189
189
  callback(arg1);
190
190
  };
191
191
  var addClass = (element, className) => {
192
- if (supportsClassList) {
193
- element.classList.add(className);
192
+ if (!runningOnBrowser) {
193
+ return;
194
+ }
195
+ if (className === "") {
194
196
  return;
195
197
  }
196
- element.className += (element.className ? " " : "") + className;
198
+ element.classList.add(className);
197
199
  };
198
200
  var removeClass = (element, className) => {
199
- if (supportsClassList) {
200
- element.classList.remove(className);
201
+ if (!runningOnBrowser) {
202
+ return;
203
+ }
204
+ if (className === "") {
201
205
  return;
202
206
  }
203
- element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ").replace(/^\s+/, "").replace(/\s+$/, "");
207
+ element.classList.remove(className);
204
208
  };
205
209
  var addTempImage = (element) => {
206
210
  element.llTempImage = document.createElement("IMG");
@@ -647,7 +651,7 @@ var NetlessAppPresentation = (function (exports) {
647
651
  observeElements(observer, elementsToObserve);
648
652
  };
649
653
  var setObserver = (settings, instance) => {
650
- if (!supportsIntersectionObserver || shouldUseNative(settings)) {
654
+ if (shouldUseNative(settings)) {
651
655
  return;
652
656
  }
653
657
  instance._observer = new IntersectionObserver((entries) => {
@@ -696,7 +700,7 @@ var NetlessAppPresentation = (function (exports) {
696
700
  const settings = this._settings;
697
701
  const elementsToLoad = getElementsToLoad(givenNodeset, settings);
698
702
  setToLoadCount(this, elementsToLoad.length);
699
- if (isBot || !supportsIntersectionObserver) {
703
+ if (isBot) {
700
704
  this.loadAll(elementsToLoad);
701
705
  return;
702
706
  }
@@ -859,6 +863,8 @@ var NetlessAppPresentation = (function (exports) {
859
863
  var _a;
860
864
  this.pages = config.pages;
861
865
  this.preload = new Preload(this.pages);
866
+ if (config.thumbnail)
867
+ this.thumbnail = config.thumbnail;
862
868
  this.dispose.add(this.preload);
863
869
  this.readonly = (_a = config.readonly) != null ? _a : false;
864
870
  this.dom = document.createElement("div");
@@ -904,7 +910,7 @@ var NetlessAppPresentation = (function (exports) {
904
910
  const c_previewPageName = this.c("preview-page-name");
905
911
  for (let index = 0; index < this.pages.length; ++index) {
906
912
  const page = this.pages[index];
907
- const previewSRC = page.thumbnail || this.x_oss_process(page.src);
913
+ const previewSRC = page.thumbnail || this.thumbnail(page.src);
908
914
  const previewPage = document.createElement("a");
909
915
  previewPage.className = `${c_previewPage} ${this.c(`preview-page-${index}`)}`;
910
916
  previewPage.setAttribute("href", "#");
@@ -1066,16 +1072,8 @@ var NetlessAppPresentation = (function (exports) {
1066
1072
  this.image.src = page.src;
1067
1073
  this.preload.touch(this.pageIndex);
1068
1074
  }
1069
- c(className) {
1070
- return `${this.namespace}-${className}`;
1071
- }
1072
- isEditable(el) {
1073
- if (!el)
1074
- return false;
1075
- const { tagName } = el;
1076
- return tagName == "INPUT" || tagName == "TEXTAREA" || tagName == "SELECT";
1077
- }
1078
- x_oss_process(src) {
1075
+ /** Returns a modified URL that points to the thumbnail image. */
1076
+ thumbnail(src) {
1079
1077
  try {
1080
1078
  const url = new URL(src);
1081
1079
  url.searchParams.set("x-oss-process", "image/resize,l_50");
@@ -1085,6 +1083,15 @@ var NetlessAppPresentation = (function (exports) {
1085
1083
  return src;
1086
1084
  }
1087
1085
  }
1086
+ c(className) {
1087
+ return `${this.namespace}-${className}`;
1088
+ }
1089
+ isEditable(el) {
1090
+ if (!el)
1091
+ return false;
1092
+ const { tagName } = el;
1093
+ return tagName == "INPUT" || tagName == "TEXTAREA" || tagName == "SELECT";
1094
+ }
1088
1095
  };
1089
1096
 
1090
1097
  // src/style.scss?inline
@@ -1102,7 +1109,7 @@ var NetlessAppPresentation = (function (exports) {
1102
1109
  var NetlessAppPresentation = {
1103
1110
  kind: "Presentation",
1104
1111
  setup(context) {
1105
- var _a;
1112
+ var _a, _b, _c;
1106
1113
  const view = context.getView();
1107
1114
  if (!view)
1108
1115
  throw new Error("[Presentation]: no whiteboard view, make sure you have added options.scenePath in addApp()");
@@ -1113,8 +1120,10 @@ var NetlessAppPresentation = (function (exports) {
1113
1120
  throw new Error("[Presentation]: legacy dynamic PPT is unsupported, please use the projector converter and @netless/slide to render it");
1114
1121
  const scenePath = context.getInitScenePath();
1115
1122
  const options = context.getAppOptions() || {};
1116
- if (!(Number.isFinite(options.maxCameraScale) && options.maxCameraScale > 0)) {
1123
+ let maxCameraScale = (_b = options.maxCameraScale) != null ? _b : 3;
1124
+ if (!(Number.isFinite(maxCameraScale) && maxCameraScale > 0)) {
1117
1125
  console.warn(`[Presentation] maxCameraScale should be a positive number, got ${options.maxCameraScale}`);
1126
+ maxCameraScale = 3;
1118
1127
  }
1119
1128
  const log = options.log || createLogger(context.getRoom());
1120
1129
  log(`[Presentation] new ${context.appId}`);
@@ -1132,9 +1141,10 @@ var NetlessAppPresentation = (function (exports) {
1132
1141
  }
1133
1142
  }
1134
1143
  }
1135
- const dispose = disposableStore();
1136
- dispose.add(() => log(`[Presentation] dispose ${context.appId}`));
1144
+ const dispose2 = disposableStore();
1145
+ dispose2.add(() => log(`[Presentation] dispose ${context.appId}`));
1137
1146
  const page$$ = context.createStorage("page", { index: 0 });
1147
+ const view$$ = context.createStorage("view", { uid: "", originX: 0, originY: 0, width: 0, height: 0 });
1138
1148
  let lastIndex = -1;
1139
1149
  const syncPage = async (index) => {
1140
1150
  if (lastIndex !== index) {
@@ -1187,7 +1197,7 @@ var NetlessAppPresentation = (function (exports) {
1187
1197
  height,
1188
1198
  animationMode: "immediately"
1189
1199
  });
1190
- const maxScale = view.camera.scale * (options.disableCameraTransform ? 1 : options.maxCameraScale || 3);
1200
+ const maxScale = view.camera.scale * (options.disableCameraTransform ? 1 : maxCameraScale);
1191
1201
  const minScale = view.camera.scale;
1192
1202
  view.setCameraBound({
1193
1203
  damping: 1,
@@ -1198,19 +1208,51 @@ var NetlessAppPresentation = (function (exports) {
1198
1208
  width,
1199
1209
  height
1200
1210
  });
1211
+ syncViewFromRemote(true);
1201
1212
  }
1202
1213
  };
1203
- syncPage(page$$.state.index);
1204
- dispose.add(page$$.addStateChangedListener(() => syncPage(page$$.state.index)));
1205
- dispose.add(context.emitter.on("roomStateChange", (state) => {
1206
- var _a2;
1207
- if ((_a2 = state.sceneState) == null ? void 0 : _a2.scenePath.endsWith("/0")) {
1208
- syncPage(page$$.state.index);
1214
+ const me = ((_c = context.getRoom()) == null ? void 0 : _c.uid) || context.getDisplayer().observerId + "";
1215
+ let throttleSyncView = 0;
1216
+ const syncView = () => {
1217
+ if (throttleSyncView > 0)
1218
+ return;
1219
+ const { width, height } = app.page() || {};
1220
+ if (width && height && context.getIsWritable()) {
1221
+ const { camera, size } = view;
1222
+ const fixedW = Math.min(size.width, size.height * width / height);
1223
+ const fixedH = Math.min(size.height, size.width * height / width);
1224
+ const w = fixedW / camera.scale;
1225
+ const h = fixedH / camera.scale;
1226
+ const x = camera.centerX - w / 2;
1227
+ const y = camera.centerY - h / 2;
1228
+ throttleSyncView = setTimeout(() => {
1229
+ throttleSyncView = 0;
1230
+ view$$.setState({ uid: me, originX: x, originY: y, width: w, height: h });
1231
+ }, 50);
1209
1232
  }
1210
- }));
1233
+ };
1234
+ dispose2.add(() => {
1235
+ clearTimeout(throttleSyncView);
1236
+ throttleSyncView = 0;
1237
+ });
1238
+ const syncViewFromRemote = (force = false, animate = false) => {
1239
+ const { uid, originX, originY, width, height } = view$$.state;
1240
+ if ((force || uid !== me) && width > 0 && height > 0) {
1241
+ view.moveCameraToContain({
1242
+ originX,
1243
+ originY,
1244
+ width,
1245
+ height,
1246
+ animationMode: animate ? "continuous" : "immediately"
1247
+ });
1248
+ }
1249
+ };
1250
+ syncPage(page$$.state.index);
1251
+ dispose2.add(page$$.addStateChangedListener(() => syncPage(page$$.state.index)));
1252
+ dispose2.add(view$$.addStateChangedListener(() => syncViewFromRemote(false, true)));
1211
1253
  const box = context.getBox();
1212
- const app = dispose.add(createPresentation(box, pages, jumpPage, page$$));
1213
- app.contentDOM.dataset.appPresentationVersion = "0.1.0";
1254
+ const app = dispose2.add(createPresentation(box, pages, jumpPage, page$$, options.thumbnail));
1255
+ app.contentDOM.dataset.appPresentationVersion = "0.1.2";
1214
1256
  app.scaleDocsToFit = scaleDocsToFit;
1215
1257
  app.log = log;
1216
1258
  context.mountView(app.whiteboardDOM);
@@ -1218,14 +1260,31 @@ var NetlessAppPresentation = (function (exports) {
1218
1260
  view.disableCameraTransform = true;
1219
1261
  }
1220
1262
  scaleDocsToFit();
1221
- dispose.make(() => {
1263
+ dispose2.make(() => {
1222
1264
  view.callbacks.on("onSizeUpdated", scaleDocsToFit);
1223
1265
  return () => view.callbacks.off("onSizeUpdated", scaleDocsToFit);
1224
1266
  });
1225
- dispose.add(context.emitter.on("writableChange", (isWritable) => {
1267
+ if (options.viewport && context.isAddApp && app.page()) {
1268
+ const page = app.page();
1269
+ const viewport = typeof options.viewport === "function" ? options.viewport(page) : options.viewport;
1270
+ const fix = (i, x) => i == 0 ? i : i <= 1 ? i * x : i;
1271
+ view$$.setState({
1272
+ uid: me,
1273
+ originX: fix(viewport.x, page.width) - page.width / 2,
1274
+ originY: fix(viewport.y, page.height) - page.height / 2,
1275
+ width: fix(viewport.width, page.width),
1276
+ height: fix(viewport.height, page.height)
1277
+ });
1278
+ }
1279
+ dispose2.make(() => {
1280
+ view.callbacks.on("onCameraUpdatedByDevice", syncView);
1281
+ return () => view.callbacks.off("onCameraUpdatedByDevice", syncView);
1282
+ });
1283
+ syncViewFromRemote(true);
1284
+ dispose2.add(context.emitter.on("writableChange", (isWritable) => {
1226
1285
  app.setReadonly(!isWritable);
1227
1286
  }));
1228
- context.emitter.on("destroy", () => dispose());
1287
+ context.emitter.on("destroy", () => dispose2());
1229
1288
  const reportProgress = (progress, result) => {
1230
1289
  window.postMessage({ type: "@netless/_result_save_pdf_", appId: context.appId, progress, result }, "*");
1231
1290
  return result;
@@ -1321,7 +1380,7 @@ var NetlessAppPresentation = (function (exports) {
1321
1380
  const title = box.title;
1322
1381
  return reportProgress(100, { pdf: data, title });
1323
1382
  };
1324
- dispose.add(listen(window, "message", (ev) => {
1383
+ dispose2.add(listen(window, "message", (ev) => {
1325
1384
  if (ev.data && ev.data.type == "@netless/_request_save_pdf_" && ev.data.appId == context.appId) {
1326
1385
  toPdf().catch((err) => {
1327
1386
  console.warn(err);
@@ -1330,10 +1389,10 @@ var NetlessAppPresentation = (function (exports) {
1330
1389
  }
1331
1390
  }));
1332
1391
  const controller = { app, view, context, jumpPage, prevPage, nextPage, pageState, toPdf, log };
1333
- dispose.add(listen(window, "message", (ev) => {
1392
+ dispose2.add(listen(window, "message", (ev) => {
1334
1393
  if (ev.data === "@netless/_presentation_") {
1335
1394
  if (typeof window !== "undefined")
1336
- dispose.make(() => {
1395
+ dispose2.make(() => {
1337
1396
  const debug = window._presentation_ || (window._presentation_ = /* @__PURE__ */ new Set());
1338
1397
  debug.add(controller);
1339
1398
  return () => debug.delete(controller);
@@ -1364,9 +1423,9 @@ var NetlessAppPresentation = (function (exports) {
1364
1423
  }
1365
1424
  }
1366
1425
  };
1367
- var createPresentation = (box, pages, jumpPage, page$$) => {
1426
+ function createPresentation(box, pages, jumpPage, page$$, thumbnail) {
1368
1427
  box.mountStyles(style_default);
1369
- const app = new AppPresentation({ pages, readonly: box.readonly, jumpPage });
1428
+ const app = new AppPresentation({ pages, readonly: box.readonly, jumpPage, thumbnail });
1370
1429
  app.box = box;
1371
1430
  box.mountContent(app.contentDOM);
1372
1431
  box.mountFooter(app.footerDOM);
@@ -1375,17 +1434,17 @@ var NetlessAppPresentation = (function (exports) {
1375
1434
  app.setPageIndex(page$$.state.index);
1376
1435
  }));
1377
1436
  return app;
1378
- };
1437
+ }
1379
1438
  var install = (register, options = {}) => {
1380
1439
  let app = NetlessAppPresentation;
1381
1440
  if (options.as) {
1382
1441
  app = Object.assign({}, app, { kind: options.as });
1383
1442
  }
1384
- return register({ kind: app.kind, src: app });
1443
+ return register({ kind: app.kind, src: app, appOptions: options.appOptions });
1385
1444
  };
1386
1445
 
1387
1446
  // src/index.ts
1388
- var version = "0.1.0";
1447
+ var version = "0.1.2";
1389
1448
 
1390
1449
  exports.NetlessAppPresentation = NetlessAppPresentation;
1391
1450
  exports.Presentation = Presentation;