@abreen/tada 1.15.2 → 1.15.3
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 +130 -190
- 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;
|
|
@@ -160,10 +146,9 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
160
146
|
let isErasing = false;
|
|
161
147
|
let activeAnnotation: {
|
|
162
148
|
slide: HTMLElement;
|
|
163
|
-
|
|
149
|
+
previousX: number;
|
|
150
|
+
previousY: number;
|
|
164
151
|
} | null = null;
|
|
165
|
-
let activeEraser: { slide: HTMLElement; erase: AnnotationErase } | null =
|
|
166
|
-
null;
|
|
167
152
|
|
|
168
153
|
const toolbarStates = new WeakMap<HTMLElement, TraceToolbarState>();
|
|
169
154
|
const slideTabIndexes = new WeakMap<HTMLElement, string | null>();
|
|
@@ -249,10 +234,10 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
249
234
|
|
|
250
235
|
function setActiveSlide(index: number): void {
|
|
251
236
|
activeAnnotation = null;
|
|
252
|
-
activeEraser = null;
|
|
253
237
|
hideEraserPreview();
|
|
254
238
|
activeIndex = Math.max(0, Math.min(index, slides.length - 1));
|
|
255
239
|
updateActiveSlide(slides, activeIndex, isPresenting);
|
|
240
|
+
updateActiveAnnotationCanvas();
|
|
256
241
|
resizeActiveAnnotationCanvas();
|
|
257
242
|
}
|
|
258
243
|
|
|
@@ -308,6 +293,7 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
308
293
|
isShiftPressed = false;
|
|
309
294
|
setAnnotationMode(false);
|
|
310
295
|
clearAnnotationCanvases();
|
|
296
|
+
annotationLayer.hidden = true;
|
|
311
297
|
hidePresentationControls();
|
|
312
298
|
showPresentationCursor();
|
|
313
299
|
document.body.classList.remove('is-presenting');
|
|
@@ -350,6 +336,7 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
350
336
|
function enterPresentation(mode: PresentationMode, slideIndex = 0): void {
|
|
351
337
|
isPresenting = true;
|
|
352
338
|
presentationMode = mode;
|
|
339
|
+
annotationLayer.hidden = false;
|
|
353
340
|
setAnnotationMode(false);
|
|
354
341
|
hidePresentationControls();
|
|
355
342
|
showPresentationCursor();
|
|
@@ -540,7 +527,6 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
540
527
|
function setAnnotationMode(enabled: boolean): void {
|
|
541
528
|
isAnnotating = enabled;
|
|
542
529
|
activeAnnotation = null;
|
|
543
|
-
activeEraser = null;
|
|
544
530
|
hideEraserPreview();
|
|
545
531
|
document.body.classList.toggle('is-slides-annotating', enabled);
|
|
546
532
|
setEraserMode(enabled && isShiftPressed);
|
|
@@ -552,7 +538,6 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
552
538
|
|
|
553
539
|
function setEraserMode(enabled: boolean): void {
|
|
554
540
|
isErasing = enabled && isPresenting && isAnnotating;
|
|
555
|
-
activeEraser = null;
|
|
556
541
|
|
|
557
542
|
if (isErasing) {
|
|
558
543
|
activeAnnotation = null;
|
|
@@ -577,33 +562,24 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
577
562
|
setAnnotationMode(!isAnnotating);
|
|
578
563
|
}
|
|
579
564
|
|
|
580
|
-
function
|
|
581
|
-
slide: HTMLElement,
|
|
565
|
+
function getViewportPoint(
|
|
582
566
|
event: Pick<PointerEvent, 'clientX' | 'clientY'>,
|
|
583
|
-
):
|
|
584
|
-
const
|
|
585
|
-
|
|
567
|
+
): { x: number; y: number } | null {
|
|
568
|
+
const width = window.innerWidth;
|
|
569
|
+
const height = window.innerHeight;
|
|
570
|
+
|
|
571
|
+
if (
|
|
572
|
+
width <= 0 ||
|
|
573
|
+
height <= 0 ||
|
|
574
|
+
event.clientX < 0 ||
|
|
575
|
+
event.clientX > width ||
|
|
576
|
+
event.clientY < 0 ||
|
|
577
|
+
event.clientY > height
|
|
578
|
+
) {
|
|
586
579
|
return null;
|
|
587
580
|
}
|
|
588
581
|
|
|
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
|
-
);
|
|
582
|
+
return { x: event.clientX, y: event.clientY };
|
|
607
583
|
}
|
|
608
584
|
|
|
609
585
|
function getCanvasContext(
|
|
@@ -624,41 +600,46 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
624
600
|
|
|
625
601
|
const canvas = document.createElement('canvas');
|
|
626
602
|
canvas.dataset.slidesAnnotations = '';
|
|
603
|
+
canvas.dataset.slideIndex = slide.getAttribute('data-slide-index') ?? '';
|
|
627
604
|
canvas.setAttribute('aria-hidden', 'true');
|
|
628
|
-
slide
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
operations: [],
|
|
633
|
-
width: 0,
|
|
634
|
-
height: 0,
|
|
635
|
-
dpr: 1,
|
|
636
|
-
};
|
|
605
|
+
canvas.hidden = slide !== getActiveSlide();
|
|
606
|
+
annotationLayer.append(canvas);
|
|
607
|
+
|
|
608
|
+
const state: SlideAnnotationState = { canvas, width: 0, height: 0, dpr: 1 };
|
|
637
609
|
annotationStates.set(slide, state);
|
|
638
|
-
resizeAnnotationCanvas(
|
|
610
|
+
resizeAnnotationCanvas(state);
|
|
639
611
|
|
|
640
612
|
return state;
|
|
641
613
|
}
|
|
642
614
|
|
|
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);
|
|
615
|
+
function resizeAnnotationCanvas(state: SlideAnnotationState): void {
|
|
616
|
+
const width = Math.max(1, window.innerWidth);
|
|
617
|
+
const height = Math.max(1, window.innerHeight);
|
|
650
618
|
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
651
619
|
|
|
652
620
|
if (state.width === width && state.height === height && state.dpr === dpr) {
|
|
653
621
|
return;
|
|
654
622
|
}
|
|
655
623
|
|
|
624
|
+
const priorCanvas =
|
|
625
|
+
state.canvas.width > 0 && state.canvas.height > 0
|
|
626
|
+
? document.createElement('canvas')
|
|
627
|
+
: null;
|
|
628
|
+
if (priorCanvas) {
|
|
629
|
+
priorCanvas.width = state.canvas.width;
|
|
630
|
+
priorCanvas.height = state.canvas.height;
|
|
631
|
+
priorCanvas.getContext('2d')?.drawImage(state.canvas, 0, 0);
|
|
632
|
+
}
|
|
633
|
+
|
|
656
634
|
state.width = width;
|
|
657
635
|
state.height = height;
|
|
658
636
|
state.dpr = dpr;
|
|
659
637
|
state.canvas.width = Math.round(width * dpr);
|
|
660
638
|
state.canvas.height = Math.round(height * dpr);
|
|
661
|
-
|
|
639
|
+
|
|
640
|
+
if (priorCanvas) {
|
|
641
|
+
getCanvasContext(state.canvas)?.drawImage(priorCanvas, 0, 0);
|
|
642
|
+
}
|
|
662
643
|
}
|
|
663
644
|
|
|
664
645
|
function resizeActiveAnnotationCanvas(): void {
|
|
@@ -669,124 +650,75 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
669
650
|
|
|
670
651
|
const state = annotationStates.get(slide);
|
|
671
652
|
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
|
-
}
|
|
653
|
+
resizeAnnotationCanvas(state);
|
|
690
654
|
}
|
|
691
655
|
}
|
|
692
656
|
|
|
693
657
|
function drawAnnotationStroke(
|
|
694
658
|
ctx: CanvasRenderingContext2D,
|
|
695
|
-
stroke: AnnotationStroke,
|
|
696
659
|
state: SlideAnnotationState,
|
|
660
|
+
from: { x: number; y: number },
|
|
661
|
+
to: { x: number; y: number },
|
|
697
662
|
): void {
|
|
698
|
-
if (stroke.points.length === 0) {
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
663
|
ctx.globalCompositeOperation = 'source-over';
|
|
703
664
|
ctx.strokeStyle = ANNOTATION_COLOR;
|
|
704
665
|
ctx.lineWidth = 3 * state.dpr;
|
|
705
666
|
ctx.lineCap = 'round';
|
|
706
667
|
ctx.lineJoin = 'round';
|
|
707
668
|
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
|
-
|
|
669
|
+
ctx.moveTo(from.x * state.dpr, from.y * state.dpr);
|
|
670
|
+
ctx.lineTo(to.x * state.dpr, to.y * state.dpr);
|
|
715
671
|
ctx.stroke();
|
|
716
672
|
}
|
|
717
673
|
|
|
718
|
-
function
|
|
674
|
+
function drawAnnotationDot(
|
|
719
675
|
ctx: CanvasRenderingContext2D,
|
|
720
|
-
erase: AnnotationErase,
|
|
721
676
|
state: SlideAnnotationState,
|
|
677
|
+
point: { x: number; y: number },
|
|
722
678
|
): void {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
679
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
680
|
+
ctx.fillStyle = ANNOTATION_COLOR;
|
|
681
|
+
ctx.beginPath();
|
|
682
|
+
ctx.arc(
|
|
683
|
+
point.x * state.dpr,
|
|
684
|
+
point.y * state.dpr,
|
|
685
|
+
1.5 * state.dpr,
|
|
686
|
+
0,
|
|
687
|
+
Math.PI * 2,
|
|
688
|
+
);
|
|
689
|
+
ctx.fill();
|
|
690
|
+
}
|
|
726
691
|
|
|
727
|
-
|
|
692
|
+
function drawAnnotationErase(
|
|
693
|
+
ctx: CanvasRenderingContext2D,
|
|
694
|
+
state: SlideAnnotationState,
|
|
695
|
+
point: { x: number; y: number },
|
|
696
|
+
): void {
|
|
697
|
+
const radius = ANNOTATION_ERASER_RADIUS * state.dpr;
|
|
728
698
|
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
699
|
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();
|
|
700
|
+
ctx.arc(point.x * state.dpr, point.y * state.dpr, radius, 0, Math.PI * 2);
|
|
701
|
+
ctx.fill();
|
|
756
702
|
ctx.globalCompositeOperation = 'source-over';
|
|
757
703
|
}
|
|
758
704
|
|
|
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
705
|
function clearAnnotationCanvases(): void {
|
|
784
706
|
for (const slide of slides) {
|
|
785
|
-
|
|
707
|
+
annotationStates.get(slide)?.canvas.remove();
|
|
786
708
|
annotationStates.delete(slide);
|
|
787
709
|
}
|
|
788
710
|
activeAnnotation = null;
|
|
789
|
-
|
|
711
|
+
hideEraserPreview();
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function updateActiveAnnotationCanvas(): void {
|
|
715
|
+
const activeSlide = getActiveSlide();
|
|
716
|
+
for (const slide of slides) {
|
|
717
|
+
const state = annotationStates.get(slide);
|
|
718
|
+
if (state) {
|
|
719
|
+
state.canvas.hidden = slide !== activeSlide;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
790
722
|
}
|
|
791
723
|
|
|
792
724
|
function handleAnnotationContextMenu(event: MouseEvent): void {
|
|
@@ -812,21 +744,23 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
812
744
|
}
|
|
813
745
|
|
|
814
746
|
const slide = getActiveSlide();
|
|
815
|
-
if (!slide
|
|
747
|
+
if (!slide) {
|
|
816
748
|
activeAnnotation = null;
|
|
817
749
|
return;
|
|
818
750
|
}
|
|
819
751
|
|
|
820
|
-
const point =
|
|
752
|
+
const point = getViewportPoint(event);
|
|
821
753
|
if (!point) {
|
|
822
754
|
return;
|
|
823
755
|
}
|
|
824
756
|
|
|
825
757
|
const state = getAnnotationState(slide);
|
|
826
|
-
|
|
827
|
-
state.
|
|
828
|
-
|
|
829
|
-
|
|
758
|
+
resizeAnnotationCanvas(state);
|
|
759
|
+
const ctx = getCanvasContext(state.canvas);
|
|
760
|
+
if (ctx) {
|
|
761
|
+
drawAnnotationDot(ctx, state, point);
|
|
762
|
+
}
|
|
763
|
+
activeAnnotation = { slide, previousX: point.x, previousY: point.y };
|
|
830
764
|
|
|
831
765
|
const target =
|
|
832
766
|
event.target instanceof domView.Element ? event.target : slide;
|
|
@@ -843,40 +777,51 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
843
777
|
return;
|
|
844
778
|
}
|
|
845
779
|
|
|
846
|
-
if (!activeAnnotation) {
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
780
|
event.preventDefault();
|
|
851
781
|
event.stopPropagation();
|
|
852
782
|
|
|
853
|
-
const point =
|
|
783
|
+
const point = getViewportPoint(event);
|
|
854
784
|
if (!point) {
|
|
785
|
+
activeAnnotation = null;
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (!activeAnnotation) {
|
|
790
|
+
if ((event.buttons & 1) !== 1) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const slide = getActiveSlide();
|
|
794
|
+
if (!slide) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
activeAnnotation = { slide, previousX: point.x, previousY: point.y };
|
|
855
798
|
return;
|
|
856
799
|
}
|
|
857
800
|
|
|
858
|
-
activeAnnotation.
|
|
859
|
-
|
|
801
|
+
const state = getAnnotationState(activeAnnotation.slide);
|
|
802
|
+
resizeAnnotationCanvas(state);
|
|
803
|
+
const ctx = getCanvasContext(state.canvas);
|
|
804
|
+
if (ctx) {
|
|
805
|
+
drawAnnotationStroke(
|
|
806
|
+
ctx,
|
|
807
|
+
state,
|
|
808
|
+
{ x: activeAnnotation.previousX, y: activeAnnotation.previousY },
|
|
809
|
+
point,
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
activeAnnotation.previousX = point.x;
|
|
813
|
+
activeAnnotation.previousY = point.y;
|
|
860
814
|
}
|
|
861
815
|
|
|
862
816
|
function handleAnnotationEraseMove(event: PointerEvent): void {
|
|
863
817
|
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;
|
|
818
|
+
if (!slide) {
|
|
873
819
|
hideEraserPreview();
|
|
874
820
|
return;
|
|
875
821
|
}
|
|
876
822
|
|
|
877
|
-
const point =
|
|
823
|
+
const point = getViewportPoint(event);
|
|
878
824
|
if (!point) {
|
|
879
|
-
activeEraser = null;
|
|
880
825
|
hideEraserPreview();
|
|
881
826
|
return;
|
|
882
827
|
}
|
|
@@ -885,18 +830,12 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
885
830
|
event.stopPropagation();
|
|
886
831
|
showEraserPreview(event);
|
|
887
832
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
};
|
|
894
|
-
state.operations.push(erase);
|
|
895
|
-
activeEraser = { slide, erase };
|
|
833
|
+
const state = getAnnotationState(slide);
|
|
834
|
+
resizeAnnotationCanvas(state);
|
|
835
|
+
const ctx = getCanvasContext(state.canvas);
|
|
836
|
+
if (ctx) {
|
|
837
|
+
drawAnnotationErase(ctx, state, point);
|
|
896
838
|
}
|
|
897
|
-
|
|
898
|
-
activeEraser.erase.points.push(point);
|
|
899
|
-
drawCurrentEraser();
|
|
900
839
|
}
|
|
901
840
|
|
|
902
841
|
function handleAnnotationPointerUp(event: PointerEvent): void {
|
|
@@ -1011,5 +950,6 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
1011
950
|
slidesRoot.removeEventListener('tada:slides-present', handleSlidesPresent);
|
|
1012
951
|
overlay.remove();
|
|
1013
952
|
eraserPreview.remove();
|
|
953
|
+
annotationLayer.remove();
|
|
1014
954
|
};
|
|
1015
955
|
}
|
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
|
}
|