@netless/app-presentation 0.1.1 → 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
@@ -290,15 +290,31 @@ declare class Presentation implements IDisposable<void> {
290
290
  declare const __inline: string;
291
291
 
292
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
+ }
293
299
  interface PresentationAppOptions {
294
300
  /** Disables user move / scale the image and whiteboard. */
295
301
  disableCameraTransform?: boolean;
296
302
  /** Max scale = `maxCameraScale` * default scale. Not working when `disableCameraTransform` is true. Default: 3 */
297
303
  maxCameraScale?: number;
298
- /** Custom logger. Default: `console.log` */
304
+ /** Custom logger. Default: a logger that reports to the whiteboard server. */
299
305
  log?: Logger;
300
306
  /** Custom thumbnail generator. Default is appending `"?x-oss-process=image/resize,l_50"` to `src`. */
301
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);
302
318
  }
303
319
  interface PresentationController {
304
320
  readonly app: Presentation;
@@ -322,7 +338,7 @@ interface PresentationController {
322
338
  log: Logger;
323
339
  }
324
340
 
325
- declare const NetlessAppPresentation: NetlessApp<{}, unknown, PresentationAppOptions, PresentationController>;
341
+ declare const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions, PresentationController>;
326
342
  type RegisterFn = typeof WindowManager["register"];
327
343
  interface InstallOptions {
328
344
  /**
@@ -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) {
194
193
  return;
195
194
  }
196
- element.className += (element.className ? " " : "") + className;
195
+ if (className === "") {
196
+ return;
197
+ }
198
+ element.classList.add(className);
197
199
  };
198
200
  var removeClass = (element, className) => {
199
- if (supportsClassList) {
200
- element.classList.remove(className);
201
+ if (!runningOnBrowser) {
201
202
  return;
202
203
  }
203
- element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ").replace(/^\s+/, "").replace(/\s+$/, "");
204
+ if (className === "") {
205
+ return;
206
+ }
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
  }
@@ -1105,7 +1109,7 @@ var NetlessAppPresentation = (function (exports) {
1105
1109
  var NetlessAppPresentation = {
1106
1110
  kind: "Presentation",
1107
1111
  setup(context) {
1108
- var _a, _b;
1112
+ var _a, _b, _c;
1109
1113
  const view = context.getView();
1110
1114
  if (!view)
1111
1115
  throw new Error("[Presentation]: no whiteboard view, make sure you have added options.scenePath in addApp()");
@@ -1137,9 +1141,10 @@ var NetlessAppPresentation = (function (exports) {
1137
1141
  }
1138
1142
  }
1139
1143
  }
1140
- const dispose = disposableStore();
1141
- dispose.add(() => log(`[Presentation] dispose ${context.appId}`));
1144
+ const dispose2 = disposableStore();
1145
+ dispose2.add(() => log(`[Presentation] dispose ${context.appId}`));
1142
1146
  const page$$ = context.createStorage("page", { index: 0 });
1147
+ const view$$ = context.createStorage("view", { uid: "", originX: 0, originY: 0, width: 0, height: 0 });
1143
1148
  let lastIndex = -1;
1144
1149
  const syncPage = async (index) => {
1145
1150
  if (lastIndex !== index) {
@@ -1203,19 +1208,51 @@ var NetlessAppPresentation = (function (exports) {
1203
1208
  width,
1204
1209
  height
1205
1210
  });
1211
+ syncViewFromRemote(true);
1206
1212
  }
1207
1213
  };
1208
- syncPage(page$$.state.index);
1209
- dispose.add(page$$.addStateChangedListener(() => syncPage(page$$.state.index)));
1210
- dispose.add(context.emitter.on("roomStateChange", (state) => {
1211
- var _a2;
1212
- if ((_a2 = state.sceneState) == null ? void 0 : _a2.scenePath.endsWith("/0")) {
1213
- 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);
1214
1232
  }
1215
- }));
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)));
1216
1253
  const box = context.getBox();
1217
- const app = dispose.add(createPresentation(box, pages, jumpPage, page$$, options.thumbnail));
1218
- app.contentDOM.dataset.appPresentationVersion = "0.1.1";
1254
+ const app = dispose2.add(createPresentation(box, pages, jumpPage, page$$, options.thumbnail));
1255
+ app.contentDOM.dataset.appPresentationVersion = "0.1.2";
1219
1256
  app.scaleDocsToFit = scaleDocsToFit;
1220
1257
  app.log = log;
1221
1258
  context.mountView(app.whiteboardDOM);
@@ -1223,14 +1260,31 @@ var NetlessAppPresentation = (function (exports) {
1223
1260
  view.disableCameraTransform = true;
1224
1261
  }
1225
1262
  scaleDocsToFit();
1226
- dispose.make(() => {
1263
+ dispose2.make(() => {
1227
1264
  view.callbacks.on("onSizeUpdated", scaleDocsToFit);
1228
1265
  return () => view.callbacks.off("onSizeUpdated", scaleDocsToFit);
1229
1266
  });
1230
- 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) => {
1231
1285
  app.setReadonly(!isWritable);
1232
1286
  }));
1233
- context.emitter.on("destroy", () => dispose());
1287
+ context.emitter.on("destroy", () => dispose2());
1234
1288
  const reportProgress = (progress, result) => {
1235
1289
  window.postMessage({ type: "@netless/_result_save_pdf_", appId: context.appId, progress, result }, "*");
1236
1290
  return result;
@@ -1326,7 +1380,7 @@ var NetlessAppPresentation = (function (exports) {
1326
1380
  const title = box.title;
1327
1381
  return reportProgress(100, { pdf: data, title });
1328
1382
  };
1329
- dispose.add(listen(window, "message", (ev) => {
1383
+ dispose2.add(listen(window, "message", (ev) => {
1330
1384
  if (ev.data && ev.data.type == "@netless/_request_save_pdf_" && ev.data.appId == context.appId) {
1331
1385
  toPdf().catch((err) => {
1332
1386
  console.warn(err);
@@ -1335,10 +1389,10 @@ var NetlessAppPresentation = (function (exports) {
1335
1389
  }
1336
1390
  }));
1337
1391
  const controller = { app, view, context, jumpPage, prevPage, nextPage, pageState, toPdf, log };
1338
- dispose.add(listen(window, "message", (ev) => {
1392
+ dispose2.add(listen(window, "message", (ev) => {
1339
1393
  if (ev.data === "@netless/_presentation_") {
1340
1394
  if (typeof window !== "undefined")
1341
- dispose.make(() => {
1395
+ dispose2.make(() => {
1342
1396
  const debug = window._presentation_ || (window._presentation_ = /* @__PURE__ */ new Set());
1343
1397
  debug.add(controller);
1344
1398
  return () => debug.delete(controller);
@@ -1390,7 +1444,7 @@ var NetlessAppPresentation = (function (exports) {
1390
1444
  };
1391
1445
 
1392
1446
  // src/index.ts
1393
- var version = "0.1.1";
1447
+ var version = "0.1.2";
1394
1448
 
1395
1449
  exports.NetlessAppPresentation = NetlessAppPresentation;
1396
1450
  exports.Presentation = Presentation;
package/dist/index.js CHANGED
@@ -2,14 +2,16 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- // node_modules/.pnpm/@wopjs+disposable@0.1.4/node_modules/@wopjs/disposable/dist/index.mjs
5
+ // node_modules/.pnpm/@wopjs+disposable@0.1.5/node_modules/@wopjs/disposable/dist/index.mjs
6
6
  var isAbortable = (disposable) => disposable && disposable.abortable;
7
- var invokeDispose = (disposable) => {
7
+ var dispose = (disposable) => {
8
8
  try {
9
- if (disposable.dispose) {
10
- disposable.dispose();
11
- } else {
12
- disposable();
9
+ if (disposable) {
10
+ if (disposable.dispose) {
11
+ disposable.dispose();
12
+ } else if (disposable.constructor && disposable.call && disposable.apply) {
13
+ disposable();
14
+ }
13
15
  }
14
16
  } catch (e) {
15
17
  console.error(e);
@@ -46,7 +48,7 @@ var methods = {
46
48
  flush(disposable) {
47
49
  if (disposable) {
48
50
  if (this.remove(disposable)) {
49
- invokeDispose(disposable);
51
+ dispose(disposable);
50
52
  }
51
53
  } else {
52
54
  this.dispose();
@@ -55,7 +57,7 @@ var methods = {
55
57
  };
56
58
  var disposableStore = (disposables) => {
57
59
  const disposableStore2 = () => {
58
- disposableStore2._d.forEach(invokeDispose);
60
+ disposableStore2._d.forEach(dispose);
59
61
  disposableStore2._d.clear();
60
62
  };
61
63
  disposableStore2._d = /* @__PURE__ */ new Set();
@@ -74,11 +76,9 @@ function addEventListener(target, type, listener, options) {
74
76
  }
75
77
  var listen = addEventListener;
76
78
 
77
- // node_modules/.pnpm/vanilla-lazyload@17.8.5/node_modules/vanilla-lazyload/dist/lazyload.esm.js
79
+ // node_modules/.pnpm/vanilla-lazyload@18.0.0/node_modules/vanilla-lazyload/dist/lazyload.esm.js
78
80
  var runningOnBrowser = typeof window !== "undefined";
79
81
  var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent);
80
- var supportsIntersectionObserver = runningOnBrowser && "IntersectionObserver" in window;
81
- var supportsClassList = runningOnBrowser && "classList" in document.createElement("p");
82
82
  var isHiDpi = runningOnBrowser && window.devicePixelRatio > 1;
83
83
  var defaultSettings = {
84
84
  elements_selector: ".lazy",
@@ -190,18 +190,22 @@ var safeCallback = (callback, arg1, arg2, arg3) => {
190
190
  callback(arg1);
191
191
  };
192
192
  var addClass = (element, className) => {
193
- if (supportsClassList) {
194
- element.classList.add(className);
193
+ if (!runningOnBrowser) {
195
194
  return;
196
195
  }
197
- element.className += (element.className ? " " : "") + className;
196
+ if (className === "") {
197
+ return;
198
+ }
199
+ element.classList.add(className);
198
200
  };
199
201
  var removeClass = (element, className) => {
200
- if (supportsClassList) {
201
- element.classList.remove(className);
202
+ if (!runningOnBrowser) {
202
203
  return;
203
204
  }
204
- element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ").replace(/^\s+/, "").replace(/\s+$/, "");
205
+ if (className === "") {
206
+ return;
207
+ }
208
+ element.classList.remove(className);
205
209
  };
206
210
  var addTempImage = (element) => {
207
211
  element.llTempImage = document.createElement("IMG");
@@ -648,7 +652,7 @@ var updateObserver = (observer, elementsToObserve) => {
648
652
  observeElements(observer, elementsToObserve);
649
653
  };
650
654
  var setObserver = (settings, instance) => {
651
- if (!supportsIntersectionObserver || shouldUseNative(settings)) {
655
+ if (shouldUseNative(settings)) {
652
656
  return;
653
657
  }
654
658
  instance._observer = new IntersectionObserver((entries) => {
@@ -697,7 +701,7 @@ LazyLoad.prototype = {
697
701
  const settings = this._settings;
698
702
  const elementsToLoad = getElementsToLoad(givenNodeset, settings);
699
703
  setToLoadCount(this, elementsToLoad.length);
700
- if (isBot || !supportsIntersectionObserver) {
704
+ if (isBot) {
701
705
  this.loadAll(elementsToLoad);
702
706
  return;
703
707
  }
@@ -1106,7 +1110,7 @@ var createLogger = (room) => {
1106
1110
  var NetlessAppPresentation = {
1107
1111
  kind: "Presentation",
1108
1112
  setup(context) {
1109
- var _a, _b;
1113
+ var _a, _b, _c;
1110
1114
  const view = context.getView();
1111
1115
  if (!view)
1112
1116
  throw new Error("[Presentation]: no whiteboard view, make sure you have added options.scenePath in addApp()");
@@ -1138,9 +1142,10 @@ var NetlessAppPresentation = {
1138
1142
  }
1139
1143
  }
1140
1144
  }
1141
- const dispose = disposableStore();
1142
- dispose.add(() => log(`[Presentation] dispose ${context.appId}`));
1145
+ const dispose2 = disposableStore();
1146
+ dispose2.add(() => log(`[Presentation] dispose ${context.appId}`));
1143
1147
  const page$$ = context.createStorage("page", { index: 0 });
1148
+ const view$$ = context.createStorage("view", { uid: "", originX: 0, originY: 0, width: 0, height: 0 });
1144
1149
  let lastIndex = -1;
1145
1150
  const syncPage = async (index) => {
1146
1151
  if (lastIndex !== index) {
@@ -1204,19 +1209,51 @@ var NetlessAppPresentation = {
1204
1209
  width,
1205
1210
  height
1206
1211
  });
1212
+ syncViewFromRemote(true);
1207
1213
  }
1208
1214
  };
1209
- syncPage(page$$.state.index);
1210
- dispose.add(page$$.addStateChangedListener(() => syncPage(page$$.state.index)));
1211
- dispose.add(context.emitter.on("roomStateChange", (state) => {
1212
- var _a2;
1213
- if ((_a2 = state.sceneState) == null ? void 0 : _a2.scenePath.endsWith("/0")) {
1214
- syncPage(page$$.state.index);
1215
+ const me = ((_c = context.getRoom()) == null ? void 0 : _c.uid) || context.getDisplayer().observerId + "";
1216
+ let throttleSyncView = 0;
1217
+ const syncView = () => {
1218
+ if (throttleSyncView > 0)
1219
+ return;
1220
+ const { width, height } = app.page() || {};
1221
+ if (width && height && context.getIsWritable()) {
1222
+ const { camera, size } = view;
1223
+ const fixedW = Math.min(size.width, size.height * width / height);
1224
+ const fixedH = Math.min(size.height, size.width * height / width);
1225
+ const w = fixedW / camera.scale;
1226
+ const h = fixedH / camera.scale;
1227
+ const x = camera.centerX - w / 2;
1228
+ const y = camera.centerY - h / 2;
1229
+ throttleSyncView = setTimeout(() => {
1230
+ throttleSyncView = 0;
1231
+ view$$.setState({ uid: me, originX: x, originY: y, width: w, height: h });
1232
+ }, 50);
1215
1233
  }
1216
- }));
1234
+ };
1235
+ dispose2.add(() => {
1236
+ clearTimeout(throttleSyncView);
1237
+ throttleSyncView = 0;
1238
+ });
1239
+ const syncViewFromRemote = (force = false, animate = false) => {
1240
+ const { uid, originX, originY, width, height } = view$$.state;
1241
+ if ((force || uid !== me) && width > 0 && height > 0) {
1242
+ view.moveCameraToContain({
1243
+ originX,
1244
+ originY,
1245
+ width,
1246
+ height,
1247
+ animationMode: animate ? "continuous" : "immediately"
1248
+ });
1249
+ }
1250
+ };
1251
+ syncPage(page$$.state.index);
1252
+ dispose2.add(page$$.addStateChangedListener(() => syncPage(page$$.state.index)));
1253
+ dispose2.add(view$$.addStateChangedListener(() => syncViewFromRemote(false, true)));
1217
1254
  const box = context.getBox();
1218
- const app = dispose.add(createPresentation(box, pages, jumpPage, page$$, options.thumbnail));
1219
- app.contentDOM.dataset.appPresentationVersion = "0.1.1";
1255
+ const app = dispose2.add(createPresentation(box, pages, jumpPage, page$$, options.thumbnail));
1256
+ app.contentDOM.dataset.appPresentationVersion = "0.1.2";
1220
1257
  app.scaleDocsToFit = scaleDocsToFit;
1221
1258
  app.log = log;
1222
1259
  context.mountView(app.whiteboardDOM);
@@ -1224,14 +1261,31 @@ var NetlessAppPresentation = {
1224
1261
  view.disableCameraTransform = true;
1225
1262
  }
1226
1263
  scaleDocsToFit();
1227
- dispose.make(() => {
1264
+ dispose2.make(() => {
1228
1265
  view.callbacks.on("onSizeUpdated", scaleDocsToFit);
1229
1266
  return () => view.callbacks.off("onSizeUpdated", scaleDocsToFit);
1230
1267
  });
1231
- dispose.add(context.emitter.on("writableChange", (isWritable) => {
1268
+ if (options.viewport && context.isAddApp && app.page()) {
1269
+ const page = app.page();
1270
+ const viewport = typeof options.viewport === "function" ? options.viewport(page) : options.viewport;
1271
+ const fix = (i, x) => i == 0 ? i : i <= 1 ? i * x : i;
1272
+ view$$.setState({
1273
+ uid: me,
1274
+ originX: fix(viewport.x, page.width) - page.width / 2,
1275
+ originY: fix(viewport.y, page.height) - page.height / 2,
1276
+ width: fix(viewport.width, page.width),
1277
+ height: fix(viewport.height, page.height)
1278
+ });
1279
+ }
1280
+ dispose2.make(() => {
1281
+ view.callbacks.on("onCameraUpdatedByDevice", syncView);
1282
+ return () => view.callbacks.off("onCameraUpdatedByDevice", syncView);
1283
+ });
1284
+ syncViewFromRemote(true);
1285
+ dispose2.add(context.emitter.on("writableChange", (isWritable) => {
1232
1286
  app.setReadonly(!isWritable);
1233
1287
  }));
1234
- context.emitter.on("destroy", () => dispose());
1288
+ context.emitter.on("destroy", () => dispose2());
1235
1289
  const reportProgress = (progress, result) => {
1236
1290
  window.postMessage({ type: "@netless/_result_save_pdf_", appId: context.appId, progress, result }, "*");
1237
1291
  return result;
@@ -1327,7 +1381,7 @@ var NetlessAppPresentation = {
1327
1381
  const title = box.title;
1328
1382
  return reportProgress(100, { pdf: data, title });
1329
1383
  };
1330
- dispose.add(listen(window, "message", (ev) => {
1384
+ dispose2.add(listen(window, "message", (ev) => {
1331
1385
  if (ev.data && ev.data.type == "@netless/_request_save_pdf_" && ev.data.appId == context.appId) {
1332
1386
  toPdf().catch((err) => {
1333
1387
  console.warn(err);
@@ -1336,10 +1390,10 @@ var NetlessAppPresentation = {
1336
1390
  }
1337
1391
  }));
1338
1392
  const controller = { app, view, context, jumpPage, prevPage, nextPage, pageState, toPdf, log };
1339
- dispose.add(listen(window, "message", (ev) => {
1393
+ dispose2.add(listen(window, "message", (ev) => {
1340
1394
  if (ev.data === "@netless/_presentation_") {
1341
1395
  if (typeof window !== "undefined")
1342
- dispose.make(() => {
1396
+ dispose2.make(() => {
1343
1397
  const debug = window._presentation_ || (window._presentation_ = /* @__PURE__ */ new Set());
1344
1398
  debug.add(controller);
1345
1399
  return () => debug.delete(controller);
@@ -1391,7 +1445,7 @@ var install = (register, options = {}) => {
1391
1445
  };
1392
1446
 
1393
1447
  // src/index.ts
1394
- var version = "0.1.1";
1448
+ var version = "0.1.2";
1395
1449
 
1396
1450
  exports.NetlessAppPresentation = NetlessAppPresentation;
1397
1451
  exports.Presentation = Presentation;