@abreen/tada 1.15.2 → 1.15.4
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/README.md +3 -0
- package/package.json +1 -1
- package/src/slides/index.ts +138 -192
- package/src/slides/style.scss +38 -12
package/README.md
CHANGED
|
@@ -26,6 +26,9 @@ A static site generator. The successor to Presto.
|
|
|
26
26
|
* Supports Java and Python source files
|
|
27
27
|
* Renders step-by-step source highlighting, output, and memory diagrams
|
|
28
28
|
- Slides mode: separate content by `---` and present sections as full-screen slides
|
|
29
|
+
* Click icon near slide titles to start presenting at that slide
|
|
30
|
+
* Right-click in presentation mode to annotate
|
|
31
|
+
* Hold Shift while annotating to erase
|
|
29
32
|
- PDF files are copied into `dist/`
|
|
30
33
|
* Text of each PDF page is extracted using `mutool` (if present) and indexed
|
|
31
34
|
- External link handling (special visual treatment for external links)
|
package/package.json
CHANGED
package/src/slides/index.ts
CHANGED
|
@@ -9,34 +9,14 @@ interface SlidesPresentDetail {
|
|
|
9
9
|
slideIndex?: number;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
interface AnnotationPoint {
|
|
13
|
-
x: number;
|
|
14
|
-
y: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface AnnotationStroke {
|
|
18
|
-
type: 'stroke';
|
|
19
|
-
points: AnnotationPoint[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface AnnotationErase {
|
|
23
|
-
type: 'erase';
|
|
24
|
-
points: AnnotationPoint[];
|
|
25
|
-
radius: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
type AnnotationOperation = AnnotationStroke | AnnotationErase;
|
|
29
|
-
|
|
30
12
|
interface SlideAnnotationState {
|
|
31
13
|
canvas: HTMLCanvasElement;
|
|
32
|
-
operations: AnnotationOperation[];
|
|
33
14
|
width: number;
|
|
34
15
|
height: number;
|
|
35
16
|
dpr: number;
|
|
36
17
|
}
|
|
37
18
|
|
|
38
19
|
const FULLSCREEN_STORAGE_KEY = 'slidesFullscreen';
|
|
39
|
-
const ANNOTATION_CANVAS_SELECTOR = '[data-slides-annotations]';
|
|
40
20
|
const ANNOTATION_COLOR = 'blueviolet';
|
|
41
21
|
const ANNOTATION_ERASER_RADIUS = 18;
|
|
42
22
|
const ANNOTATION_ERASER_DIAMETER = ANNOTATION_ERASER_RADIUS * 2;
|
|
@@ -151,6 +131,12 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
151
131
|
);
|
|
152
132
|
document.body.appendChild(eraserPreview);
|
|
153
133
|
|
|
134
|
+
const annotationLayer = document.createElement('div');
|
|
135
|
+
annotationLayer.hidden = true;
|
|
136
|
+
annotationLayer.dataset.slidesAnnotationLayer = '';
|
|
137
|
+
annotationLayer.setAttribute('aria-hidden', 'true');
|
|
138
|
+
slidesRoot.append(annotationLayer);
|
|
139
|
+
|
|
154
140
|
let isPresenting = false;
|
|
155
141
|
let activeIndex = -1;
|
|
156
142
|
let presentationMode: PresentationMode | null = null;
|
|
@@ -158,12 +144,12 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
158
144
|
let isAnnotating = false;
|
|
159
145
|
let isShiftPressed = false;
|
|
160
146
|
let isErasing = false;
|
|
147
|
+
let isDrivingTrace = false;
|
|
161
148
|
let activeAnnotation: {
|
|
162
149
|
slide: HTMLElement;
|
|
163
|
-
|
|
150
|
+
previousX: number;
|
|
151
|
+
previousY: number;
|
|
164
152
|
} | null = null;
|
|
165
|
-
let activeEraser: { slide: HTMLElement; erase: AnnotationErase } | null =
|
|
166
|
-
null;
|
|
167
153
|
|
|
168
154
|
const toolbarStates = new WeakMap<HTMLElement, TraceToolbarState>();
|
|
169
155
|
const slideTabIndexes = new WeakMap<HTMLElement, string | null>();
|
|
@@ -249,10 +235,10 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
249
235
|
|
|
250
236
|
function setActiveSlide(index: number): void {
|
|
251
237
|
activeAnnotation = null;
|
|
252
|
-
activeEraser = null;
|
|
253
238
|
hideEraserPreview();
|
|
254
239
|
activeIndex = Math.max(0, Math.min(index, slides.length - 1));
|
|
255
240
|
updateActiveSlide(slides, activeIndex, isPresenting);
|
|
241
|
+
updateActiveAnnotationCanvas();
|
|
256
242
|
resizeActiveAnnotationCanvas();
|
|
257
243
|
}
|
|
258
244
|
|
|
@@ -308,6 +294,7 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
308
294
|
isShiftPressed = false;
|
|
309
295
|
setAnnotationMode(false);
|
|
310
296
|
clearAnnotationCanvases();
|
|
297
|
+
annotationLayer.hidden = true;
|
|
311
298
|
hidePresentationControls();
|
|
312
299
|
showPresentationCursor();
|
|
313
300
|
document.body.classList.remove('is-presenting');
|
|
@@ -350,6 +337,7 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
350
337
|
function enterPresentation(mode: PresentationMode, slideIndex = 0): void {
|
|
351
338
|
isPresenting = true;
|
|
352
339
|
presentationMode = mode;
|
|
340
|
+
annotationLayer.hidden = false;
|
|
353
341
|
setAnnotationMode(false);
|
|
354
342
|
hidePresentationControls();
|
|
355
343
|
showPresentationCursor();
|
|
@@ -387,7 +375,12 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
387
375
|
continue;
|
|
388
376
|
}
|
|
389
377
|
|
|
390
|
-
|
|
378
|
+
isDrivingTrace = true;
|
|
379
|
+
try {
|
|
380
|
+
button.click();
|
|
381
|
+
} finally {
|
|
382
|
+
isDrivingTrace = false;
|
|
383
|
+
}
|
|
391
384
|
return true;
|
|
392
385
|
}
|
|
393
386
|
|
|
@@ -540,7 +533,6 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
540
533
|
function setAnnotationMode(enabled: boolean): void {
|
|
541
534
|
isAnnotating = enabled;
|
|
542
535
|
activeAnnotation = null;
|
|
543
|
-
activeEraser = null;
|
|
544
536
|
hideEraserPreview();
|
|
545
537
|
document.body.classList.toggle('is-slides-annotating', enabled);
|
|
546
538
|
setEraserMode(enabled && isShiftPressed);
|
|
@@ -552,7 +544,6 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
552
544
|
|
|
553
545
|
function setEraserMode(enabled: boolean): void {
|
|
554
546
|
isErasing = enabled && isPresenting && isAnnotating;
|
|
555
|
-
activeEraser = null;
|
|
556
547
|
|
|
557
548
|
if (isErasing) {
|
|
558
549
|
activeAnnotation = null;
|
|
@@ -577,33 +568,24 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
577
568
|
setAnnotationMode(!isAnnotating);
|
|
578
569
|
}
|
|
579
570
|
|
|
580
|
-
function
|
|
581
|
-
slide: HTMLElement,
|
|
571
|
+
function getViewportPoint(
|
|
582
572
|
event: Pick<PointerEvent, 'clientX' | 'clientY'>,
|
|
583
|
-
):
|
|
584
|
-
const
|
|
585
|
-
|
|
573
|
+
): { x: number; y: number } | null {
|
|
574
|
+
const width = window.innerWidth;
|
|
575
|
+
const height = window.innerHeight;
|
|
576
|
+
|
|
577
|
+
if (
|
|
578
|
+
width <= 0 ||
|
|
579
|
+
height <= 0 ||
|
|
580
|
+
event.clientX < 0 ||
|
|
581
|
+
event.clientX > width ||
|
|
582
|
+
event.clientY < 0 ||
|
|
583
|
+
event.clientY > height
|
|
584
|
+
) {
|
|
586
585
|
return null;
|
|
587
586
|
}
|
|
588
587
|
|
|
589
|
-
|
|
590
|
-
const y = Math.min(Math.max(event.clientY - rect.top, 0), rect.height);
|
|
591
|
-
|
|
592
|
-
return { x: x / rect.width, y: y / rect.height };
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function eventIsInsideSlide(
|
|
596
|
-
slide: HTMLElement,
|
|
597
|
-
event: Pick<PointerEvent, 'clientX' | 'clientY'>,
|
|
598
|
-
): boolean {
|
|
599
|
-
const rect = slide.getBoundingClientRect();
|
|
600
|
-
|
|
601
|
-
return (
|
|
602
|
-
event.clientX >= rect.left &&
|
|
603
|
-
event.clientX <= rect.right &&
|
|
604
|
-
event.clientY >= rect.top &&
|
|
605
|
-
event.clientY <= rect.bottom
|
|
606
|
-
);
|
|
588
|
+
return { x: event.clientX, y: event.clientY };
|
|
607
589
|
}
|
|
608
590
|
|
|
609
591
|
function getCanvasContext(
|
|
@@ -624,41 +606,46 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
624
606
|
|
|
625
607
|
const canvas = document.createElement('canvas');
|
|
626
608
|
canvas.dataset.slidesAnnotations = '';
|
|
609
|
+
canvas.dataset.slideIndex = slide.getAttribute('data-slide-index') ?? '';
|
|
627
610
|
canvas.setAttribute('aria-hidden', 'true');
|
|
628
|
-
slide
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
operations: [],
|
|
633
|
-
width: 0,
|
|
634
|
-
height: 0,
|
|
635
|
-
dpr: 1,
|
|
636
|
-
};
|
|
611
|
+
canvas.hidden = slide !== getActiveSlide();
|
|
612
|
+
annotationLayer.append(canvas);
|
|
613
|
+
|
|
614
|
+
const state: SlideAnnotationState = { canvas, width: 0, height: 0, dpr: 1 };
|
|
637
615
|
annotationStates.set(slide, state);
|
|
638
|
-
resizeAnnotationCanvas(
|
|
616
|
+
resizeAnnotationCanvas(state);
|
|
639
617
|
|
|
640
618
|
return state;
|
|
641
619
|
}
|
|
642
620
|
|
|
643
|
-
function resizeAnnotationCanvas(
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
): void {
|
|
647
|
-
const rect = slide.getBoundingClientRect();
|
|
648
|
-
const width = Math.max(1, rect.width);
|
|
649
|
-
const height = Math.max(1, rect.height);
|
|
621
|
+
function resizeAnnotationCanvas(state: SlideAnnotationState): void {
|
|
622
|
+
const width = Math.max(1, window.innerWidth);
|
|
623
|
+
const height = Math.max(1, window.innerHeight);
|
|
650
624
|
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
651
625
|
|
|
652
626
|
if (state.width === width && state.height === height && state.dpr === dpr) {
|
|
653
627
|
return;
|
|
654
628
|
}
|
|
655
629
|
|
|
630
|
+
const priorCanvas =
|
|
631
|
+
state.canvas.width > 0 && state.canvas.height > 0
|
|
632
|
+
? document.createElement('canvas')
|
|
633
|
+
: null;
|
|
634
|
+
if (priorCanvas) {
|
|
635
|
+
priorCanvas.width = state.canvas.width;
|
|
636
|
+
priorCanvas.height = state.canvas.height;
|
|
637
|
+
priorCanvas.getContext('2d')?.drawImage(state.canvas, 0, 0);
|
|
638
|
+
}
|
|
639
|
+
|
|
656
640
|
state.width = width;
|
|
657
641
|
state.height = height;
|
|
658
642
|
state.dpr = dpr;
|
|
659
643
|
state.canvas.width = Math.round(width * dpr);
|
|
660
644
|
state.canvas.height = Math.round(height * dpr);
|
|
661
|
-
|
|
645
|
+
|
|
646
|
+
if (priorCanvas) {
|
|
647
|
+
getCanvasContext(state.canvas)?.drawImage(priorCanvas, 0, 0);
|
|
648
|
+
}
|
|
662
649
|
}
|
|
663
650
|
|
|
664
651
|
function resizeActiveAnnotationCanvas(): void {
|
|
@@ -669,124 +656,75 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
669
656
|
|
|
670
657
|
const state = annotationStates.get(slide);
|
|
671
658
|
if (state) {
|
|
672
|
-
resizeAnnotationCanvas(
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
function redrawAnnotationCanvas(state: SlideAnnotationState): void {
|
|
677
|
-
const ctx = getCanvasContext(state.canvas);
|
|
678
|
-
if (!ctx) {
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
ctx.clearRect(0, 0, state.canvas.width, state.canvas.height);
|
|
683
|
-
|
|
684
|
-
for (const operation of state.operations) {
|
|
685
|
-
if (operation.type === 'stroke') {
|
|
686
|
-
drawAnnotationStroke(ctx, operation, state);
|
|
687
|
-
} else {
|
|
688
|
-
drawAnnotationErase(ctx, operation, state);
|
|
689
|
-
}
|
|
659
|
+
resizeAnnotationCanvas(state);
|
|
690
660
|
}
|
|
691
661
|
}
|
|
692
662
|
|
|
693
663
|
function drawAnnotationStroke(
|
|
694
664
|
ctx: CanvasRenderingContext2D,
|
|
695
|
-
stroke: AnnotationStroke,
|
|
696
665
|
state: SlideAnnotationState,
|
|
666
|
+
from: { x: number; y: number },
|
|
667
|
+
to: { x: number; y: number },
|
|
697
668
|
): void {
|
|
698
|
-
if (stroke.points.length === 0) {
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
669
|
ctx.globalCompositeOperation = 'source-over';
|
|
703
670
|
ctx.strokeStyle = ANNOTATION_COLOR;
|
|
704
671
|
ctx.lineWidth = 3 * state.dpr;
|
|
705
672
|
ctx.lineCap = 'round';
|
|
706
673
|
ctx.lineJoin = 'round';
|
|
707
674
|
ctx.beginPath();
|
|
708
|
-
|
|
709
|
-
ctx.
|
|
710
|
-
|
|
711
|
-
for (const point of stroke.points.slice(1)) {
|
|
712
|
-
ctx.lineTo(point.x * state.canvas.width, point.y * state.canvas.height);
|
|
713
|
-
}
|
|
714
|
-
|
|
675
|
+
ctx.moveTo(from.x * state.dpr, from.y * state.dpr);
|
|
676
|
+
ctx.lineTo(to.x * state.dpr, to.y * state.dpr);
|
|
715
677
|
ctx.stroke();
|
|
716
678
|
}
|
|
717
679
|
|
|
718
|
-
function
|
|
680
|
+
function drawAnnotationDot(
|
|
719
681
|
ctx: CanvasRenderingContext2D,
|
|
720
|
-
erase: AnnotationErase,
|
|
721
682
|
state: SlideAnnotationState,
|
|
683
|
+
point: { x: number; y: number },
|
|
722
684
|
): void {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
685
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
686
|
+
ctx.fillStyle = ANNOTATION_COLOR;
|
|
687
|
+
ctx.beginPath();
|
|
688
|
+
ctx.arc(
|
|
689
|
+
point.x * state.dpr,
|
|
690
|
+
point.y * state.dpr,
|
|
691
|
+
1.5 * state.dpr,
|
|
692
|
+
0,
|
|
693
|
+
Math.PI * 2,
|
|
694
|
+
);
|
|
695
|
+
ctx.fill();
|
|
696
|
+
}
|
|
726
697
|
|
|
727
|
-
|
|
698
|
+
function drawAnnotationErase(
|
|
699
|
+
ctx: CanvasRenderingContext2D,
|
|
700
|
+
state: SlideAnnotationState,
|
|
701
|
+
point: { x: number; y: number },
|
|
702
|
+
): void {
|
|
703
|
+
const radius = ANNOTATION_ERASER_RADIUS * state.dpr;
|
|
728
704
|
ctx.globalCompositeOperation = 'destination-out';
|
|
729
|
-
ctx.lineWidth = radius * 2;
|
|
730
|
-
ctx.lineCap = 'round';
|
|
731
|
-
ctx.lineJoin = 'round';
|
|
732
|
-
|
|
733
|
-
const [first] = erase.points;
|
|
734
|
-
if (erase.points.length === 1) {
|
|
735
|
-
ctx.beginPath();
|
|
736
|
-
ctx.arc(
|
|
737
|
-
first.x * state.canvas.width,
|
|
738
|
-
first.y * state.canvas.height,
|
|
739
|
-
radius,
|
|
740
|
-
0,
|
|
741
|
-
Math.PI * 2,
|
|
742
|
-
);
|
|
743
|
-
ctx.fill();
|
|
744
|
-
ctx.globalCompositeOperation = 'source-over';
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
705
|
ctx.beginPath();
|
|
749
|
-
ctx.
|
|
750
|
-
|
|
751
|
-
for (const point of erase.points.slice(1)) {
|
|
752
|
-
ctx.lineTo(point.x * state.canvas.width, point.y * state.canvas.height);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
ctx.stroke();
|
|
706
|
+
ctx.arc(point.x * state.dpr, point.y * state.dpr, radius, 0, Math.PI * 2);
|
|
707
|
+
ctx.fill();
|
|
756
708
|
ctx.globalCompositeOperation = 'source-over';
|
|
757
709
|
}
|
|
758
710
|
|
|
759
|
-
function drawCurrentAnnotation(): void {
|
|
760
|
-
if (!activeAnnotation) {
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
const state = getAnnotationState(activeAnnotation.slide);
|
|
765
|
-
resizeAnnotationCanvas(activeAnnotation.slide, state);
|
|
766
|
-
redrawAnnotationCanvas(state);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
function drawCurrentEraser(): void {
|
|
770
|
-
if (!activeEraser) {
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
const state = annotationStates.get(activeEraser.slide);
|
|
775
|
-
if (!state) {
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
resizeAnnotationCanvas(activeEraser.slide, state);
|
|
780
|
-
redrawAnnotationCanvas(state);
|
|
781
|
-
}
|
|
782
|
-
|
|
783
711
|
function clearAnnotationCanvases(): void {
|
|
784
712
|
for (const slide of slides) {
|
|
785
|
-
|
|
713
|
+
annotationStates.get(slide)?.canvas.remove();
|
|
786
714
|
annotationStates.delete(slide);
|
|
787
715
|
}
|
|
788
716
|
activeAnnotation = null;
|
|
789
|
-
|
|
717
|
+
hideEraserPreview();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function updateActiveAnnotationCanvas(): void {
|
|
721
|
+
const activeSlide = getActiveSlide();
|
|
722
|
+
for (const slide of slides) {
|
|
723
|
+
const state = annotationStates.get(slide);
|
|
724
|
+
if (state) {
|
|
725
|
+
state.canvas.hidden = slide !== activeSlide;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
790
728
|
}
|
|
791
729
|
|
|
792
730
|
function handleAnnotationContextMenu(event: MouseEvent): void {
|
|
@@ -812,21 +750,23 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
812
750
|
}
|
|
813
751
|
|
|
814
752
|
const slide = getActiveSlide();
|
|
815
|
-
if (!slide
|
|
753
|
+
if (!slide) {
|
|
816
754
|
activeAnnotation = null;
|
|
817
755
|
return;
|
|
818
756
|
}
|
|
819
757
|
|
|
820
|
-
const point =
|
|
758
|
+
const point = getViewportPoint(event);
|
|
821
759
|
if (!point) {
|
|
822
760
|
return;
|
|
823
761
|
}
|
|
824
762
|
|
|
825
763
|
const state = getAnnotationState(slide);
|
|
826
|
-
|
|
827
|
-
state.
|
|
828
|
-
|
|
829
|
-
|
|
764
|
+
resizeAnnotationCanvas(state);
|
|
765
|
+
const ctx = getCanvasContext(state.canvas);
|
|
766
|
+
if (ctx) {
|
|
767
|
+
drawAnnotationDot(ctx, state, point);
|
|
768
|
+
}
|
|
769
|
+
activeAnnotation = { slide, previousX: point.x, previousY: point.y };
|
|
830
770
|
|
|
831
771
|
const target =
|
|
832
772
|
event.target instanceof domView.Element ? event.target : slide;
|
|
@@ -843,40 +783,51 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
843
783
|
return;
|
|
844
784
|
}
|
|
845
785
|
|
|
846
|
-
if (!activeAnnotation) {
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
786
|
event.preventDefault();
|
|
851
787
|
event.stopPropagation();
|
|
852
788
|
|
|
853
|
-
const point =
|
|
789
|
+
const point = getViewportPoint(event);
|
|
854
790
|
if (!point) {
|
|
791
|
+
activeAnnotation = null;
|
|
855
792
|
return;
|
|
856
793
|
}
|
|
857
794
|
|
|
858
|
-
activeAnnotation
|
|
859
|
-
|
|
795
|
+
if (!activeAnnotation) {
|
|
796
|
+
if ((event.buttons & 1) !== 1) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const slide = getActiveSlide();
|
|
800
|
+
if (!slide) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
activeAnnotation = { slide, previousX: point.x, previousY: point.y };
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const state = getAnnotationState(activeAnnotation.slide);
|
|
808
|
+
resizeAnnotationCanvas(state);
|
|
809
|
+
const ctx = getCanvasContext(state.canvas);
|
|
810
|
+
if (ctx) {
|
|
811
|
+
drawAnnotationStroke(
|
|
812
|
+
ctx,
|
|
813
|
+
state,
|
|
814
|
+
{ x: activeAnnotation.previousX, y: activeAnnotation.previousY },
|
|
815
|
+
point,
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
activeAnnotation.previousX = point.x;
|
|
819
|
+
activeAnnotation.previousY = point.y;
|
|
860
820
|
}
|
|
861
821
|
|
|
862
822
|
function handleAnnotationEraseMove(event: PointerEvent): void {
|
|
863
823
|
const slide = getActiveSlide();
|
|
864
|
-
if (!slide
|
|
865
|
-
activeEraser = null;
|
|
866
|
-
hideEraserPreview();
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
const state = annotationStates.get(slide);
|
|
871
|
-
if (!state) {
|
|
872
|
-
activeEraser = null;
|
|
824
|
+
if (!slide) {
|
|
873
825
|
hideEraserPreview();
|
|
874
826
|
return;
|
|
875
827
|
}
|
|
876
828
|
|
|
877
|
-
const point =
|
|
829
|
+
const point = getViewportPoint(event);
|
|
878
830
|
if (!point) {
|
|
879
|
-
activeEraser = null;
|
|
880
831
|
hideEraserPreview();
|
|
881
832
|
return;
|
|
882
833
|
}
|
|
@@ -885,18 +836,12 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
885
836
|
event.stopPropagation();
|
|
886
837
|
showEraserPreview(event);
|
|
887
838
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
};
|
|
894
|
-
state.operations.push(erase);
|
|
895
|
-
activeEraser = { slide, erase };
|
|
839
|
+
const state = getAnnotationState(slide);
|
|
840
|
+
resizeAnnotationCanvas(state);
|
|
841
|
+
const ctx = getCanvasContext(state.canvas);
|
|
842
|
+
if (ctx) {
|
|
843
|
+
drawAnnotationErase(ctx, state, point);
|
|
896
844
|
}
|
|
897
|
-
|
|
898
|
-
activeEraser.erase.points.push(point);
|
|
899
|
-
drawCurrentEraser();
|
|
900
845
|
}
|
|
901
846
|
|
|
902
847
|
function handleAnnotationPointerUp(event: PointerEvent): void {
|
|
@@ -924,7 +869,7 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
924
869
|
}
|
|
925
870
|
|
|
926
871
|
function handleAnnotationClick(event: MouseEvent): void {
|
|
927
|
-
if (!isPresenting || !isAnnotating) {
|
|
872
|
+
if (!isPresenting || !isAnnotating || isDrivingTrace) {
|
|
928
873
|
return;
|
|
929
874
|
}
|
|
930
875
|
|
|
@@ -1011,5 +956,6 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
1011
956
|
slidesRoot.removeEventListener('tada:slides-present', handleSlidesPresent);
|
|
1012
957
|
overlay.remove();
|
|
1013
958
|
eraserPreview.remove();
|
|
959
|
+
annotationLayer.remove();
|
|
1014
960
|
};
|
|
1015
961
|
}
|
package/src/slides/style.scss
CHANGED
|
@@ -6,14 +6,24 @@
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
body.is-presenting {
|
|
9
|
-
--slides-annotation-cursor:
|
|
9
|
+
--slides-annotation-cursor-light:
|
|
10
10
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg transform='rotate(45 12 12)'%3E%3Crect x='10' y='4.8' width='4' height='13.2' rx='2' fill='white' stroke='black' stroke-width='1.5'/%3E%3Cline x1='12' y1='18' x2='12' y2='20.8' stroke='black' stroke-width='1.5' stroke-linecap='round'/%3E%3C/g%3E%3C/svg%3E")
|
|
11
11
|
6 18,
|
|
12
12
|
crosshair;
|
|
13
|
-
--slides-
|
|
13
|
+
--slides-annotation-cursor-dark:
|
|
14
|
+
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg transform='rotate(45 12 12)'%3E%3Crect x='10' y='4.8' width='4' height='13.2' rx='2' fill='black' stroke='white' stroke-width='1.5'/%3E%3Cline x1='12' y1='18' x2='12' y2='20.8' stroke='white' stroke-width='1.5' stroke-linecap='round'/%3E%3C/g%3E%3C/svg%3E")
|
|
15
|
+
6 18,
|
|
16
|
+
crosshair;
|
|
17
|
+
--slides-annotation-cursor: var(--slides-annotation-cursor-light);
|
|
18
|
+
--slides-eraser-cursor-light:
|
|
14
19
|
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg transform='rotate(-45 12 12)'%3E%3Cpath d='M6 10 Q6 8.5 7.5 8.5 H16.5 Q18 8.5 18 10 V14 Q18 15.5 16.5 15.5 H7.5 Q6 15.5 6 14 Z' fill='white' stroke='black' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cline x1='10' y1='8.5' x2='10' y2='15.5' stroke='black' stroke-width='1.5' stroke-linecap='round'/%3E%3C/g%3E%3C/svg%3E")
|
|
15
20
|
12 12,
|
|
16
21
|
cell;
|
|
22
|
+
--slides-eraser-cursor-dark:
|
|
23
|
+
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg transform='rotate(-45 12 12)'%3E%3Cpath d='M6 10 Q6 8.5 7.5 8.5 H16.5 Q18 8.5 18 10 V14 Q18 15.5 16.5 15.5 H7.5 Q6 15.5 6 14 Z' fill='black' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cline x1='10' y1='8.5' x2='10' y2='15.5' stroke='white' stroke-width='1.5' stroke-linecap='round'/%3E%3C/g%3E%3C/svg%3E")
|
|
24
|
+
12 12,
|
|
25
|
+
cell;
|
|
26
|
+
--slides-eraser-cursor: var(--slides-eraser-cursor-light);
|
|
17
27
|
--slides-scale: max(
|
|
18
28
|
100%,
|
|
19
29
|
min(
|
|
@@ -26,6 +36,13 @@ body.is-presenting {
|
|
|
26
36
|
overflow: hidden;
|
|
27
37
|
}
|
|
28
38
|
|
|
39
|
+
@media (prefers-color-scheme: dark) {
|
|
40
|
+
body.is-presenting {
|
|
41
|
+
--slides-annotation-cursor: var(--slides-annotation-cursor-dark);
|
|
42
|
+
--slides-eraser-cursor: var(--slides-eraser-cursor-dark);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
29
46
|
body.is-presenting.is-presentation-cursor-hidden:not(.is-slides-annotating),
|
|
30
47
|
body.is-presenting.is-presentation-cursor-hidden:not(.is-slides-annotating) * {
|
|
31
48
|
cursor: none !important;
|
|
@@ -54,6 +71,25 @@ body.is-presenting [data-slides-eraser-preview] {
|
|
|
54
71
|
transform: translate(-50%, -50%);
|
|
55
72
|
}
|
|
56
73
|
|
|
74
|
+
body.is-presenting [data-slides-annotation-layer] {
|
|
75
|
+
position: fixed;
|
|
76
|
+
inset: 0;
|
|
77
|
+
z-index: 1001;
|
|
78
|
+
pointer-events: none;
|
|
79
|
+
|
|
80
|
+
> [data-slides-annotations] {
|
|
81
|
+
position: absolute;
|
|
82
|
+
inset: 0;
|
|
83
|
+
display: block;
|
|
84
|
+
width: 100vw;
|
|
85
|
+
height: 100vh;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
> [data-slides-annotations][hidden] {
|
|
89
|
+
display: none;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
57
93
|
body.is-presenting header,
|
|
58
94
|
body.is-presenting .title-and-info,
|
|
59
95
|
body.is-presenting nav.toc,
|
|
@@ -108,16 +144,6 @@ body.is-presenting .slide {
|
|
|
108
144
|
background: var(--bg-color);
|
|
109
145
|
transform: translateX(-50%);
|
|
110
146
|
|
|
111
|
-
> [data-slides-annotations] {
|
|
112
|
-
position: absolute;
|
|
113
|
-
inset: 0;
|
|
114
|
-
z-index: 1;
|
|
115
|
-
display: block;
|
|
116
|
-
width: 100%;
|
|
117
|
-
height: 100%;
|
|
118
|
-
pointer-events: none;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
147
|
> :first-child {
|
|
122
148
|
margin-top: 0;
|
|
123
149
|
}
|