@netless/app-presentation 0.1.9-beta.5 → 0.1.9-beta.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netless/app-presentation",
3
- "version": "0.1.9-beta.5",
3
+ "version": "0.1.9-beta.7",
4
4
  "description": "Netless App for presentation slides (images)",
5
5
  "author": "netless",
6
6
  "license": "MIT",
@@ -31,8 +31,8 @@
31
31
  "@hyrious/configs": "^0.1.1",
32
32
  "@hyrious/dts": "^0.2.1",
33
33
  "@hyrious/esbuild-dev": "^0.10.5",
34
- "@netless/fastboard": "^1.0.6",
35
- "@netless/window-manager": "^1.0.4",
34
+ "@netless/fastboard": "1.1.1-beta.0",
35
+ "@netless/window-manager": "^1.0.7-beta.9",
36
36
  "@types/lodash": "^4.17.20",
37
37
  "@types/node": "^20.11.30",
38
38
  "@wopjs/disposable": "^0.1.5",
@@ -42,7 +42,9 @@
42
42
  "rollup": "^4.13.1",
43
43
  "sass": "^1.72.0",
44
44
  "vanilla-lazyload": "^18.0.0",
45
- "vite": "^5.2.6"
45
+ "vite": "^5.2.6",
46
+ "white-web-sdk": "^2.16.53",
47
+ "@netless/appliance-plugin": "1.1.22-beta.11"
46
48
  },
47
49
  "dependencies": {
48
50
  "lodash": "^4.17.21"
@@ -3,10 +3,11 @@ import type { AnimationMode, AppContext, AppPayload, NetlessApp, PublicEvent, Re
3
3
  import { disposableStore } from '@wopjs/disposable'
4
4
  import { listen } from '@wopjs/dom'
5
5
 
6
- import styles from './style.scss?inline'
6
+ import styles from './style.scss?inline';
7
7
  import { Presentation, type PresentationConfig, type PresentationPage } from "./presentation";
8
8
  import { readable, type Readable } from "./store";
9
9
  import { Scrollbar, type ScrollbarEventCallback } from "./scrollbar";
10
+ import { debounce } from "lodash";
10
11
 
11
12
  export type Logger = (...data: any[]) => void
12
13
 
@@ -46,6 +47,10 @@ export interface PresentationAppOptions {
46
47
  /** debounceSync is used to set the presentation debounce sync, it will be used in the presentation, and the presentation will be debounce sync when the app is initialized */
47
48
  debounceSync?: boolean;
48
49
  scrollbarEventCallback?: ScrollbarEventCallback
50
+ /** goToPageByClick is used to set the presentation go to page by click, it will be used in the presentation, and the presentation will be go to page by click when the app is initialized */
51
+ goToPageByClick?: boolean;
52
+ /** useClipView is used to set the presentation use clip view, it will be used in the presentation, and the presentation will be use clip view when the app is initialized */
53
+ useClipView?: boolean;
49
54
  }
50
55
 
51
56
  export interface PresentationController {
@@ -74,10 +79,12 @@ export interface PresentationController {
74
79
  getOriginScale: () => number;
75
80
  /** get the view scale */
76
81
  getScale: () =>number;
82
+ /** get the page size */
83
+ getPageSize: () => { width: number, height: number };
84
+ /** screenshot the current page */
85
+ screenshotCurrentPageAsync: (context: CanvasRenderingContext2D, width?: number, height?: number) => Promise<void>;
77
86
  }
78
87
 
79
- export { styles }
80
-
81
88
  const ppt2page = (ppt: SceneDefinition["ppt"], name?: string): PresentationPage | null =>
82
89
  ppt ? { width: ppt.width, height: ppt.height, src: ppt.src, thumbnail: ppt.previewURL, name } : null
83
90
 
@@ -88,7 +95,6 @@ const createLogger = (room: Room | undefined): Logger => {
88
95
  return (...args) => console.log(...args)
89
96
  }
90
97
  }
91
- ``
92
98
  const scenesEqual = (scenes1?: SceneDefinition[], scenes2?: SceneDefinition[]): boolean => {
93
99
  if (!scenes1 || !scenes2) {return false}
94
100
  if (scenes1.length !== scenes2.length) return false;
@@ -338,8 +344,13 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
338
344
 
339
345
  dispose.add(view$$.addStateChangedListener(() => syncViewFromRemote(false, true)))
340
346
 
347
+ const getPageSize = () => {
348
+ const { width, height } = app.page() || {}
349
+ return { width: width || 0, height: height || 0 }
350
+ }
351
+
341
352
  const box = context.getBox()
342
- const app = dispose.add(createPresentation(box, pages, jumpPage, pageIndex$, options.thumbnail))
353
+ const app = dispose.add(createPresentation(box, pages, jumpPage, pageIndex$, view, options.thumbnail, options.useClipView))
343
354
  app.contentDOM.dataset.appPresentationVersion = __VERSION__
344
355
  app.scaleDocsToFit = scaleDocsToFit
345
356
  app.log = log
@@ -348,6 +359,21 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
348
359
  app.setDocsViewReadonly(true)
349
360
  }
350
361
 
362
+ const room = context.getRoom();
363
+ const goToPageByClick = () => {
364
+ const currentApplianceName = context.getRoom()?.state?.memberState?.currentApplianceName ?? '';
365
+ if (!app.readonly && currentApplianceName === 'clicker') {
366
+ nextPage();
367
+ }
368
+ }
369
+
370
+ if(room && options.goToPageByClick && app.whiteboardDOM) {
371
+ dispose.make(() => {
372
+ app.whiteboardDOM.addEventListener('click', goToPageByClick);
373
+ return () => app.whiteboardDOM.removeEventListener('click', goToPageByClick);
374
+ })
375
+ }
376
+
351
377
  context.mountView(app.whiteboardDOM)
352
378
  if (options.disableCameraTransform) {
353
379
  view.disableCameraTransform = true
@@ -376,17 +402,13 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
376
402
  view.callbacks.on('onCameraUpdatedByDevice', syncView)
377
403
  return () => view.callbacks.off('onCameraUpdatedByDevice', syncView)
378
404
  })
405
+
379
406
  syncViewFromRemote(true)
380
407
 
381
408
  dispose.add(context.emitter.on("writableChange", (isWritable: boolean): void => {
382
409
  app.setReadonly(!isWritable)
383
410
  }))
384
411
 
385
- const getPageSize = () => {
386
- const { width, height } = app.page() || {}
387
- return { width: width || 0, height: height || 0 }
388
- }
389
-
390
412
  const getOriginScale = () => {
391
413
  const { size } = view;
392
414
  const { width, height } = getPageSize();
@@ -397,13 +419,39 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
397
419
  return view.camera.scale
398
420
  }
399
421
 
400
- const moveCamera = (camera: { centerX: number, centerY: number, scale: number }) => {
401
- if (context.getIsWritable()) {
402
- view.moveCamera({...camera, animationMode: 'immediately' as AnimationMode});
403
- syncView();
404
- return;
422
+ const screenshotCurrentPageAsync = async (_context: CanvasRenderingContext2D, _width?: number, _height?: number) => {
423
+ const uuid = room?.calibrationTimestamp?.toString() ?? Date.now().toString();
424
+
425
+ const currentPage = pages[pageIndex$.value];
426
+ if (!currentPage) {
427
+ throw new Error('[Presentation]: current page not found')
428
+ }
429
+ const { width, height, src } = currentPage;
430
+
431
+ const img = document.createElement('img')
432
+ img.width = width;
433
+ img.height = height;
434
+ img.crossOrigin = 'Anonymous';
435
+ await new Promise(resolve => { img.onload = resolve; img.src = src })
436
+ _context.drawImage(img, 0, 0, width, height, 0, 0, _width || width, _height || height);
437
+ const currentScenePath = view.focusScenePath;
438
+ if (!currentScenePath) {
439
+ throw new Error('[Presentation]: current scene path not found')
440
+ }
441
+ const windowManger = context.getWindowManager() as any;
442
+ if (windowManger._appliancePlugin) {
443
+ await windowManger._appliancePlugin.screenshotToCanvasAsync(_context, currentScenePath, _width || width, _height || height, {
444
+ centerX: 0,
445
+ centerY: 0,
446
+ scale: _width && _height ? Math.min(_width / width, _height / height) : 1,
447
+ });
448
+ } else {
449
+ await view.screenshotToCanvasAsync(_context, currentScenePath, _width || width, _height || height, {
450
+ centerX: 0,
451
+ centerY: 0,
452
+ scale: _width && _height ? Math.min(_width / width, _height / height) : 1,
453
+ });
405
454
  }
406
- throw new Error('[Presentation]: moveCamera must be called in writable room')
407
455
  }
408
456
 
409
457
  let scrollbar:Scrollbar | undefined;
@@ -421,6 +469,17 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
421
469
  })
422
470
  }
423
471
 
472
+ const moveCamera = (camera: { centerX: number, centerY: number, scale: number }) => {
473
+ if (context.getIsWritable() && scrollbar) {
474
+ if (!scrollbar) {
475
+ throw new Error('[Presentation]: moveCamera must be called when appOptions: useScrollbar is true')
476
+ }
477
+ scrollbar.moveCamera(camera);
478
+ return;
479
+ }
480
+ throw new Error('[Presentation]: moveCamera must be called in writable room')
481
+ }
482
+
424
483
  context.emitter.on('destroy', () => dispose())
425
484
 
426
485
  const reportProgress = (progress: number, result: { pdf: ArrayBuffer, title: string } | null) => {
@@ -493,13 +552,13 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
493
552
  const name = p.name ?? String(index + 1)
494
553
  if (scenes.some(scene => scene.name == name)) {
495
554
  const camera = { centerX: 0, centerY: 0, scale: Math.min(wb_canvas.width / width, wb_canvas.height / height) };
496
- const sPath = `${scenePath}/${index + 1}`;
555
+ const sPath = `${scenePath}/${name}`;
497
556
  // appliancePlugin is a performance optimization for whiteboard;
498
- const windowManger = (context as any).manager.windowManger as any
557
+ const windowManger = context.getWindowManager() as any;
499
558
  if (windowManger._appliancePlugin) {
500
- await windowManger._appliancePlugin.screenshotToCanvasAsync(wb, sPath, wb_canvas.width, wb_canvas.height, camera);
559
+ await (windowManger as any)._appliancePlugin.screenshotToCanvasAsync(wb, sPath, wb_canvas.width, wb_canvas.height, camera);
501
560
  } else {
502
- view.screenshotToCanvas(
561
+ await view.screenshotToCanvasAsync(
503
562
  wb, sPath,
504
563
  wb_canvas.width, wb_canvas.height,
505
564
  camera,
@@ -542,12 +601,15 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
542
601
 
543
602
  const setReadonly = (bol: boolean) => {
544
603
  app.setReadonly(bol)
604
+ if (options.goToPageByClick) {
605
+
606
+ }
545
607
  if(scrollbar) {
546
608
  scrollbar.setReadonly(bol)
547
609
  }
548
610
  }
549
611
 
550
- const controller: PresentationController = { app, view, context, jumpPage, prevPage, nextPage, pageState, toPdf, log, setDocsViewReadonly, setReadonly, moveCamera, getOriginScale, getScale }
612
+ const controller: PresentationController = { app, view, context, jumpPage, prevPage, nextPage, pageState, toPdf, log, setDocsViewReadonly, setReadonly, moveCamera, getOriginScale, getScale, getPageSize, screenshotCurrentPageAsync }
551
613
 
552
614
  dispose.add(listen(window, 'message', (ev: MessageEvent<"@netless/_presentation_">) => {
553
615
  if (ev.data === "@netless/_presentation_") {
@@ -576,7 +638,7 @@ class AppPresentation extends Presentation {
576
638
  constructor(config: PresentationConfig & { jumpPage: (index: number) => void }) {
577
639
  super(config)
578
640
  this.jumpPage = config.jumpPage
579
- this.image.style.display = 'none'
641
+ // this.image.style.display = 'none'
580
642
  }
581
643
 
582
644
  override updateImage() {
@@ -604,17 +666,44 @@ function createPresentation(
604
666
  pages: PresentationPage[],
605
667
  jumpPage: (index: number) => void,
606
668
  pageIndex$: Readable<number>,
669
+ view: View,
607
670
  thumbnail?: (src: string) => string,
671
+ useClipView?: boolean,
608
672
  ): AppPresentation {
609
673
  box.mountStyles(styles)
610
674
 
611
- const app = new AppPresentation({ pages, readonly: box.readonly, jumpPage, thumbnail })
675
+ const app = new AppPresentation({ pages, readonly: box.readonly, jumpPage, thumbnail})
612
676
  app.box = box
613
677
  box.mountContent(app.contentDOM)
614
678
  box.mountFooter(app.footerDOM)
615
679
 
616
680
  app.dispose.add(pageIndex$.subscribe(pageIndex => { app.setPageIndex(pageIndex) }))
617
681
 
682
+ if(useClipView && view){
683
+ const onCameraUpdatedEffectForMaskView = debounce(() => {
684
+ const { width: pageWidth, height: pageHeight } = app.page() || {};
685
+ if(!pageWidth || !pageHeight) {
686
+ return;
687
+ }
688
+ const { scale } = view.camera;
689
+ const { width, height } = view.size;
690
+ const pageRatioX = Math.round((1 - (pageWidth * scale / width)) / 2 * 100);
691
+ const pageRatioY = Math.round((1 - (pageHeight * scale / height)) / 2 * 100);
692
+ app.whiteboardDOM.style.clipPath = `inset(${pageRatioY}% ${pageRatioX}%)`;
693
+ }, 50)
694
+ onCameraUpdatedEffectForMaskView();
695
+ const onCameraUpdatedEffect = () => {
696
+ view.callbacks.on("onSizeUpdated", onCameraUpdatedEffectForMaskView)
697
+ view.callbacks.on("onCameraUpdated", onCameraUpdatedEffectForMaskView)
698
+ return () => {
699
+ view.callbacks.off("onCameraUpdated", onCameraUpdatedEffectForMaskView);
700
+ view.callbacks.off("onSizeUpdated", onCameraUpdatedEffectForMaskView);
701
+ }
702
+ }
703
+ app.dispose.make(onCameraUpdatedEffect)
704
+ }
705
+
706
+
618
707
  return app
619
708
  }
620
709
 
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./presentation"
2
2
  export * from "./app-presentation"
3
+ export * from "./scrollbar"
3
4
  export { NetlessAppPresentation as default } from "./app-presentation"
4
5
 
5
6
  export const version = __VERSION__
@@ -90,9 +90,9 @@ export class Presentation implements IDisposable<void> {
90
90
  this.contentDOM.className = this.c('content')
91
91
  this.previewDOM = document.createElement('div')
92
92
  this.previewDOM.className = this.c('preview')
93
- this.imageDOM = document.createElement('div')
94
- this.imageDOM.className = this.c('image')
95
- this.image = document.createElement('img')
93
+ // this.imageDOM = document.createElement('div')
94
+ // this.imageDOM.className = this.c('image')
95
+ // this.image = document.createElement('img')
96
96
  this.whiteboardDOM = document.createElement('div')
97
97
  this.whiteboardDOM.className = this.c('wb-view')
98
98
  this.footerDOM = document.createElement('div')
@@ -166,8 +166,8 @@ export class Presentation implements IDisposable<void> {
166
166
  }
167
167
  }))
168
168
 
169
- this.imageDOM.appendChild(this.image)
170
- this.contentDOM.appendChild(this.imageDOM)
169
+ // this.imageDOM.appendChild(this.image)
170
+ // this.contentDOM.appendChild(this.imageDOM)
171
171
  this.updateImage()
172
172
 
173
173
  this.contentDOM.appendChild(this.whiteboardDOM)
@@ -1,7 +1,7 @@
1
1
  import './style.scss?inline';
2
2
  import type { View, AnimationMode } from "@netless/window-manager";
3
3
  import { makeDraggable } from "./Draggable";
4
- import { debounce } from 'lodash';
4
+ import { debounce, isNumber } from 'lodash';
5
5
 
6
6
  export interface ScrollbarOption {
7
7
  appId: string;
@@ -79,17 +79,22 @@ export class Scrollbar {
79
79
  if (this.readonly) return;
80
80
  const { scale, centerX, centerY } = this.view.camera;
81
81
  const originScale = this.option.getOriginScale();
82
- const ratio = Math.round(originScale / scale * 1000) / 1000;
83
- const scrollX = Math.round(centerX * originScale);
84
- const scrollY = Math.round(centerY * originScale);
82
+ const { width: pageWidth, height: pageHeight } = this.option.getPageSize();
83
+ const { width: viewWidth, height: viewHeight } = this.view.size;
84
+ const originScaleX = viewWidth / pageWidth;
85
+ const originScaleY = viewHeight / pageHeight;
86
+ const ratioX = Math.min(Math.round(originScaleX / scale * 100) / 100, 1);
87
+ const ratioY = Math.min(Math.round(originScaleY / scale * 100) / 100, 1);
88
+ const scrollX = Math.round(centerX * originScaleX);
89
+ const scrollY = Math.round(centerY * originScaleY);
85
90
  if (this.scrollbarX) {
86
- this.scrollbarX.style.width = `${ratio * 100}%`;
87
- this.scrollbarX.style.display = ratio === 1 ? 'none' : 'block';
91
+ this.scrollbarX.style.width = `${ratioX * 100}%`;
92
+ this.scrollbarX.style.display = ratioX === 1 ? 'none' : 'block';
88
93
  this.scrollbarX.style.transform = `translateX(${scrollX}px)`;
89
94
  }
90
95
  if (this.scrollbarY) {
91
- this.scrollbarY.style.height = `${ratio * 100}%`;
92
- this.scrollbarY.style.display = ratio === 1 ? 'none' : 'block';
96
+ this.scrollbarY.style.height = `${ratioY * 100}%`;
97
+ this.scrollbarY.style.display = ratioY === 1 ? 'none' : 'block';
93
98
  this.scrollbarY.style.transform = `translateY(${scrollY}px)`;
94
99
  }
95
100
  this.option.scrollbarEventCallback?.onScrollCameraUpdated?.(this.appId, originScale, scale);
@@ -106,22 +111,26 @@ export class Scrollbar {
106
111
  }
107
112
 
108
113
  getScrollXRange(camera: {scale: number}) {
109
- const { width } = this.option.getPageSize();
110
- const originScale = this.option.getOriginScale();
114
+ const { width: pageWidth } = this.option.getPageSize();
115
+ const { width: viewWidth } = this.view.size;
116
+ const originScaleX = viewWidth / pageWidth;
117
+ // const originScale = this.option.getOriginScale();
111
118
  const scale = camera.scale;
112
- const ratio = Math.round(originScale / scale * 1000) / 1000;
113
- const scrollWidth = width * (1-ratio);
119
+ const ratio = Math.min(Math.round(originScaleX / scale * 100) / 100, 1);
120
+ const scrollWidth = pageWidth * (1-ratio);
114
121
  const minX = -scrollWidth / 2;
115
122
  const maxX = scrollWidth / 2;
116
123
  return { minX, maxX };
117
124
  }
118
125
 
119
126
  getScrollYRange(camera: {scale: number}) {
120
- const { height } = this.option.getPageSize();
121
- const originScale = this.option.getOriginScale();
127
+ const { height: pageHeight } = this.option.getPageSize();
128
+ const { height: viewHeight } = this.view.size;
129
+ const originScaleY = viewHeight / pageHeight;
130
+ // const originScale = this.option.getOriginScale();
122
131
  const scale = camera.scale;
123
- const ratio = Math.round(originScale / scale * 1000) / 1000;
124
- const scrollHeight = height * (1-ratio);
132
+ const ratio = Math.min(Math.round(originScaleY / scale * 100) / 100, 1);
133
+ const scrollHeight = pageHeight * (1-ratio);
125
134
  const minY = -scrollHeight / 2;
126
135
  const maxY = scrollHeight / 2;
127
136
  return { minY, maxY };
@@ -130,9 +139,12 @@ export class Scrollbar {
130
139
  onDragX = (transformedDrag: {x: number, y: number}) => {
131
140
  if (this.cameraCache) {
132
141
  const {x} = transformedDrag;
133
- const originScale = this.option.getOriginScale();
142
+ // const originScale = this.option.getOriginScale();
143
+ const { width: pageWidth } = this.option.getPageSize();
144
+ const { width: viewWidth } = this.view.size;
145
+ const originScaleX = viewWidth / pageWidth;
134
146
  const { minX, maxX } = this.getScrollXRange(this.cameraCache);
135
- const scrollX = Math.round(x / originScale);
147
+ const scrollX =Math.round(x / originScaleX);
136
148
  let centerX = scrollX + this.cameraCache.centerX;
137
149
  if (centerX < minX) {
138
150
  centerX = minX;
@@ -150,9 +162,12 @@ export class Scrollbar {
150
162
  onDragY = (transformedDrag: {x: number, y: number}) => {
151
163
  if(this.cameraCache){
152
164
  const {y} = transformedDrag;
153
- const originScale = this.option.getOriginScale();
165
+ // const originScale = this.option.getOriginScale();
166
+ const { height: pageHeight } = this.option.getPageSize();
167
+ const { height: viewHeight } = this.view.size;
168
+ const originScaleY = viewHeight / pageHeight;
154
169
  const { minY, maxY } = this.getScrollYRange(this.cameraCache);
155
- const scrollY = Math.round(y / originScale);
170
+ const scrollY = Math.round(y / originScaleY);
156
171
  let centerY = scrollY + this.cameraCache.centerY;
157
172
  if (centerY < minY) {
158
173
  centerY = minY;
@@ -241,4 +256,35 @@ export class Scrollbar {
241
256
 
242
257
  }
243
258
 
259
+ moveCamera = (camera: { centerX: number, centerY: number, scale: number }) => {
260
+ const { centerX, centerY } = camera;
261
+ const cacheCamera = this.view.camera;
262
+ const _camera: { centerX?: number, centerY?: number, scale?: number} = {};
263
+ if(isNumber(centerX)) {
264
+ const { minX, maxX } = this.getScrollXRange(cacheCamera);
265
+ let newCenterX = centerX;
266
+ if (centerX < minX) {
267
+ newCenterX = minX;
268
+ } else if (centerX > maxX) {
269
+ newCenterX = maxX;
270
+ }
271
+ _camera.centerX = newCenterX;
272
+ }
273
+ if(isNumber(centerY)) {
274
+ const { minY, maxY } = this.getScrollYRange(cacheCamera);
275
+ let newCenterY = centerY;
276
+ if (centerY < minY) {
277
+ newCenterY = minY;
278
+ } else if (centerY > maxY) {
279
+ newCenterY = maxY;
280
+ }
281
+ _camera.centerY = newCenterY;
282
+ }
283
+ this.view.moveCamera({
284
+ ..._camera,
285
+ animationMode: 'immediately' as AnimationMode
286
+ })
287
+ this.option.syncView();
288
+ }
289
+
244
290
  }