@abreen/tada 1.15.1 → 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/question/style.scss +2 -2
- package/src/slides/index.ts +426 -0
- package/src/slides/style.scss +77 -3
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/question/style.scss
CHANGED
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
.js .question-a-body[data-revealed] {
|
|
116
116
|
cursor: unset;
|
|
117
117
|
|
|
118
|
-
@include user-select(
|
|
118
|
+
@include user-select(unset);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
.question-multiple-choice {
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
.question-multiple-choice-option {
|
|
186
186
|
cursor: unset;
|
|
187
187
|
|
|
188
|
-
@include user-select(
|
|
188
|
+
@include user-select(unset);
|
|
189
189
|
|
|
190
190
|
&:focus,
|
|
191
191
|
&:focus-visible {
|
package/src/slides/index.ts
CHANGED
|
@@ -9,7 +9,17 @@ interface SlidesPresentDetail {
|
|
|
9
9
|
slideIndex?: number;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
interface SlideAnnotationState {
|
|
13
|
+
canvas: HTMLCanvasElement;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
dpr: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
const FULLSCREEN_STORAGE_KEY = 'slidesFullscreen';
|
|
20
|
+
const ANNOTATION_COLOR = 'blueviolet';
|
|
21
|
+
const ANNOTATION_ERASER_RADIUS = 18;
|
|
22
|
+
const ANNOTATION_ERASER_DIAMETER = ANNOTATION_ERASER_RADIUS * 2;
|
|
13
23
|
|
|
14
24
|
function isInteractive(
|
|
15
25
|
view: Window & typeof globalThis,
|
|
@@ -111,15 +121,44 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
111
121
|
overlay.append(closeButton);
|
|
112
122
|
document.body.appendChild(overlay);
|
|
113
123
|
|
|
124
|
+
const eraserPreview = document.createElement('div');
|
|
125
|
+
eraserPreview.hidden = true;
|
|
126
|
+
eraserPreview.dataset.slidesEraserPreview = '';
|
|
127
|
+
eraserPreview.setAttribute('aria-hidden', 'true');
|
|
128
|
+
eraserPreview.style.setProperty(
|
|
129
|
+
'--slides-eraser-preview-size',
|
|
130
|
+
`${ANNOTATION_ERASER_DIAMETER}px`,
|
|
131
|
+
);
|
|
132
|
+
document.body.appendChild(eraserPreview);
|
|
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
|
+
|
|
114
140
|
let isPresenting = false;
|
|
115
141
|
let activeIndex = -1;
|
|
116
142
|
let presentationMode: PresentationMode | null = null;
|
|
117
143
|
let isToolbarPinnedVisible = false;
|
|
144
|
+
let isAnnotating = false;
|
|
145
|
+
let isShiftPressed = false;
|
|
146
|
+
let isErasing = false;
|
|
147
|
+
let activeAnnotation: {
|
|
148
|
+
slide: HTMLElement;
|
|
149
|
+
previousX: number;
|
|
150
|
+
previousY: number;
|
|
151
|
+
} | null = null;
|
|
118
152
|
|
|
119
153
|
const toolbarStates = new WeakMap<HTMLElement, TraceToolbarState>();
|
|
120
154
|
const slideTabIndexes = new WeakMap<HTMLElement, string | null>();
|
|
155
|
+
const annotationStates = new WeakMap<HTMLElement, SlideAnnotationState>();
|
|
121
156
|
|
|
122
157
|
function hidePresentationCursor(): void {
|
|
158
|
+
if (isAnnotating) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
123
162
|
document.body.classList.add('is-presentation-cursor-hidden');
|
|
124
163
|
}
|
|
125
164
|
|
|
@@ -194,8 +233,12 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
194
233
|
}
|
|
195
234
|
|
|
196
235
|
function setActiveSlide(index: number): void {
|
|
236
|
+
activeAnnotation = null;
|
|
237
|
+
hideEraserPreview();
|
|
197
238
|
activeIndex = Math.max(0, Math.min(index, slides.length - 1));
|
|
198
239
|
updateActiveSlide(slides, activeIndex, isPresenting);
|
|
240
|
+
updateActiveAnnotationCanvas();
|
|
241
|
+
resizeActiveAnnotationCanvas();
|
|
199
242
|
}
|
|
200
243
|
|
|
201
244
|
function focusSlide(slide: HTMLElement | undefined): void {
|
|
@@ -247,6 +290,10 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
247
290
|
isPresenting = false;
|
|
248
291
|
activeIndex = -1;
|
|
249
292
|
presentationMode = null;
|
|
293
|
+
isShiftPressed = false;
|
|
294
|
+
setAnnotationMode(false);
|
|
295
|
+
clearAnnotationCanvases();
|
|
296
|
+
annotationLayer.hidden = true;
|
|
250
297
|
hidePresentationControls();
|
|
251
298
|
showPresentationCursor();
|
|
252
299
|
document.body.classList.remove('is-presenting');
|
|
@@ -289,6 +336,8 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
289
336
|
function enterPresentation(mode: PresentationMode, slideIndex = 0): void {
|
|
290
337
|
isPresenting = true;
|
|
291
338
|
presentationMode = mode;
|
|
339
|
+
annotationLayer.hidden = false;
|
|
340
|
+
setAnnotationMode(false);
|
|
292
341
|
hidePresentationControls();
|
|
293
342
|
showPresentationCursor();
|
|
294
343
|
document.body.classList.add('is-presenting');
|
|
@@ -428,6 +477,12 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
428
477
|
return;
|
|
429
478
|
}
|
|
430
479
|
|
|
480
|
+
if (event.key === 'Shift') {
|
|
481
|
+
isShiftPressed = true;
|
|
482
|
+
setEraserMode(isAnnotating);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
431
486
|
if (
|
|
432
487
|
(event.key === 'ArrowRight' ||
|
|
433
488
|
event.key === 'ArrowLeft' ||
|
|
@@ -465,6 +520,357 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
465
520
|
move(1);
|
|
466
521
|
}
|
|
467
522
|
|
|
523
|
+
function getActiveSlide(): HTMLElement | null {
|
|
524
|
+
return activeIndex >= 0 ? (slides[activeIndex] ?? null) : null;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function setAnnotationMode(enabled: boolean): void {
|
|
528
|
+
isAnnotating = enabled;
|
|
529
|
+
activeAnnotation = null;
|
|
530
|
+
hideEraserPreview();
|
|
531
|
+
document.body.classList.toggle('is-slides-annotating', enabled);
|
|
532
|
+
setEraserMode(enabled && isShiftPressed);
|
|
533
|
+
|
|
534
|
+
if (enabled) {
|
|
535
|
+
showPresentationCursor();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function setEraserMode(enabled: boolean): void {
|
|
540
|
+
isErasing = enabled && isPresenting && isAnnotating;
|
|
541
|
+
|
|
542
|
+
if (isErasing) {
|
|
543
|
+
activeAnnotation = null;
|
|
544
|
+
} else {
|
|
545
|
+
hideEraserPreview();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
document.body.classList.toggle('is-slides-erasing', isErasing);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function hideEraserPreview(): void {
|
|
552
|
+
eraserPreview.hidden = true;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function showEraserPreview(event: PointerEvent): void {
|
|
556
|
+
eraserPreview.hidden = false;
|
|
557
|
+
eraserPreview.style.left = `${event.clientX}px`;
|
|
558
|
+
eraserPreview.style.top = `${event.clientY}px`;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function toggleAnnotationMode(): void {
|
|
562
|
+
setAnnotationMode(!isAnnotating);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function getViewportPoint(
|
|
566
|
+
event: Pick<PointerEvent, 'clientX' | 'clientY'>,
|
|
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
|
+
) {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return { x: event.clientX, y: event.clientY };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function getCanvasContext(
|
|
586
|
+
canvas: HTMLCanvasElement,
|
|
587
|
+
): CanvasRenderingContext2D | null {
|
|
588
|
+
try {
|
|
589
|
+
return canvas.getContext('2d');
|
|
590
|
+
} catch {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function getAnnotationState(slide: HTMLElement): SlideAnnotationState {
|
|
596
|
+
const existing = annotationStates.get(slide);
|
|
597
|
+
if (existing) {
|
|
598
|
+
return existing;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const canvas = document.createElement('canvas');
|
|
602
|
+
canvas.dataset.slidesAnnotations = '';
|
|
603
|
+
canvas.dataset.slideIndex = slide.getAttribute('data-slide-index') ?? '';
|
|
604
|
+
canvas.setAttribute('aria-hidden', 'true');
|
|
605
|
+
canvas.hidden = slide !== getActiveSlide();
|
|
606
|
+
annotationLayer.append(canvas);
|
|
607
|
+
|
|
608
|
+
const state: SlideAnnotationState = { canvas, width: 0, height: 0, dpr: 1 };
|
|
609
|
+
annotationStates.set(slide, state);
|
|
610
|
+
resizeAnnotationCanvas(state);
|
|
611
|
+
|
|
612
|
+
return state;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function resizeAnnotationCanvas(state: SlideAnnotationState): void {
|
|
616
|
+
const width = Math.max(1, window.innerWidth);
|
|
617
|
+
const height = Math.max(1, window.innerHeight);
|
|
618
|
+
const dpr = Math.max(1, window.devicePixelRatio || 1);
|
|
619
|
+
|
|
620
|
+
if (state.width === width && state.height === height && state.dpr === dpr) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
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
|
+
|
|
634
|
+
state.width = width;
|
|
635
|
+
state.height = height;
|
|
636
|
+
state.dpr = dpr;
|
|
637
|
+
state.canvas.width = Math.round(width * dpr);
|
|
638
|
+
state.canvas.height = Math.round(height * dpr);
|
|
639
|
+
|
|
640
|
+
if (priorCanvas) {
|
|
641
|
+
getCanvasContext(state.canvas)?.drawImage(priorCanvas, 0, 0);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function resizeActiveAnnotationCanvas(): void {
|
|
646
|
+
const slide = getActiveSlide();
|
|
647
|
+
if (!slide) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const state = annotationStates.get(slide);
|
|
652
|
+
if (state) {
|
|
653
|
+
resizeAnnotationCanvas(state);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function drawAnnotationStroke(
|
|
658
|
+
ctx: CanvasRenderingContext2D,
|
|
659
|
+
state: SlideAnnotationState,
|
|
660
|
+
from: { x: number; y: number },
|
|
661
|
+
to: { x: number; y: number },
|
|
662
|
+
): void {
|
|
663
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
664
|
+
ctx.strokeStyle = ANNOTATION_COLOR;
|
|
665
|
+
ctx.lineWidth = 3 * state.dpr;
|
|
666
|
+
ctx.lineCap = 'round';
|
|
667
|
+
ctx.lineJoin = 'round';
|
|
668
|
+
ctx.beginPath();
|
|
669
|
+
ctx.moveTo(from.x * state.dpr, from.y * state.dpr);
|
|
670
|
+
ctx.lineTo(to.x * state.dpr, to.y * state.dpr);
|
|
671
|
+
ctx.stroke();
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function drawAnnotationDot(
|
|
675
|
+
ctx: CanvasRenderingContext2D,
|
|
676
|
+
state: SlideAnnotationState,
|
|
677
|
+
point: { x: number; y: number },
|
|
678
|
+
): void {
|
|
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
|
+
}
|
|
691
|
+
|
|
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;
|
|
698
|
+
ctx.globalCompositeOperation = 'destination-out';
|
|
699
|
+
ctx.beginPath();
|
|
700
|
+
ctx.arc(point.x * state.dpr, point.y * state.dpr, radius, 0, Math.PI * 2);
|
|
701
|
+
ctx.fill();
|
|
702
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function clearAnnotationCanvases(): void {
|
|
706
|
+
for (const slide of slides) {
|
|
707
|
+
annotationStates.get(slide)?.canvas.remove();
|
|
708
|
+
annotationStates.delete(slide);
|
|
709
|
+
}
|
|
710
|
+
activeAnnotation = null;
|
|
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
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function handleAnnotationContextMenu(event: MouseEvent): void {
|
|
725
|
+
if (!isPresenting) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
event.preventDefault();
|
|
730
|
+
event.stopPropagation();
|
|
731
|
+
toggleAnnotationMode();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function handleAnnotationPointerDown(event: PointerEvent): void {
|
|
735
|
+
if (!isPresenting || !isAnnotating || event.button !== 0) {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
event.preventDefault();
|
|
740
|
+
event.stopPropagation();
|
|
741
|
+
|
|
742
|
+
if (isErasing) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const slide = getActiveSlide();
|
|
747
|
+
if (!slide) {
|
|
748
|
+
activeAnnotation = null;
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const point = getViewportPoint(event);
|
|
753
|
+
if (!point) {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const state = getAnnotationState(slide);
|
|
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 };
|
|
764
|
+
|
|
765
|
+
const target =
|
|
766
|
+
event.target instanceof domView.Element ? event.target : slide;
|
|
767
|
+
target.setPointerCapture?.(event.pointerId);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function handleAnnotationPointerMove(event: PointerEvent): void {
|
|
771
|
+
if (!isPresenting || !isAnnotating) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (isErasing) {
|
|
776
|
+
handleAnnotationEraseMove(event);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
event.preventDefault();
|
|
781
|
+
event.stopPropagation();
|
|
782
|
+
|
|
783
|
+
const point = getViewportPoint(event);
|
|
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 };
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
|
|
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;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function handleAnnotationEraseMove(event: PointerEvent): void {
|
|
817
|
+
const slide = getActiveSlide();
|
|
818
|
+
if (!slide) {
|
|
819
|
+
hideEraserPreview();
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const point = getViewportPoint(event);
|
|
824
|
+
if (!point) {
|
|
825
|
+
hideEraserPreview();
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
event.preventDefault();
|
|
830
|
+
event.stopPropagation();
|
|
831
|
+
showEraserPreview(event);
|
|
832
|
+
|
|
833
|
+
const state = getAnnotationState(slide);
|
|
834
|
+
resizeAnnotationCanvas(state);
|
|
835
|
+
const ctx = getCanvasContext(state.canvas);
|
|
836
|
+
if (ctx) {
|
|
837
|
+
drawAnnotationErase(ctx, state, point);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function handleAnnotationPointerUp(event: PointerEvent): void {
|
|
842
|
+
if (!isPresenting || !isAnnotating || !activeAnnotation) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
event.preventDefault();
|
|
847
|
+
event.stopPropagation();
|
|
848
|
+
activeAnnotation = null;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function handleKeyup(event: KeyboardEvent): void {
|
|
852
|
+
if (event.key !== 'Shift') {
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
isShiftPressed = false;
|
|
857
|
+
setEraserMode(false);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function handleWindowBlur(): void {
|
|
861
|
+
isShiftPressed = false;
|
|
862
|
+
setEraserMode(false);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function handleAnnotationClick(event: MouseEvent): void {
|
|
866
|
+
if (!isPresenting || !isAnnotating) {
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
event.preventDefault();
|
|
871
|
+
event.stopPropagation();
|
|
872
|
+
}
|
|
873
|
+
|
|
468
874
|
function handleMouseMove(event: MouseEvent): void {
|
|
469
875
|
if (!isPresenting) {
|
|
470
876
|
return;
|
|
@@ -507,8 +913,17 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
507
913
|
closeButton.addEventListener('click', handleCloseClick);
|
|
508
914
|
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
|
509
915
|
window.addEventListener('keydown', handleKeydown);
|
|
916
|
+
window.addEventListener('keyup', handleKeyup);
|
|
917
|
+
window.addEventListener('blur', handleWindowBlur);
|
|
510
918
|
window.addEventListener('mousemove', handleMouseMove);
|
|
919
|
+
window.addEventListener('resize', resizeActiveAnnotationCanvas);
|
|
920
|
+
window.addEventListener('pointermove', handleAnnotationPointerMove);
|
|
921
|
+
window.addEventListener('pointerup', handleAnnotationPointerUp);
|
|
922
|
+
window.addEventListener('pointercancel', handleAnnotationPointerUp);
|
|
923
|
+
slidesRoot.addEventListener('click', handleAnnotationClick, true);
|
|
511
924
|
slidesRoot.addEventListener('click', handleSlideClick);
|
|
925
|
+
slidesRoot.addEventListener('contextmenu', handleAnnotationContextMenu);
|
|
926
|
+
slidesRoot.addEventListener('pointerdown', handleAnnotationPointerDown);
|
|
512
927
|
slidesRoot.addEventListener('tada:slides-present', handleSlidesPresent);
|
|
513
928
|
|
|
514
929
|
return () => {
|
|
@@ -521,9 +936,20 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
521
936
|
closeButton.removeEventListener('click', handleCloseClick);
|
|
522
937
|
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
|
523
938
|
window.removeEventListener('keydown', handleKeydown);
|
|
939
|
+
window.removeEventListener('keyup', handleKeyup);
|
|
940
|
+
window.removeEventListener('blur', handleWindowBlur);
|
|
524
941
|
window.removeEventListener('mousemove', handleMouseMove);
|
|
942
|
+
window.removeEventListener('resize', resizeActiveAnnotationCanvas);
|
|
943
|
+
window.removeEventListener('pointermove', handleAnnotationPointerMove);
|
|
944
|
+
window.removeEventListener('pointerup', handleAnnotationPointerUp);
|
|
945
|
+
window.removeEventListener('pointercancel', handleAnnotationPointerUp);
|
|
946
|
+
slidesRoot.removeEventListener('click', handleAnnotationClick, true);
|
|
525
947
|
slidesRoot.removeEventListener('click', handleSlideClick);
|
|
948
|
+
slidesRoot.removeEventListener('contextmenu', handleAnnotationContextMenu);
|
|
949
|
+
slidesRoot.removeEventListener('pointerdown', handleAnnotationPointerDown);
|
|
526
950
|
slidesRoot.removeEventListener('tada:slides-present', handleSlidesPresent);
|
|
527
951
|
overlay.remove();
|
|
952
|
+
eraserPreview.remove();
|
|
953
|
+
annotationLayer.remove();
|
|
528
954
|
};
|
|
529
955
|
}
|
package/src/slides/style.scss
CHANGED
|
@@ -6,16 +6,90 @@
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
body.is-presenting {
|
|
9
|
-
--slides-
|
|
9
|
+
--slides-annotation-cursor-light:
|
|
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
|
+
6 18,
|
|
12
|
+
crosshair;
|
|
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:
|
|
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")
|
|
20
|
+
12 12,
|
|
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);
|
|
27
|
+
--slides-scale: max(
|
|
28
|
+
100%,
|
|
29
|
+
min(
|
|
30
|
+
clamp(150%, 2.8vmin, 200%),
|
|
31
|
+
calc((100vw - var(--gap) - var(--gap)) / 35),
|
|
32
|
+
calc(100vh / 26.25)
|
|
33
|
+
)
|
|
34
|
+
);
|
|
10
35
|
|
|
11
36
|
overflow: hidden;
|
|
12
37
|
}
|
|
13
38
|
|
|
14
|
-
|
|
15
|
-
body.is-presenting
|
|
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
|
+
|
|
46
|
+
body.is-presenting.is-presentation-cursor-hidden:not(.is-slides-annotating),
|
|
47
|
+
body.is-presenting.is-presentation-cursor-hidden:not(.is-slides-annotating) * {
|
|
16
48
|
cursor: none !important;
|
|
17
49
|
}
|
|
18
50
|
|
|
51
|
+
body.is-presenting.is-slides-annotating .slide-deck,
|
|
52
|
+
body.is-presenting.is-slides-annotating .slide-deck * {
|
|
53
|
+
cursor: var(--slides-annotation-cursor) !important;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
body.is-presenting.is-slides-annotating.is-slides-erasing .slide-deck,
|
|
57
|
+
body.is-presenting.is-slides-annotating.is-slides-erasing .slide-deck * {
|
|
58
|
+
cursor: var(--slides-eraser-cursor) !important;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
body.is-presenting [data-slides-eraser-preview] {
|
|
62
|
+
position: fixed;
|
|
63
|
+
z-index: 1002;
|
|
64
|
+
box-sizing: border-box;
|
|
65
|
+
width: var(--slides-eraser-preview-size, 36px);
|
|
66
|
+
height: var(--slides-eraser-preview-size, 36px);
|
|
67
|
+
pointer-events: none;
|
|
68
|
+
background: transparent;
|
|
69
|
+
border: 2px solid var(--bg2-color);
|
|
70
|
+
border-radius: 50%;
|
|
71
|
+
transform: translate(-50%, -50%);
|
|
72
|
+
}
|
|
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
|
+
|
|
19
93
|
body.is-presenting header,
|
|
20
94
|
body.is-presenting .title-and-info,
|
|
21
95
|
body.is-presenting nav.toc,
|