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

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.6",
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,6 +419,41 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
397
419
  return view.camera.scale
398
420
  }
399
421
 
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
+ });
454
+ }
455
+ }
456
+
400
457
  const moveCamera = (camera: { centerX: number, centerY: number, scale: number }) => {
401
458
  if (context.getIsWritable()) {
402
459
  view.moveCamera({...camera, animationMode: 'immediately' as AnimationMode});
@@ -493,13 +550,13 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
493
550
  const name = p.name ?? String(index + 1)
494
551
  if (scenes.some(scene => scene.name == name)) {
495
552
  const camera = { centerX: 0, centerY: 0, scale: Math.min(wb_canvas.width / width, wb_canvas.height / height) };
496
- const sPath = `${scenePath}/${index + 1}`;
553
+ const sPath = `${scenePath}/${name}`;
497
554
  // appliancePlugin is a performance optimization for whiteboard;
498
- const windowManger = (context as any).manager.windowManger as any
555
+ const windowManger = context.getWindowManager() as any;
499
556
  if (windowManger._appliancePlugin) {
500
- await windowManger._appliancePlugin.screenshotToCanvasAsync(wb, sPath, wb_canvas.width, wb_canvas.height, camera);
557
+ await (windowManger as any)._appliancePlugin.screenshotToCanvasAsync(wb, sPath, wb_canvas.width, wb_canvas.height, camera);
501
558
  } else {
502
- view.screenshotToCanvas(
559
+ await view.screenshotToCanvasAsync(
503
560
  wb, sPath,
504
561
  wb_canvas.width, wb_canvas.height,
505
562
  camera,
@@ -542,12 +599,15 @@ export const NetlessAppPresentation: NetlessApp<{}, {}, PresentationAppOptions,
542
599
 
543
600
  const setReadonly = (bol: boolean) => {
544
601
  app.setReadonly(bol)
602
+ if (options.goToPageByClick) {
603
+
604
+ }
545
605
  if(scrollbar) {
546
606
  scrollbar.setReadonly(bol)
547
607
  }
548
608
  }
549
609
 
550
- const controller: PresentationController = { app, view, context, jumpPage, prevPage, nextPage, pageState, toPdf, log, setDocsViewReadonly, setReadonly, moveCamera, getOriginScale, getScale }
610
+ const controller: PresentationController = { app, view, context, jumpPage, prevPage, nextPage, pageState, toPdf, log, setDocsViewReadonly, setReadonly, moveCamera, getOriginScale, getScale, getPageSize, screenshotCurrentPageAsync }
551
611
 
552
612
  dispose.add(listen(window, 'message', (ev: MessageEvent<"@netless/_presentation_">) => {
553
613
  if (ev.data === "@netless/_presentation_") {
@@ -576,7 +636,7 @@ class AppPresentation extends Presentation {
576
636
  constructor(config: PresentationConfig & { jumpPage: (index: number) => void }) {
577
637
  super(config)
578
638
  this.jumpPage = config.jumpPage
579
- this.image.style.display = 'none'
639
+ // this.image.style.display = 'none'
580
640
  }
581
641
 
582
642
  override updateImage() {
@@ -604,17 +664,44 @@ function createPresentation(
604
664
  pages: PresentationPage[],
605
665
  jumpPage: (index: number) => void,
606
666
  pageIndex$: Readable<number>,
667
+ view: View,
607
668
  thumbnail?: (src: string) => string,
669
+ useClipView?: boolean,
608
670
  ): AppPresentation {
609
671
  box.mountStyles(styles)
610
672
 
611
- const app = new AppPresentation({ pages, readonly: box.readonly, jumpPage, thumbnail })
673
+ const app = new AppPresentation({ pages, readonly: box.readonly, jumpPage, thumbnail})
612
674
  app.box = box
613
675
  box.mountContent(app.contentDOM)
614
676
  box.mountFooter(app.footerDOM)
615
677
 
616
678
  app.dispose.add(pageIndex$.subscribe(pageIndex => { app.setPageIndex(pageIndex) }))
617
679
 
680
+ if(useClipView && view){
681
+ const onCameraUpdatedEffectForMaskView = debounce(() => {
682
+ const { width: pageWidth, height: pageHeight } = app.page() || {};
683
+ if(!pageWidth || !pageHeight) {
684
+ return;
685
+ }
686
+ const { scale } = view.camera;
687
+ const { width, height } = view.size;
688
+ const pageRatioX = Math.round((1 - (pageWidth * scale / width)) / 2 * 100);
689
+ const pageRatioY = Math.round((1 - (pageHeight * scale / height)) / 2 * 100);
690
+ app.whiteboardDOM.style.clipPath = `inset(${pageRatioY}% ${pageRatioX}%)`;
691
+ }, 50)
692
+ onCameraUpdatedEffectForMaskView();
693
+ const onCameraUpdatedEffect = () => {
694
+ view.callbacks.on("onSizeUpdated", onCameraUpdatedEffectForMaskView)
695
+ view.callbacks.on("onCameraUpdated", onCameraUpdatedEffectForMaskView)
696
+ return () => {
697
+ view.callbacks.off("onCameraUpdated", onCameraUpdatedEffectForMaskView);
698
+ view.callbacks.off("onSizeUpdated", onCameraUpdatedEffectForMaskView);
699
+ }
700
+ }
701
+ app.dispose.make(onCameraUpdatedEffect)
702
+ }
703
+
704
+
618
705
  return app
619
706
  }
620
707
 
@@ -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)
@@ -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;