promptslide 0.3.16 → 0.3.18
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/CHANGELOG.md +14 -0
- package/README.md +16 -12
- package/dist/index.d.ts +17 -7
- package/dist/index.js +1072 -225
- package/dist/index.js.map +1 -1
- package/package.json +31 -31
- package/src/commands/clone.mjs +54 -21
- package/src/commands/pull.mjs +57 -27
- package/src/commands/studio.mjs +7 -3
- package/src/commands/to-image.mjs +18 -5
- package/src/commands/to-pdf.mjs +27 -0
- package/src/core/animated.tsx +3 -3
- package/src/core/annotations/adapters/http.ts +8 -0
- package/src/core/annotations/annotation-overlay.tsx +77 -32
- package/src/core/annotations/annotation-panel.tsx +184 -51
- package/src/core/annotations/types.ts +5 -0
- package/src/core/annotations/use-annotations.ts +57 -7
- package/src/core/slide-deck.tsx +740 -116
- package/src/core/slide-renderer.tsx +3 -8
- package/src/core/transitions.ts +5 -3
- package/src/core/types.ts +2 -0
- package/src/core/use-slide-navigation.ts +93 -3
- package/src/editor/README.md +35 -0
- package/src/editor/geometry.ts +76 -0
- package/src/editor/mount.ts +24 -0
- package/src/editor/plugin.mjs +189 -0
- package/src/editor/source.mjs +363 -0
- package/src/editor/studio.css +1004 -0
- package/src/editor/studio.tsx +1948 -0
- package/src/index.mjs +6 -0
- package/src/utils/deck-config.mjs +30 -11
- package/src/utils/export.mjs +151 -52
- package/src/utils/studio-discovery.mjs +37 -0
- package/src/vite/config.mjs +23 -2
- package/src/vite/plugin.mjs +147 -16
- package/templates/default/AGENTS.md +3 -2
- package/templates/default/src/deck-config.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -115,7 +115,7 @@ function Animated({
|
|
|
115
115
|
return /* @__PURE__ */ jsx2(
|
|
116
116
|
motion.div,
|
|
117
117
|
{
|
|
118
|
-
initial: "hidden",
|
|
118
|
+
initial: isVisible ? false : "hidden",
|
|
119
119
|
animate: isVisible ? "visible" : "hidden",
|
|
120
120
|
variants: animationVariants[animation],
|
|
121
121
|
transition: {
|
|
@@ -149,7 +149,7 @@ function AnimatedGroup({
|
|
|
149
149
|
return /* @__PURE__ */ jsx2(
|
|
150
150
|
motion.div,
|
|
151
151
|
{
|
|
152
|
-
initial: "hidden",
|
|
152
|
+
initial: isVisible ? false : "hidden",
|
|
153
153
|
animate: isVisible ? "visible" : "hidden",
|
|
154
154
|
variants: containerVariants,
|
|
155
155
|
className,
|
|
@@ -160,9 +160,11 @@ function AnimatedGroup({
|
|
|
160
160
|
|
|
161
161
|
// src/core/transitions.ts
|
|
162
162
|
var fadeVariants = {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
// Render the next slide fully beneath the current one, then fade the
|
|
164
|
+
// outgoing slide away. This keeps the frame opaque throughout the change.
|
|
165
|
+
enter: { opacity: 1, zIndex: 0 },
|
|
166
|
+
center: { opacity: 1, zIndex: 0 },
|
|
167
|
+
exit: { opacity: 0, zIndex: 1 }
|
|
166
168
|
};
|
|
167
169
|
var slideLeftVariants = {
|
|
168
170
|
enter: { x: SLIDE_DISTANCE, opacity: 0 },
|
|
@@ -352,20 +354,102 @@ function MorphText({
|
|
|
352
354
|
}
|
|
353
355
|
|
|
354
356
|
// src/core/use-slide-navigation.ts
|
|
355
|
-
import { useCallback, useEffect, useState } from "react";
|
|
357
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
358
|
+
var POSITION_KEY = "promptslide:position";
|
|
359
|
+
function slideKey(slides, index) {
|
|
360
|
+
return slides[index]?.id || String(index + 1);
|
|
361
|
+
}
|
|
362
|
+
function slideFromHash(slides) {
|
|
363
|
+
if (typeof window === "undefined" || !window.location.hash) return null;
|
|
364
|
+
let hash;
|
|
365
|
+
try {
|
|
366
|
+
hash = decodeURIComponent(window.location.hash.slice(1));
|
|
367
|
+
} catch {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
const byId = slides.findIndex((slide) => slide.id === hash);
|
|
371
|
+
if (byId >= 0) return byId;
|
|
372
|
+
if (/^[1-9]\d*$/.test(hash)) {
|
|
373
|
+
const index = Number(hash) - 1;
|
|
374
|
+
if (index < slides.length) return index;
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
function savedStep(slides, index) {
|
|
379
|
+
if (typeof window === "undefined") return 0;
|
|
380
|
+
try {
|
|
381
|
+
const saved = JSON.parse(window.sessionStorage.getItem(POSITION_KEY) || "null");
|
|
382
|
+
if (saved?.slide === slideKey(slides, index) && Number.isInteger(saved.step)) {
|
|
383
|
+
return Math.max(0, Math.min(saved.step, slides[index]?.steps ?? 0));
|
|
384
|
+
}
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
356
389
|
function useSlideNavigation({
|
|
357
390
|
slides,
|
|
358
391
|
initialSlide = 0,
|
|
359
392
|
onSlideChange
|
|
360
393
|
}) {
|
|
361
|
-
const [currentSlide, setCurrentSlide] = useState(initialSlide);
|
|
362
|
-
const [animationStep, setAnimationStep] = useState(
|
|
394
|
+
const [currentSlide, setCurrentSlide] = useState(() => slideFromHash(slides) ?? initialSlide);
|
|
395
|
+
const [animationStep, setAnimationStep] = useState(
|
|
396
|
+
() => savedStep(slides, slideFromHash(slides) ?? initialSlide)
|
|
397
|
+
);
|
|
398
|
+
const slidesRef = useRef(slides);
|
|
399
|
+
const activeKeyRef = useRef(slideKey(slides, slideFromHash(slides) ?? initialSlide));
|
|
363
400
|
const [navState, setNavState] = useState({
|
|
364
401
|
status: "idle",
|
|
365
402
|
direction: 0
|
|
366
403
|
});
|
|
367
404
|
const [queuedAction, setQueuedAction] = useState(null);
|
|
368
405
|
const totalSteps = slides[currentSlide]?.steps ?? 0;
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
if (typeof window === "undefined" || slides.length === 0) return;
|
|
408
|
+
if (slidesRef.current !== slides) {
|
|
409
|
+
slidesRef.current = slides;
|
|
410
|
+
const previousKey = activeKeyRef.current;
|
|
411
|
+
const movedIndex = slides.findIndex((slide) => slide.id && slide.id === previousKey);
|
|
412
|
+
if (movedIndex >= 0 && movedIndex !== currentSlide) {
|
|
413
|
+
setCurrentSlide(movedIndex);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (movedIndex < 0 && !/^[1-9]\d*$/.test(previousKey) && currentSlide !== 0) {
|
|
417
|
+
setAnimationStep(0);
|
|
418
|
+
setCurrentSlide(0);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (!slides[currentSlide]) {
|
|
423
|
+
setCurrentSlide(0);
|
|
424
|
+
setAnimationStep(0);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const key = slideKey(slides, currentSlide);
|
|
428
|
+
activeKeyRef.current = key;
|
|
429
|
+
const hash = `#${encodeURIComponent(key)}`;
|
|
430
|
+
if (window.location.hash !== hash) {
|
|
431
|
+
window.history.replaceState(window.history.state, "", hash);
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
window.sessionStorage.setItem(
|
|
435
|
+
POSITION_KEY,
|
|
436
|
+
JSON.stringify({ slide: key, step: animationStep })
|
|
437
|
+
);
|
|
438
|
+
} catch {
|
|
439
|
+
}
|
|
440
|
+
}, [slides, currentSlide, animationStep]);
|
|
441
|
+
useEffect(() => {
|
|
442
|
+
const handleHashChange = () => {
|
|
443
|
+
const index = slideFromHash(slides) ?? 0;
|
|
444
|
+
if (index === currentSlide) return;
|
|
445
|
+
setNavState({ status: "transitioning", direction: index > currentSlide ? 1 : -1 });
|
|
446
|
+
setAnimationStep(0);
|
|
447
|
+
setCurrentSlide(index);
|
|
448
|
+
onSlideChange?.(index);
|
|
449
|
+
};
|
|
450
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
451
|
+
return () => window.removeEventListener("hashchange", handleHashChange);
|
|
452
|
+
}, [slides, currentSlide, onSlideChange]);
|
|
369
453
|
const onTransitionComplete = useCallback(() => {
|
|
370
454
|
setNavState((prev) => {
|
|
371
455
|
if (prev.status === "transitioning") {
|
|
@@ -532,6 +616,7 @@ function SlideRenderer({
|
|
|
532
616
|
return /* @__PURE__ */ jsx6(AnimatePresence, { initial: false, children: /* @__PURE__ */ jsx6(
|
|
533
617
|
motion3.div,
|
|
534
618
|
{
|
|
619
|
+
"data-slide-render-index": currentSlide,
|
|
535
620
|
variants: slideVariants,
|
|
536
621
|
initial: "enter",
|
|
537
622
|
animate: "center",
|
|
@@ -549,20 +634,7 @@ function SlideRenderer({
|
|
|
549
634
|
currentStep: animationStep,
|
|
550
635
|
totalSteps,
|
|
551
636
|
showAllAnimations,
|
|
552
|
-
children: /* @__PURE__ */ jsx6(
|
|
553
|
-
SlideErrorBoundary,
|
|
554
|
-
{
|
|
555
|
-
slideIndex: currentSlide,
|
|
556
|
-
slideTitle: slides[currentSlide]?.title,
|
|
557
|
-
children: /* @__PURE__ */ jsx6(
|
|
558
|
-
CurrentSlideComponent,
|
|
559
|
-
{
|
|
560
|
-
slideNumber: currentSlide + 1,
|
|
561
|
-
totalSlides: slides.length
|
|
562
|
-
}
|
|
563
|
-
)
|
|
564
|
-
}
|
|
565
|
-
)
|
|
637
|
+
children: /* @__PURE__ */ jsx6(SlideErrorBoundary, { slideIndex: currentSlide, slideTitle: slides[currentSlide]?.title, children: /* @__PURE__ */ jsx6(CurrentSlideComponent, { slideNumber: currentSlide + 1, totalSlides: slides.length }) })
|
|
566
638
|
}
|
|
567
639
|
)
|
|
568
640
|
},
|
|
@@ -668,19 +740,30 @@ function SlideEmbed({ slides, transition, directionalTransition }) {
|
|
|
668
740
|
|
|
669
741
|
// src/core/slide-deck.tsx
|
|
670
742
|
import { LayoutGroup } from "framer-motion";
|
|
671
|
-
import {
|
|
672
|
-
|
|
743
|
+
import {
|
|
744
|
+
ChevronLeft,
|
|
745
|
+
ChevronRight,
|
|
746
|
+
Download,
|
|
747
|
+
Grid3X3,
|
|
748
|
+
List,
|
|
749
|
+
MessageCircle,
|
|
750
|
+
Monitor,
|
|
751
|
+
PanelLeftClose,
|
|
752
|
+
PanelLeftOpen,
|
|
753
|
+
Play
|
|
754
|
+
} from "lucide-react";
|
|
755
|
+
import { useCallback as useCallback5, useEffect as useEffect7, useLayoutEffect, useMemo as useMemo4, useRef as useRef6, useState as useState7 } from "react";
|
|
673
756
|
|
|
674
757
|
// src/core/annotations/annotation-overlay.tsx
|
|
675
|
-
import { useCallback as useCallback3, useEffect as useEffect4, useRef as
|
|
758
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
|
|
676
759
|
|
|
677
760
|
// src/core/annotations/annotation-form.tsx
|
|
678
|
-
import { useEffect as useEffect3, useRef, useState as useState3 } from "react";
|
|
761
|
+
import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
|
|
679
762
|
import { ArrowUp, X } from "lucide-react";
|
|
680
763
|
import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
681
764
|
function AnnotationForm({ xPercent, yPercent, onSubmit, onCancel }) {
|
|
682
765
|
const [text, setText] = useState3("");
|
|
683
|
-
const textareaRef =
|
|
766
|
+
const textareaRef = useRef2(null);
|
|
684
767
|
useEffect3(() => {
|
|
685
768
|
textareaRef.current?.focus();
|
|
686
769
|
}, []);
|
|
@@ -829,15 +912,30 @@ function findByTextContent(root, text) {
|
|
|
829
912
|
|
|
830
913
|
// src/core/annotations/annotation-overlay.tsx
|
|
831
914
|
import { Fragment, jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
832
|
-
function AnnotationOverlay({
|
|
915
|
+
function AnnotationOverlay({
|
|
916
|
+
slides,
|
|
917
|
+
currentSlide,
|
|
918
|
+
slideContainerRef,
|
|
919
|
+
selectedId,
|
|
920
|
+
hoveredId,
|
|
921
|
+
onSelectId,
|
|
922
|
+
onShowPanel,
|
|
923
|
+
slideAnnotations,
|
|
924
|
+
addAnnotation
|
|
925
|
+
}) {
|
|
833
926
|
const [pending, setPending] = useState4(null);
|
|
834
927
|
const [hoveredElement, setHoveredElement] = useState4(null);
|
|
835
|
-
const overlayRef =
|
|
928
|
+
const overlayRef = useRef3(null);
|
|
836
929
|
const slideTitle = slides[currentSlide]?.title || `Slide ${currentSlide + 1}`;
|
|
837
930
|
const resolvedAnnotations = slideAnnotations.map((a, i) => {
|
|
838
931
|
const container = slideContainerRef.current;
|
|
839
932
|
if (!container) {
|
|
840
|
-
return {
|
|
933
|
+
return {
|
|
934
|
+
annotation: a,
|
|
935
|
+
xPercent: a.target.position.xPercent,
|
|
936
|
+
yPercent: a.target.position.yPercent,
|
|
937
|
+
number: i + 1
|
|
938
|
+
};
|
|
841
939
|
}
|
|
842
940
|
const { element } = resolveTarget(a.target, container);
|
|
843
941
|
if (element) {
|
|
@@ -847,7 +945,12 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
|
|
|
847
945
|
const yPercent = (rect.top + rect.height / 2 - containerRect.top) / containerRect.height * 100;
|
|
848
946
|
return { annotation: a, xPercent, yPercent, number: i + 1 };
|
|
849
947
|
}
|
|
850
|
-
return {
|
|
948
|
+
return {
|
|
949
|
+
annotation: a,
|
|
950
|
+
xPercent: a.target.position.xPercent,
|
|
951
|
+
yPercent: a.target.position.yPercent,
|
|
952
|
+
number: i + 1
|
|
953
|
+
};
|
|
851
954
|
});
|
|
852
955
|
const handleOverlayClick = useCallback3(
|
|
853
956
|
(e) => {
|
|
@@ -881,11 +984,11 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
|
|
|
881
984
|
const handleSubmit = useCallback3(
|
|
882
985
|
(text) => {
|
|
883
986
|
if (!pending) return;
|
|
884
|
-
addAnnotation(currentSlide, slideTitle, pending.target, text);
|
|
987
|
+
addAnnotation(currentSlide, slideTitle, pending.target, text, slides[currentSlide]?.id);
|
|
885
988
|
setPending(null);
|
|
886
989
|
onShowPanel();
|
|
887
990
|
},
|
|
888
|
-
[pending, currentSlide, slideTitle, addAnnotation, onShowPanel]
|
|
991
|
+
[pending, currentSlide, slideTitle, slides, addAnnotation, onShowPanel]
|
|
889
992
|
);
|
|
890
993
|
const handleMouseMove = useCallback3(
|
|
891
994
|
(e) => {
|
|
@@ -902,12 +1005,14 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
|
|
|
902
1005
|
if (elementUnder && container.contains(elementUnder) && elementUnder !== container) {
|
|
903
1006
|
const containerRect = container.getBoundingClientRect();
|
|
904
1007
|
const elRect = elementUnder.getBoundingClientRect();
|
|
1008
|
+
const scaleX = containerRect.width / container.offsetWidth;
|
|
1009
|
+
const scaleY = containerRect.height / container.offsetHeight;
|
|
905
1010
|
setHoveredElement(
|
|
906
1011
|
new DOMRect(
|
|
907
|
-
elRect.left - containerRect.left,
|
|
908
|
-
elRect.top - containerRect.top,
|
|
909
|
-
elRect.width,
|
|
910
|
-
elRect.height
|
|
1012
|
+
(elRect.left - containerRect.left) / scaleX,
|
|
1013
|
+
(elRect.top - containerRect.top) / scaleY,
|
|
1014
|
+
elRect.width / scaleX,
|
|
1015
|
+
elRect.height / scaleY
|
|
911
1016
|
)
|
|
912
1017
|
);
|
|
913
1018
|
} else {
|
|
@@ -941,7 +1046,9 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
|
|
|
941
1046
|
role: "button",
|
|
942
1047
|
tabIndex: 0,
|
|
943
1048
|
className: "absolute inset-0 z-20",
|
|
944
|
-
style: {
|
|
1049
|
+
style: {
|
|
1050
|
+
cursor: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none'%3E%3Ccircle cx='12' cy='12' r='8' stroke='%23FF6B35' stroke-width='2' opacity='0.8'/%3E%3Ccircle cx='12' cy='12' r='2' fill='%23FF6B35'/%3E%3C/svg%3E") 12 12, crosshair`
|
|
1051
|
+
},
|
|
945
1052
|
onClick: handleOverlayClick,
|
|
946
1053
|
onKeyDown: (e) => {
|
|
947
1054
|
if (e.key === "Escape") {
|
|
@@ -953,7 +1060,7 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
|
|
|
953
1060
|
onMouseLeave: () => setHoveredElement(null)
|
|
954
1061
|
}
|
|
955
1062
|
),
|
|
956
|
-
resolvedAnnotations.map(({ annotation, xPercent, yPercent, number }) => /* @__PURE__ */ jsx10(
|
|
1063
|
+
resolvedAnnotations.filter(({ annotation }) => annotation.status === "open" || annotation.id === hoveredId).map(({ annotation, xPercent, yPercent, number }) => /* @__PURE__ */ jsx10(
|
|
957
1064
|
AnnotationPin,
|
|
958
1065
|
{
|
|
959
1066
|
number,
|
|
@@ -982,61 +1089,96 @@ function AnnotationOverlay({ slides, currentSlide, slideContainerRef, selectedId
|
|
|
982
1089
|
}
|
|
983
1090
|
|
|
984
1091
|
// src/core/annotations/annotation-panel.tsx
|
|
985
|
-
import {
|
|
1092
|
+
import { Check, Pencil, PanelRightClose, RotateCcw, Trash2 } from "lucide-react";
|
|
1093
|
+
import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
|
|
986
1094
|
import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
987
|
-
function AnnotationPanel({
|
|
1095
|
+
function AnnotationPanel({
|
|
1096
|
+
annotations,
|
|
1097
|
+
unlinkedAnnotations = [],
|
|
1098
|
+
selectedId,
|
|
1099
|
+
onSelect,
|
|
1100
|
+
onHover,
|
|
1101
|
+
onDelete,
|
|
1102
|
+
onUpdate,
|
|
1103
|
+
onClose
|
|
1104
|
+
}) {
|
|
988
1105
|
const open = annotations.filter((a) => a.status === "open");
|
|
989
1106
|
const resolved = annotations.filter((a) => a.status === "resolved");
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
/* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-white", children: "Annotations" }),
|
|
995
|
-
open.length > 0 && /* @__PURE__ */ jsx11("span", { className: "flex h-5 min-w-5 items-center justify-center rounded-full bg-[#FF6B35]/15 px-1.5 text-[11px] font-semibold text-[#FF6B35]", children: open.length })
|
|
996
|
-
] }),
|
|
1107
|
+
const total = annotations.length + unlinkedAnnotations.length;
|
|
1108
|
+
const numberFor = (annotation) => annotations.findIndex((a) => a.id === annotation.id) + 1;
|
|
1109
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex h-full w-80 max-w-[90vw] flex-shrink-0 flex-col border-l border-neutral-800 bg-neutral-950 text-white", children: [
|
|
1110
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
|
|
997
1111
|
/* @__PURE__ */ jsx11(
|
|
998
1112
|
"button",
|
|
999
1113
|
{
|
|
1000
1114
|
onClick: onClose,
|
|
1001
|
-
className: "rounded
|
|
1002
|
-
|
|
1115
|
+
className: "rounded p-2 text-neutral-400 hover:bg-neutral-800 hover:text-white",
|
|
1116
|
+
"aria-label": "Hide comments",
|
|
1117
|
+
title: "Hide comments (C)",
|
|
1118
|
+
children: /* @__PURE__ */ jsx11(PanelRightClose, { className: "h-4 w-4" })
|
|
1003
1119
|
}
|
|
1004
|
-
)
|
|
1120
|
+
),
|
|
1121
|
+
/* @__PURE__ */ jsxs5("span", { className: "text-right text-sm font-semibold", children: [
|
|
1122
|
+
"Comments (",
|
|
1123
|
+
total,
|
|
1124
|
+
")"
|
|
1125
|
+
] })
|
|
1005
1126
|
] }),
|
|
1006
|
-
/* @__PURE__ */
|
|
1007
|
-
|
|
1008
|
-
annotations.length === 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-4 px-3 py-6", children: /* @__PURE__ */ jsx11(WorkflowHint, {}) }),
|
|
1127
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-h-0 flex-1 overflow-y-auto p-3", children: [
|
|
1128
|
+
annotations.length === 0 && unlinkedAnnotations.length === 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-4 px-3 py-6", children: /* @__PURE__ */ jsx11(WorkflowHint, {}) }),
|
|
1009
1129
|
open.length > 0 && /* @__PURE__ */ jsxs5("div", { children: [
|
|
1010
1130
|
/* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Open" }),
|
|
1011
|
-
open.map((a
|
|
1131
|
+
open.map((a) => /* @__PURE__ */ jsx11(
|
|
1012
1132
|
AnnotationItem,
|
|
1013
1133
|
{
|
|
1014
1134
|
annotation: a,
|
|
1015
|
-
number:
|
|
1135
|
+
number: numberFor(a),
|
|
1016
1136
|
isSelected: a.id === selectedId,
|
|
1017
1137
|
onSelect: () => onSelect(a.id),
|
|
1018
|
-
|
|
1138
|
+
onHover: (hovered) => onHover(hovered ? a.id : null),
|
|
1139
|
+
onDelete: () => onDelete(a.id),
|
|
1140
|
+
onUpdate: (patch) => onUpdate(a.id, patch)
|
|
1019
1141
|
},
|
|
1020
1142
|
a.id
|
|
1021
1143
|
))
|
|
1022
1144
|
] }),
|
|
1023
1145
|
resolved.length > 0 && /* @__PURE__ */ jsxs5("div", { className: open.length > 0 ? "mt-3" : "", children: [
|
|
1024
1146
|
/* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Resolved" }),
|
|
1025
|
-
resolved.map((a
|
|
1147
|
+
resolved.map((a) => /* @__PURE__ */ jsx11(
|
|
1026
1148
|
AnnotationItem,
|
|
1027
1149
|
{
|
|
1028
1150
|
annotation: a,
|
|
1029
|
-
number:
|
|
1151
|
+
number: numberFor(a),
|
|
1030
1152
|
isSelected: a.id === selectedId,
|
|
1031
1153
|
onSelect: () => onSelect(a.id),
|
|
1032
|
-
|
|
1154
|
+
onHover: (hovered) => onHover(hovered ? a.id : null),
|
|
1155
|
+
onDelete: () => onDelete(a.id),
|
|
1156
|
+
onUpdate: (patch) => onUpdate(a.id, patch)
|
|
1033
1157
|
},
|
|
1034
1158
|
a.id
|
|
1035
1159
|
))
|
|
1160
|
+
] }),
|
|
1161
|
+
unlinkedAnnotations.length > 0 && /* @__PURE__ */ jsxs5("div", { className: "mt-3", children: [
|
|
1162
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Removed slides" }),
|
|
1163
|
+
unlinkedAnnotations.map((a, i) => /* @__PURE__ */ jsxs5("div", { children: [
|
|
1164
|
+
/* @__PURE__ */ jsx11("p", { className: "px-2 text-[11px] text-neutral-500", children: a.slideTitle || a.slideId }),
|
|
1165
|
+
/* @__PURE__ */ jsx11(
|
|
1166
|
+
AnnotationItem,
|
|
1167
|
+
{
|
|
1168
|
+
annotation: a,
|
|
1169
|
+
number: i + 1,
|
|
1170
|
+
isSelected: a.id === selectedId,
|
|
1171
|
+
onSelect: () => onSelect(a.id),
|
|
1172
|
+
onHover: (hovered) => onHover(hovered ? a.id : null),
|
|
1173
|
+
onDelete: () => onDelete(a.id),
|
|
1174
|
+
onUpdate: (patch) => onUpdate(a.id, patch)
|
|
1175
|
+
}
|
|
1176
|
+
)
|
|
1177
|
+
] }, a.id))
|
|
1036
1178
|
] })
|
|
1037
1179
|
] }),
|
|
1038
|
-
annotations.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1039
|
-
/* @__PURE__ */ jsx11("div", { className: "
|
|
1180
|
+
annotations.length + unlinkedAnnotations.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1181
|
+
/* @__PURE__ */ jsx11("div", { className: "border-t border-neutral-800" }),
|
|
1040
1182
|
/* @__PURE__ */ jsx11("div", { className: "px-3 py-3", children: /* @__PURE__ */ jsx11(WorkflowHint, {}) })
|
|
1041
1183
|
] })
|
|
1042
1184
|
] });
|
|
@@ -1046,7 +1188,7 @@ function WorkflowHint() {
|
|
|
1046
1188
|
/* @__PURE__ */ jsx11("p", { className: "text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Workflow" }),
|
|
1047
1189
|
/* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
|
|
1048
1190
|
/* @__PURE__ */ jsx11("div", { className: "mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-[#FF6B35]/15 text-[10px] font-semibold text-[#FF6B35]", children: "1" }),
|
|
1049
|
-
/* @__PURE__ */ jsx11("p", { className: "text-[12px] leading-snug text-neutral-400", children: "
|
|
1191
|
+
/* @__PURE__ */ jsx11("p", { className: "text-[12px] leading-snug text-neutral-400", children: "Comment on everything you want to fix" })
|
|
1050
1192
|
] }),
|
|
1051
1193
|
/* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
|
|
1052
1194
|
/* @__PURE__ */ jsx11("div", { className: "mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-[#FF6B35]/15 text-[10px] font-semibold text-[#FF6B35]", children: "2" }),
|
|
@@ -1054,7 +1196,7 @@ function WorkflowHint() {
|
|
|
1054
1196
|
] }),
|
|
1055
1197
|
/* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
|
|
1056
1198
|
/* @__PURE__ */ jsx11("div", { className: "mt-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-[#FF6B35]/15 text-[10px] font-semibold text-[#FF6B35]", children: "3" }),
|
|
1057
|
-
/* @__PURE__ */ jsx11("p", { className: "text-[12px] leading-snug text-neutral-400", children: '"Fix open
|
|
1199
|
+
/* @__PURE__ */ jsx11("p", { className: "text-[12px] leading-snug text-neutral-400", children: '"Fix open comments"' })
|
|
1058
1200
|
] })
|
|
1059
1201
|
] });
|
|
1060
1202
|
}
|
|
@@ -1063,30 +1205,81 @@ function AnnotationItem({
|
|
|
1063
1205
|
number,
|
|
1064
1206
|
isSelected,
|
|
1065
1207
|
onSelect,
|
|
1066
|
-
|
|
1208
|
+
onHover,
|
|
1209
|
+
onDelete,
|
|
1210
|
+
onUpdate
|
|
1067
1211
|
}) {
|
|
1212
|
+
const [isEditing, setIsEditing] = useState5(false);
|
|
1213
|
+
const [draft, setDraft] = useState5(annotation.body);
|
|
1214
|
+
const editRef = useRef4(null);
|
|
1215
|
+
useEffect5(() => {
|
|
1216
|
+
if (isEditing) editRef.current?.focus();
|
|
1217
|
+
}, [isEditing]);
|
|
1218
|
+
const saveEdit = () => {
|
|
1219
|
+
const body = draft.trim();
|
|
1220
|
+
if (!body) return;
|
|
1221
|
+
if (body !== annotation.body) onUpdate({ body });
|
|
1222
|
+
setIsEditing(false);
|
|
1223
|
+
};
|
|
1068
1224
|
return /* @__PURE__ */ jsxs5(
|
|
1069
1225
|
"div",
|
|
1070
1226
|
{
|
|
1071
1227
|
role: "button",
|
|
1072
1228
|
tabIndex: 0,
|
|
1073
1229
|
onClick: onSelect,
|
|
1230
|
+
onMouseEnter: () => onHover(true),
|
|
1231
|
+
onMouseLeave: () => onHover(false),
|
|
1074
1232
|
onKeyDown: (e) => {
|
|
1075
|
-
if (e.key === "Enter" || e.key === " ") onSelect();
|
|
1233
|
+
if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) onSelect();
|
|
1076
1234
|
},
|
|
1077
1235
|
className: `group relative mb-0.5 cursor-pointer rounded-xl p-2.5 transition-all duration-150 ${isSelected ? "bg-[#FF6B35]/10 ring-1 ring-[#FF6B35]/20" : "hover:bg-white/[0.04]"}`,
|
|
1078
1236
|
children: [
|
|
1079
|
-
/* @__PURE__ */
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1237
|
+
/* @__PURE__ */ jsxs5("div", { className: "absolute top-2 right-2 z-10 flex gap-0.5 rounded-full border border-neutral-700 bg-neutral-900/70 p-0.5 opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100 group-focus-within:opacity-100", children: [
|
|
1238
|
+
/* @__PURE__ */ jsx11(
|
|
1239
|
+
"button",
|
|
1240
|
+
{
|
|
1241
|
+
type: "button",
|
|
1242
|
+
title: "Edit comment",
|
|
1243
|
+
"aria-label": "Edit comment",
|
|
1244
|
+
onClick: (e) => {
|
|
1245
|
+
e.stopPropagation();
|
|
1246
|
+
setDraft(annotation.body);
|
|
1247
|
+
setIsEditing(true);
|
|
1248
|
+
},
|
|
1249
|
+
className: "rounded-full p-1 text-neutral-400 hover:bg-white/[0.08] hover:text-white",
|
|
1250
|
+
children: /* @__PURE__ */ jsx11(Pencil, { className: "h-3.5 w-3.5" })
|
|
1251
|
+
}
|
|
1252
|
+
),
|
|
1253
|
+
/* @__PURE__ */ jsx11(
|
|
1254
|
+
"button",
|
|
1255
|
+
{
|
|
1256
|
+
type: "button",
|
|
1257
|
+
title: annotation.status === "open" ? "Resolve comment" : "Reopen comment",
|
|
1258
|
+
"aria-label": annotation.status === "open" ? "Resolve comment" : "Reopen comment",
|
|
1259
|
+
onClick: (e) => {
|
|
1260
|
+
e.stopPropagation();
|
|
1261
|
+
onHover(false);
|
|
1262
|
+
onUpdate({ status: annotation.status === "open" ? "resolved" : "open" });
|
|
1263
|
+
},
|
|
1264
|
+
className: "rounded-full p-1 text-neutral-400 hover:bg-white/[0.08] hover:text-white",
|
|
1265
|
+
children: annotation.status === "open" ? /* @__PURE__ */ jsx11(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx11(RotateCcw, { className: "h-3.5 w-3.5" })
|
|
1266
|
+
}
|
|
1267
|
+
),
|
|
1268
|
+
/* @__PURE__ */ jsx11(
|
|
1269
|
+
"button",
|
|
1270
|
+
{
|
|
1271
|
+
type: "button",
|
|
1272
|
+
title: "Delete comment",
|
|
1273
|
+
"aria-label": "Delete comment",
|
|
1274
|
+
onClick: (e) => {
|
|
1275
|
+
e.stopPropagation();
|
|
1276
|
+
onDelete();
|
|
1277
|
+
},
|
|
1278
|
+
className: "rounded-full p-1 text-neutral-400 hover:bg-white/[0.08] hover:text-white",
|
|
1279
|
+
children: /* @__PURE__ */ jsx11(Trash2, { className: "h-3.5 w-3.5" })
|
|
1280
|
+
}
|
|
1281
|
+
)
|
|
1282
|
+
] }),
|
|
1090
1283
|
/* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2.5", children: [
|
|
1091
1284
|
/* @__PURE__ */ jsx11(
|
|
1092
1285
|
"div",
|
|
@@ -1095,8 +1288,52 @@ function AnnotationItem({
|
|
|
1095
1288
|
children: number
|
|
1096
1289
|
}
|
|
1097
1290
|
),
|
|
1098
|
-
/* @__PURE__ */ jsxs5("div", { className: "min-w-0 pr-4", children: [
|
|
1099
|
-
/* @__PURE__ */
|
|
1291
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1 pr-4", children: [
|
|
1292
|
+
isEditing ? /* @__PURE__ */ jsxs5("div", { children: [
|
|
1293
|
+
/* @__PURE__ */ jsx11(
|
|
1294
|
+
"textarea",
|
|
1295
|
+
{
|
|
1296
|
+
ref: editRef,
|
|
1297
|
+
value: draft,
|
|
1298
|
+
onClick: (e) => e.stopPropagation(),
|
|
1299
|
+
onChange: (e) => setDraft(e.target.value),
|
|
1300
|
+
onKeyDown: (e) => {
|
|
1301
|
+
if (e.key === "Escape") setIsEditing(false);
|
|
1302
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") saveEdit();
|
|
1303
|
+
},
|
|
1304
|
+
className: "w-full resize-y rounded border border-neutral-700 bg-neutral-900 p-2 text-[13px] text-white outline-none focus:border-[#FF6B35]",
|
|
1305
|
+
rows: 3,
|
|
1306
|
+
maxLength: 4096
|
|
1307
|
+
}
|
|
1308
|
+
),
|
|
1309
|
+
/* @__PURE__ */ jsxs5("div", { className: "mt-1 flex gap-2 text-xs", children: [
|
|
1310
|
+
/* @__PURE__ */ jsx11(
|
|
1311
|
+
"button",
|
|
1312
|
+
{
|
|
1313
|
+
type: "button",
|
|
1314
|
+
onClick: (e) => {
|
|
1315
|
+
e.stopPropagation();
|
|
1316
|
+
saveEdit();
|
|
1317
|
+
},
|
|
1318
|
+
disabled: !draft.trim(),
|
|
1319
|
+
className: "text-[#FF6B35] disabled:opacity-40",
|
|
1320
|
+
children: "Save"
|
|
1321
|
+
}
|
|
1322
|
+
),
|
|
1323
|
+
/* @__PURE__ */ jsx11(
|
|
1324
|
+
"button",
|
|
1325
|
+
{
|
|
1326
|
+
type: "button",
|
|
1327
|
+
onClick: (e) => {
|
|
1328
|
+
e.stopPropagation();
|
|
1329
|
+
setIsEditing(false);
|
|
1330
|
+
},
|
|
1331
|
+
className: "text-neutral-400",
|
|
1332
|
+
children: "Cancel"
|
|
1333
|
+
}
|
|
1334
|
+
)
|
|
1335
|
+
] })
|
|
1336
|
+
] }) : /* @__PURE__ */ jsx11("p", { className: "text-[13px] leading-relaxed text-neutral-200", children: annotation.body }),
|
|
1100
1337
|
annotation.target.contentNearPin && /* @__PURE__ */ jsx11("p", { className: "mt-1 truncate text-[11px] text-neutral-600", children: annotation.target.contentNearPin }),
|
|
1101
1338
|
annotation.resolution && /* @__PURE__ */ jsx11("p", { className: "mt-1.5 text-[11px] text-emerald-400/80 italic", children: annotation.resolution })
|
|
1102
1339
|
] })
|
|
@@ -1107,7 +1344,7 @@ function AnnotationItem({
|
|
|
1107
1344
|
}
|
|
1108
1345
|
|
|
1109
1346
|
// src/core/annotations/use-annotations.ts
|
|
1110
|
-
import { useCallback as useCallback4, useEffect as
|
|
1347
|
+
import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef5, useState as useState6 } from "react";
|
|
1111
1348
|
|
|
1112
1349
|
// src/core/annotations/adapters/http.ts
|
|
1113
1350
|
var ENDPOINT = "/__promptslide_annotations";
|
|
@@ -1143,23 +1380,47 @@ function createHttpAdapter() {
|
|
|
1143
1380
|
async remove(id) {
|
|
1144
1381
|
cache = cache.filter((a) => a.id !== id);
|
|
1145
1382
|
await saveAll(cache);
|
|
1383
|
+
},
|
|
1384
|
+
async update(id, patch) {
|
|
1385
|
+
cache = cache.map((a) => a.id === id ? { ...a, ...patch } : a);
|
|
1386
|
+
await saveAll(cache);
|
|
1387
|
+
},
|
|
1388
|
+
async replaceAll(annotations) {
|
|
1389
|
+
cache = annotations;
|
|
1390
|
+
await saveAll(cache);
|
|
1146
1391
|
}
|
|
1147
1392
|
};
|
|
1148
1393
|
}
|
|
1149
1394
|
|
|
1150
1395
|
// src/core/annotations/use-annotations.ts
|
|
1151
|
-
function useAnnotations(adapter) {
|
|
1152
|
-
const
|
|
1153
|
-
const
|
|
1154
|
-
|
|
1155
|
-
|
|
1396
|
+
function useAnnotations(slidesOrAdapter, adapter) {
|
|
1397
|
+
const slides = Array.isArray(slidesOrAdapter) ? slidesOrAdapter : void 0;
|
|
1398
|
+
const storage = Array.isArray(slidesOrAdapter) ? adapter : slidesOrAdapter ?? adapter;
|
|
1399
|
+
const adapterRef = useRef5(storage ?? createHttpAdapter());
|
|
1400
|
+
const slidesRef = useRef5(slides);
|
|
1401
|
+
slidesRef.current = slides;
|
|
1402
|
+
const [annotations, setAnnotations] = useState6([]);
|
|
1403
|
+
useEffect6(() => {
|
|
1404
|
+
adapterRef.current.load().then((loaded) => {
|
|
1405
|
+
const configuredSlides = slidesRef.current;
|
|
1406
|
+
const migrated = loaded.map((annotation) => {
|
|
1407
|
+
if (annotation.slideId || !configuredSlides) return annotation;
|
|
1408
|
+
const id = configuredSlides[annotation.slideIndex]?.id;
|
|
1409
|
+
return id ? { ...annotation, slideId: id } : annotation;
|
|
1410
|
+
});
|
|
1411
|
+
setAnnotations(migrated);
|
|
1412
|
+
if (migrated.some((annotation, index) => annotation !== loaded[index])) {
|
|
1413
|
+
void adapterRef.current.replaceAll?.(migrated);
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1156
1416
|
return adapterRef.current.subscribe?.(setAnnotations);
|
|
1157
1417
|
}, []);
|
|
1158
1418
|
const addAnnotation = useCallback4(
|
|
1159
|
-
(slideIndex, slideTitle, target, body) => {
|
|
1419
|
+
(slideIndex, slideTitle, target, body, slideId) => {
|
|
1160
1420
|
const annotation = {
|
|
1161
1421
|
id: crypto.randomUUID(),
|
|
1162
1422
|
slideIndex,
|
|
1423
|
+
...slideId && { slideId },
|
|
1163
1424
|
slideTitle,
|
|
1164
1425
|
target,
|
|
1165
1426
|
body,
|
|
@@ -1175,15 +1436,36 @@ function useAnnotations(adapter) {
|
|
|
1175
1436
|
setAnnotations((prev) => prev.filter((a) => a.id !== id));
|
|
1176
1437
|
adapterRef.current.remove(id);
|
|
1177
1438
|
}, []);
|
|
1439
|
+
const updateAnnotation = useCallback4(
|
|
1440
|
+
(id, patch) => {
|
|
1441
|
+
const current = annotations.find((a) => a.id === id);
|
|
1442
|
+
if (!current) return;
|
|
1443
|
+
const updated = { body: patch.body ?? current.body, status: patch.status ?? current.status };
|
|
1444
|
+
setAnnotations((prev) => prev.map((a) => a.id === id ? { ...a, ...updated } : a));
|
|
1445
|
+
void adapterRef.current.update?.(id, updated);
|
|
1446
|
+
},
|
|
1447
|
+
[annotations]
|
|
1448
|
+
);
|
|
1178
1449
|
const getSlideAnnotations = useCallback4(
|
|
1179
1450
|
(slideIndex) => annotations.filter((a) => a.slideIndex === slideIndex),
|
|
1180
1451
|
[annotations]
|
|
1181
1452
|
);
|
|
1182
|
-
const openCount = useMemo3(
|
|
1453
|
+
const openCount = useMemo3(
|
|
1454
|
+
() => annotations.filter((a) => a.status === "open").length,
|
|
1455
|
+
[annotations]
|
|
1456
|
+
);
|
|
1183
1457
|
const updateAnnotations = useCallback4((updated) => {
|
|
1184
1458
|
setAnnotations(updated);
|
|
1185
1459
|
}, []);
|
|
1186
|
-
return {
|
|
1460
|
+
return {
|
|
1461
|
+
annotations,
|
|
1462
|
+
addAnnotation,
|
|
1463
|
+
deleteAnnotation,
|
|
1464
|
+
updateAnnotation,
|
|
1465
|
+
getSlideAnnotations,
|
|
1466
|
+
openCount,
|
|
1467
|
+
updateAnnotations
|
|
1468
|
+
};
|
|
1187
1469
|
}
|
|
1188
1470
|
|
|
1189
1471
|
// src/core/utils.ts
|
|
@@ -1194,14 +1476,96 @@ function cn(...inputs) {
|
|
|
1194
1476
|
}
|
|
1195
1477
|
|
|
1196
1478
|
// src/core/slide-deck.tsx
|
|
1197
|
-
import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1479
|
+
import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1480
|
+
var NARROW_VIEWPORT_WIDTH = 768;
|
|
1481
|
+
var DEFAULT_THUMBNAIL_WIDTH = 240;
|
|
1482
|
+
var MIN_THUMBNAIL_WIDTH = 176;
|
|
1483
|
+
var MAX_THUMBNAIL_WIDTH = 480;
|
|
1484
|
+
var THUMBNAIL_WIDTH_STORAGE_KEY = "promptslide:thumbnail-width";
|
|
1485
|
+
function GridThumbnail({
|
|
1486
|
+
slide,
|
|
1487
|
+
index,
|
|
1488
|
+
total
|
|
1489
|
+
}) {
|
|
1490
|
+
const viewportRef = useRef6(null);
|
|
1491
|
+
const canvasRef = useRef6(null);
|
|
1492
|
+
const SlideComponent = slide.component;
|
|
1493
|
+
useLayoutEffect(() => {
|
|
1494
|
+
const viewport = viewportRef.current;
|
|
1495
|
+
const canvas = canvasRef.current;
|
|
1496
|
+
if (!viewport || !canvas) return;
|
|
1497
|
+
const resize = () => {
|
|
1498
|
+
canvas.style.transform = `scale(${viewport.clientWidth / SLIDE_DIMENSIONS.width})`;
|
|
1499
|
+
};
|
|
1500
|
+
resize();
|
|
1501
|
+
const observer = new ResizeObserver(resize);
|
|
1502
|
+
observer.observe(viewport);
|
|
1503
|
+
return () => observer.disconnect();
|
|
1504
|
+
}, []);
|
|
1505
|
+
return /* @__PURE__ */ jsx12("div", { ref: viewportRef, className: "absolute inset-0 overflow-hidden", children: /* @__PURE__ */ jsx12(
|
|
1506
|
+
"div",
|
|
1507
|
+
{
|
|
1508
|
+
ref: canvasRef,
|
|
1509
|
+
className: "absolute top-0 left-0 origin-top-left",
|
|
1510
|
+
style: { width: SLIDE_DIMENSIONS.width, height: SLIDE_DIMENSIONS.height },
|
|
1511
|
+
children: /* @__PURE__ */ jsx12(AnimationProvider, { currentStep: slide.steps, totalSteps: slide.steps, showAllAnimations: true, children: /* @__PURE__ */ jsx12(SlideErrorBoundary, { slideIndex: index, slideTitle: slide.title, children: /* @__PURE__ */ jsx12(SlideComponent, { slideNumber: index + 1, totalSlides: total }) }) })
|
|
1512
|
+
}
|
|
1513
|
+
) });
|
|
1514
|
+
}
|
|
1515
|
+
function ListSlide({ slide, index, total }) {
|
|
1516
|
+
const viewportRef = useRef6(null);
|
|
1517
|
+
const canvasRef = useRef6(null);
|
|
1518
|
+
const SlideComponent = slide.component;
|
|
1519
|
+
useLayoutEffect(() => {
|
|
1520
|
+
const viewport = viewportRef.current;
|
|
1521
|
+
const canvas = canvasRef.current;
|
|
1522
|
+
if (!viewport || !canvas) return;
|
|
1523
|
+
const resize = () => {
|
|
1524
|
+
canvas.style.transform = `scale(${viewport.clientWidth / SLIDE_DIMENSIONS.width})`;
|
|
1525
|
+
};
|
|
1526
|
+
resize();
|
|
1527
|
+
const observer = new ResizeObserver(resize);
|
|
1528
|
+
observer.observe(viewport);
|
|
1529
|
+
return () => observer.disconnect();
|
|
1530
|
+
}, []);
|
|
1531
|
+
return /* @__PURE__ */ jsx12(
|
|
1532
|
+
"div",
|
|
1533
|
+
{
|
|
1534
|
+
ref: viewportRef,
|
|
1535
|
+
className: "relative aspect-video w-full overflow-hidden rounded-xl border border-neutral-800 bg-black shadow-sm print:m-0 print:h-[1080px] print:w-[1920px] print:break-after-page print:rounded-none print:border-0 print:shadow-none",
|
|
1536
|
+
children: /* @__PURE__ */ jsx12(
|
|
1537
|
+
"div",
|
|
1538
|
+
{
|
|
1539
|
+
ref: canvasRef,
|
|
1540
|
+
className: "list-slide-canvas absolute top-0 left-0 origin-top-left",
|
|
1541
|
+
style: { width: SLIDE_DIMENSIONS.width, height: SLIDE_DIMENSIONS.height },
|
|
1542
|
+
children: /* @__PURE__ */ jsx12(AnimationProvider, { currentStep: slide.steps, totalSteps: slide.steps, showAllAnimations: true, children: /* @__PURE__ */ jsx12(SlideErrorBoundary, { slideIndex: index, slideTitle: slide.title, children: /* @__PURE__ */ jsx12(SlideComponent, { slideNumber: index + 1, totalSlides: total }) }) })
|
|
1543
|
+
}
|
|
1544
|
+
)
|
|
1545
|
+
}
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1198
1548
|
function SlideExportView({ slides, slideIndex }) {
|
|
1199
|
-
const [ready, setReady] =
|
|
1549
|
+
const [ready, setReady] = useState7(false);
|
|
1200
1550
|
const clampedIndex = Math.max(0, Math.min(slideIndex, slides.length - 1));
|
|
1201
1551
|
const slideConfig = slides[clampedIndex];
|
|
1202
1552
|
const SlideComponent = slideConfig.component;
|
|
1203
|
-
|
|
1204
|
-
|
|
1553
|
+
useEffect7(() => {
|
|
1554
|
+
let cancelled = false;
|
|
1555
|
+
const markReady = async () => {
|
|
1556
|
+
await document.fonts.ready;
|
|
1557
|
+
await Promise.all(Array.from(document.images, (image) => image.decode().catch(() => {
|
|
1558
|
+
})));
|
|
1559
|
+
requestAnimationFrame(
|
|
1560
|
+
() => requestAnimationFrame(() => {
|
|
1561
|
+
if (!cancelled) setReady(true);
|
|
1562
|
+
})
|
|
1563
|
+
);
|
|
1564
|
+
};
|
|
1565
|
+
void markReady();
|
|
1566
|
+
return () => {
|
|
1567
|
+
cancelled = true;
|
|
1568
|
+
};
|
|
1205
1569
|
}, []);
|
|
1206
1570
|
return /* @__PURE__ */ jsx12(
|
|
1207
1571
|
"div",
|
|
@@ -1226,8 +1590,16 @@ function SlideExportView({ slides, slideIndex }) {
|
|
|
1226
1590
|
}
|
|
1227
1591
|
);
|
|
1228
1592
|
}
|
|
1229
|
-
function SlideDeck({
|
|
1230
|
-
|
|
1593
|
+
function SlideDeck({
|
|
1594
|
+
slides,
|
|
1595
|
+
transition,
|
|
1596
|
+
directionalTransition,
|
|
1597
|
+
annotations,
|
|
1598
|
+
onAnnotationAdd,
|
|
1599
|
+
onAnnotationDelete,
|
|
1600
|
+
onAnnotationUpdate
|
|
1601
|
+
}) {
|
|
1602
|
+
const [exportParams] = useState7(() => {
|
|
1231
1603
|
if (typeof window === "undefined") return null;
|
|
1232
1604
|
const params = new URLSearchParams(window.location.search);
|
|
1233
1605
|
if (params.get("export") !== "true") return null;
|
|
@@ -1236,24 +1608,178 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1236
1608
|
if (exportParams) {
|
|
1237
1609
|
return /* @__PURE__ */ jsx12(SlideExportView, { slides, slideIndex: exportParams.slideIndex });
|
|
1238
1610
|
}
|
|
1239
|
-
const internal = useAnnotations();
|
|
1611
|
+
const internal = useAnnotations(slides);
|
|
1240
1612
|
const isExternallyManaged = annotations !== void 0;
|
|
1241
1613
|
const effectiveAnnotations = isExternallyManaged ? annotations : internal.annotations;
|
|
1242
1614
|
const effectiveAdd = isExternallyManaged ? onAnnotationAdd : internal.addAnnotation;
|
|
1243
1615
|
const effectiveDelete = isExternallyManaged ? onAnnotationDelete : internal.deleteAnnotation;
|
|
1244
|
-
const
|
|
1245
|
-
const
|
|
1246
|
-
(
|
|
1616
|
+
const effectiveUpdate = isExternallyManaged ? onAnnotationUpdate : internal.updateAnnotation;
|
|
1617
|
+
const openCount = useMemo4(
|
|
1618
|
+
() => effectiveAnnotations.filter((a) => a.status === "open").length,
|
|
1247
1619
|
[effectiveAnnotations]
|
|
1248
1620
|
);
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
const
|
|
1256
|
-
|
|
1621
|
+
const getSlideAnnotations = useCallback5(
|
|
1622
|
+
(slideIndex) => effectiveAnnotations.filter(
|
|
1623
|
+
(a) => a.slideId ? a.slideId === slides[slideIndex]?.id : a.slideIndex === slideIndex
|
|
1624
|
+
),
|
|
1625
|
+
[effectiveAnnotations, slides]
|
|
1626
|
+
);
|
|
1627
|
+
const unlinkedAnnotations = useMemo4(
|
|
1628
|
+
() => effectiveAnnotations.filter((a) => a.slideId && !slides.some((slide) => slide.id === a.slideId)),
|
|
1629
|
+
[effectiveAnnotations, slides]
|
|
1630
|
+
);
|
|
1631
|
+
const [viewMode, setViewMode] = useState7("slide");
|
|
1632
|
+
const [isEditorOpen, setIsEditorOpen] = useState7(false);
|
|
1633
|
+
const [editorRevision, setEditorRevision] = useState7(0);
|
|
1634
|
+
useEffect7(() => {
|
|
1635
|
+
const onEditorState = (event) => {
|
|
1636
|
+
const open = Boolean(event.detail?.open);
|
|
1637
|
+
setIsEditorOpen(open);
|
|
1638
|
+
if (open) {
|
|
1639
|
+
setViewMode("slide");
|
|
1640
|
+
setIsAnnotationMode(false);
|
|
1641
|
+
setShowAnnotationPanel(false);
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
const refreshEditor = () => setEditorRevision((value) => value + 1);
|
|
1645
|
+
window.addEventListener("promptslide:editor-state", onEditorState);
|
|
1646
|
+
window.addEventListener("promptslide:editor-refresh", refreshEditor);
|
|
1647
|
+
return () => {
|
|
1648
|
+
window.removeEventListener("promptslide:editor-state", onEditorState);
|
|
1649
|
+
window.removeEventListener("promptslide:editor-refresh", refreshEditor);
|
|
1650
|
+
};
|
|
1651
|
+
}, []);
|
|
1652
|
+
const [isPresentationMode, setIsPresentationMode] = useState7(false);
|
|
1653
|
+
const [viewportWidth, setViewportWidth] = useState7(
|
|
1654
|
+
() => typeof window === "undefined" ? 1280 : window.innerWidth
|
|
1655
|
+
);
|
|
1656
|
+
const [showThumbnailRail, setShowThumbnailRail] = useState7(
|
|
1657
|
+
() => typeof window === "undefined" ? true : window.innerWidth >= NARROW_VIEWPORT_WIDTH
|
|
1658
|
+
);
|
|
1659
|
+
const [thumbnailWidth, setThumbnailWidth] = useState7(() => {
|
|
1660
|
+
if (typeof window === "undefined") return DEFAULT_THUMBNAIL_WIDTH;
|
|
1661
|
+
const savedWidth = Number(window.localStorage.getItem(THUMBNAIL_WIDTH_STORAGE_KEY));
|
|
1662
|
+
return Number.isFinite(savedWidth) && savedWidth >= MIN_THUMBNAIL_WIDTH ? Math.min(savedWidth, MAX_THUMBNAIL_WIDTH) : DEFAULT_THUMBNAIL_WIDTH;
|
|
1663
|
+
});
|
|
1664
|
+
const [isAnnotationMode, setIsAnnotationMode] = useState7(false);
|
|
1665
|
+
const [showAnnotationPanel, setShowAnnotationPanel] = useState7(false);
|
|
1666
|
+
const [selectedAnnotationId, setSelectedAnnotationId] = useState7(null);
|
|
1667
|
+
const [hoveredAnnotationId, setHoveredAnnotationId] = useState7(null);
|
|
1668
|
+
const [isResizingThumbnails, setIsResizingThumbnails] = useState7(false);
|
|
1669
|
+
const [isDragCollapsed, setIsDragCollapsed] = useState7(false);
|
|
1670
|
+
const [isAnimatingDragBoundary, setIsAnimatingDragBoundary] = useState7(false);
|
|
1671
|
+
const [scale, setScale] = useState7(1);
|
|
1672
|
+
const containerRef = useRef6(null);
|
|
1673
|
+
const slideContainerRef = useRef6(null);
|
|
1674
|
+
const previewViewportRef = useRef6(null);
|
|
1675
|
+
const activeThumbnailRef = useRef6(null);
|
|
1676
|
+
const preferredRailOpenRef = useRef6(true);
|
|
1677
|
+
const wasNarrowViewportRef = useRef6(viewportWidth < NARROW_VIEWPORT_WIDTH);
|
|
1678
|
+
const resizeStartRef = useRef6(null);
|
|
1679
|
+
const previousUserSelectRef = useRef6("");
|
|
1680
|
+
const previousCursorRef = useRef6("");
|
|
1681
|
+
const dragCollapsedRef = useRef6(false);
|
|
1682
|
+
const dragAnimationTimerRef = useRef6(null);
|
|
1683
|
+
const isNarrowViewport = viewportWidth < NARROW_VIEWPORT_WIDTH;
|
|
1684
|
+
const maxThumbnailWidth = isNarrowViewport ? Math.max(120, Math.floor(viewportWidth * 0.8)) : Math.min(MAX_THUMBNAIL_WIDTH, viewportWidth - 360);
|
|
1685
|
+
const minThumbnailWidth = Math.min(MIN_THUMBNAIL_WIDTH, maxThumbnailWidth);
|
|
1686
|
+
const visibleThumbnailWidth = Math.min(thumbnailWidth, maxThumbnailWidth);
|
|
1687
|
+
const isThumbnailRailVisible = showThumbnailRail && !isDragCollapsed;
|
|
1688
|
+
const setThumbnailRailOpen = useCallback5((open) => {
|
|
1689
|
+
preferredRailOpenRef.current = open;
|
|
1690
|
+
setShowThumbnailRail(open);
|
|
1691
|
+
}, []);
|
|
1692
|
+
useEffect7(() => {
|
|
1693
|
+
const handleResize = () => setViewportWidth(window.innerWidth);
|
|
1694
|
+
window.addEventListener("resize", handleResize);
|
|
1695
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
1696
|
+
}, []);
|
|
1697
|
+
useEffect7(() => {
|
|
1698
|
+
if (isNarrowViewport !== wasNarrowViewportRef.current) {
|
|
1699
|
+
setShowThumbnailRail(isNarrowViewport ? false : preferredRailOpenRef.current);
|
|
1700
|
+
if (isNarrowViewport) {
|
|
1701
|
+
setIsAnnotationMode(false);
|
|
1702
|
+
setShowAnnotationPanel(false);
|
|
1703
|
+
setSelectedAnnotationId(null);
|
|
1704
|
+
}
|
|
1705
|
+
wasNarrowViewportRef.current = isNarrowViewport;
|
|
1706
|
+
}
|
|
1707
|
+
}, [isNarrowViewport]);
|
|
1708
|
+
useEffect7(() => {
|
|
1709
|
+
window.localStorage.setItem(THUMBNAIL_WIDTH_STORAGE_KEY, String(thumbnailWidth));
|
|
1710
|
+
}, [thumbnailWidth]);
|
|
1711
|
+
useEffect7(
|
|
1712
|
+
() => () => {
|
|
1713
|
+
if (resizeStartRef.current) {
|
|
1714
|
+
document.body.style.userSelect = previousUserSelectRef.current;
|
|
1715
|
+
document.body.style.cursor = previousCursorRef.current;
|
|
1716
|
+
}
|
|
1717
|
+
if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
|
|
1718
|
+
},
|
|
1719
|
+
[]
|
|
1720
|
+
);
|
|
1721
|
+
useEffect7(() => {
|
|
1722
|
+
if ((!showThumbnailRail || isPresentationMode || viewMode !== "slide") && resizeStartRef.current) {
|
|
1723
|
+
resizeStartRef.current = null;
|
|
1724
|
+
dragCollapsedRef.current = false;
|
|
1725
|
+
setIsResizingThumbnails(false);
|
|
1726
|
+
setIsDragCollapsed(false);
|
|
1727
|
+
setIsAnimatingDragBoundary(false);
|
|
1728
|
+
if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
|
|
1729
|
+
document.body.style.userSelect = previousUserSelectRef.current;
|
|
1730
|
+
document.body.style.cursor = previousCursorRef.current;
|
|
1731
|
+
}
|
|
1732
|
+
}, [showThumbnailRail, isPresentationMode, viewMode]);
|
|
1733
|
+
useEffect7(() => {
|
|
1734
|
+
if (!isResizingThumbnails) return;
|
|
1735
|
+
const getDraggedWidth = (clientX) => {
|
|
1736
|
+
const start = resizeStartRef.current;
|
|
1737
|
+
return start ? start.width + clientX - start.pointerX : null;
|
|
1738
|
+
};
|
|
1739
|
+
const handlePointerMove = (event) => {
|
|
1740
|
+
if (event.pointerId !== resizeStartRef.current?.pointerId) return;
|
|
1741
|
+
const nextWidth = getDraggedWidth(event.clientX);
|
|
1742
|
+
if (nextWidth === null) return;
|
|
1743
|
+
const collapsed = nextWidth < minThumbnailWidth;
|
|
1744
|
+
if (collapsed !== dragCollapsedRef.current) {
|
|
1745
|
+
dragCollapsedRef.current = collapsed;
|
|
1746
|
+
setIsDragCollapsed(collapsed);
|
|
1747
|
+
setIsAnimatingDragBoundary(true);
|
|
1748
|
+
if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
|
|
1749
|
+
dragAnimationTimerRef.current = setTimeout(() => {
|
|
1750
|
+
setIsAnimatingDragBoundary(false);
|
|
1751
|
+
dragAnimationTimerRef.current = null;
|
|
1752
|
+
}, 200);
|
|
1753
|
+
}
|
|
1754
|
+
if (!collapsed) setThumbnailWidth(Math.min(maxThumbnailWidth, nextWidth));
|
|
1755
|
+
};
|
|
1756
|
+
const stopResize = (event) => {
|
|
1757
|
+
if (event && event.pointerId !== resizeStartRef.current?.pointerId) return;
|
|
1758
|
+
const nextWidth = event ? getDraggedWidth(event.clientX) : null;
|
|
1759
|
+
const collapsed = event?.type === "pointerup" && nextWidth !== null && nextWidth < minThumbnailWidth;
|
|
1760
|
+
resizeStartRef.current = null;
|
|
1761
|
+
dragCollapsedRef.current = false;
|
|
1762
|
+
document.body.style.userSelect = previousUserSelectRef.current;
|
|
1763
|
+
document.body.style.cursor = previousCursorRef.current;
|
|
1764
|
+
setIsResizingThumbnails(false);
|
|
1765
|
+
setIsDragCollapsed(false);
|
|
1766
|
+
if (collapsed) setThumbnailRailOpen(false);
|
|
1767
|
+
else if (nextWidth !== null) {
|
|
1768
|
+
setThumbnailWidth(Math.max(minThumbnailWidth, Math.min(maxThumbnailWidth, nextWidth)));
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
const handleBlur = () => stopResize();
|
|
1772
|
+
window.addEventListener("pointermove", handlePointerMove);
|
|
1773
|
+
window.addEventListener("pointerup", stopResize);
|
|
1774
|
+
window.addEventListener("pointercancel", stopResize);
|
|
1775
|
+
window.addEventListener("blur", handleBlur);
|
|
1776
|
+
return () => {
|
|
1777
|
+
window.removeEventListener("pointermove", handlePointerMove);
|
|
1778
|
+
window.removeEventListener("pointerup", stopResize);
|
|
1779
|
+
window.removeEventListener("pointercancel", stopResize);
|
|
1780
|
+
window.removeEventListener("blur", handleBlur);
|
|
1781
|
+
};
|
|
1782
|
+
}, [isResizingThumbnails, minThumbnailWidth, maxThumbnailWidth, setThumbnailRailOpen]);
|
|
1257
1783
|
const {
|
|
1258
1784
|
currentSlide,
|
|
1259
1785
|
animationStep,
|
|
@@ -1274,22 +1800,27 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1274
1800
|
await document.exitFullscreen();
|
|
1275
1801
|
}
|
|
1276
1802
|
}, []);
|
|
1277
|
-
|
|
1803
|
+
useEffect7(() => {
|
|
1278
1804
|
const handleFullscreenChange = () => {
|
|
1279
1805
|
setIsPresentationMode(!!document.fullscreenElement);
|
|
1280
1806
|
};
|
|
1281
1807
|
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|
1282
1808
|
return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
|
1283
1809
|
}, []);
|
|
1284
|
-
|
|
1810
|
+
useEffect7(() => {
|
|
1811
|
+
if (viewMode === "slide" && !isPresentationMode && showThumbnailRail) {
|
|
1812
|
+
activeThumbnailRef.current?.scrollIntoView({ block: "nearest" });
|
|
1813
|
+
}
|
|
1814
|
+
}, [currentSlide, isPresentationMode, showThumbnailRail, viewMode]);
|
|
1815
|
+
useEffect7(() => {
|
|
1285
1816
|
const calculateScale = () => {
|
|
1286
1817
|
if (!isPresentationMode) {
|
|
1287
1818
|
setScale(1);
|
|
1288
1819
|
return;
|
|
1289
1820
|
}
|
|
1290
|
-
const
|
|
1821
|
+
const availableWidth = window.innerWidth;
|
|
1291
1822
|
const viewportHeight = window.innerHeight;
|
|
1292
|
-
const scaleX =
|
|
1823
|
+
const scaleX = availableWidth / SLIDE_DIMENSIONS.width;
|
|
1293
1824
|
const scaleY = viewportHeight / SLIDE_DIMENSIONS.height;
|
|
1294
1825
|
setScale(Math.min(scaleX, scaleY));
|
|
1295
1826
|
};
|
|
@@ -1297,7 +1828,46 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1297
1828
|
window.addEventListener("resize", calculateScale);
|
|
1298
1829
|
return () => window.removeEventListener("resize", calculateScale);
|
|
1299
1830
|
}, [isPresentationMode]);
|
|
1300
|
-
|
|
1831
|
+
useLayoutEffect(() => {
|
|
1832
|
+
const viewport = previewViewportRef.current;
|
|
1833
|
+
const canvas = slideContainerRef.current;
|
|
1834
|
+
if (!viewport || !canvas) return;
|
|
1835
|
+
const resize = (width, height) => {
|
|
1836
|
+
const fitScale = Math.min(width / SLIDE_DIMENSIONS.width, height / SLIDE_DIMENSIONS.height);
|
|
1837
|
+
canvas.style.transform = `scale(${fitScale})`;
|
|
1838
|
+
};
|
|
1839
|
+
resize(viewport.clientWidth, viewport.clientHeight);
|
|
1840
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
1841
|
+
if (!entry) return;
|
|
1842
|
+
resize(entry.contentRect.width, entry.contentRect.height);
|
|
1843
|
+
});
|
|
1844
|
+
observer.observe(viewport);
|
|
1845
|
+
return () => observer.disconnect();
|
|
1846
|
+
}, [viewMode, isPresentationMode]);
|
|
1847
|
+
const toggleCommentMode = useCallback5(() => {
|
|
1848
|
+
const next = !isAnnotationMode;
|
|
1849
|
+
setIsAnnotationMode(next);
|
|
1850
|
+
setShowAnnotationPanel(next);
|
|
1851
|
+
if (!next) {
|
|
1852
|
+
setSelectedAnnotationId(null);
|
|
1853
|
+
setHoveredAnnotationId(null);
|
|
1854
|
+
}
|
|
1855
|
+
}, [isAnnotationMode]);
|
|
1856
|
+
const handleExportPdf = useCallback5(async () => {
|
|
1857
|
+
try {
|
|
1858
|
+
const response = await fetch("/__promptslide_pdf");
|
|
1859
|
+
if (response.ok && response.headers.get("content-type")?.includes("application/pdf")) {
|
|
1860
|
+
const blob = await response.blob();
|
|
1861
|
+
const url = URL.createObjectURL(blob);
|
|
1862
|
+
const link = document.createElement("a");
|
|
1863
|
+
link.href = url;
|
|
1864
|
+
link.download = "slides.pdf";
|
|
1865
|
+
link.click();
|
|
1866
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
} catch {
|
|
1870
|
+
}
|
|
1301
1871
|
const previousMode = viewMode;
|
|
1302
1872
|
setViewMode("list");
|
|
1303
1873
|
setTimeout(() => {
|
|
@@ -1308,17 +1878,50 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1308
1878
|
window.addEventListener("afterprint", handleAfterPrint);
|
|
1309
1879
|
window.print();
|
|
1310
1880
|
}, 100);
|
|
1311
|
-
};
|
|
1312
|
-
|
|
1881
|
+
}, [viewMode]);
|
|
1882
|
+
useEffect7(() => {
|
|
1313
1883
|
const handleKeyDown = (e) => {
|
|
1314
|
-
|
|
1315
|
-
|
|
1884
|
+
const target = e.target;
|
|
1885
|
+
if (target?.closest("input, textarea, [contenteditable='true']")) return;
|
|
1886
|
+
if (e.altKey || e.ctrlKey || e.metaKey) return;
|
|
1887
|
+
const key = e.key.toLowerCase();
|
|
1888
|
+
if (e.repeat && "fvglcdt".includes(key)) return;
|
|
1889
|
+
if (isEditorOpen && ["f", "v", "g", "l", "c", "d"].includes(key)) return;
|
|
1890
|
+
if (key === "f") {
|
|
1891
|
+
e.preventDefault();
|
|
1892
|
+
void togglePresentationMode();
|
|
1316
1893
|
return;
|
|
1317
1894
|
}
|
|
1318
|
-
if (
|
|
1895
|
+
if (key === "v" && !isPresentationMode) {
|
|
1896
|
+
e.preventDefault();
|
|
1897
|
+
setViewMode("slide");
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
if (key === "g") {
|
|
1901
|
+
e.preventDefault();
|
|
1319
1902
|
setViewMode((prev) => prev === "grid" ? "slide" : "grid");
|
|
1320
1903
|
return;
|
|
1321
1904
|
}
|
|
1905
|
+
if (key === "l") {
|
|
1906
|
+
e.preventDefault();
|
|
1907
|
+
setViewMode((prev) => prev === "list" ? "slide" : "list");
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
if (key === "c" && !isPresentationMode) {
|
|
1911
|
+
e.preventDefault();
|
|
1912
|
+
toggleCommentMode();
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
if (key === "d" && !isPresentationMode) {
|
|
1916
|
+
e.preventDefault();
|
|
1917
|
+
void handleExportPdf();
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
if (viewMode === "slide" && !isPresentationMode && key === "t") {
|
|
1921
|
+
e.preventDefault();
|
|
1922
|
+
setThumbnailRailOpen(!showThumbnailRail);
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1322
1925
|
if (viewMode !== "slide" || isAnnotationMode && !isPresentationMode) return;
|
|
1323
1926
|
if (e.key === "ArrowRight" || e.key === " ") {
|
|
1324
1927
|
e.preventDefault();
|
|
@@ -1330,10 +1933,48 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1330
1933
|
};
|
|
1331
1934
|
window.addEventListener("keydown", handleKeyDown);
|
|
1332
1935
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1333
|
-
}, [
|
|
1936
|
+
}, [
|
|
1937
|
+
advance,
|
|
1938
|
+
goBack,
|
|
1939
|
+
viewMode,
|
|
1940
|
+
togglePresentationMode,
|
|
1941
|
+
isAnnotationMode,
|
|
1942
|
+
isEditorOpen,
|
|
1943
|
+
isPresentationMode,
|
|
1944
|
+
setThumbnailRailOpen,
|
|
1945
|
+
showThumbnailRail,
|
|
1946
|
+
toggleCommentMode,
|
|
1947
|
+
handleExportPdf
|
|
1948
|
+
]);
|
|
1949
|
+
const editorPanelWidth = viewportWidth <= 850 ? 280 : 320;
|
|
1334
1950
|
return /* @__PURE__ */ jsxs6("div", { className: "min-h-screen w-full bg-neutral-950 text-foreground", children: [
|
|
1335
1951
|
/* @__PURE__ */ jsx12("style", { children: `
|
|
1952
|
+
.slide-thumbnail-scroll-area {
|
|
1953
|
+
scrollbar-width: thin;
|
|
1954
|
+
scrollbar-color: #404040 transparent;
|
|
1955
|
+
}
|
|
1956
|
+
.slide-thumbnail-scroll-area::-webkit-scrollbar {
|
|
1957
|
+
width: 10px;
|
|
1958
|
+
}
|
|
1959
|
+
.slide-thumbnail-scroll-area::-webkit-scrollbar-track {
|
|
1960
|
+
background: transparent;
|
|
1961
|
+
}
|
|
1962
|
+
.slide-thumbnail-scroll-area::-webkit-scrollbar-thumb {
|
|
1963
|
+
background-color: #404040;
|
|
1964
|
+
border: 3px solid transparent;
|
|
1965
|
+
border-radius: 9999px;
|
|
1966
|
+
background-clip: padding-box;
|
|
1967
|
+
}
|
|
1968
|
+
.slide-thumbnail-scroll-area::-webkit-scrollbar-thumb:hover {
|
|
1969
|
+
background-color: #525252;
|
|
1970
|
+
}
|
|
1971
|
+
.slide-thumbnail-scroll-area::-webkit-scrollbar-corner {
|
|
1972
|
+
background: transparent;
|
|
1973
|
+
}
|
|
1336
1974
|
@media print {
|
|
1975
|
+
.list-slide-canvas {
|
|
1976
|
+
transform: scale(1.5) !important;
|
|
1977
|
+
}
|
|
1337
1978
|
@page {
|
|
1338
1979
|
size: 1920px 1080px;
|
|
1339
1980
|
margin: 0;
|
|
@@ -1356,10 +1997,14 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1356
1997
|
/* @__PURE__ */ jsxs6(
|
|
1357
1998
|
"div",
|
|
1358
1999
|
{
|
|
2000
|
+
"data-ps-viewer-toolbar": true,
|
|
2001
|
+
style: isEditorOpen ? { right: editorPanelWidth + 16, maxWidth: `calc(100vw - ${editorPanelWidth + 32}px)` } : void 0,
|
|
1359
2002
|
className: cn(
|
|
1360
|
-
"fixed top-4 z-50 flex gap-1 rounded-lg border border-neutral-800 bg-neutral-950/90 p-1 backdrop-blur-sm transition-[right] print:hidden",
|
|
1361
|
-
isPresentationMode && "hidden",
|
|
1362
|
-
|
|
2003
|
+
"fixed top-4 z-50 flex gap-1 rounded-lg border border-neutral-800 bg-neutral-950/90 p-1 backdrop-blur-sm transition-[right] duration-200 ease-out print:hidden",
|
|
2004
|
+
(isPresentationMode || isNarrowViewport && isAnnotationMode && showAnnotationPanel) && "hidden",
|
|
2005
|
+
isEditorOpen && "flex-wrap",
|
|
2006
|
+
isEditorOpen && viewportWidth <= 850 && "[&_button:disabled]:hidden [&>div]:hidden",
|
|
2007
|
+
!isEditorOpen && (isAnnotationMode && showAnnotationPanel ? "right-[21rem]" : "right-4")
|
|
1363
2008
|
),
|
|
1364
2009
|
children: [
|
|
1365
2010
|
/* @__PURE__ */ jsx12(
|
|
@@ -1370,7 +2015,8 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1370
2015
|
"rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
|
|
1371
2016
|
viewMode === "slide" && "bg-neutral-800 text-white"
|
|
1372
2017
|
),
|
|
1373
|
-
|
|
2018
|
+
disabled: isEditorOpen,
|
|
2019
|
+
title: "Presentation View (V)",
|
|
1374
2020
|
children: /* @__PURE__ */ jsx12(Monitor, { className: "h-4 w-4" })
|
|
1375
2021
|
}
|
|
1376
2022
|
),
|
|
@@ -1382,7 +2028,8 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1382
2028
|
"rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
|
|
1383
2029
|
viewMode === "list" && "bg-neutral-800 text-white"
|
|
1384
2030
|
),
|
|
1385
|
-
|
|
2031
|
+
disabled: isEditorOpen,
|
|
2032
|
+
title: "List View (L)",
|
|
1386
2033
|
children: /* @__PURE__ */ jsx12(List, { className: "h-4 w-4" })
|
|
1387
2034
|
}
|
|
1388
2035
|
),
|
|
@@ -1394,7 +2041,8 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1394
2041
|
"rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
|
|
1395
2042
|
viewMode === "grid" && "bg-neutral-800 text-white"
|
|
1396
2043
|
),
|
|
1397
|
-
|
|
2044
|
+
disabled: isEditorOpen,
|
|
2045
|
+
title: "Grid View (G)",
|
|
1398
2046
|
children: /* @__PURE__ */ jsx12(Grid3X3, { className: "h-4 w-4" })
|
|
1399
2047
|
}
|
|
1400
2048
|
),
|
|
@@ -1402,42 +2050,45 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1402
2050
|
/* @__PURE__ */ jsxs6(
|
|
1403
2051
|
"button",
|
|
1404
2052
|
{
|
|
1405
|
-
onClick:
|
|
1406
|
-
setIsAnnotationMode((prev) => {
|
|
1407
|
-
const next = !prev;
|
|
1408
|
-
setShowAnnotationPanel(next);
|
|
1409
|
-
if (!next) setSelectedAnnotationId(null);
|
|
1410
|
-
return next;
|
|
1411
|
-
});
|
|
1412
|
-
},
|
|
2053
|
+
onClick: toggleCommentMode,
|
|
1413
2054
|
className: cn(
|
|
1414
|
-
"relative
|
|
1415
|
-
isAnnotationMode
|
|
2055
|
+
"relative inline-flex h-8 items-center justify-center gap-1.5 rounded-md font-[family-name:system-ui] text-[13px] leading-none font-medium transition-colors",
|
|
2056
|
+
isAnnotationMode ? "border border-[#FF6B35]/50 bg-[#FF6B35]/15 p-[7px] text-[#FF6B35] hover:border-[#FF6B35] hover:bg-[#FF6B35]/25" : "px-2.5 text-neutral-400 hover:bg-neutral-800 hover:text-white"
|
|
1416
2057
|
),
|
|
1417
|
-
|
|
2058
|
+
disabled: isEditorOpen,
|
|
2059
|
+
"aria-label": "Comment",
|
|
2060
|
+
"aria-pressed": isAnnotationMode,
|
|
2061
|
+
title: "Comment (C)",
|
|
1418
2062
|
children: [
|
|
1419
|
-
/* @__PURE__ */ jsx12(
|
|
2063
|
+
/* @__PURE__ */ jsx12(MessageCircle, { className: "h-[15px] w-[15px] shrink-0" }),
|
|
2064
|
+
!isAnnotationMode && /* @__PURE__ */ jsx12("span", { children: "Comment" }),
|
|
1420
2065
|
openCount > 0 && /* @__PURE__ */ jsx12("span", { className: "absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-[#FF6B35] text-[10px] font-bold text-white", children: openCount })
|
|
1421
2066
|
]
|
|
1422
2067
|
}
|
|
1423
2068
|
),
|
|
2069
|
+
/* @__PURE__ */ jsx12("span", { "data-ps-editor-toolbar": true, className: "contents" }),
|
|
1424
2070
|
/* @__PURE__ */ jsx12("div", { className: "mx-1 w-px bg-neutral-800" }),
|
|
1425
2071
|
/* @__PURE__ */ jsx12(
|
|
1426
2072
|
"button",
|
|
1427
2073
|
{
|
|
1428
2074
|
onClick: handleExportPdf,
|
|
1429
2075
|
className: "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
|
|
1430
|
-
|
|
2076
|
+
disabled: isEditorOpen,
|
|
2077
|
+
title: "Download PDF (D)",
|
|
1431
2078
|
children: /* @__PURE__ */ jsx12(Download, { className: "h-4 w-4" })
|
|
1432
2079
|
}
|
|
1433
2080
|
),
|
|
1434
|
-
/* @__PURE__ */
|
|
2081
|
+
/* @__PURE__ */ jsxs6(
|
|
1435
2082
|
"button",
|
|
1436
2083
|
{
|
|
1437
2084
|
onClick: togglePresentationMode,
|
|
1438
|
-
className: "rounded-md
|
|
2085
|
+
className: "inline-flex h-8 items-center justify-center gap-1.5 rounded-md border border-[#FF6B35]/50 bg-[#FF6B35]/15 px-3 text-sm font-semibold text-[#FF6B35] transition-colors hover:border-[#FF6B35] hover:bg-[#FF6B35]/25",
|
|
2086
|
+
disabled: isEditorOpen,
|
|
1439
2087
|
title: "Present (F)",
|
|
1440
|
-
children:
|
|
2088
|
+
children: [
|
|
2089
|
+
/* @__PURE__ */ jsx12(Play, { className: "h-3.5 w-3.5 fill-current" }),
|
|
2090
|
+
/* @__PURE__ */ jsx12("span", { children: "Present" })
|
|
2091
|
+
]
|
|
1441
2092
|
}
|
|
1442
2093
|
)
|
|
1443
2094
|
]
|
|
@@ -1446,11 +2097,157 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1446
2097
|
viewMode === "slide" && /* @__PURE__ */ jsxs6(
|
|
1447
2098
|
"div",
|
|
1448
2099
|
{
|
|
1449
|
-
className: cn(
|
|
1450
|
-
"flex h-screen w-full print:hidden",
|
|
1451
|
-
isPresentationMode ? "bg-black" : ""
|
|
1452
|
-
),
|
|
2100
|
+
className: cn("flex h-screen w-full print:hidden", isPresentationMode ? "bg-black" : ""),
|
|
1453
2101
|
children: [
|
|
2102
|
+
!isPresentationMode && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
2103
|
+
/* @__PURE__ */ jsxs6(
|
|
2104
|
+
"div",
|
|
2105
|
+
{
|
|
2106
|
+
className: cn(
|
|
2107
|
+
"relative z-10 h-screen shrink-0",
|
|
2108
|
+
isNarrowViewport && "fixed inset-y-0 left-0",
|
|
2109
|
+
isResizingThumbnails && !isAnimatingDragBoundary ? "transition-none" : "transition-[width] duration-200 ease-out motion-reduce:transition-none"
|
|
2110
|
+
),
|
|
2111
|
+
style: { width: isThumbnailRailVisible ? visibleThumbnailWidth : 0 },
|
|
2112
|
+
children: [
|
|
2113
|
+
/* @__PURE__ */ jsx12("div", { className: "h-full w-full overflow-hidden", children: /* @__PURE__ */ jsxs6(
|
|
2114
|
+
"aside",
|
|
2115
|
+
{
|
|
2116
|
+
id: "presentation-thumbnails",
|
|
2117
|
+
"aria-label": "Slide thumbnails",
|
|
2118
|
+
"aria-hidden": !isThumbnailRailVisible,
|
|
2119
|
+
inert: !isThumbnailRailVisible,
|
|
2120
|
+
className: cn(
|
|
2121
|
+
"flex h-screen shrink-0 flex-col border-r border-neutral-800 bg-neutral-950 text-white transition-opacity motion-reduce:transition-none",
|
|
2122
|
+
isThumbnailRailVisible ? "opacity-100 delay-100 duration-100" : "opacity-0 duration-75"
|
|
2123
|
+
),
|
|
2124
|
+
style: { width: visibleThumbnailWidth },
|
|
2125
|
+
children: [
|
|
2126
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
|
|
2127
|
+
/* @__PURE__ */ jsxs6("span", { className: "text-sm font-semibold", children: [
|
|
2128
|
+
"Slides (",
|
|
2129
|
+
slides.length,
|
|
2130
|
+
")"
|
|
2131
|
+
] }),
|
|
2132
|
+
/* @__PURE__ */ jsx12(
|
|
2133
|
+
"button",
|
|
2134
|
+
{
|
|
2135
|
+
type: "button",
|
|
2136
|
+
onClick: () => setThumbnailRailOpen(false),
|
|
2137
|
+
className: "rounded p-2 text-neutral-400 hover:bg-neutral-800 hover:text-white",
|
|
2138
|
+
"aria-label": "Hide slide thumbnails",
|
|
2139
|
+
title: "Hide thumbnails (T)",
|
|
2140
|
+
children: /* @__PURE__ */ jsx12(PanelLeftClose, { className: "h-4 w-4" })
|
|
2141
|
+
}
|
|
2142
|
+
)
|
|
2143
|
+
] }),
|
|
2144
|
+
/* @__PURE__ */ jsx12("div", { className: "slide-thumbnail-scroll-area min-h-0 flex-1 space-y-3 overflow-y-auto p-3", children: slides.map((slideConfig, index) => /* @__PURE__ */ jsxs6(
|
|
2145
|
+
"button",
|
|
2146
|
+
{
|
|
2147
|
+
ref: index === currentSlide ? activeThumbnailRef : void 0,
|
|
2148
|
+
type: "button",
|
|
2149
|
+
onClick: () => goToSlide(index),
|
|
2150
|
+
"aria-label": `Go to slide ${index + 1}${slideConfig.title ? `: ${slideConfig.title}` : ""}`,
|
|
2151
|
+
"aria-current": index === currentSlide ? "page" : void 0,
|
|
2152
|
+
className: cn(
|
|
2153
|
+
"block w-full rounded-lg border p-1.5 text-left transition-colors focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#FF6B35]",
|
|
2154
|
+
index === currentSlide ? "border-[#FF6B35] bg-[#FF6B35]/15 ring-1 ring-[#FF6B35]" : "border-transparent hover:border-neutral-600 hover:bg-neutral-800"
|
|
2155
|
+
),
|
|
2156
|
+
children: [
|
|
2157
|
+
/* @__PURE__ */ jsx12("div", { className: "relative aspect-video w-full overflow-hidden rounded bg-black", children: /* @__PURE__ */ jsx12(
|
|
2158
|
+
GridThumbnail,
|
|
2159
|
+
{
|
|
2160
|
+
slide: slideConfig,
|
|
2161
|
+
index,
|
|
2162
|
+
total: slides.length
|
|
2163
|
+
}
|
|
2164
|
+
) }),
|
|
2165
|
+
/* @__PURE__ */ jsx12("div", { className: "mt-1.5 flex items-baseline gap-2 px-0.5 text-xs", children: slideConfig.title ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
2166
|
+
/* @__PURE__ */ jsx12("span", { className: "font-mono text-neutral-400", children: index + 1 }),
|
|
2167
|
+
/* @__PURE__ */ jsx12("span", { className: "truncate", children: slideConfig.title })
|
|
2168
|
+
] }) : /* @__PURE__ */ jsxs6("span", { children: [
|
|
2169
|
+
"Slide ",
|
|
2170
|
+
index + 1
|
|
2171
|
+
] }) })
|
|
2172
|
+
]
|
|
2173
|
+
},
|
|
2174
|
+
slideConfig.id ?? index
|
|
2175
|
+
)) })
|
|
2176
|
+
]
|
|
2177
|
+
}
|
|
2178
|
+
) }),
|
|
2179
|
+
isThumbnailRailVisible && /* @__PURE__ */ jsx12(
|
|
2180
|
+
"div",
|
|
2181
|
+
{
|
|
2182
|
+
role: "separator",
|
|
2183
|
+
"aria-label": "Resize slide thumbnails",
|
|
2184
|
+
"aria-orientation": "vertical",
|
|
2185
|
+
"aria-valuemin": minThumbnailWidth,
|
|
2186
|
+
"aria-valuemax": maxThumbnailWidth,
|
|
2187
|
+
"aria-valuenow": visibleThumbnailWidth,
|
|
2188
|
+
tabIndex: 0,
|
|
2189
|
+
className: cn(
|
|
2190
|
+
"absolute inset-y-0 -right-1 w-2 cursor-col-resize touch-none focus-visible:outline-none after:pointer-events-none after:absolute after:inset-y-0 after:right-1 after:w-0.5 after:bg-transparent after:content-[''] after:transition-colors hover:after:bg-[#FF6B35]/50 focus-visible:after:bg-[#FF6B35]/50",
|
|
2191
|
+
isResizingThumbnails && "after:bg-[#FF6B35]/60"
|
|
2192
|
+
),
|
|
2193
|
+
onPointerDown: (event) => {
|
|
2194
|
+
if (event.button !== 0) return;
|
|
2195
|
+
event.preventDefault();
|
|
2196
|
+
resizeStartRef.current = {
|
|
2197
|
+
pointerId: event.pointerId,
|
|
2198
|
+
pointerX: event.clientX,
|
|
2199
|
+
width: visibleThumbnailWidth
|
|
2200
|
+
};
|
|
2201
|
+
dragCollapsedRef.current = false;
|
|
2202
|
+
setIsAnimatingDragBoundary(false);
|
|
2203
|
+
if (dragAnimationTimerRef.current) clearTimeout(dragAnimationTimerRef.current);
|
|
2204
|
+
setIsResizingThumbnails(true);
|
|
2205
|
+
previousUserSelectRef.current = document.body.style.userSelect;
|
|
2206
|
+
previousCursorRef.current = document.body.style.cursor;
|
|
2207
|
+
document.body.style.userSelect = "none";
|
|
2208
|
+
document.body.style.cursor = "col-resize";
|
|
2209
|
+
},
|
|
2210
|
+
onKeyDown: (event) => {
|
|
2211
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
2212
|
+
event.preventDefault();
|
|
2213
|
+
event.stopPropagation();
|
|
2214
|
+
const amount = event.key === "ArrowRight" ? 16 : -16;
|
|
2215
|
+
setThumbnailWidth(
|
|
2216
|
+
Math.max(
|
|
2217
|
+
minThumbnailWidth,
|
|
2218
|
+
Math.min(maxThumbnailWidth, visibleThumbnailWidth + amount)
|
|
2219
|
+
)
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
)
|
|
2225
|
+
]
|
|
2226
|
+
}
|
|
2227
|
+
),
|
|
2228
|
+
/* @__PURE__ */ jsx12(
|
|
2229
|
+
"div",
|
|
2230
|
+
{
|
|
2231
|
+
"aria-hidden": isThumbnailRailVisible || isResizingThumbnails,
|
|
2232
|
+
inert: isThumbnailRailVisible || isResizingThumbnails,
|
|
2233
|
+
className: cn(
|
|
2234
|
+
"fixed top-4 left-4 z-50 rounded-lg border border-neutral-800 bg-neutral-950/90 backdrop-blur-sm transition-opacity duration-150 motion-reduce:transition-none",
|
|
2235
|
+
isThumbnailRailVisible || isResizingThumbnails ? "pointer-events-none opacity-0" : "opacity-100 delay-200"
|
|
2236
|
+
),
|
|
2237
|
+
children: /* @__PURE__ */ jsx12(
|
|
2238
|
+
"button",
|
|
2239
|
+
{
|
|
2240
|
+
type: "button",
|
|
2241
|
+
onClick: () => setThumbnailRailOpen(true),
|
|
2242
|
+
className: "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
|
|
2243
|
+
"aria-label": "Show slide thumbnails",
|
|
2244
|
+
title: "Show thumbnails (T)",
|
|
2245
|
+
children: /* @__PURE__ */ jsx12(PanelLeftOpen, { className: "h-4 w-4" })
|
|
2246
|
+
}
|
|
2247
|
+
)
|
|
2248
|
+
}
|
|
2249
|
+
)
|
|
2250
|
+
] }),
|
|
1454
2251
|
/* @__PURE__ */ jsxs6(
|
|
1455
2252
|
"div",
|
|
1456
2253
|
{
|
|
@@ -1458,7 +2255,7 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1458
2255
|
role: "presentation",
|
|
1459
2256
|
tabIndex: isPresentationMode ? 0 : void 0,
|
|
1460
2257
|
className: cn(
|
|
1461
|
-
"flex flex-1 flex-col items-center justify-center overflow-hidden",
|
|
2258
|
+
"flex min-w-0 flex-1 flex-col items-center justify-center overflow-hidden",
|
|
1462
2259
|
isPresentationMode ? "bg-black p-0" : "p-4 md:p-8"
|
|
1463
2260
|
),
|
|
1464
2261
|
onClick: isPresentationMode ? advance : void 0,
|
|
@@ -1491,42 +2288,76 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1491
2288
|
}
|
|
1492
2289
|
)
|
|
1493
2290
|
}
|
|
1494
|
-
) : /* @__PURE__ */ jsxs6(
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
2291
|
+
) : /* @__PURE__ */ jsxs6(
|
|
2292
|
+
"div",
|
|
2293
|
+
{
|
|
2294
|
+
ref: previewViewportRef,
|
|
2295
|
+
className: "relative aspect-video w-full max-w-7xl overflow-hidden rounded-xl border border-neutral-800 bg-black shadow-2xl",
|
|
2296
|
+
children: [
|
|
2297
|
+
/* @__PURE__ */ jsxs6(
|
|
2298
|
+
"div",
|
|
2299
|
+
{
|
|
2300
|
+
ref: slideContainerRef,
|
|
2301
|
+
"data-ps-editor-canvas": true,
|
|
2302
|
+
tabIndex: -1,
|
|
2303
|
+
"data-slide-index": currentSlide,
|
|
2304
|
+
className: "absolute top-0 left-0 overflow-hidden outline-none",
|
|
2305
|
+
style: {
|
|
2306
|
+
width: SLIDE_DIMENSIONS.width,
|
|
2307
|
+
height: SLIDE_DIMENSIONS.height,
|
|
2308
|
+
transformOrigin: "top left"
|
|
2309
|
+
},
|
|
2310
|
+
children: [
|
|
2311
|
+
/* @__PURE__ */ jsx12(
|
|
2312
|
+
SlideRenderer,
|
|
2313
|
+
{
|
|
2314
|
+
slides,
|
|
2315
|
+
currentSlide,
|
|
2316
|
+
animationStep,
|
|
2317
|
+
totalSteps,
|
|
2318
|
+
direction,
|
|
2319
|
+
showAllAnimations,
|
|
2320
|
+
transition,
|
|
2321
|
+
directionalTransition,
|
|
2322
|
+
onTransitionComplete
|
|
2323
|
+
},
|
|
2324
|
+
editorRevision
|
|
2325
|
+
),
|
|
2326
|
+
isAnnotationMode && /* @__PURE__ */ jsx12(
|
|
2327
|
+
AnnotationOverlay,
|
|
2328
|
+
{
|
|
2329
|
+
slides,
|
|
2330
|
+
currentSlide,
|
|
2331
|
+
slideContainerRef,
|
|
2332
|
+
selectedId: selectedAnnotationId,
|
|
2333
|
+
hoveredId: hoveredAnnotationId,
|
|
2334
|
+
onSelectId: setSelectedAnnotationId,
|
|
2335
|
+
onShowPanel: () => setShowAnnotationPanel(true),
|
|
2336
|
+
slideAnnotations: getSlideAnnotations(currentSlide),
|
|
2337
|
+
addAnnotation: effectiveAdd ?? (() => {
|
|
2338
|
+
})
|
|
2339
|
+
}
|
|
2340
|
+
)
|
|
2341
|
+
]
|
|
2342
|
+
}
|
|
2343
|
+
),
|
|
2344
|
+
/* @__PURE__ */ jsx12(
|
|
2345
|
+
"div",
|
|
2346
|
+
{
|
|
2347
|
+
"data-ps-editor-overlay": true,
|
|
2348
|
+
className: "pointer-events-none absolute inset-0 z-20"
|
|
2349
|
+
}
|
|
2350
|
+
)
|
|
2351
|
+
]
|
|
2352
|
+
}
|
|
2353
|
+
) }),
|
|
1524
2354
|
!isPresentationMode && /* @__PURE__ */ jsxs6("div", { className: "mt-6 flex items-center gap-4", children: [
|
|
1525
2355
|
/* @__PURE__ */ jsx12(
|
|
1526
2356
|
"button",
|
|
1527
2357
|
{
|
|
1528
2358
|
onClick: goBack,
|
|
1529
2359
|
className: "rounded-full border border-neutral-800 bg-black/50 p-2 text-neutral-400 backdrop-blur-sm transition-colors hover:bg-neutral-900 hover:text-white",
|
|
2360
|
+
title: "Previous step or slide (\u2190)",
|
|
1530
2361
|
children: /* @__PURE__ */ jsx12(ChevronLeft, { className: "h-5 w-5" })
|
|
1531
2362
|
}
|
|
1532
2363
|
),
|
|
@@ -1540,6 +2371,7 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1540
2371
|
{
|
|
1541
2372
|
onClick: advance,
|
|
1542
2373
|
className: "rounded-full border border-neutral-800 bg-black/50 p-2 text-neutral-400 backdrop-blur-sm transition-colors hover:bg-neutral-900 hover:text-white",
|
|
2374
|
+
title: "Next step or slide (\u2192 / Space)",
|
|
1543
2375
|
children: /* @__PURE__ */ jsx12(ChevronRight, { className: "h-5 w-5" })
|
|
1544
2376
|
}
|
|
1545
2377
|
)
|
|
@@ -1547,29 +2379,69 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1547
2379
|
]
|
|
1548
2380
|
}
|
|
1549
2381
|
),
|
|
1550
|
-
|
|
1551
|
-
|
|
2382
|
+
/* @__PURE__ */ jsx12(
|
|
2383
|
+
"div",
|
|
1552
2384
|
{
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
2385
|
+
"data-ps-editor-panel": true,
|
|
2386
|
+
"data-open": isEditorOpen,
|
|
2387
|
+
"aria-hidden": !isEditorOpen,
|
|
2388
|
+
inert: !isEditorOpen,
|
|
2389
|
+
className: "h-full shrink-0 overflow-hidden transition-[width] duration-200 ease-out motion-reduce:transition-none",
|
|
2390
|
+
style: { width: isEditorOpen ? editorPanelWidth : 0 }
|
|
2391
|
+
}
|
|
2392
|
+
),
|
|
2393
|
+
!isPresentationMode && /* @__PURE__ */ jsx12(
|
|
2394
|
+
"div",
|
|
2395
|
+
{
|
|
2396
|
+
"aria-hidden": !isAnnotationMode || !showAnnotationPanel,
|
|
2397
|
+
inert: !isAnnotationMode || !showAnnotationPanel,
|
|
2398
|
+
className: cn(
|
|
2399
|
+
"h-full shrink-0 overflow-hidden transition-[width,transform] duration-200 ease-out motion-reduce:transition-none",
|
|
2400
|
+
isNarrowViewport && "fixed inset-y-0 right-0 z-50 shadow-xl",
|
|
2401
|
+
isNarrowViewport && (!isAnnotationMode || !showAnnotationPanel) && "translate-x-full"
|
|
2402
|
+
),
|
|
2403
|
+
style: {
|
|
2404
|
+
width: isAnnotationMode && showAnnotationPanel ? isNarrowViewport ? "min(20rem, 90vw)" : "20rem" : 0
|
|
2405
|
+
},
|
|
2406
|
+
children: /* @__PURE__ */ jsx12(
|
|
2407
|
+
"div",
|
|
2408
|
+
{
|
|
2409
|
+
className: cn(
|
|
2410
|
+
"h-full transition-opacity motion-reduce:transition-none",
|
|
2411
|
+
isAnnotationMode && showAnnotationPanel ? "opacity-100 delay-100 duration-100" : "opacity-0 duration-75"
|
|
2412
|
+
),
|
|
2413
|
+
children: /* @__PURE__ */ jsx12(
|
|
2414
|
+
AnnotationPanel,
|
|
2415
|
+
{
|
|
2416
|
+
annotations: getSlideAnnotations(currentSlide),
|
|
2417
|
+
unlinkedAnnotations,
|
|
2418
|
+
selectedId: selectedAnnotationId,
|
|
2419
|
+
onSelect: setSelectedAnnotationId,
|
|
2420
|
+
onHover: setHoveredAnnotationId,
|
|
2421
|
+
onDelete: effectiveDelete ?? (() => {
|
|
2422
|
+
}),
|
|
2423
|
+
onUpdate: effectiveUpdate ?? (() => {
|
|
2424
|
+
}),
|
|
2425
|
+
onClose: () => {
|
|
2426
|
+
setIsAnnotationMode(false);
|
|
2427
|
+
setShowAnnotationPanel(false);
|
|
2428
|
+
setSelectedAnnotationId(null);
|
|
2429
|
+
setHoveredAnnotationId(null);
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
)
|
|
2433
|
+
}
|
|
2434
|
+
)
|
|
1562
2435
|
}
|
|
1563
2436
|
)
|
|
1564
2437
|
]
|
|
1565
2438
|
}
|
|
1566
2439
|
),
|
|
1567
2440
|
viewMode === "grid" && /* @__PURE__ */ jsx12("div", { className: "mx-auto max-w-7xl p-8 pt-16 print:hidden", children: /* @__PURE__ */ jsx12("div", { className: "grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4", children: slides.map((slideConfig, index) => {
|
|
1568
|
-
const SlideComponent = slideConfig.component;
|
|
1569
2441
|
const prevSection = index > 0 ? slides[index - 1]?.section : void 0;
|
|
1570
2442
|
const showSectionHeader = slideConfig.section && slideConfig.section !== prevSection;
|
|
1571
|
-
return /* @__PURE__ */ jsxs6("div", { className:
|
|
1572
|
-
showSectionHeader && /* @__PURE__ */ jsx12("h3", { className: "mt-4 mb-
|
|
2443
|
+
return /* @__PURE__ */ jsxs6("div", { className: "contents", children: [
|
|
2444
|
+
showSectionHeader && /* @__PURE__ */ jsx12("h3", { className: "col-span-full mt-4 mb-0 text-xs font-bold tracking-[0.2em] text-neutral-500 uppercase first:mt-0", children: slideConfig.section }),
|
|
1573
2445
|
/* @__PURE__ */ jsxs6(
|
|
1574
2446
|
"button",
|
|
1575
2447
|
{
|
|
@@ -1577,30 +2449,15 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1577
2449
|
goToSlide(index);
|
|
1578
2450
|
setViewMode("slide");
|
|
1579
2451
|
},
|
|
1580
|
-
className: "group relative aspect-video w-full overflow-hidden rounded-lg border border-neutral-800 bg-black shadow-sm transition-all hover:border-
|
|
2452
|
+
className: "group relative aspect-video w-full overflow-hidden rounded-lg border border-neutral-800 bg-black shadow-sm transition-all hover:border-[#FF6B35] hover:shadow-lg hover:shadow-[#FF6B35]/10",
|
|
1581
2453
|
children: [
|
|
1582
|
-
/* @__PURE__ */ jsx12(
|
|
1583
|
-
"div",
|
|
1584
|
-
{
|
|
1585
|
-
className: "h-full w-full origin-top-left scale-[0.25]",
|
|
1586
|
-
style: { width: "400%", height: "400%" },
|
|
1587
|
-
children: /* @__PURE__ */ jsx12(
|
|
1588
|
-
AnimationProvider,
|
|
1589
|
-
{
|
|
1590
|
-
currentStep: slideConfig.steps,
|
|
1591
|
-
totalSteps: slideConfig.steps,
|
|
1592
|
-
showAllAnimations: true,
|
|
1593
|
-
children: /* @__PURE__ */ jsx12(SlideErrorBoundary, { slideIndex: index, slideTitle: slideConfig.title, children: /* @__PURE__ */ jsx12(SlideComponent, { slideNumber: index + 1, totalSlides: slides.length }) })
|
|
1594
|
-
}
|
|
1595
|
-
)
|
|
1596
|
-
}
|
|
1597
|
-
),
|
|
2454
|
+
/* @__PURE__ */ jsx12(GridThumbnail, { slide: slideConfig, index, total: slides.length }),
|
|
1598
2455
|
/* @__PURE__ */ jsx12("div", { className: "absolute inset-0 bg-black/0 transition-colors group-hover:bg-black/20" }),
|
|
1599
2456
|
/* @__PURE__ */ jsx12("div", { className: "absolute bottom-2 left-2 rounded bg-black/70 px-2 py-1 text-xs font-medium text-white", children: slideConfig.title ? `${index + 1}. ${slideConfig.title}` : index + 1 })
|
|
1600
2457
|
]
|
|
1601
2458
|
}
|
|
1602
2459
|
)
|
|
1603
|
-
] }, index);
|
|
2460
|
+
] }, slideConfig.id ?? index);
|
|
1604
2461
|
}) }) }),
|
|
1605
2462
|
/* @__PURE__ */ jsx12(
|
|
1606
2463
|
"div",
|
|
@@ -1610,25 +2467,15 @@ function SlideDeck({ slides, transition, directionalTransition, annotations, onA
|
|
|
1610
2467
|
"print:m-0 print:block print:max-w-none print:p-0",
|
|
1611
2468
|
viewMode === "list" ? "block" : "hidden print:block"
|
|
1612
2469
|
),
|
|
1613
|
-
children: /* @__PURE__ */ jsx12("div", { className: "grid grid-cols-1 gap-8 print:block", children: slides.map((slideConfig, index) =>
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
currentStep: slideConfig.steps,
|
|
1623
|
-
totalSteps: slideConfig.steps,
|
|
1624
|
-
showAllAnimations: true,
|
|
1625
|
-
children: /* @__PURE__ */ jsx12(SlideErrorBoundary, { slideIndex: index, slideTitle: slideConfig.title, children: /* @__PURE__ */ jsx12(SlideComponent, { slideNumber: index + 1, totalSlides: slides.length }) })
|
|
1626
|
-
}
|
|
1627
|
-
) })
|
|
1628
|
-
},
|
|
1629
|
-
index
|
|
1630
|
-
);
|
|
1631
|
-
}) })
|
|
2470
|
+
children: /* @__PURE__ */ jsx12("div", { className: "grid grid-cols-1 gap-8 print:block", children: slides.map((slideConfig, index) => /* @__PURE__ */ jsx12(
|
|
2471
|
+
ListSlide,
|
|
2472
|
+
{
|
|
2473
|
+
slide: slideConfig,
|
|
2474
|
+
index,
|
|
2475
|
+
total: slides.length
|
|
2476
|
+
},
|
|
2477
|
+
slideConfig.id ?? index
|
|
2478
|
+
)) })
|
|
1632
2479
|
}
|
|
1633
2480
|
)
|
|
1634
2481
|
] });
|