promptslide 0.3.17 → 0.3.19
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 +76 -0
- package/dist/index.d.ts +29 -4
- package/dist/index.js +1427 -359
- package/dist/index.js.map +1 -1
- package/package.json +31 -31
- package/src/commands/build.mjs +4 -1
- package/src/commands/clone.mjs +5 -2
- package/src/commands/create.mjs +45 -14
- package/src/commands/pull.mjs +6 -3
- package/src/commands/studio.mjs +4 -3
- package/src/core/annotations/annotation-panel.tsx +18 -12
- package/src/core/index.ts +11 -5
- package/src/core/inspector.tsx +155 -0
- package/src/core/loop/config.d.mts +13 -0
- package/src/core/loop/config.mjs +65 -0
- package/src/core/loop/context.tsx +20 -0
- package/src/core/loop/loop-settings.tsx +292 -0
- package/src/core/loop/use-loop-playback.ts +115 -0
- package/src/core/slide-deck.tsx +459 -125
- package/src/core/slide-renderer.tsx +5 -4
- package/src/core/toolbar-action.tsx +38 -0
- package/src/core/types.ts +14 -0
- package/src/core/use-slide-navigation.ts +27 -0
- package/src/core/view-menu.tsx +130 -0
- package/src/editor/README.md +45 -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 +978 -0
- package/src/editor/studio.tsx +1964 -0
- package/src/utils/deck-config.mjs +6 -0
- package/src/utils/loop-config.mjs +70 -0
- package/src/vite/config.mjs +23 -2
- package/src/vite/plugin.mjs +21 -4
- package/templates/default/src/deck-config.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -503,6 +503,26 @@ function useSlideNavigation({
|
|
|
503
503
|
},
|
|
504
504
|
[currentSlide, slides.length, onSlideChange]
|
|
505
505
|
);
|
|
506
|
+
const goToStep = useCallback(
|
|
507
|
+
(step) => {
|
|
508
|
+
if (!Number.isInteger(step)) return;
|
|
509
|
+
setQueuedAction(null);
|
|
510
|
+
setAnimationStep(Math.max(0, Math.min(step, slides[currentSlide]?.steps ?? 0)));
|
|
511
|
+
setNavState((previous) => ({ ...previous, direction: 0 }));
|
|
512
|
+
},
|
|
513
|
+
[slides, currentSlide]
|
|
514
|
+
);
|
|
515
|
+
const restartAtSlide = useCallback(
|
|
516
|
+
(index) => {
|
|
517
|
+
if (index < 0 || index >= slides.length) return;
|
|
518
|
+
setQueuedAction(null);
|
|
519
|
+
setAnimationStep(0);
|
|
520
|
+
setCurrentSlide(index);
|
|
521
|
+
setNavState({ status: index === currentSlide ? "idle" : "transitioning", direction: 1 });
|
|
522
|
+
onSlideChange?.(index);
|
|
523
|
+
},
|
|
524
|
+
[slides.length, currentSlide, onSlideChange]
|
|
525
|
+
);
|
|
506
526
|
useEffect(() => {
|
|
507
527
|
if (navState.status === "idle" && queuedAction !== null) {
|
|
508
528
|
setQueuedAction(null);
|
|
@@ -523,6 +543,8 @@ function useSlideNavigation({
|
|
|
523
543
|
advance,
|
|
524
544
|
goBack,
|
|
525
545
|
goToSlide,
|
|
546
|
+
goToStep,
|
|
547
|
+
restartAtSlide,
|
|
526
548
|
onTransitionComplete
|
|
527
549
|
};
|
|
528
550
|
}
|
|
@@ -595,6 +617,7 @@ var SlideErrorBoundary = class extends React.Component {
|
|
|
595
617
|
// src/core/slide-renderer.tsx
|
|
596
618
|
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
597
619
|
function SlideRenderer({
|
|
620
|
+
playbackKey,
|
|
598
621
|
slides,
|
|
599
622
|
currentSlide,
|
|
600
623
|
animationStep,
|
|
@@ -616,6 +639,7 @@ function SlideRenderer({
|
|
|
616
639
|
return /* @__PURE__ */ jsx6(AnimatePresence, { initial: false, children: /* @__PURE__ */ jsx6(
|
|
617
640
|
motion3.div,
|
|
618
641
|
{
|
|
642
|
+
"data-slide-render-index": currentSlide,
|
|
619
643
|
variants: slideVariants,
|
|
620
644
|
initial: "enter",
|
|
621
645
|
animate: "center",
|
|
@@ -633,20 +657,14 @@ function SlideRenderer({
|
|
|
633
657
|
currentStep: animationStep,
|
|
634
658
|
totalSteps,
|
|
635
659
|
showAllAnimations,
|
|
636
|
-
children: /* @__PURE__ */ jsx6(
|
|
637
|
-
|
|
660
|
+
children: /* @__PURE__ */ jsx6(SlideErrorBoundary, { slideIndex: currentSlide, slideTitle: slides[currentSlide]?.title, children: /* @__PURE__ */ jsx6(
|
|
661
|
+
CurrentSlideComponent,
|
|
638
662
|
{
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
slideNumber: currentSlide + 1,
|
|
645
|
-
totalSlides: slides.length
|
|
646
|
-
}
|
|
647
|
-
)
|
|
648
|
-
}
|
|
649
|
-
)
|
|
663
|
+
slideNumber: currentSlide + 1,
|
|
664
|
+
totalSlides: slides.length
|
|
665
|
+
},
|
|
666
|
+
playbackKey
|
|
667
|
+
) })
|
|
650
668
|
}
|
|
651
669
|
)
|
|
652
670
|
},
|
|
@@ -753,18 +771,17 @@ function SlideEmbed({ slides, transition, directionalTransition }) {
|
|
|
753
771
|
// src/core/slide-deck.tsx
|
|
754
772
|
import { LayoutGroup } from "framer-motion";
|
|
755
773
|
import {
|
|
774
|
+
ChevronDown as ChevronDown2,
|
|
756
775
|
ChevronLeft,
|
|
757
776
|
ChevronRight,
|
|
758
|
-
Download,
|
|
759
|
-
Grid3X3,
|
|
760
|
-
List,
|
|
777
|
+
Download as Download2,
|
|
761
778
|
MessageCircle,
|
|
762
|
-
Monitor,
|
|
763
779
|
PanelLeftClose,
|
|
764
780
|
PanelLeftOpen,
|
|
765
|
-
Play
|
|
781
|
+
Play as Play2,
|
|
782
|
+
Settings2
|
|
766
783
|
} from "lucide-react";
|
|
767
|
-
import { useCallback as
|
|
784
|
+
import { useCallback as useCallback6, useEffect as useEffect9, useLayoutEffect as useLayoutEffect2, useMemo as useMemo5, useRef as useRef8, useState as useState10 } from "react";
|
|
768
785
|
|
|
769
786
|
// src/core/annotations/annotation-overlay.tsx
|
|
770
787
|
import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
|
|
@@ -1105,6 +1122,7 @@ import { Check, Pencil, PanelRightClose, RotateCcw, Trash2 } from "lucide-react"
|
|
|
1105
1122
|
import { useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
|
|
1106
1123
|
import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1107
1124
|
function AnnotationPanel({
|
|
1125
|
+
embedded = false,
|
|
1108
1126
|
annotations,
|
|
1109
1127
|
unlinkedAnnotations = [],
|
|
1110
1128
|
selectedId,
|
|
@@ -1118,82 +1136,88 @@ function AnnotationPanel({
|
|
|
1118
1136
|
const resolved = annotations.filter((a) => a.status === "resolved");
|
|
1119
1137
|
const total = annotations.length + unlinkedAnnotations.length;
|
|
1120
1138
|
const numberFor = (annotation) => annotations.findIndex((a) => a.id === annotation.id) + 1;
|
|
1121
|
-
return /* @__PURE__ */ jsxs5(
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
className: "rounded p-2 text-neutral-400 hover:bg-neutral-800 hover:text-white",
|
|
1128
|
-
"aria-label": "Hide comments",
|
|
1129
|
-
title: "Hide comments (C)",
|
|
1130
|
-
children: /* @__PURE__ */ jsx11(PanelRightClose, { className: "h-4 w-4" })
|
|
1131
|
-
}
|
|
1132
|
-
),
|
|
1133
|
-
/* @__PURE__ */ jsxs5("span", { className: "text-right text-sm font-semibold", children: [
|
|
1134
|
-
"Comments (",
|
|
1135
|
-
total,
|
|
1136
|
-
")"
|
|
1137
|
-
] })
|
|
1138
|
-
] }),
|
|
1139
|
-
/* @__PURE__ */ jsxs5("div", { className: "min-h-0 flex-1 overflow-y-auto p-3", children: [
|
|
1140
|
-
annotations.length === 0 && unlinkedAnnotations.length === 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-4 px-3 py-6", children: /* @__PURE__ */ jsx11(WorkflowHint, {}) }),
|
|
1141
|
-
open.length > 0 && /* @__PURE__ */ jsxs5("div", { children: [
|
|
1142
|
-
/* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Open" }),
|
|
1143
|
-
open.map((a) => /* @__PURE__ */ jsx11(
|
|
1144
|
-
AnnotationItem,
|
|
1145
|
-
{
|
|
1146
|
-
annotation: a,
|
|
1147
|
-
number: numberFor(a),
|
|
1148
|
-
isSelected: a.id === selectedId,
|
|
1149
|
-
onSelect: () => onSelect(a.id),
|
|
1150
|
-
onHover: (hovered) => onHover(hovered ? a.id : null),
|
|
1151
|
-
onDelete: () => onDelete(a.id),
|
|
1152
|
-
onUpdate: (patch) => onUpdate(a.id, patch)
|
|
1153
|
-
},
|
|
1154
|
-
a.id
|
|
1155
|
-
))
|
|
1156
|
-
] }),
|
|
1157
|
-
resolved.length > 0 && /* @__PURE__ */ jsxs5("div", { className: open.length > 0 ? "mt-3" : "", children: [
|
|
1158
|
-
/* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Resolved" }),
|
|
1159
|
-
resolved.map((a) => /* @__PURE__ */ jsx11(
|
|
1160
|
-
AnnotationItem,
|
|
1161
|
-
{
|
|
1162
|
-
annotation: a,
|
|
1163
|
-
number: numberFor(a),
|
|
1164
|
-
isSelected: a.id === selectedId,
|
|
1165
|
-
onSelect: () => onSelect(a.id),
|
|
1166
|
-
onHover: (hovered) => onHover(hovered ? a.id : null),
|
|
1167
|
-
onDelete: () => onDelete(a.id),
|
|
1168
|
-
onUpdate: (patch) => onUpdate(a.id, patch)
|
|
1169
|
-
},
|
|
1170
|
-
a.id
|
|
1171
|
-
))
|
|
1172
|
-
] }),
|
|
1173
|
-
unlinkedAnnotations.length > 0 && /* @__PURE__ */ jsxs5("div", { className: "mt-3", children: [
|
|
1174
|
-
/* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Removed slides" }),
|
|
1175
|
-
unlinkedAnnotations.map((a, i) => /* @__PURE__ */ jsxs5("div", { children: [
|
|
1176
|
-
/* @__PURE__ */ jsx11("p", { className: "px-2 text-[11px] text-neutral-500", children: a.slideTitle || a.slideId }),
|
|
1139
|
+
return /* @__PURE__ */ jsxs5(
|
|
1140
|
+
"div",
|
|
1141
|
+
{
|
|
1142
|
+
className: `flex h-full max-w-[90vw] flex-shrink-0 flex-col bg-neutral-950 text-white ${embedded ? "w-full" : "w-80 border-l border-neutral-800"}`,
|
|
1143
|
+
children: [
|
|
1144
|
+
!embedded && /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
|
|
1177
1145
|
/* @__PURE__ */ jsx11(
|
|
1178
|
-
|
|
1146
|
+
"button",
|
|
1179
1147
|
{
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
onDelete: () => onDelete(a.id),
|
|
1186
|
-
onUpdate: (patch) => onUpdate(a.id, patch)
|
|
1148
|
+
onClick: onClose,
|
|
1149
|
+
className: "rounded p-2 text-neutral-400 hover:bg-neutral-800 hover:text-white",
|
|
1150
|
+
"aria-label": "Hide comments",
|
|
1151
|
+
title: "Hide comments (C)",
|
|
1152
|
+
children: /* @__PURE__ */ jsx11(PanelRightClose, { className: "h-4 w-4" })
|
|
1187
1153
|
}
|
|
1188
|
-
)
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1154
|
+
),
|
|
1155
|
+
/* @__PURE__ */ jsxs5("span", { className: "text-right text-sm font-semibold", children: [
|
|
1156
|
+
"Comments (",
|
|
1157
|
+
total,
|
|
1158
|
+
")"
|
|
1159
|
+
] })
|
|
1160
|
+
] }),
|
|
1161
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-h-0 flex-1 overflow-y-auto p-3", children: [
|
|
1162
|
+
annotations.length === 0 && unlinkedAnnotations.length === 0 && /* @__PURE__ */ jsx11("div", { className: "flex flex-col gap-4 px-3 py-6", children: /* @__PURE__ */ jsx11(WorkflowHint, {}) }),
|
|
1163
|
+
open.length > 0 && /* @__PURE__ */ jsxs5("div", { children: [
|
|
1164
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Open" }),
|
|
1165
|
+
open.map((a) => /* @__PURE__ */ jsx11(
|
|
1166
|
+
AnnotationItem,
|
|
1167
|
+
{
|
|
1168
|
+
annotation: a,
|
|
1169
|
+
number: numberFor(a),
|
|
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
|
+
a.id
|
|
1177
|
+
))
|
|
1178
|
+
] }),
|
|
1179
|
+
resolved.length > 0 && /* @__PURE__ */ jsxs5("div", { className: open.length > 0 ? "mt-3" : "", children: [
|
|
1180
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Resolved" }),
|
|
1181
|
+
resolved.map((a) => /* @__PURE__ */ jsx11(
|
|
1182
|
+
AnnotationItem,
|
|
1183
|
+
{
|
|
1184
|
+
annotation: a,
|
|
1185
|
+
number: numberFor(a),
|
|
1186
|
+
isSelected: a.id === selectedId,
|
|
1187
|
+
onSelect: () => onSelect(a.id),
|
|
1188
|
+
onHover: (hovered) => onHover(hovered ? a.id : null),
|
|
1189
|
+
onDelete: () => onDelete(a.id),
|
|
1190
|
+
onUpdate: (patch) => onUpdate(a.id, patch)
|
|
1191
|
+
},
|
|
1192
|
+
a.id
|
|
1193
|
+
))
|
|
1194
|
+
] }),
|
|
1195
|
+
unlinkedAnnotations.length > 0 && /* @__PURE__ */ jsxs5("div", { className: "mt-3", children: [
|
|
1196
|
+
/* @__PURE__ */ jsx11("div", { className: "mb-1.5 px-2 pt-1 text-[11px] font-medium tracking-wider text-neutral-500 uppercase", children: "Removed slides" }),
|
|
1197
|
+
unlinkedAnnotations.map((a, i) => /* @__PURE__ */ jsxs5("div", { children: [
|
|
1198
|
+
/* @__PURE__ */ jsx11("p", { className: "px-2 text-[11px] text-neutral-500", children: a.slideTitle || a.slideId }),
|
|
1199
|
+
/* @__PURE__ */ jsx11(
|
|
1200
|
+
AnnotationItem,
|
|
1201
|
+
{
|
|
1202
|
+
annotation: a,
|
|
1203
|
+
number: i + 1,
|
|
1204
|
+
isSelected: a.id === selectedId,
|
|
1205
|
+
onSelect: () => onSelect(a.id),
|
|
1206
|
+
onHover: (hovered) => onHover(hovered ? a.id : null),
|
|
1207
|
+
onDelete: () => onDelete(a.id),
|
|
1208
|
+
onUpdate: (patch) => onUpdate(a.id, patch)
|
|
1209
|
+
}
|
|
1210
|
+
)
|
|
1211
|
+
] }, a.id))
|
|
1212
|
+
] })
|
|
1213
|
+
] }),
|
|
1214
|
+
annotations.length + unlinkedAnnotations.length > 0 && /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1215
|
+
/* @__PURE__ */ jsx11("div", { className: "border-t border-neutral-800" }),
|
|
1216
|
+
/* @__PURE__ */ jsx11("div", { className: "px-3 py-3", children: /* @__PURE__ */ jsx11(WorkflowHint, {}) })
|
|
1217
|
+
] })
|
|
1218
|
+
]
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1197
1221
|
}
|
|
1198
1222
|
function WorkflowHint() {
|
|
1199
1223
|
return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2", children: [
|
|
@@ -1480,6 +1504,10 @@ function useAnnotations(slidesOrAdapter, adapter) {
|
|
|
1480
1504
|
};
|
|
1481
1505
|
}
|
|
1482
1506
|
|
|
1507
|
+
// src/core/inspector.tsx
|
|
1508
|
+
import { X as X2 } from "lucide-react";
|
|
1509
|
+
import { useId } from "react";
|
|
1510
|
+
|
|
1483
1511
|
// src/core/utils.ts
|
|
1484
1512
|
import { clsx } from "clsx";
|
|
1485
1513
|
import { twMerge } from "tailwind-merge";
|
|
@@ -1487,22 +1515,752 @@ function cn(...inputs) {
|
|
|
1487
1515
|
return twMerge(clsx(inputs));
|
|
1488
1516
|
}
|
|
1489
1517
|
|
|
1518
|
+
// src/core/inspector.tsx
|
|
1519
|
+
import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1520
|
+
function Inspector({
|
|
1521
|
+
mode,
|
|
1522
|
+
visible,
|
|
1523
|
+
editorAvailable,
|
|
1524
|
+
width,
|
|
1525
|
+
overlay,
|
|
1526
|
+
onSelect,
|
|
1527
|
+
onClose,
|
|
1528
|
+
slideSettings,
|
|
1529
|
+
comments
|
|
1530
|
+
}) {
|
|
1531
|
+
const prefix = useId();
|
|
1532
|
+
const tabs = [
|
|
1533
|
+
{ id: "slide", label: "Slide" },
|
|
1534
|
+
...editorAvailable ? [{ id: "editor", label: "Elements" }] : [],
|
|
1535
|
+
{ id: "comments", label: "Comments" }
|
|
1536
|
+
];
|
|
1537
|
+
const closeLabel = mode === "editor" ? "Done editing" : mode === "comments" ? "Hide comments" : "Close slide settings";
|
|
1538
|
+
return /* @__PURE__ */ jsx12(
|
|
1539
|
+
"aside",
|
|
1540
|
+
{
|
|
1541
|
+
"data-ps-inspector": true,
|
|
1542
|
+
"aria-label": "Inspector",
|
|
1543
|
+
"aria-hidden": !visible,
|
|
1544
|
+
inert: !visible,
|
|
1545
|
+
className: cn(
|
|
1546
|
+
"h-full shrink-0 overflow-hidden bg-neutral-950 text-white transition-[width,transform] duration-200 ease-out motion-reduce:transition-none print:hidden",
|
|
1547
|
+
overlay && "fixed inset-y-0 right-0 z-[60]",
|
|
1548
|
+
overlay && !visible && "translate-x-full"
|
|
1549
|
+
),
|
|
1550
|
+
style: { width: visible ? width : 0, maxWidth: overlay ? "90vw" : void 0 },
|
|
1551
|
+
onKeyDown: (event) => {
|
|
1552
|
+
if ((event.key === " " || event.key === "Enter") && event.target.closest("button"))
|
|
1553
|
+
event.stopPropagation();
|
|
1554
|
+
},
|
|
1555
|
+
children: /* @__PURE__ */ jsxs6("div", { className: "flex h-full min-h-0 w-full flex-col border-l border-neutral-800", children: [
|
|
1556
|
+
/* @__PURE__ */ jsxs6(
|
|
1557
|
+
"div",
|
|
1558
|
+
{
|
|
1559
|
+
"data-ps-inspector-header": true,
|
|
1560
|
+
className: "flex shrink-0 items-center gap-1 border-b border-neutral-800 px-2 py-2",
|
|
1561
|
+
children: [
|
|
1562
|
+
/* @__PURE__ */ jsx12(
|
|
1563
|
+
"div",
|
|
1564
|
+
{
|
|
1565
|
+
role: "tablist",
|
|
1566
|
+
"aria-label": "Inspector sections",
|
|
1567
|
+
className: "flex min-w-0 flex-1 gap-1",
|
|
1568
|
+
onKeyDown: (event) => {
|
|
1569
|
+
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
|
1570
|
+
event.preventDefault();
|
|
1571
|
+
event.stopPropagation();
|
|
1572
|
+
const buttons = Array.from(
|
|
1573
|
+
event.currentTarget.querySelectorAll("button")
|
|
1574
|
+
);
|
|
1575
|
+
const index = buttons.indexOf(event.target);
|
|
1576
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (index + (event.key === "ArrowRight" ? 1 : -1) + buttons.length) % buttons.length;
|
|
1577
|
+
buttons[next]?.focus();
|
|
1578
|
+
},
|
|
1579
|
+
children: tabs.map((tab) => /* @__PURE__ */ jsx12(
|
|
1580
|
+
"button",
|
|
1581
|
+
{
|
|
1582
|
+
id: `${prefix}-tab-${tab.id}`,
|
|
1583
|
+
role: "tab",
|
|
1584
|
+
"aria-selected": mode === tab.id,
|
|
1585
|
+
"aria-controls": `${prefix}-panel-${tab.id}`,
|
|
1586
|
+
tabIndex: mode === tab.id ? 0 : -1,
|
|
1587
|
+
className: cn(
|
|
1588
|
+
"rounded-md px-2 py-1.5 text-xs font-medium hover:bg-neutral-800 focus-visible:outline-2 focus-visible:outline-orange-500",
|
|
1589
|
+
mode === tab.id ? "bg-neutral-800 text-white" : "text-neutral-400"
|
|
1590
|
+
),
|
|
1591
|
+
onClick: () => onSelect(tab.id),
|
|
1592
|
+
children: tab.label
|
|
1593
|
+
},
|
|
1594
|
+
tab.id
|
|
1595
|
+
))
|
|
1596
|
+
}
|
|
1597
|
+
),
|
|
1598
|
+
/* @__PURE__ */ jsx12(
|
|
1599
|
+
"button",
|
|
1600
|
+
{
|
|
1601
|
+
onClick: onClose,
|
|
1602
|
+
"aria-label": closeLabel,
|
|
1603
|
+
title: closeLabel,
|
|
1604
|
+
className: "shrink-0 rounded-md p-1.5 text-neutral-400 hover:bg-neutral-800 hover:text-white focus-visible:outline-2 focus-visible:outline-orange-500",
|
|
1605
|
+
children: /* @__PURE__ */ jsx12(X2, { size: 16 })
|
|
1606
|
+
}
|
|
1607
|
+
)
|
|
1608
|
+
]
|
|
1609
|
+
}
|
|
1610
|
+
),
|
|
1611
|
+
/* @__PURE__ */ jsx12(
|
|
1612
|
+
"div",
|
|
1613
|
+
{
|
|
1614
|
+
id: `${prefix}-panel-slide`,
|
|
1615
|
+
role: "tabpanel",
|
|
1616
|
+
"aria-labelledby": `${prefix}-tab-slide`,
|
|
1617
|
+
hidden: mode !== "slide",
|
|
1618
|
+
className: "min-h-0 flex-1",
|
|
1619
|
+
children: slideSettings
|
|
1620
|
+
}
|
|
1621
|
+
),
|
|
1622
|
+
/* @__PURE__ */ jsx12(
|
|
1623
|
+
"div",
|
|
1624
|
+
{
|
|
1625
|
+
id: `${prefix}-panel-editor`,
|
|
1626
|
+
role: "tabpanel",
|
|
1627
|
+
"aria-labelledby": `${prefix}-tab-editor`,
|
|
1628
|
+
hidden: mode !== "editor",
|
|
1629
|
+
className: "min-h-0 flex-1",
|
|
1630
|
+
children: /* @__PURE__ */ jsx12(
|
|
1631
|
+
"div",
|
|
1632
|
+
{
|
|
1633
|
+
"data-ps-editor-panel": true,
|
|
1634
|
+
"data-open": mode === "editor" && visible,
|
|
1635
|
+
"aria-hidden": mode !== "editor" || !visible,
|
|
1636
|
+
inert: mode !== "editor" || !visible,
|
|
1637
|
+
className: "h-full w-full"
|
|
1638
|
+
}
|
|
1639
|
+
)
|
|
1640
|
+
}
|
|
1641
|
+
),
|
|
1642
|
+
/* @__PURE__ */ jsx12(
|
|
1643
|
+
"div",
|
|
1644
|
+
{
|
|
1645
|
+
id: `${prefix}-panel-comments`,
|
|
1646
|
+
role: "tabpanel",
|
|
1647
|
+
"aria-labelledby": `${prefix}-tab-comments`,
|
|
1648
|
+
hidden: mode !== "comments",
|
|
1649
|
+
className: "min-h-0 flex-1",
|
|
1650
|
+
children: comments
|
|
1651
|
+
}
|
|
1652
|
+
)
|
|
1653
|
+
] })
|
|
1654
|
+
}
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// src/core/loop/config.mjs
|
|
1659
|
+
var DEFAULT_LOOP_CONFIG = { version: 1, defaultDuration: 5, slides: {} };
|
|
1660
|
+
function validateLoopConfig(value) {
|
|
1661
|
+
if (!value || value.version !== 1 || !validDuration(value.defaultDuration) || !value.slides || typeof value.slides !== "object" || Array.isArray(value.slides)) {
|
|
1662
|
+
throw new Error("Invalid loop settings");
|
|
1663
|
+
}
|
|
1664
|
+
const slides = /* @__PURE__ */ Object.create(null);
|
|
1665
|
+
if (Object.keys(value.slides).length > 1e3) throw new Error("Too many slides");
|
|
1666
|
+
for (const [id, item] of Object.entries(value.slides)) {
|
|
1667
|
+
if (!id || id.length > 256 || !item || typeof item !== "object" || item.excluded !== void 0 && typeof item.excluded !== "boolean" || item.steps !== void 0 && (!Number.isInteger(item.steps) || item.steps < 0 || item.steps > 1e3) || item.dwell !== void 0 && (!Array.isArray(item.dwell) || item.dwell.length > 1001 || !item.dwell.every((duration) => duration === null || validDuration(duration))))
|
|
1668
|
+
throw new Error(`Invalid timing for ${id}`);
|
|
1669
|
+
slides[id] = {
|
|
1670
|
+
...item.excluded !== void 0 ? { excluded: item.excluded } : {},
|
|
1671
|
+
...item.steps !== void 0 ? { steps: item.steps } : {},
|
|
1672
|
+
...item.dwell !== void 0 ? { dwell: [...item.dwell] } : {}
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
return { version: 1, defaultDuration: value.defaultDuration, slides };
|
|
1676
|
+
}
|
|
1677
|
+
function validDuration(value) {
|
|
1678
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0.1 && value <= 3600;
|
|
1679
|
+
}
|
|
1680
|
+
function loopSlideKey(slide, index) {
|
|
1681
|
+
return slide.id || String(index + 1);
|
|
1682
|
+
}
|
|
1683
|
+
function stateDuration(config, slide, index, step) {
|
|
1684
|
+
return config.slides[loopSlideKey(slide, index)]?.dwell?.[step] ?? config.defaultDuration;
|
|
1685
|
+
}
|
|
1686
|
+
function includedSlides(config, slides) {
|
|
1687
|
+
return slides.flatMap(
|
|
1688
|
+
(slide, index) => config.slides[loopSlideKey(slide, index)]?.excluded ? [] : [index]
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
function loopDuration(config, slides) {
|
|
1692
|
+
return includedSlides(config, slides).reduce((total, index) => {
|
|
1693
|
+
const slide = slides[index];
|
|
1694
|
+
for (let step = 0; step <= slide.steps; step++)
|
|
1695
|
+
total += stateDuration(config, slide, index, step);
|
|
1696
|
+
return total;
|
|
1697
|
+
}, 0);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// src/core/loop/context.tsx
|
|
1701
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
1702
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1703
|
+
var LoopContext = createContext3(DEFAULT_LOOP_CONFIG);
|
|
1704
|
+
function LoopConfigProvider({
|
|
1705
|
+
config,
|
|
1706
|
+
children
|
|
1707
|
+
}) {
|
|
1708
|
+
return /* @__PURE__ */ jsx13(LoopContext.Provider, { value: config, children });
|
|
1709
|
+
}
|
|
1710
|
+
var useDefaultLoopConfig = () => useContext3(LoopContext);
|
|
1711
|
+
|
|
1712
|
+
// src/core/loop/loop-settings.tsx
|
|
1713
|
+
import { Check as Check2, Download, Play, RotateCcw as RotateCcw2 } from "lucide-react";
|
|
1714
|
+
import { useId as useId2, useState as useState7 } from "react";
|
|
1715
|
+
import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1716
|
+
var button = "rounded-md border border-neutral-700 px-2.5 py-2 text-xs hover:bg-neutral-800 disabled:opacity-40 focus-visible:outline-2 focus-visible:outline-orange-500";
|
|
1717
|
+
function DurationInput({
|
|
1718
|
+
value,
|
|
1719
|
+
label,
|
|
1720
|
+
onCommit,
|
|
1721
|
+
onFocus,
|
|
1722
|
+
id
|
|
1723
|
+
}) {
|
|
1724
|
+
return /* @__PURE__ */ jsxs7("span", { className: "inline-flex w-20 shrink-0 items-center gap-1 rounded-md border border-neutral-700 bg-neutral-950 px-2.5 focus-within:border-orange-500 focus-within:outline-2 focus-within:outline-orange-500/30", children: [
|
|
1725
|
+
/* @__PURE__ */ jsx14(
|
|
1726
|
+
"input",
|
|
1727
|
+
{
|
|
1728
|
+
id,
|
|
1729
|
+
"aria-label": label,
|
|
1730
|
+
className: "min-w-0 w-full bg-transparent py-2 text-right text-sm tabular-nums text-white outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none",
|
|
1731
|
+
style: { colorScheme: "dark" },
|
|
1732
|
+
type: "number",
|
|
1733
|
+
min: "0.1",
|
|
1734
|
+
max: "3600",
|
|
1735
|
+
step: "0.1",
|
|
1736
|
+
defaultValue: value,
|
|
1737
|
+
onFocus,
|
|
1738
|
+
onBlur: (event) => {
|
|
1739
|
+
const next = event.target.valueAsNumber;
|
|
1740
|
+
if (Number.isFinite(next) && next >= 0.1 && next <= 3600) {
|
|
1741
|
+
if (next !== value) onCommit(next);
|
|
1742
|
+
} else event.target.value = String(value);
|
|
1743
|
+
}
|
|
1744
|
+
},
|
|
1745
|
+
value
|
|
1746
|
+
),
|
|
1747
|
+
/* @__PURE__ */ jsx14("span", { "aria-hidden": "true", className: "text-xs text-neutral-500", children: "s" })
|
|
1748
|
+
] });
|
|
1749
|
+
}
|
|
1750
|
+
function LoopSettings({
|
|
1751
|
+
slides,
|
|
1752
|
+
currentSlide,
|
|
1753
|
+
animationStep,
|
|
1754
|
+
config,
|
|
1755
|
+
onChange,
|
|
1756
|
+
onStepChange,
|
|
1757
|
+
onSave,
|
|
1758
|
+
onStart,
|
|
1759
|
+
onRevert,
|
|
1760
|
+
dirty
|
|
1761
|
+
}) {
|
|
1762
|
+
const defaultDurationId = useId2();
|
|
1763
|
+
const [message, setMessage] = useState7("");
|
|
1764
|
+
const [saving, setSaving] = useState7(false);
|
|
1765
|
+
const slide = slides[currentSlide];
|
|
1766
|
+
const key = slide ? loopSlideKey(slide, currentSlide) : "";
|
|
1767
|
+
const settings = config.slides[key];
|
|
1768
|
+
const changedSteps = settings?.steps !== void 0 && settings.steps !== slide?.steps;
|
|
1769
|
+
const updateDuration = (step, value) => {
|
|
1770
|
+
if (!slide) return;
|
|
1771
|
+
const dwell = Array.from({ length: slide.steps + 1 }, (_, i) => settings?.dwell?.[i] ?? null);
|
|
1772
|
+
dwell[step] = value;
|
|
1773
|
+
onChange({
|
|
1774
|
+
...config,
|
|
1775
|
+
slides: { ...config.slides, [key]: { ...settings, steps: slide.steps, dwell } }
|
|
1776
|
+
});
|
|
1777
|
+
};
|
|
1778
|
+
const save = async () => {
|
|
1779
|
+
setSaving(true);
|
|
1780
|
+
try {
|
|
1781
|
+
setMessage(await onSave(validateLoopConfig(config)));
|
|
1782
|
+
} catch (error) {
|
|
1783
|
+
setMessage(error instanceof Error ? error.message : "Could not save loop settings");
|
|
1784
|
+
} finally {
|
|
1785
|
+
setSaving(false);
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
1788
|
+
const download = () => {
|
|
1789
|
+
const url = URL.createObjectURL(
|
|
1790
|
+
new Blob([JSON.stringify(config, null, 2) + "\n"], { type: "application/json" })
|
|
1791
|
+
);
|
|
1792
|
+
const a = document.createElement("a");
|
|
1793
|
+
a.href = url;
|
|
1794
|
+
a.download = "loop-config.json";
|
|
1795
|
+
a.click();
|
|
1796
|
+
URL.revokeObjectURL(url);
|
|
1797
|
+
setMessage(
|
|
1798
|
+
"Place loop-config.json in the project folder to include these settings in builds and publishing."
|
|
1799
|
+
);
|
|
1800
|
+
};
|
|
1801
|
+
return /* @__PURE__ */ jsxs7(
|
|
1802
|
+
"section",
|
|
1803
|
+
{
|
|
1804
|
+
"aria-label": "Slide settings",
|
|
1805
|
+
className: "flex h-full min-h-0 flex-col text-sm text-white",
|
|
1806
|
+
children: [
|
|
1807
|
+
/* @__PURE__ */ jsxs7("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children: [
|
|
1808
|
+
slide && /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
1809
|
+
/* @__PURE__ */ jsxs7("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-neutral-500", children: [
|
|
1810
|
+
"Slide ",
|
|
1811
|
+
currentSlide + 1
|
|
1812
|
+
] }),
|
|
1813
|
+
/* @__PURE__ */ jsx14("h2", { className: "mt-1 truncate text-base font-semibold", children: slide.title || slide.id || `Slide ${currentSlide + 1}` }),
|
|
1814
|
+
/* @__PURE__ */ jsx14("p", { className: "mt-1 text-xs text-neutral-400", children: "Select slides in the thumbnail rail. Select a state below to preview it on the canvas." }),
|
|
1815
|
+
/* @__PURE__ */ jsxs7("label", { className: "mt-5 flex items-center justify-between gap-3 border-b border-neutral-800 pb-4", children: [
|
|
1816
|
+
"Include in loop",
|
|
1817
|
+
/* @__PURE__ */ jsxs7("span", { className: "relative flex h-5 w-5 shrink-0 items-center justify-center", children: [
|
|
1818
|
+
/* @__PURE__ */ jsx14(
|
|
1819
|
+
"input",
|
|
1820
|
+
{
|
|
1821
|
+
"aria-label": "Include current slide in loop",
|
|
1822
|
+
className: "peer h-5 w-5 cursor-pointer appearance-none rounded border border-neutral-600 bg-neutral-950 transition-colors checked:border-orange-500 checked:bg-orange-500 hover:border-neutral-400 checked:hover:border-orange-400 checked:hover:bg-orange-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-500",
|
|
1823
|
+
type: "checkbox",
|
|
1824
|
+
checked: !settings?.excluded,
|
|
1825
|
+
onChange: (event) => onChange({
|
|
1826
|
+
...config,
|
|
1827
|
+
slides: {
|
|
1828
|
+
...config.slides,
|
|
1829
|
+
[key]: { ...settings, excluded: !event.target.checked }
|
|
1830
|
+
}
|
|
1831
|
+
})
|
|
1832
|
+
}
|
|
1833
|
+
),
|
|
1834
|
+
/* @__PURE__ */ jsx14(
|
|
1835
|
+
Check2,
|
|
1836
|
+
{
|
|
1837
|
+
"aria-hidden": "true",
|
|
1838
|
+
size: 14,
|
|
1839
|
+
strokeWidth: 3,
|
|
1840
|
+
className: "pointer-events-none absolute text-white opacity-0 peer-checked:opacity-100"
|
|
1841
|
+
}
|
|
1842
|
+
)
|
|
1843
|
+
] })
|
|
1844
|
+
] }),
|
|
1845
|
+
/* @__PURE__ */ jsxs7("h3", { className: "mb-2 mt-4 text-xs font-semibold text-neutral-300", children: [
|
|
1846
|
+
"State timings ",
|
|
1847
|
+
/* @__PURE__ */ jsx14("span", { className: "font-normal text-neutral-500", children: "\xB7 seconds" })
|
|
1848
|
+
] }),
|
|
1849
|
+
changedSteps && /* @__PURE__ */ jsx14("p", { role: "status", className: "mb-3 text-xs text-amber-300", children: "This slide's step count changed. Review its timings; new states use the default." }),
|
|
1850
|
+
/* @__PURE__ */ jsx14("div", { className: "space-y-1.5", "aria-label": "Slide states", children: Array.from({ length: slide.steps + 1 }, (_, step) => {
|
|
1851
|
+
const label = step === 0 ? "Initial view" : `Step ${step}`;
|
|
1852
|
+
const inherited = settings?.dwell?.[step] == null;
|
|
1853
|
+
return /* @__PURE__ */ jsxs7(
|
|
1854
|
+
"div",
|
|
1855
|
+
{
|
|
1856
|
+
className: `flex items-center gap-1 rounded-md border p-1 ${animationStep === step ? "border-orange-500/50 bg-orange-500/5" : "border-transparent"}`,
|
|
1857
|
+
children: [
|
|
1858
|
+
/* @__PURE__ */ jsxs7(
|
|
1859
|
+
"button",
|
|
1860
|
+
{
|
|
1861
|
+
"aria-label": label,
|
|
1862
|
+
"aria-pressed": animationStep === step,
|
|
1863
|
+
className: "min-w-0 flex-1 rounded px-1 py-1.5 text-left text-xs hover:bg-neutral-800",
|
|
1864
|
+
onClick: () => onStepChange(step),
|
|
1865
|
+
children: [
|
|
1866
|
+
label,
|
|
1867
|
+
/* @__PURE__ */ jsx14("span", { className: "mt-0.5 block text-[10px] text-neutral-500", children: inherited ? "Default duration" : "Custom duration" })
|
|
1868
|
+
]
|
|
1869
|
+
}
|
|
1870
|
+
),
|
|
1871
|
+
/* @__PURE__ */ jsx14(
|
|
1872
|
+
DurationInput,
|
|
1873
|
+
{
|
|
1874
|
+
value: stateDuration(config, slide, currentSlide, step),
|
|
1875
|
+
label: `${label} duration in seconds`,
|
|
1876
|
+
onFocus: () => onStepChange(step),
|
|
1877
|
+
onCommit: (value) => updateDuration(step, value)
|
|
1878
|
+
}
|
|
1879
|
+
),
|
|
1880
|
+
/* @__PURE__ */ jsx14(
|
|
1881
|
+
"button",
|
|
1882
|
+
{
|
|
1883
|
+
className: "rounded p-1 text-neutral-500 hover:text-white disabled:opacity-30",
|
|
1884
|
+
disabled: inherited,
|
|
1885
|
+
"aria-label": `Use default for ${label.toLowerCase()}`,
|
|
1886
|
+
title: "Use default duration",
|
|
1887
|
+
onClick: () => updateDuration(step, null),
|
|
1888
|
+
children: /* @__PURE__ */ jsx14(RotateCcw2, { size: 13 })
|
|
1889
|
+
}
|
|
1890
|
+
)
|
|
1891
|
+
]
|
|
1892
|
+
},
|
|
1893
|
+
`${key}:${step}`
|
|
1894
|
+
);
|
|
1895
|
+
}) }),
|
|
1896
|
+
/* @__PURE__ */ jsx14("p", { className: "mt-3 text-xs leading-relaxed text-neutral-500", children: "Durations include reveal animations. Allow enough time for videos and animations that play by themselves." }),
|
|
1897
|
+
/* @__PURE__ */ jsx14(
|
|
1898
|
+
"button",
|
|
1899
|
+
{
|
|
1900
|
+
className: `${button} mt-3 w-full`,
|
|
1901
|
+
onClick: () => {
|
|
1902
|
+
const rest = { ...settings };
|
|
1903
|
+
delete rest.dwell;
|
|
1904
|
+
delete rest.steps;
|
|
1905
|
+
onChange({ ...config, slides: { ...config.slides, [key]: rest } });
|
|
1906
|
+
},
|
|
1907
|
+
children: "Use default for this slide"
|
|
1908
|
+
}
|
|
1909
|
+
)
|
|
1910
|
+
] }),
|
|
1911
|
+
/* @__PURE__ */ jsxs7("details", { className: "mt-5 border-t border-neutral-800 pt-4", open: true, children: [
|
|
1912
|
+
/* @__PURE__ */ jsx14("summary", { className: "cursor-pointer text-xs font-semibold text-neutral-300", children: "Loop defaults" }),
|
|
1913
|
+
/* @__PURE__ */ jsxs7(
|
|
1914
|
+
"label",
|
|
1915
|
+
{
|
|
1916
|
+
htmlFor: defaultDurationId,
|
|
1917
|
+
className: "mt-3 flex items-center justify-between gap-2 text-xs text-neutral-400",
|
|
1918
|
+
children: [
|
|
1919
|
+
"Seconds per state",
|
|
1920
|
+
/* @__PURE__ */ jsx14(
|
|
1921
|
+
DurationInput,
|
|
1922
|
+
{
|
|
1923
|
+
id: defaultDurationId,
|
|
1924
|
+
value: config.defaultDuration,
|
|
1925
|
+
label: "Default duration in seconds",
|
|
1926
|
+
onCommit: (value) => onChange({ ...config, defaultDuration: value })
|
|
1927
|
+
}
|
|
1928
|
+
)
|
|
1929
|
+
]
|
|
1930
|
+
}
|
|
1931
|
+
),
|
|
1932
|
+
/* @__PURE__ */ jsxs7("p", { className: "mt-3 text-xs text-neutral-500", children: [
|
|
1933
|
+
includedSlides(config, slides).length,
|
|
1934
|
+
" slides \xB7",
|
|
1935
|
+
" ",
|
|
1936
|
+
loopDuration(config, slides).toFixed(1),
|
|
1937
|
+
"s + slide transitions"
|
|
1938
|
+
] })
|
|
1939
|
+
] })
|
|
1940
|
+
] }),
|
|
1941
|
+
/* @__PURE__ */ jsxs7("footer", { className: "shrink-0 space-y-2 border-t border-neutral-800 p-3", children: [
|
|
1942
|
+
/* @__PURE__ */ jsx14("p", { role: "status", className: "text-xs text-neutral-400", children: dirty ? "Unsaved loop settings" : message || "Loop settings saved" }),
|
|
1943
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex gap-2", children: [
|
|
1944
|
+
/* @__PURE__ */ jsx14(
|
|
1945
|
+
"button",
|
|
1946
|
+
{
|
|
1947
|
+
className: `${button} flex-1`,
|
|
1948
|
+
disabled: !dirty || saving,
|
|
1949
|
+
onClick: () => {
|
|
1950
|
+
onRevert();
|
|
1951
|
+
setMessage("");
|
|
1952
|
+
},
|
|
1953
|
+
children: "Revert"
|
|
1954
|
+
}
|
|
1955
|
+
),
|
|
1956
|
+
/* @__PURE__ */ jsx14(
|
|
1957
|
+
"button",
|
|
1958
|
+
{
|
|
1959
|
+
className: `${button} flex-1 border-orange-500/50 text-orange-400`,
|
|
1960
|
+
disabled: saving,
|
|
1961
|
+
onClick: () => void save(),
|
|
1962
|
+
children: saving ? "Saving\u2026" : "Save settings"
|
|
1963
|
+
}
|
|
1964
|
+
)
|
|
1965
|
+
] }),
|
|
1966
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex gap-2", children: [
|
|
1967
|
+
/* @__PURE__ */ jsx14(
|
|
1968
|
+
"button",
|
|
1969
|
+
{
|
|
1970
|
+
className: `${button} flex items-center justify-center gap-1.5`,
|
|
1971
|
+
"aria-label": "Download loop settings",
|
|
1972
|
+
onClick: download,
|
|
1973
|
+
children: /* @__PURE__ */ jsx14(Download, { size: 14 })
|
|
1974
|
+
}
|
|
1975
|
+
),
|
|
1976
|
+
/* @__PURE__ */ jsxs7(
|
|
1977
|
+
"button",
|
|
1978
|
+
{
|
|
1979
|
+
className: `${button} flex flex-1 items-center justify-center gap-1.5`,
|
|
1980
|
+
disabled: !includedSlides(config, slides).length || saving,
|
|
1981
|
+
onClick: () => onStart(validateLoopConfig(config)),
|
|
1982
|
+
children: [
|
|
1983
|
+
/* @__PURE__ */ jsx14(Play, { size: 14 }),
|
|
1984
|
+
" Start loop"
|
|
1985
|
+
]
|
|
1986
|
+
}
|
|
1987
|
+
)
|
|
1988
|
+
] })
|
|
1989
|
+
] })
|
|
1990
|
+
]
|
|
1991
|
+
}
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
// src/core/loop/use-loop-playback.ts
|
|
1996
|
+
import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef6, useState as useState8 } from "react";
|
|
1997
|
+
function useLoopPlayback({
|
|
1998
|
+
config,
|
|
1999
|
+
slides,
|
|
2000
|
+
currentSlide,
|
|
2001
|
+
animationStep,
|
|
2002
|
+
isTransitioning,
|
|
2003
|
+
enabled,
|
|
2004
|
+
advance,
|
|
2005
|
+
goBack,
|
|
2006
|
+
restartAtSlide
|
|
2007
|
+
}) {
|
|
2008
|
+
const [paused, setPaused] = useState8(false);
|
|
2009
|
+
const [hidden, setHidden] = useState8(() => typeof document !== "undefined" && document.hidden);
|
|
2010
|
+
const [epoch, setEpoch] = useState8(0);
|
|
2011
|
+
const [revision, setRevision] = useState8(0);
|
|
2012
|
+
const remaining = useRef6(void 0);
|
|
2013
|
+
const identity = useRef6("");
|
|
2014
|
+
const included = useMemo4(() => includedSlides(config, slides), [config, slides]);
|
|
2015
|
+
useEffect7(() => {
|
|
2016
|
+
const onVisibility = () => setHidden(document.hidden);
|
|
2017
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
2018
|
+
return () => document.removeEventListener("visibilitychange", onVisibility);
|
|
2019
|
+
}, []);
|
|
2020
|
+
const restart = useCallback5(
|
|
2021
|
+
(index) => {
|
|
2022
|
+
setEpoch((value) => value + 1);
|
|
2023
|
+
remaining.current = void 0;
|
|
2024
|
+
restartAtSlide(index);
|
|
2025
|
+
},
|
|
2026
|
+
[restartAtSlide]
|
|
2027
|
+
);
|
|
2028
|
+
const resetTimer = useCallback5(() => {
|
|
2029
|
+
remaining.current = void 0;
|
|
2030
|
+
setRevision((value) => value + 1);
|
|
2031
|
+
}, []);
|
|
2032
|
+
const move = (direction) => {
|
|
2033
|
+
resetTimer();
|
|
2034
|
+
if (isTransitioning || included.length === 0) return;
|
|
2035
|
+
if (direction === 1 && animationStep < (slides[currentSlide]?.steps ?? 0)) advance();
|
|
2036
|
+
else if (direction === -1 && animationStep > 0) goBack();
|
|
2037
|
+
else {
|
|
2038
|
+
const position = included.indexOf(currentSlide);
|
|
2039
|
+
restart(included[(position + direction + included.length) % included.length]);
|
|
2040
|
+
}
|
|
2041
|
+
};
|
|
2042
|
+
useEffect7(() => {
|
|
2043
|
+
if (!enabled) {
|
|
2044
|
+
remaining.current = void 0;
|
|
2045
|
+
setPaused(false);
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
const key = `${currentSlide}:${animationStep}:${epoch}:${revision}`;
|
|
2049
|
+
if (identity.current !== key) {
|
|
2050
|
+
identity.current = key;
|
|
2051
|
+
remaining.current = void 0;
|
|
2052
|
+
}
|
|
2053
|
+
if (paused || hidden || isTransitioning || included.length === 0) return;
|
|
2054
|
+
const slide = slides[currentSlide];
|
|
2055
|
+
if (!slide) return;
|
|
2056
|
+
const duration = remaining.current ?? stateDuration(config, slide, currentSlide, animationStep) * 1e3;
|
|
2057
|
+
const deadline = performance.now() + duration;
|
|
2058
|
+
const timer = window.setTimeout(() => {
|
|
2059
|
+
remaining.current = void 0;
|
|
2060
|
+
if (animationStep < slide.steps) advance();
|
|
2061
|
+
else {
|
|
2062
|
+
const position = included.indexOf(currentSlide);
|
|
2063
|
+
restart(included[(position + 1) % included.length]);
|
|
2064
|
+
}
|
|
2065
|
+
}, duration);
|
|
2066
|
+
return () => {
|
|
2067
|
+
window.clearTimeout(timer);
|
|
2068
|
+
remaining.current = Math.max(0, deadline - performance.now());
|
|
2069
|
+
};
|
|
2070
|
+
}, [
|
|
2071
|
+
enabled,
|
|
2072
|
+
paused,
|
|
2073
|
+
hidden,
|
|
2074
|
+
isTransitioning,
|
|
2075
|
+
currentSlide,
|
|
2076
|
+
animationStep,
|
|
2077
|
+
epoch,
|
|
2078
|
+
revision,
|
|
2079
|
+
config,
|
|
2080
|
+
slides,
|
|
2081
|
+
advance,
|
|
2082
|
+
restart,
|
|
2083
|
+
included
|
|
2084
|
+
]);
|
|
2085
|
+
return { paused, setPaused, epoch, restart, move };
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
// src/core/toolbar-action.tsx
|
|
2089
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
2090
|
+
function ToolbarAction({
|
|
2091
|
+
label,
|
|
2092
|
+
selected,
|
|
2093
|
+
title = label,
|
|
2094
|
+
children,
|
|
2095
|
+
...props
|
|
2096
|
+
}) {
|
|
2097
|
+
return /* @__PURE__ */ jsx15(
|
|
2098
|
+
"button",
|
|
2099
|
+
{
|
|
2100
|
+
...props,
|
|
2101
|
+
type: "button",
|
|
2102
|
+
"aria-label": label,
|
|
2103
|
+
"aria-pressed": selected,
|
|
2104
|
+
title,
|
|
2105
|
+
className: cn(
|
|
2106
|
+
"relative inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-transparent transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#FF6B35] disabled:cursor-default disabled:opacity-35 [&>svg]:h-4 [&>svg]:w-4 [&>svg]:shrink-0",
|
|
2107
|
+
selected ? "border-[#FF6B35]/50 bg-[#FF6B35]/15 text-[#FF6B35] enabled:hover:border-[#FF6B35] enabled:hover:bg-[#FF6B35]/25" : "text-neutral-400 enabled:hover:bg-neutral-800 enabled:hover:text-white"
|
|
2108
|
+
),
|
|
2109
|
+
children
|
|
2110
|
+
}
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// src/core/view-menu.tsx
|
|
2115
|
+
import { Check as Check3, ChevronDown, Grid3X3, List, Monitor } from "lucide-react";
|
|
2116
|
+
import { useEffect as useEffect8, useId as useId3, useLayoutEffect, useRef as useRef7, useState as useState9 } from "react";
|
|
2117
|
+
import { jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2118
|
+
var views = [
|
|
2119
|
+
{ mode: "slide", label: "Presentation view", shortcut: "V", icon: Monitor },
|
|
2120
|
+
{ mode: "list", label: "List view", shortcut: "L", icon: List },
|
|
2121
|
+
{ mode: "grid", label: "Grid view", shortcut: "G", icon: Grid3X3 }
|
|
2122
|
+
];
|
|
2123
|
+
function ViewMenu({
|
|
2124
|
+
value,
|
|
2125
|
+
onChange,
|
|
2126
|
+
disabled
|
|
2127
|
+
}) {
|
|
2128
|
+
const [open, setOpen] = useState9(false);
|
|
2129
|
+
const id = useId3();
|
|
2130
|
+
const root = useRef7(null);
|
|
2131
|
+
const trigger = useRef7(null);
|
|
2132
|
+
const active = views.find((view) => view.mode === value);
|
|
2133
|
+
const Icon = active.icon;
|
|
2134
|
+
useLayoutEffect(() => {
|
|
2135
|
+
if (open) root.current?.querySelector('[aria-checked="true"]')?.focus();
|
|
2136
|
+
}, [open]);
|
|
2137
|
+
useEffect8(() => {
|
|
2138
|
+
if (!open) return;
|
|
2139
|
+
const close = (event) => {
|
|
2140
|
+
if (!root.current?.contains(event.target)) setOpen(false);
|
|
2141
|
+
};
|
|
2142
|
+
document.addEventListener("pointerdown", close);
|
|
2143
|
+
return () => document.removeEventListener("pointerdown", close);
|
|
2144
|
+
}, [open]);
|
|
2145
|
+
return /* @__PURE__ */ jsxs8(
|
|
2146
|
+
"div",
|
|
2147
|
+
{
|
|
2148
|
+
role: "group",
|
|
2149
|
+
"aria-label": "View selector",
|
|
2150
|
+
ref: root,
|
|
2151
|
+
className: "relative flex",
|
|
2152
|
+
onBlur: (event) => {
|
|
2153
|
+
const next = event.relatedTarget;
|
|
2154
|
+
if (next && !event.currentTarget.contains(next)) setOpen(false);
|
|
2155
|
+
},
|
|
2156
|
+
onKeyDown: (event) => {
|
|
2157
|
+
event.stopPropagation();
|
|
2158
|
+
if (event.key === "Escape") {
|
|
2159
|
+
setOpen(false);
|
|
2160
|
+
trigger.current?.focus();
|
|
2161
|
+
} else if (open && event.key === "Tab") {
|
|
2162
|
+
trigger.current?.focus();
|
|
2163
|
+
setOpen(false);
|
|
2164
|
+
} else if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
|
|
2165
|
+
event.preventDefault();
|
|
2166
|
+
if (!open) {
|
|
2167
|
+
setOpen(true);
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
const items = Array.from(
|
|
2171
|
+
root.current.querySelectorAll('[role="menuitemradio"]')
|
|
2172
|
+
);
|
|
2173
|
+
const index = items.indexOf(document.activeElement);
|
|
2174
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? items.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + items.length) % items.length;
|
|
2175
|
+
items[next]?.focus();
|
|
2176
|
+
}
|
|
2177
|
+
},
|
|
2178
|
+
children: [
|
|
2179
|
+
/* @__PURE__ */ jsxs8(
|
|
2180
|
+
"button",
|
|
2181
|
+
{
|
|
2182
|
+
ref: trigger,
|
|
2183
|
+
disabled,
|
|
2184
|
+
"aria-label": "View options",
|
|
2185
|
+
title: `View options \xB7 ${active.label}`,
|
|
2186
|
+
"aria-haspopup": "menu",
|
|
2187
|
+
"aria-expanded": open,
|
|
2188
|
+
"aria-controls": open ? id : void 0,
|
|
2189
|
+
className: "inline-flex h-8 items-center gap-1.5 rounded-md bg-neutral-800 px-2 text-white hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-orange-500",
|
|
2190
|
+
onClick: () => setOpen((previous) => !previous),
|
|
2191
|
+
children: [
|
|
2192
|
+
/* @__PURE__ */ jsx16(Icon, { size: 16 }),
|
|
2193
|
+
/* @__PURE__ */ jsx16(ChevronDown, { size: 12 })
|
|
2194
|
+
]
|
|
2195
|
+
}
|
|
2196
|
+
),
|
|
2197
|
+
open && /* @__PURE__ */ jsx16(
|
|
2198
|
+
"div",
|
|
2199
|
+
{
|
|
2200
|
+
id,
|
|
2201
|
+
role: "menu",
|
|
2202
|
+
"aria-label": "View options",
|
|
2203
|
+
className: "absolute left-0 top-10 w-56 rounded-lg border border-neutral-700 bg-neutral-950 p-1 text-sm text-white",
|
|
2204
|
+
children: views.map(({ mode, label, shortcut, icon: OptionIcon }) => /* @__PURE__ */ jsxs8(
|
|
2205
|
+
"button",
|
|
2206
|
+
{
|
|
2207
|
+
role: "menuitemradio",
|
|
2208
|
+
"aria-checked": mode === value,
|
|
2209
|
+
tabIndex: -1,
|
|
2210
|
+
className: cn(
|
|
2211
|
+
"flex w-full items-center gap-2 rounded px-2 py-2 text-left text-neutral-300 hover:bg-neutral-800 focus:bg-neutral-800 focus:outline-none",
|
|
2212
|
+
mode === value && "text-white"
|
|
2213
|
+
),
|
|
2214
|
+
onClick: () => {
|
|
2215
|
+
onChange(mode);
|
|
2216
|
+
setOpen(false);
|
|
2217
|
+
trigger.current?.focus();
|
|
2218
|
+
},
|
|
2219
|
+
children: [
|
|
2220
|
+
/* @__PURE__ */ jsx16(OptionIcon, { size: 16 }),
|
|
2221
|
+
/* @__PURE__ */ jsx16("span", { className: "flex-1", children: label }),
|
|
2222
|
+
/* @__PURE__ */ jsx16("span", { "aria-hidden": "true", className: "text-xs text-neutral-500", children: shortcut }),
|
|
2223
|
+
/* @__PURE__ */ jsx16(Check3, { size: 14, className: mode === value ? "text-orange-400" : "invisible" })
|
|
2224
|
+
]
|
|
2225
|
+
},
|
|
2226
|
+
mode
|
|
2227
|
+
))
|
|
2228
|
+
}
|
|
2229
|
+
)
|
|
2230
|
+
]
|
|
2231
|
+
}
|
|
2232
|
+
);
|
|
2233
|
+
}
|
|
2234
|
+
|
|
1490
2235
|
// src/core/slide-deck.tsx
|
|
1491
|
-
import { Fragment as
|
|
2236
|
+
import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1492
2237
|
var NARROW_VIEWPORT_WIDTH = 768;
|
|
1493
2238
|
var DEFAULT_THUMBNAIL_WIDTH = 240;
|
|
1494
2239
|
var MIN_THUMBNAIL_WIDTH = 176;
|
|
1495
2240
|
var MAX_THUMBNAIL_WIDTH = 480;
|
|
1496
2241
|
var THUMBNAIL_WIDTH_STORAGE_KEY = "promptslide:thumbnail-width";
|
|
2242
|
+
function loadLoopConfig(base, storageKey) {
|
|
2243
|
+
let fallback = DEFAULT_LOOP_CONFIG;
|
|
2244
|
+
try {
|
|
2245
|
+
fallback = validateLoopConfig(base);
|
|
2246
|
+
} catch {
|
|
2247
|
+
}
|
|
2248
|
+
try {
|
|
2249
|
+
const saved = JSON.parse(localStorage.getItem(storageKey) || "null");
|
|
2250
|
+
if (saved?.base === JSON.stringify(base)) return validateLoopConfig(saved.config);
|
|
2251
|
+
} catch {
|
|
2252
|
+
}
|
|
2253
|
+
return fallback;
|
|
2254
|
+
}
|
|
1497
2255
|
function GridThumbnail({
|
|
1498
2256
|
slide,
|
|
1499
2257
|
index,
|
|
1500
2258
|
total
|
|
1501
2259
|
}) {
|
|
1502
|
-
const viewportRef =
|
|
1503
|
-
const canvasRef =
|
|
2260
|
+
const viewportRef = useRef8(null);
|
|
2261
|
+
const canvasRef = useRef8(null);
|
|
1504
2262
|
const SlideComponent = slide.component;
|
|
1505
|
-
|
|
2263
|
+
useLayoutEffect2(() => {
|
|
1506
2264
|
const viewport = viewportRef.current;
|
|
1507
2265
|
const canvas = canvasRef.current;
|
|
1508
2266
|
if (!viewport || !canvas) return;
|
|
@@ -1514,21 +2272,21 @@ function GridThumbnail({
|
|
|
1514
2272
|
observer.observe(viewport);
|
|
1515
2273
|
return () => observer.disconnect();
|
|
1516
2274
|
}, []);
|
|
1517
|
-
return /* @__PURE__ */
|
|
2275
|
+
return /* @__PURE__ */ jsx17("div", { ref: viewportRef, className: "absolute inset-0 overflow-hidden", children: /* @__PURE__ */ jsx17(
|
|
1518
2276
|
"div",
|
|
1519
2277
|
{
|
|
1520
2278
|
ref: canvasRef,
|
|
1521
2279
|
className: "absolute top-0 left-0 origin-top-left",
|
|
1522
2280
|
style: { width: SLIDE_DIMENSIONS.width, height: SLIDE_DIMENSIONS.height },
|
|
1523
|
-
children: /* @__PURE__ */
|
|
2281
|
+
children: /* @__PURE__ */ jsx17(AnimationProvider, { currentStep: slide.steps, totalSteps: slide.steps, showAllAnimations: true, children: /* @__PURE__ */ jsx17(SlideErrorBoundary, { slideIndex: index, slideTitle: slide.title, children: /* @__PURE__ */ jsx17(SlideComponent, { slideNumber: index + 1, totalSlides: total }) }) })
|
|
1524
2282
|
}
|
|
1525
2283
|
) });
|
|
1526
2284
|
}
|
|
1527
2285
|
function ListSlide({ slide, index, total }) {
|
|
1528
|
-
const viewportRef =
|
|
1529
|
-
const canvasRef =
|
|
2286
|
+
const viewportRef = useRef8(null);
|
|
2287
|
+
const canvasRef = useRef8(null);
|
|
1530
2288
|
const SlideComponent = slide.component;
|
|
1531
|
-
|
|
2289
|
+
useLayoutEffect2(() => {
|
|
1532
2290
|
const viewport = viewportRef.current;
|
|
1533
2291
|
const canvas = canvasRef.current;
|
|
1534
2292
|
if (!viewport || !canvas) return;
|
|
@@ -1540,29 +2298,29 @@ function ListSlide({ slide, index, total }) {
|
|
|
1540
2298
|
observer.observe(viewport);
|
|
1541
2299
|
return () => observer.disconnect();
|
|
1542
2300
|
}, []);
|
|
1543
|
-
return /* @__PURE__ */
|
|
2301
|
+
return /* @__PURE__ */ jsx17(
|
|
1544
2302
|
"div",
|
|
1545
2303
|
{
|
|
1546
2304
|
ref: viewportRef,
|
|
1547
2305
|
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",
|
|
1548
|
-
children: /* @__PURE__ */
|
|
2306
|
+
children: /* @__PURE__ */ jsx17(
|
|
1549
2307
|
"div",
|
|
1550
2308
|
{
|
|
1551
2309
|
ref: canvasRef,
|
|
1552
2310
|
className: "list-slide-canvas absolute top-0 left-0 origin-top-left",
|
|
1553
2311
|
style: { width: SLIDE_DIMENSIONS.width, height: SLIDE_DIMENSIONS.height },
|
|
1554
|
-
children: /* @__PURE__ */
|
|
2312
|
+
children: /* @__PURE__ */ jsx17(AnimationProvider, { currentStep: slide.steps, totalSteps: slide.steps, showAllAnimations: true, children: /* @__PURE__ */ jsx17(SlideErrorBoundary, { slideIndex: index, slideTitle: slide.title, children: /* @__PURE__ */ jsx17(SlideComponent, { slideNumber: index + 1, totalSlides: total }) }) })
|
|
1555
2313
|
}
|
|
1556
2314
|
)
|
|
1557
2315
|
}
|
|
1558
2316
|
);
|
|
1559
2317
|
}
|
|
1560
2318
|
function SlideExportView({ slides, slideIndex }) {
|
|
1561
|
-
const [ready, setReady] =
|
|
2319
|
+
const [ready, setReady] = useState10(false);
|
|
1562
2320
|
const clampedIndex = Math.max(0, Math.min(slideIndex, slides.length - 1));
|
|
1563
2321
|
const slideConfig = slides[clampedIndex];
|
|
1564
2322
|
const SlideComponent = slideConfig.component;
|
|
1565
|
-
|
|
2323
|
+
useEffect9(() => {
|
|
1566
2324
|
let cancelled = false;
|
|
1567
2325
|
const markReady = async () => {
|
|
1568
2326
|
await document.fonts.ready;
|
|
@@ -1579,7 +2337,7 @@ function SlideExportView({ slides, slideIndex }) {
|
|
|
1579
2337
|
cancelled = true;
|
|
1580
2338
|
};
|
|
1581
2339
|
}, []);
|
|
1582
|
-
return /* @__PURE__ */
|
|
2340
|
+
return /* @__PURE__ */ jsx17(
|
|
1583
2341
|
"div",
|
|
1584
2342
|
{
|
|
1585
2343
|
"data-export-ready": ready ? "true" : void 0,
|
|
@@ -1590,13 +2348,13 @@ function SlideExportView({ slides, slideIndex }) {
|
|
|
1590
2348
|
position: "relative",
|
|
1591
2349
|
background: "black"
|
|
1592
2350
|
},
|
|
1593
|
-
children: /* @__PURE__ */
|
|
2351
|
+
children: /* @__PURE__ */ jsx17(
|
|
1594
2352
|
AnimationProvider,
|
|
1595
2353
|
{
|
|
1596
2354
|
currentStep: slideConfig.steps,
|
|
1597
2355
|
totalSteps: slideConfig.steps,
|
|
1598
2356
|
showAllAnimations: true,
|
|
1599
|
-
children: /* @__PURE__ */
|
|
2357
|
+
children: /* @__PURE__ */ jsx17(SlideErrorBoundary, { slideIndex: clampedIndex, slideTitle: slideConfig.title, children: /* @__PURE__ */ jsx17(SlideComponent, { slideNumber: clampedIndex + 1, totalSlides: slides.length }) })
|
|
1600
2358
|
}
|
|
1601
2359
|
)
|
|
1602
2360
|
}
|
|
@@ -1606,19 +2364,21 @@ function SlideDeck({
|
|
|
1606
2364
|
slides,
|
|
1607
2365
|
transition,
|
|
1608
2366
|
directionalTransition,
|
|
2367
|
+
loopConfig: suppliedLoopConfig,
|
|
2368
|
+
onLoopConfigChange,
|
|
1609
2369
|
annotations,
|
|
1610
2370
|
onAnnotationAdd,
|
|
1611
2371
|
onAnnotationDelete,
|
|
1612
2372
|
onAnnotationUpdate
|
|
1613
2373
|
}) {
|
|
1614
|
-
const [exportParams] =
|
|
2374
|
+
const [exportParams] = useState10(() => {
|
|
1615
2375
|
if (typeof window === "undefined") return null;
|
|
1616
2376
|
const params = new URLSearchParams(window.location.search);
|
|
1617
2377
|
if (params.get("export") !== "true") return null;
|
|
1618
2378
|
return { slideIndex: parseInt(params.get("slide") || "0", 10) };
|
|
1619
2379
|
});
|
|
1620
2380
|
if (exportParams) {
|
|
1621
|
-
return /* @__PURE__ */
|
|
2381
|
+
return /* @__PURE__ */ jsx17(SlideExportView, { slides, slideIndex: exportParams.slideIndex });
|
|
1622
2382
|
}
|
|
1623
2383
|
const internal = useAnnotations(slides);
|
|
1624
2384
|
const isExternallyManaged = annotations !== void 0;
|
|
@@ -1626,81 +2386,179 @@ function SlideDeck({
|
|
|
1626
2386
|
const effectiveAdd = isExternallyManaged ? onAnnotationAdd : internal.addAnnotation;
|
|
1627
2387
|
const effectiveDelete = isExternallyManaged ? onAnnotationDelete : internal.deleteAnnotation;
|
|
1628
2388
|
const effectiveUpdate = isExternallyManaged ? onAnnotationUpdate : internal.updateAnnotation;
|
|
1629
|
-
const openCount =
|
|
2389
|
+
const openCount = useMemo5(
|
|
1630
2390
|
() => effectiveAnnotations.filter((a) => a.status === "open").length,
|
|
1631
2391
|
[effectiveAnnotations]
|
|
1632
2392
|
);
|
|
1633
|
-
const getSlideAnnotations =
|
|
2393
|
+
const getSlideAnnotations = useCallback6(
|
|
1634
2394
|
(slideIndex) => effectiveAnnotations.filter(
|
|
1635
2395
|
(a) => a.slideId ? a.slideId === slides[slideIndex]?.id : a.slideIndex === slideIndex
|
|
1636
2396
|
),
|
|
1637
2397
|
[effectiveAnnotations, slides]
|
|
1638
2398
|
);
|
|
1639
|
-
const unlinkedAnnotations =
|
|
2399
|
+
const unlinkedAnnotations = useMemo5(
|
|
1640
2400
|
() => effectiveAnnotations.filter((a) => a.slideId && !slides.some((slide) => slide.id === a.slideId)),
|
|
1641
2401
|
[effectiveAnnotations, slides]
|
|
1642
2402
|
);
|
|
1643
|
-
const [viewMode, setViewMode] =
|
|
1644
|
-
const [
|
|
1645
|
-
const
|
|
2403
|
+
const [viewMode, setViewMode] = useState10("slide");
|
|
2404
|
+
const [isEditorOpen, setIsEditorOpen] = useState10(false);
|
|
2405
|
+
const editorOpenRef = useRef8(false);
|
|
2406
|
+
const [editorAvailable, setEditorAvailable] = useState10(false);
|
|
2407
|
+
const [inspectorMode, setInspectorMode] = useState10(null);
|
|
2408
|
+
const pendingInspector = useRef8(void 0);
|
|
2409
|
+
const [editorRevision, setEditorRevision] = useState10(0);
|
|
2410
|
+
const activateInspector = useCallback6((mode) => {
|
|
2411
|
+
setInspectorMode(mode);
|
|
2412
|
+
setLoopMenuOpen(false);
|
|
2413
|
+
if (mode) setViewMode("slide");
|
|
2414
|
+
if (mode !== "comments") {
|
|
2415
|
+
setSelectedAnnotationId(null);
|
|
2416
|
+
setHoveredAnnotationId(null);
|
|
2417
|
+
}
|
|
2418
|
+
}, []);
|
|
2419
|
+
const requestInspector = useCallback6(
|
|
2420
|
+
(mode) => {
|
|
2421
|
+
if (isEditorOpen) {
|
|
2422
|
+
if (mode === "editor") return;
|
|
2423
|
+
pendingInspector.current = mode;
|
|
2424
|
+
window.dispatchEvent(
|
|
2425
|
+
new CustomEvent("promptslide:editor-request", { detail: { open: false } })
|
|
2426
|
+
);
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
if (mode === "editor") {
|
|
2430
|
+
if (editorAvailable)
|
|
2431
|
+
window.dispatchEvent(
|
|
2432
|
+
new CustomEvent("promptslide:editor-request", { detail: { open: true } })
|
|
2433
|
+
);
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
activateInspector(mode);
|
|
2437
|
+
},
|
|
2438
|
+
[isEditorOpen, editorAvailable, activateInspector]
|
|
2439
|
+
);
|
|
2440
|
+
useEffect9(() => {
|
|
2441
|
+
const onEditorState = (event) => {
|
|
2442
|
+
const open = Boolean(event.detail?.open);
|
|
2443
|
+
const changed = editorOpenRef.current !== open;
|
|
2444
|
+
editorOpenRef.current = open;
|
|
2445
|
+
setIsEditorOpen(open);
|
|
2446
|
+
if (open) {
|
|
2447
|
+
if (changed) pendingInspector.current = void 0;
|
|
2448
|
+
activateInspector("editor");
|
|
2449
|
+
} else if (changed) {
|
|
2450
|
+
const next = pendingInspector.current;
|
|
2451
|
+
pendingInspector.current = void 0;
|
|
2452
|
+
activateInspector(next === void 0 ? null : next);
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
const onEditorReady = (event) => setEditorAvailable(Boolean(event.detail?.available));
|
|
2456
|
+
const onCloseCancelled = () => {
|
|
2457
|
+
pendingInspector.current = void 0;
|
|
2458
|
+
};
|
|
2459
|
+
const refreshEditor = () => setEditorRevision((value) => value + 1);
|
|
2460
|
+
window.addEventListener("promptslide:editor-state", onEditorState);
|
|
2461
|
+
window.addEventListener("promptslide:editor-ready", onEditorReady);
|
|
2462
|
+
window.addEventListener("promptslide:editor-close-cancelled", onCloseCancelled);
|
|
2463
|
+
window.addEventListener("promptslide:editor-refresh", refreshEditor);
|
|
2464
|
+
return () => {
|
|
2465
|
+
window.removeEventListener("promptslide:editor-state", onEditorState);
|
|
2466
|
+
window.removeEventListener("promptslide:editor-ready", onEditorReady);
|
|
2467
|
+
window.removeEventListener("promptslide:editor-close-cancelled", onCloseCancelled);
|
|
2468
|
+
window.removeEventListener("promptslide:editor-refresh", refreshEditor);
|
|
2469
|
+
};
|
|
2470
|
+
}, [activateInspector]);
|
|
2471
|
+
const [isPresentationMode, setIsPresentationMode] = useState10(false);
|
|
2472
|
+
const providedLoopConfig = useDefaultLoopConfig();
|
|
2473
|
+
const defaultLoopKey = JSON.stringify(suppliedLoopConfig ?? providedLoopConfig);
|
|
2474
|
+
const defaultLoopConfig = useMemo5(() => JSON.parse(defaultLoopKey), [defaultLoopKey]);
|
|
2475
|
+
const storageKey = `promptslide:loop:${typeof location === "undefined" ? "" : location.pathname}:${JSON.stringify(slides.map(loopSlideKey))}`;
|
|
2476
|
+
const [loopConfig, setLoopConfig] = useState10(
|
|
2477
|
+
() => loadLoopConfig(defaultLoopConfig, storageKey)
|
|
2478
|
+
);
|
|
2479
|
+
const [loopDraft, setLoopDraft] = useState10(
|
|
2480
|
+
() => loadLoopConfig(defaultLoopConfig, storageKey)
|
|
2481
|
+
);
|
|
2482
|
+
const [playbackConfig, setPlaybackConfig] = useState10(loopConfig);
|
|
2483
|
+
const [isLooping, setIsLooping] = useState10(false);
|
|
2484
|
+
const [loopMenuOpen, setLoopMenuOpen] = useState10(false);
|
|
2485
|
+
const [loopError, setLoopError] = useState10("");
|
|
2486
|
+
const [loopStartScreen, setLoopStartScreen] = useState10(
|
|
2487
|
+
() => typeof window !== "undefined" && new URLSearchParams(window.location.search).has("loop")
|
|
2488
|
+
);
|
|
2489
|
+
const loopMenuRef = useRef8(null);
|
|
2490
|
+
useEffect9(() => {
|
|
2491
|
+
const config = loadLoopConfig(defaultLoopConfig, storageKey);
|
|
2492
|
+
setLoopConfig(config);
|
|
2493
|
+
setLoopDraft(config);
|
|
2494
|
+
setPlaybackConfig(config);
|
|
2495
|
+
setIsLooping(false);
|
|
2496
|
+
}, [defaultLoopConfig, storageKey]);
|
|
2497
|
+
useEffect9(() => {
|
|
2498
|
+
if (!loopMenuOpen) return;
|
|
2499
|
+
const close = (event) => {
|
|
2500
|
+
if (!loopMenuRef.current?.contains(event.target)) setLoopMenuOpen(false);
|
|
2501
|
+
};
|
|
2502
|
+
document.addEventListener("pointerdown", close);
|
|
2503
|
+
return () => document.removeEventListener("pointerdown", close);
|
|
2504
|
+
}, [loopMenuOpen]);
|
|
2505
|
+
const [viewportWidth, setViewportWidth] = useState10(
|
|
1646
2506
|
() => typeof window === "undefined" ? 1280 : window.innerWidth
|
|
1647
2507
|
);
|
|
1648
|
-
const [showThumbnailRail, setShowThumbnailRail] =
|
|
2508
|
+
const [showThumbnailRail, setShowThumbnailRail] = useState10(
|
|
1649
2509
|
() => typeof window === "undefined" ? true : window.innerWidth >= NARROW_VIEWPORT_WIDTH
|
|
1650
2510
|
);
|
|
1651
|
-
const [thumbnailWidth, setThumbnailWidth] =
|
|
2511
|
+
const [thumbnailWidth, setThumbnailWidth] = useState10(() => {
|
|
1652
2512
|
if (typeof window === "undefined") return DEFAULT_THUMBNAIL_WIDTH;
|
|
1653
2513
|
const savedWidth = Number(window.localStorage.getItem(THUMBNAIL_WIDTH_STORAGE_KEY));
|
|
1654
2514
|
return Number.isFinite(savedWidth) && savedWidth >= MIN_THUMBNAIL_WIDTH ? Math.min(savedWidth, MAX_THUMBNAIL_WIDTH) : DEFAULT_THUMBNAIL_WIDTH;
|
|
1655
2515
|
});
|
|
1656
|
-
const
|
|
1657
|
-
const [
|
|
1658
|
-
const [
|
|
1659
|
-
const [
|
|
1660
|
-
const [
|
|
1661
|
-
const [
|
|
1662
|
-
const [
|
|
1663
|
-
const
|
|
1664
|
-
const
|
|
1665
|
-
const
|
|
1666
|
-
const
|
|
1667
|
-
const
|
|
1668
|
-
const
|
|
1669
|
-
const
|
|
1670
|
-
const
|
|
1671
|
-
const
|
|
1672
|
-
const
|
|
1673
|
-
const
|
|
1674
|
-
const dragAnimationTimerRef = useRef6(null);
|
|
2516
|
+
const isAnnotationMode = inspectorMode === "comments";
|
|
2517
|
+
const [selectedAnnotationId, setSelectedAnnotationId] = useState10(null);
|
|
2518
|
+
const [hoveredAnnotationId, setHoveredAnnotationId] = useState10(null);
|
|
2519
|
+
const [isResizingThumbnails, setIsResizingThumbnails] = useState10(false);
|
|
2520
|
+
const [isDragCollapsed, setIsDragCollapsed] = useState10(false);
|
|
2521
|
+
const [isAnimatingDragBoundary, setIsAnimatingDragBoundary] = useState10(false);
|
|
2522
|
+
const [scale, setScale] = useState10(1);
|
|
2523
|
+
const containerRef = useRef8(null);
|
|
2524
|
+
const slideContainerRef = useRef8(null);
|
|
2525
|
+
const previewViewportRef = useRef8(null);
|
|
2526
|
+
const activeThumbnailRef = useRef8(null);
|
|
2527
|
+
const preferredRailOpenRef = useRef8(true);
|
|
2528
|
+
const wasNarrowViewportRef = useRef8(viewportWidth < NARROW_VIEWPORT_WIDTH);
|
|
2529
|
+
const resizeStartRef = useRef8(null);
|
|
2530
|
+
const previousUserSelectRef = useRef8("");
|
|
2531
|
+
const previousCursorRef = useRef8("");
|
|
2532
|
+
const dragCollapsedRef = useRef8(false);
|
|
2533
|
+
const dragAnimationTimerRef = useRef8(null);
|
|
1675
2534
|
const isNarrowViewport = viewportWidth < NARROW_VIEWPORT_WIDTH;
|
|
1676
2535
|
const maxThumbnailWidth = isNarrowViewport ? Math.max(120, Math.floor(viewportWidth * 0.8)) : Math.min(MAX_THUMBNAIL_WIDTH, viewportWidth - 360);
|
|
1677
2536
|
const minThumbnailWidth = Math.min(MIN_THUMBNAIL_WIDTH, maxThumbnailWidth);
|
|
1678
2537
|
const visibleThumbnailWidth = Math.min(thumbnailWidth, maxThumbnailWidth);
|
|
1679
2538
|
const isThumbnailRailVisible = showThumbnailRail && !isDragCollapsed;
|
|
1680
|
-
const setThumbnailRailOpen =
|
|
2539
|
+
const setThumbnailRailOpen = useCallback6((open) => {
|
|
1681
2540
|
preferredRailOpenRef.current = open;
|
|
1682
2541
|
setShowThumbnailRail(open);
|
|
1683
2542
|
}, []);
|
|
1684
|
-
|
|
2543
|
+
useEffect9(() => {
|
|
1685
2544
|
const handleResize = () => setViewportWidth(window.innerWidth);
|
|
1686
2545
|
window.addEventListener("resize", handleResize);
|
|
1687
2546
|
return () => window.removeEventListener("resize", handleResize);
|
|
1688
2547
|
}, []);
|
|
1689
|
-
|
|
2548
|
+
useEffect9(() => {
|
|
1690
2549
|
if (isNarrowViewport !== wasNarrowViewportRef.current) {
|
|
1691
2550
|
setShowThumbnailRail(isNarrowViewport ? false : preferredRailOpenRef.current);
|
|
1692
2551
|
if (isNarrowViewport) {
|
|
1693
|
-
|
|
1694
|
-
setShowAnnotationPanel(false);
|
|
2552
|
+
setInspectorMode((mode) => mode === "comments" ? null : mode);
|
|
1695
2553
|
setSelectedAnnotationId(null);
|
|
1696
2554
|
}
|
|
1697
2555
|
wasNarrowViewportRef.current = isNarrowViewport;
|
|
1698
2556
|
}
|
|
1699
2557
|
}, [isNarrowViewport]);
|
|
1700
|
-
|
|
2558
|
+
useEffect9(() => {
|
|
1701
2559
|
window.localStorage.setItem(THUMBNAIL_WIDTH_STORAGE_KEY, String(thumbnailWidth));
|
|
1702
2560
|
}, [thumbnailWidth]);
|
|
1703
|
-
|
|
2561
|
+
useEffect9(
|
|
1704
2562
|
() => () => {
|
|
1705
2563
|
if (resizeStartRef.current) {
|
|
1706
2564
|
document.body.style.userSelect = previousUserSelectRef.current;
|
|
@@ -1710,7 +2568,7 @@ function SlideDeck({
|
|
|
1710
2568
|
},
|
|
1711
2569
|
[]
|
|
1712
2570
|
);
|
|
1713
|
-
|
|
2571
|
+
useEffect9(() => {
|
|
1714
2572
|
if ((!showThumbnailRail || isPresentationMode || viewMode !== "slide") && resizeStartRef.current) {
|
|
1715
2573
|
resizeStartRef.current = null;
|
|
1716
2574
|
dragCollapsedRef.current = false;
|
|
@@ -1722,7 +2580,7 @@ function SlideDeck({
|
|
|
1722
2580
|
document.body.style.cursor = previousCursorRef.current;
|
|
1723
2581
|
}
|
|
1724
2582
|
}, [showThumbnailRail, isPresentationMode, viewMode]);
|
|
1725
|
-
|
|
2583
|
+
useEffect9(() => {
|
|
1726
2584
|
if (!isResizingThumbnails) return;
|
|
1727
2585
|
const getDraggedWidth = (clientX) => {
|
|
1728
2586
|
const start = resizeStartRef.current;
|
|
@@ -1776,35 +2634,113 @@ function SlideDeck({
|
|
|
1776
2634
|
currentSlide,
|
|
1777
2635
|
animationStep,
|
|
1778
2636
|
totalSteps,
|
|
2637
|
+
isTransitioning,
|
|
1779
2638
|
direction,
|
|
1780
2639
|
showAllAnimations,
|
|
1781
2640
|
advance,
|
|
1782
2641
|
goBack,
|
|
1783
2642
|
goToSlide,
|
|
2643
|
+
goToStep,
|
|
2644
|
+
restartAtSlide,
|
|
1784
2645
|
onTransitionComplete
|
|
1785
2646
|
} = useSlideNavigation({
|
|
1786
2647
|
slides
|
|
1787
2648
|
});
|
|
1788
|
-
const
|
|
2649
|
+
const playback = useLoopPlayback({
|
|
2650
|
+
config: playbackConfig,
|
|
2651
|
+
slides,
|
|
2652
|
+
currentSlide,
|
|
2653
|
+
animationStep,
|
|
2654
|
+
isTransitioning,
|
|
2655
|
+
enabled: isLooping && isPresentationMode,
|
|
2656
|
+
advance,
|
|
2657
|
+
goBack,
|
|
2658
|
+
restartAtSlide
|
|
2659
|
+
});
|
|
2660
|
+
const rememberLoopConfig = (config) => {
|
|
2661
|
+
setLoopConfig(config);
|
|
2662
|
+
setLoopDraft(config);
|
|
2663
|
+
try {
|
|
2664
|
+
localStorage.setItem(
|
|
2665
|
+
storageKey,
|
|
2666
|
+
JSON.stringify({ base: JSON.stringify(defaultLoopConfig), config })
|
|
2667
|
+
);
|
|
2668
|
+
return true;
|
|
2669
|
+
} catch {
|
|
2670
|
+
return false;
|
|
2671
|
+
}
|
|
2672
|
+
};
|
|
2673
|
+
const saveLoopConfig = async (config) => {
|
|
2674
|
+
if (onLoopConfigChange) {
|
|
2675
|
+
await onLoopConfigChange(config);
|
|
2676
|
+
rememberLoopConfig(config);
|
|
2677
|
+
return "Loop settings saved.";
|
|
2678
|
+
}
|
|
2679
|
+
try {
|
|
2680
|
+
const response = await fetch("/__promptslide_loop", {
|
|
2681
|
+
method: "POST",
|
|
2682
|
+
headers: { "Content-Type": "application/json" },
|
|
2683
|
+
body: JSON.stringify(config)
|
|
2684
|
+
});
|
|
2685
|
+
if (response.status === 204) {
|
|
2686
|
+
rememberLoopConfig(config);
|
|
2687
|
+
return "Saved to loop-config.json. Included in future builds and publishing.";
|
|
2688
|
+
}
|
|
2689
|
+
if (response.status !== 404 && response.status !== 405)
|
|
2690
|
+
throw new Error(
|
|
2691
|
+
"Studio could not save loop-config.json. Download the settings to keep a copy."
|
|
2692
|
+
);
|
|
2693
|
+
} catch (error) {
|
|
2694
|
+
if (error instanceof Error && error.message.startsWith("Studio")) throw error;
|
|
2695
|
+
}
|
|
2696
|
+
return rememberLoopConfig(config) ? "Saved in this browser. Download settings and place loop-config.json in the project folder to share or publish them." : "Applied for this session. Browser storage is unavailable; download the settings to keep them.";
|
|
2697
|
+
};
|
|
2698
|
+
const startLoop = async (config = loopDraft) => {
|
|
2699
|
+
if (isEditorOpen) return;
|
|
2700
|
+
const first = includedSlides(config, slides)[0];
|
|
2701
|
+
if (first === void 0) {
|
|
2702
|
+
setLoopError("Include at least one slide before starting the loop.");
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
setLoopError("");
|
|
2706
|
+
try {
|
|
2707
|
+
if (!document.fullscreenElement) await document.documentElement.requestFullscreen();
|
|
2708
|
+
setPlaybackConfig(config);
|
|
2709
|
+
setLoopDraft(config);
|
|
2710
|
+
setViewMode("slide");
|
|
2711
|
+
setInspectorMode(null);
|
|
2712
|
+
setLoopMenuOpen(false);
|
|
2713
|
+
setLoopStartScreen(false);
|
|
2714
|
+
playback.setPaused(false);
|
|
2715
|
+
playback.restart(first);
|
|
2716
|
+
setIsLooping(true);
|
|
2717
|
+
} catch {
|
|
2718
|
+
setLoopError(
|
|
2719
|
+
"Full screen could not start. Allow full screen in your browser, then try again."
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2722
|
+
};
|
|
2723
|
+
const togglePresentationMode = useCallback6(async () => {
|
|
1789
2724
|
if (!document.fullscreenElement) {
|
|
1790
2725
|
await document.documentElement.requestFullscreen();
|
|
1791
2726
|
} else {
|
|
1792
2727
|
await document.exitFullscreen();
|
|
1793
2728
|
}
|
|
1794
2729
|
}, []);
|
|
1795
|
-
|
|
2730
|
+
useEffect9(() => {
|
|
1796
2731
|
const handleFullscreenChange = () => {
|
|
1797
2732
|
setIsPresentationMode(!!document.fullscreenElement);
|
|
2733
|
+
if (!document.fullscreenElement) setIsLooping(false);
|
|
1798
2734
|
};
|
|
1799
2735
|
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|
1800
2736
|
return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
|
1801
2737
|
}, []);
|
|
1802
|
-
|
|
2738
|
+
useEffect9(() => {
|
|
1803
2739
|
if (viewMode === "slide" && !isPresentationMode && showThumbnailRail) {
|
|
1804
2740
|
activeThumbnailRef.current?.scrollIntoView({ block: "nearest" });
|
|
1805
2741
|
}
|
|
1806
2742
|
}, [currentSlide, isPresentationMode, showThumbnailRail, viewMode]);
|
|
1807
|
-
|
|
2743
|
+
useEffect9(() => {
|
|
1808
2744
|
const calculateScale = () => {
|
|
1809
2745
|
if (!isPresentationMode) {
|
|
1810
2746
|
setScale(1);
|
|
@@ -1820,7 +2756,7 @@ function SlideDeck({
|
|
|
1820
2756
|
window.addEventListener("resize", calculateScale);
|
|
1821
2757
|
return () => window.removeEventListener("resize", calculateScale);
|
|
1822
2758
|
}, [isPresentationMode]);
|
|
1823
|
-
|
|
2759
|
+
useLayoutEffect2(() => {
|
|
1824
2760
|
const viewport = previewViewportRef.current;
|
|
1825
2761
|
const canvas = slideContainerRef.current;
|
|
1826
2762
|
if (!viewport || !canvas) return;
|
|
@@ -1836,16 +2772,10 @@ function SlideDeck({
|
|
|
1836
2772
|
observer.observe(viewport);
|
|
1837
2773
|
return () => observer.disconnect();
|
|
1838
2774
|
}, [viewMode, isPresentationMode]);
|
|
1839
|
-
const toggleCommentMode =
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
if (!next) {
|
|
1844
|
-
setSelectedAnnotationId(null);
|
|
1845
|
-
setHoveredAnnotationId(null);
|
|
1846
|
-
}
|
|
1847
|
-
}, [isAnnotationMode]);
|
|
1848
|
-
const handleExportPdf = useCallback5(async () => {
|
|
2775
|
+
const toggleCommentMode = useCallback6(() => {
|
|
2776
|
+
requestInspector(isAnnotationMode && viewMode === "slide" ? null : "comments");
|
|
2777
|
+
}, [isAnnotationMode, viewMode, requestInspector]);
|
|
2778
|
+
const handleExportPdf = useCallback6(async () => {
|
|
1849
2779
|
try {
|
|
1850
2780
|
const response = await fetch("/__promptslide_pdf");
|
|
1851
2781
|
if (response.ok && response.headers.get("content-type")?.includes("application/pdf")) {
|
|
@@ -1871,13 +2801,36 @@ function SlideDeck({
|
|
|
1871
2801
|
window.print();
|
|
1872
2802
|
}, 100);
|
|
1873
2803
|
}, [viewMode]);
|
|
1874
|
-
|
|
2804
|
+
useEffect9(() => {
|
|
1875
2805
|
const handleKeyDown = (e) => {
|
|
1876
2806
|
const target = e.target;
|
|
1877
2807
|
if (target?.closest("input, textarea, [contenteditable='true']")) return;
|
|
1878
2808
|
if (e.altKey || e.ctrlKey || e.metaKey) return;
|
|
1879
2809
|
const key = e.key.toLowerCase();
|
|
1880
2810
|
if (e.repeat && "fvglcdt".includes(key)) return;
|
|
2811
|
+
if (isEditorOpen && ["f", "v", "g", "l", "c", "d"].includes(key)) return;
|
|
2812
|
+
if (loopStartScreen) return;
|
|
2813
|
+
if (loopMenuOpen) {
|
|
2814
|
+
if (e.key === "Escape") {
|
|
2815
|
+
setLoopMenuOpen(false);
|
|
2816
|
+
loopMenuRef.current?.querySelector("button")?.focus();
|
|
2817
|
+
}
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2820
|
+
if (isLooping) {
|
|
2821
|
+
if (e.key === "Escape") {
|
|
2822
|
+
setIsLooping(false);
|
|
2823
|
+
if (document.fullscreenElement) void document.exitFullscreen().catch(() => {
|
|
2824
|
+
});
|
|
2825
|
+
} else if (e.key === " ") {
|
|
2826
|
+
e.preventDefault();
|
|
2827
|
+
if (!e.repeat) playback.setPaused((value) => !value);
|
|
2828
|
+
} else if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
|
|
2829
|
+
e.preventDefault();
|
|
2830
|
+
playback.move(e.key === "ArrowRight" ? 1 : -1);
|
|
2831
|
+
}
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
1881
2834
|
if (key === "f") {
|
|
1882
2835
|
e.preventDefault();
|
|
1883
2836
|
void togglePresentationMode();
|
|
@@ -1927,17 +2880,46 @@ function SlideDeck({
|
|
|
1927
2880
|
}, [
|
|
1928
2881
|
advance,
|
|
1929
2882
|
goBack,
|
|
2883
|
+
playback,
|
|
2884
|
+
isLooping,
|
|
2885
|
+
loopMenuOpen,
|
|
2886
|
+
loopStartScreen,
|
|
1930
2887
|
viewMode,
|
|
1931
2888
|
togglePresentationMode,
|
|
1932
2889
|
isAnnotationMode,
|
|
2890
|
+
isEditorOpen,
|
|
1933
2891
|
isPresentationMode,
|
|
1934
2892
|
setThumbnailRailOpen,
|
|
1935
2893
|
showThumbnailRail,
|
|
1936
2894
|
toggleCommentMode,
|
|
1937
2895
|
handleExportPdf
|
|
1938
2896
|
]);
|
|
1939
|
-
|
|
1940
|
-
|
|
2897
|
+
const inspectorWidth = viewportWidth <= 850 ? 280 : 320;
|
|
2898
|
+
const inspectorVisible = inspectorMode !== null && !isPresentationMode && viewMode === "slide";
|
|
2899
|
+
return /* @__PURE__ */ jsxs9("div", { className: "min-h-screen w-full bg-neutral-950 text-foreground", children: [
|
|
2900
|
+
/* @__PURE__ */ jsx17("style", { children: `
|
|
2901
|
+
.slide-thumbnail-scroll-area {
|
|
2902
|
+
scrollbar-width: thin;
|
|
2903
|
+
scrollbar-color: #404040 transparent;
|
|
2904
|
+
}
|
|
2905
|
+
.slide-thumbnail-scroll-area::-webkit-scrollbar {
|
|
2906
|
+
width: 10px;
|
|
2907
|
+
}
|
|
2908
|
+
.slide-thumbnail-scroll-area::-webkit-scrollbar-track {
|
|
2909
|
+
background: transparent;
|
|
2910
|
+
}
|
|
2911
|
+
.slide-thumbnail-scroll-area::-webkit-scrollbar-thumb {
|
|
2912
|
+
background-color: #404040;
|
|
2913
|
+
border: 3px solid transparent;
|
|
2914
|
+
border-radius: 9999px;
|
|
2915
|
+
background-clip: padding-box;
|
|
2916
|
+
}
|
|
2917
|
+
.slide-thumbnail-scroll-area::-webkit-scrollbar-thumb:hover {
|
|
2918
|
+
background-color: #525252;
|
|
2919
|
+
}
|
|
2920
|
+
.slide-thumbnail-scroll-area::-webkit-scrollbar-corner {
|
|
2921
|
+
background: transparent;
|
|
2922
|
+
}
|
|
1941
2923
|
@media print {
|
|
1942
2924
|
.list-slide-canvas {
|
|
1943
2925
|
transform: scale(1.5) !important;
|
|
@@ -1961,99 +2943,161 @@ function SlideDeck({
|
|
|
1961
2943
|
}
|
|
1962
2944
|
}
|
|
1963
2945
|
` }),
|
|
1964
|
-
/* @__PURE__ */
|
|
2946
|
+
loopError && /* @__PURE__ */ jsxs9(
|
|
1965
2947
|
"div",
|
|
1966
2948
|
{
|
|
2949
|
+
role: "alert",
|
|
2950
|
+
className: "fixed bottom-4 left-4 z-[110] max-w-md rounded-lg border border-red-700 bg-neutral-950 p-4 text-sm text-red-300",
|
|
2951
|
+
children: [
|
|
2952
|
+
loopError,
|
|
2953
|
+
/* @__PURE__ */ jsx17("button", { className: "ml-3 underline", onClick: () => setLoopError(""), children: "Dismiss" })
|
|
2954
|
+
]
|
|
2955
|
+
}
|
|
2956
|
+
),
|
|
2957
|
+
loopStartScreen && /* @__PURE__ */ jsxs9("div", { className: "fixed inset-0 z-[90] flex flex-col items-center justify-center gap-5 bg-neutral-950 text-white", children: [
|
|
2958
|
+
/* @__PURE__ */ jsx17("h1", { className: "text-3xl font-semibold", children: "Ready to loop" }),
|
|
2959
|
+
/* @__PURE__ */ jsx17("p", { className: "text-neutral-400", children: "Space pauses or resumes. Escape stops the loop." }),
|
|
2960
|
+
/* @__PURE__ */ jsx17(
|
|
2961
|
+
"button",
|
|
2962
|
+
{
|
|
2963
|
+
className: "rounded-lg border border-orange-500 px-6 py-3 text-orange-400",
|
|
2964
|
+
onClick: () => void startLoop(),
|
|
2965
|
+
children: "Click to start"
|
|
2966
|
+
}
|
|
2967
|
+
),
|
|
2968
|
+
/* @__PURE__ */ jsx17(
|
|
2969
|
+
"button",
|
|
2970
|
+
{
|
|
2971
|
+
className: "text-sm text-neutral-400 underline",
|
|
2972
|
+
onClick: () => {
|
|
2973
|
+
setLoopStartScreen(false);
|
|
2974
|
+
requestInspector("slide");
|
|
2975
|
+
},
|
|
2976
|
+
children: "Configure loop"
|
|
2977
|
+
}
|
|
2978
|
+
)
|
|
2979
|
+
] }),
|
|
2980
|
+
isLooping && playback.paused && /* @__PURE__ */ jsx17(
|
|
2981
|
+
"div",
|
|
2982
|
+
{
|
|
2983
|
+
role: "status",
|
|
2984
|
+
className: "pointer-events-none fixed bottom-4 left-4 z-[90] rounded-md bg-neutral-950 px-4 py-2 text-sm text-white",
|
|
2985
|
+
children: "Loop paused \xB7 Space to resume"
|
|
2986
|
+
}
|
|
2987
|
+
),
|
|
2988
|
+
/* @__PURE__ */ jsxs9(
|
|
2989
|
+
"div",
|
|
2990
|
+
{
|
|
2991
|
+
"data-ps-viewer-toolbar": true,
|
|
2992
|
+
style: inspectorVisible ? { right: inspectorWidth + 16, maxWidth: `calc(100vw - ${inspectorWidth + 32}px)` } : void 0,
|
|
1967
2993
|
className: cn(
|
|
1968
|
-
"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",
|
|
1969
|
-
(isPresentationMode || isNarrowViewport &&
|
|
1970
|
-
|
|
2994
|
+
"fixed top-4 z-50 flex flex-wrap 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",
|
|
2995
|
+
(isPresentationMode || isNarrowViewport && inspectorVisible && !isEditorOpen) && "hidden",
|
|
2996
|
+
isEditorOpen && viewportWidth <= 850 && "[&_button:disabled]:hidden [&>div]:hidden",
|
|
2997
|
+
!inspectorVisible && "right-4"
|
|
1971
2998
|
),
|
|
1972
2999
|
children: [
|
|
1973
|
-
/* @__PURE__ */
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
className: cn(
|
|
1978
|
-
"rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
|
|
1979
|
-
viewMode === "slide" && "bg-neutral-800 text-white"
|
|
1980
|
-
),
|
|
1981
|
-
title: "Presentation View (V)",
|
|
1982
|
-
children: /* @__PURE__ */ jsx12(Monitor, { className: "h-4 w-4" })
|
|
1983
|
-
}
|
|
1984
|
-
),
|
|
1985
|
-
/* @__PURE__ */ jsx12(
|
|
1986
|
-
"button",
|
|
1987
|
-
{
|
|
1988
|
-
onClick: () => setViewMode("list"),
|
|
1989
|
-
className: cn(
|
|
1990
|
-
"rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
|
|
1991
|
-
viewMode === "list" && "bg-neutral-800 text-white"
|
|
1992
|
-
),
|
|
1993
|
-
title: "List View (L)",
|
|
1994
|
-
children: /* @__PURE__ */ jsx12(List, { className: "h-4 w-4" })
|
|
1995
|
-
}
|
|
1996
|
-
),
|
|
1997
|
-
/* @__PURE__ */ jsx12(
|
|
1998
|
-
"button",
|
|
1999
|
-
{
|
|
2000
|
-
onClick: () => setViewMode("grid"),
|
|
2001
|
-
className: cn(
|
|
2002
|
-
"rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
|
|
2003
|
-
viewMode === "grid" && "bg-neutral-800 text-white"
|
|
2004
|
-
),
|
|
2005
|
-
title: "Grid View (G)",
|
|
2006
|
-
children: /* @__PURE__ */ jsx12(Grid3X3, { className: "h-4 w-4" })
|
|
2007
|
-
}
|
|
2008
|
-
),
|
|
2009
|
-
/* @__PURE__ */ jsx12("div", { className: "mx-1 w-px bg-neutral-800" }),
|
|
2010
|
-
/* @__PURE__ */ jsxs6(
|
|
2011
|
-
"button",
|
|
3000
|
+
/* @__PURE__ */ jsx17(ViewMenu, { value: viewMode, onChange: setViewMode, disabled: isEditorOpen }),
|
|
3001
|
+
/* @__PURE__ */ jsx17("div", { className: "mx-1 w-px bg-neutral-800" }),
|
|
3002
|
+
/* @__PURE__ */ jsxs9(
|
|
3003
|
+
ToolbarAction,
|
|
2012
3004
|
{
|
|
2013
3005
|
onClick: toggleCommentMode,
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
isAnnotationMode ? "border border-[#FF6B35]/50 bg-[#FF6B35]/15 p-[7px] text-[#FF6B35] hover:border-[#FF6B35] hover:bg-[#FF6B35]/25" : "p-2 text-neutral-400 hover:bg-neutral-800 hover:text-white"
|
|
2017
|
-
),
|
|
3006
|
+
label: "Comment",
|
|
3007
|
+
selected: isAnnotationMode,
|
|
2018
3008
|
title: "Comment (C)",
|
|
2019
3009
|
children: [
|
|
2020
|
-
/* @__PURE__ */
|
|
2021
|
-
openCount > 0 && /* @__PURE__ */
|
|
3010
|
+
/* @__PURE__ */ jsx17(MessageCircle, { className: "h-[15px] w-[15px] shrink-0" }),
|
|
3011
|
+
openCount > 0 && /* @__PURE__ */ jsx17("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 })
|
|
2022
3012
|
]
|
|
2023
3013
|
}
|
|
2024
3014
|
),
|
|
2025
|
-
/* @__PURE__ */
|
|
2026
|
-
/* @__PURE__ */
|
|
2027
|
-
|
|
3015
|
+
/* @__PURE__ */ jsx17("span", { "data-ps-editor-toolbar": true, className: "contents" }),
|
|
3016
|
+
/* @__PURE__ */ jsx17(
|
|
3017
|
+
ToolbarAction,
|
|
2028
3018
|
{
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
children: /* @__PURE__ */
|
|
3019
|
+
label: "Slide settings",
|
|
3020
|
+
selected: inspectorMode === "slide" && inspectorVisible,
|
|
3021
|
+
onClick: () => requestInspector(inspectorMode === "slide" && inspectorVisible ? null : "slide"),
|
|
3022
|
+
children: /* @__PURE__ */ jsx17(Settings2, { size: 16 })
|
|
2033
3023
|
}
|
|
2034
3024
|
),
|
|
2035
|
-
/* @__PURE__ */
|
|
2036
|
-
|
|
3025
|
+
/* @__PURE__ */ jsx17("div", { className: "mx-1 w-px bg-neutral-800" }),
|
|
3026
|
+
/* @__PURE__ */ jsx17(
|
|
3027
|
+
ToolbarAction,
|
|
2037
3028
|
{
|
|
2038
|
-
onClick:
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
/* @__PURE__ */ jsx12("span", { children: "Present" })
|
|
2044
|
-
]
|
|
3029
|
+
onClick: handleExportPdf,
|
|
3030
|
+
disabled: isEditorOpen,
|
|
3031
|
+
label: "Download PDF",
|
|
3032
|
+
title: "Download PDF (D)",
|
|
3033
|
+
children: /* @__PURE__ */ jsx17(Download2, { className: "h-4 w-4" })
|
|
2045
3034
|
}
|
|
2046
|
-
)
|
|
3035
|
+
),
|
|
3036
|
+
/* @__PURE__ */ jsxs9("div", { ref: loopMenuRef, className: "relative flex", children: [
|
|
3037
|
+
/* @__PURE__ */ jsxs9(
|
|
3038
|
+
"button",
|
|
3039
|
+
{
|
|
3040
|
+
onClick: togglePresentationMode,
|
|
3041
|
+
className: "inline-flex h-8 items-center justify-center gap-1.5 rounded-l-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",
|
|
3042
|
+
disabled: isEditorOpen,
|
|
3043
|
+
title: "Present (F)",
|
|
3044
|
+
children: [
|
|
3045
|
+
/* @__PURE__ */ jsx17(Play2, { className: "h-3.5 w-3.5 fill-current" }),
|
|
3046
|
+
/* @__PURE__ */ jsx17("span", { children: "Present" })
|
|
3047
|
+
]
|
|
3048
|
+
}
|
|
3049
|
+
),
|
|
3050
|
+
/* @__PURE__ */ jsx17(
|
|
3051
|
+
"button",
|
|
3052
|
+
{
|
|
3053
|
+
disabled: isEditorOpen,
|
|
3054
|
+
"aria-label": "Presentation options",
|
|
3055
|
+
"aria-expanded": loopMenuOpen,
|
|
3056
|
+
"aria-controls": "presentation-options",
|
|
3057
|
+
className: "rounded-r-md border border-l-0 border-[#FF6B35]/50 bg-[#FF6B35]/15 px-2 text-[#FF6B35] hover:bg-[#FF6B35]/25",
|
|
3058
|
+
onClick: () => setLoopMenuOpen((value) => !value),
|
|
3059
|
+
children: /* @__PURE__ */ jsx17(ChevronDown2, { size: 16 })
|
|
3060
|
+
}
|
|
3061
|
+
),
|
|
3062
|
+
loopMenuOpen && /* @__PURE__ */ jsxs9(
|
|
3063
|
+
"div",
|
|
3064
|
+
{
|
|
3065
|
+
id: "presentation-options",
|
|
3066
|
+
className: "absolute right-0 top-10 w-52 rounded-lg border border-neutral-700 bg-neutral-950 p-1 text-sm text-white",
|
|
3067
|
+
children: [
|
|
3068
|
+
/* @__PURE__ */ jsx17(
|
|
3069
|
+
"button",
|
|
3070
|
+
{
|
|
3071
|
+
className: "w-full rounded px-3 py-2 text-left hover:bg-neutral-800",
|
|
3072
|
+
onClick: () => void startLoop(),
|
|
3073
|
+
children: "Auto-loop"
|
|
3074
|
+
}
|
|
3075
|
+
),
|
|
3076
|
+
/* @__PURE__ */ jsx17(
|
|
3077
|
+
"button",
|
|
3078
|
+
{
|
|
3079
|
+
className: "w-full rounded px-3 py-2 text-left hover:bg-neutral-800",
|
|
3080
|
+
onClick: () => {
|
|
3081
|
+
setLoopMenuOpen(false);
|
|
3082
|
+
requestInspector("slide");
|
|
3083
|
+
},
|
|
3084
|
+
children: "Configure loop\u2026"
|
|
3085
|
+
}
|
|
3086
|
+
)
|
|
3087
|
+
]
|
|
3088
|
+
}
|
|
3089
|
+
)
|
|
3090
|
+
] })
|
|
2047
3091
|
]
|
|
2048
3092
|
}
|
|
2049
3093
|
),
|
|
2050
|
-
viewMode === "slide" && /* @__PURE__ */
|
|
3094
|
+
viewMode === "slide" && /* @__PURE__ */ jsxs9(
|
|
2051
3095
|
"div",
|
|
2052
3096
|
{
|
|
2053
3097
|
className: cn("flex h-screen w-full print:hidden", isPresentationMode ? "bg-black" : ""),
|
|
2054
3098
|
children: [
|
|
2055
|
-
!isPresentationMode && /* @__PURE__ */
|
|
2056
|
-
/* @__PURE__ */
|
|
3099
|
+
!isPresentationMode && /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
3100
|
+
/* @__PURE__ */ jsxs9(
|
|
2057
3101
|
"div",
|
|
2058
3102
|
{
|
|
2059
3103
|
className: cn(
|
|
@@ -2063,7 +3107,7 @@ function SlideDeck({
|
|
|
2063
3107
|
),
|
|
2064
3108
|
style: { width: isThumbnailRailVisible ? visibleThumbnailWidth : 0 },
|
|
2065
3109
|
children: [
|
|
2066
|
-
/* @__PURE__ */
|
|
3110
|
+
/* @__PURE__ */ jsx17("div", { className: "h-full w-full overflow-hidden", children: /* @__PURE__ */ jsxs9(
|
|
2067
3111
|
"aside",
|
|
2068
3112
|
{
|
|
2069
3113
|
id: "presentation-thumbnails",
|
|
@@ -2076,13 +3120,13 @@ function SlideDeck({
|
|
|
2076
3120
|
),
|
|
2077
3121
|
style: { width: visibleThumbnailWidth },
|
|
2078
3122
|
children: [
|
|
2079
|
-
/* @__PURE__ */
|
|
2080
|
-
/* @__PURE__ */
|
|
3123
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between border-b border-neutral-800 px-3 py-2", children: [
|
|
3124
|
+
/* @__PURE__ */ jsxs9("span", { className: "text-sm font-semibold", children: [
|
|
2081
3125
|
"Slides (",
|
|
2082
3126
|
slides.length,
|
|
2083
3127
|
")"
|
|
2084
3128
|
] }),
|
|
2085
|
-
/* @__PURE__ */
|
|
3129
|
+
/* @__PURE__ */ jsx17(
|
|
2086
3130
|
"button",
|
|
2087
3131
|
{
|
|
2088
3132
|
type: "button",
|
|
@@ -2090,11 +3134,11 @@ function SlideDeck({
|
|
|
2090
3134
|
className: "rounded p-2 text-neutral-400 hover:bg-neutral-800 hover:text-white",
|
|
2091
3135
|
"aria-label": "Hide slide thumbnails",
|
|
2092
3136
|
title: "Hide thumbnails (T)",
|
|
2093
|
-
children: /* @__PURE__ */
|
|
3137
|
+
children: /* @__PURE__ */ jsx17(PanelLeftClose, { className: "h-4 w-4" })
|
|
2094
3138
|
}
|
|
2095
3139
|
)
|
|
2096
3140
|
] }),
|
|
2097
|
-
/* @__PURE__ */
|
|
3141
|
+
/* @__PURE__ */ jsx17("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__ */ jsxs9(
|
|
2098
3142
|
"button",
|
|
2099
3143
|
{
|
|
2100
3144
|
ref: index === currentSlide ? activeThumbnailRef : void 0,
|
|
@@ -2107,18 +3151,21 @@ function SlideDeck({
|
|
|
2107
3151
|
index === currentSlide ? "border-[#FF6B35] bg-[#FF6B35]/15 ring-1 ring-[#FF6B35]" : "border-transparent hover:border-neutral-600 hover:bg-neutral-800"
|
|
2108
3152
|
),
|
|
2109
3153
|
children: [
|
|
2110
|
-
/* @__PURE__ */
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
3154
|
+
/* @__PURE__ */ jsxs9("div", { className: "relative aspect-video w-full overflow-hidden rounded bg-black", children: [
|
|
3155
|
+
loopDraft.slides[loopSlideKey(slideConfig, index)]?.excluded && /* @__PURE__ */ jsx17("span", { className: "absolute right-1 top-1 z-10 rounded border border-neutral-700 bg-neutral-950 px-1.5 py-0.5 text-[9px] text-neutral-400", children: "Skip in loop" }),
|
|
3156
|
+
/* @__PURE__ */ jsx17(
|
|
3157
|
+
GridThumbnail,
|
|
3158
|
+
{
|
|
3159
|
+
slide: slideConfig,
|
|
3160
|
+
index,
|
|
3161
|
+
total: slides.length
|
|
3162
|
+
}
|
|
3163
|
+
)
|
|
3164
|
+
] }),
|
|
3165
|
+
/* @__PURE__ */ jsx17("div", { className: "mt-1.5 flex items-baseline gap-2 px-0.5 text-xs", children: slideConfig.title ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
3166
|
+
/* @__PURE__ */ jsx17("span", { className: "font-mono text-neutral-400", children: index + 1 }),
|
|
3167
|
+
/* @__PURE__ */ jsx17("span", { className: "truncate", children: slideConfig.title })
|
|
3168
|
+
] }) : /* @__PURE__ */ jsxs9("span", { children: [
|
|
2122
3169
|
"Slide ",
|
|
2123
3170
|
index + 1
|
|
2124
3171
|
] }) })
|
|
@@ -2129,7 +3176,7 @@ function SlideDeck({
|
|
|
2129
3176
|
]
|
|
2130
3177
|
}
|
|
2131
3178
|
) }),
|
|
2132
|
-
isThumbnailRailVisible && /* @__PURE__ */
|
|
3179
|
+
isThumbnailRailVisible && /* @__PURE__ */ jsx17(
|
|
2133
3180
|
"div",
|
|
2134
3181
|
{
|
|
2135
3182
|
role: "separator",
|
|
@@ -2178,16 +3225,17 @@ function SlideDeck({
|
|
|
2178
3225
|
]
|
|
2179
3226
|
}
|
|
2180
3227
|
),
|
|
2181
|
-
/* @__PURE__ */
|
|
3228
|
+
/* @__PURE__ */ jsx17(
|
|
2182
3229
|
"div",
|
|
2183
3230
|
{
|
|
2184
3231
|
"aria-hidden": isThumbnailRailVisible || isResizingThumbnails,
|
|
2185
3232
|
inert: isThumbnailRailVisible || isResizingThumbnails,
|
|
2186
3233
|
className: cn(
|
|
2187
|
-
"fixed
|
|
3234
|
+
"fixed 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",
|
|
3235
|
+
isNarrowViewport && !inspectorVisible ? "top-16" : "top-4",
|
|
2188
3236
|
isThumbnailRailVisible || isResizingThumbnails ? "pointer-events-none opacity-0" : "opacity-100 delay-200"
|
|
2189
3237
|
),
|
|
2190
|
-
children: /* @__PURE__ */
|
|
3238
|
+
children: /* @__PURE__ */ jsx17(
|
|
2191
3239
|
"button",
|
|
2192
3240
|
{
|
|
2193
3241
|
type: "button",
|
|
@@ -2195,28 +3243,34 @@ function SlideDeck({
|
|
|
2195
3243
|
className: "rounded-md p-2 text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-white",
|
|
2196
3244
|
"aria-label": "Show slide thumbnails",
|
|
2197
3245
|
title: "Show thumbnails (T)",
|
|
2198
|
-
children: /* @__PURE__ */
|
|
3246
|
+
children: /* @__PURE__ */ jsx17(PanelLeftOpen, { className: "h-4 w-4" })
|
|
2199
3247
|
}
|
|
2200
3248
|
)
|
|
2201
3249
|
}
|
|
2202
3250
|
)
|
|
2203
3251
|
] }),
|
|
2204
|
-
/* @__PURE__ */
|
|
3252
|
+
/* @__PURE__ */ jsxs9(
|
|
2205
3253
|
"div",
|
|
2206
3254
|
{
|
|
2207
3255
|
ref: containerRef,
|
|
2208
3256
|
role: "presentation",
|
|
3257
|
+
"data-loop-running": isLooping ? "true" : void 0,
|
|
2209
3258
|
tabIndex: isPresentationMode ? 0 : void 0,
|
|
2210
3259
|
className: cn(
|
|
2211
3260
|
"flex min-w-0 flex-1 flex-col items-center justify-center overflow-hidden",
|
|
2212
|
-
isPresentationMode ?
|
|
3261
|
+
isPresentationMode ? `bg-black p-0 ${isLooping ? "cursor-none" : ""}` : "p-4 md:p-8"
|
|
2213
3262
|
),
|
|
2214
|
-
onClick: isPresentationMode ? advance : void 0,
|
|
3263
|
+
onClick: isPresentationMode ? () => isLooping ? playback.move(1) : advance() : void 0,
|
|
2215
3264
|
onKeyDown: isPresentationMode ? (e) => {
|
|
2216
|
-
if (e.key === "Enter"
|
|
3265
|
+
if (e.key === "Enter") {
|
|
3266
|
+
e.preventDefault();
|
|
3267
|
+
e.stopPropagation();
|
|
3268
|
+
if (isLooping) playback.move(1);
|
|
3269
|
+
else advance();
|
|
3270
|
+
}
|
|
2217
3271
|
} : void 0,
|
|
2218
3272
|
children: [
|
|
2219
|
-
/* @__PURE__ */
|
|
3273
|
+
/* @__PURE__ */ jsx17(LayoutGroup, { id: "slide-deck", children: isPresentationMode ? /* @__PURE__ */ jsx17(
|
|
2220
3274
|
"div",
|
|
2221
3275
|
{
|
|
2222
3276
|
className: "pointer-events-none relative overflow-hidden bg-black",
|
|
@@ -2226,9 +3280,10 @@ function SlideDeck({
|
|
|
2226
3280
|
transform: `scale(${scale})`,
|
|
2227
3281
|
transformOrigin: "center center"
|
|
2228
3282
|
},
|
|
2229
|
-
children: /* @__PURE__ */
|
|
3283
|
+
children: /* @__PURE__ */ jsx17(
|
|
2230
3284
|
SlideRenderer,
|
|
2231
3285
|
{
|
|
3286
|
+
playbackKey: isLooping ? playback.epoch : void 0,
|
|
2232
3287
|
slides,
|
|
2233
3288
|
currentSlide,
|
|
2234
3289
|
animationStep,
|
|
@@ -2241,124 +3296,136 @@ function SlideDeck({
|
|
|
2241
3296
|
}
|
|
2242
3297
|
)
|
|
2243
3298
|
}
|
|
2244
|
-
) : /* @__PURE__ */
|
|
3299
|
+
) : /* @__PURE__ */ jsxs9(
|
|
2245
3300
|
"div",
|
|
2246
3301
|
{
|
|
2247
3302
|
ref: previewViewportRef,
|
|
2248
3303
|
className: "relative aspect-video w-full max-w-7xl overflow-hidden rounded-xl border border-neutral-800 bg-black shadow-2xl",
|
|
2249
|
-
children:
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
3304
|
+
children: [
|
|
3305
|
+
/* @__PURE__ */ jsxs9(
|
|
3306
|
+
"div",
|
|
3307
|
+
{
|
|
3308
|
+
ref: slideContainerRef,
|
|
3309
|
+
"data-ps-editor-canvas": true,
|
|
3310
|
+
tabIndex: -1,
|
|
3311
|
+
"data-slide-index": currentSlide,
|
|
3312
|
+
className: "absolute top-0 left-0 overflow-hidden outline-none",
|
|
3313
|
+
style: {
|
|
3314
|
+
width: SLIDE_DIMENSIONS.width,
|
|
3315
|
+
height: SLIDE_DIMENSIONS.height,
|
|
3316
|
+
transformOrigin: "top left"
|
|
3317
|
+
},
|
|
3318
|
+
children: [
|
|
3319
|
+
/* @__PURE__ */ jsx17(
|
|
3320
|
+
SlideRenderer,
|
|
3321
|
+
{
|
|
3322
|
+
slides,
|
|
3323
|
+
currentSlide,
|
|
3324
|
+
animationStep,
|
|
3325
|
+
totalSteps,
|
|
3326
|
+
direction,
|
|
3327
|
+
showAllAnimations,
|
|
3328
|
+
transition,
|
|
3329
|
+
directionalTransition,
|
|
3330
|
+
onTransitionComplete
|
|
3331
|
+
},
|
|
3332
|
+
editorRevision
|
|
3333
|
+
),
|
|
3334
|
+
isAnnotationMode && /* @__PURE__ */ jsx17(
|
|
3335
|
+
AnnotationOverlay,
|
|
3336
|
+
{
|
|
3337
|
+
slides,
|
|
3338
|
+
currentSlide,
|
|
3339
|
+
slideContainerRef,
|
|
3340
|
+
selectedId: selectedAnnotationId,
|
|
3341
|
+
hoveredId: hoveredAnnotationId,
|
|
3342
|
+
onSelectId: setSelectedAnnotationId,
|
|
3343
|
+
onShowPanel: () => requestInspector("comments"),
|
|
3344
|
+
slideAnnotations: getSlideAnnotations(currentSlide),
|
|
3345
|
+
addAnnotation: effectiveAdd ?? (() => {
|
|
3346
|
+
})
|
|
3347
|
+
}
|
|
3348
|
+
)
|
|
3349
|
+
]
|
|
3350
|
+
}
|
|
3351
|
+
),
|
|
3352
|
+
/* @__PURE__ */ jsx17(
|
|
3353
|
+
"div",
|
|
3354
|
+
{
|
|
3355
|
+
"data-ps-editor-overlay": true,
|
|
3356
|
+
className: "pointer-events-none absolute inset-0 z-20"
|
|
3357
|
+
}
|
|
3358
|
+
)
|
|
3359
|
+
]
|
|
2292
3360
|
}
|
|
2293
3361
|
) }),
|
|
2294
|
-
!isPresentationMode && /* @__PURE__ */
|
|
2295
|
-
/* @__PURE__ */
|
|
3362
|
+
!isPresentationMode && /* @__PURE__ */ jsxs9("div", { className: "mt-6 flex items-center gap-4", children: [
|
|
3363
|
+
/* @__PURE__ */ jsx17(
|
|
2296
3364
|
"button",
|
|
2297
3365
|
{
|
|
2298
3366
|
onClick: goBack,
|
|
2299
3367
|
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",
|
|
2300
3368
|
title: "Previous step or slide (\u2190)",
|
|
2301
|
-
children: /* @__PURE__ */
|
|
3369
|
+
children: /* @__PURE__ */ jsx17(ChevronLeft, { className: "h-5 w-5" })
|
|
2302
3370
|
}
|
|
2303
3371
|
),
|
|
2304
|
-
/* @__PURE__ */
|
|
3372
|
+
/* @__PURE__ */ jsx17("div", { className: "flex min-w-[4rem] flex-col items-center", children: /* @__PURE__ */ jsxs9("span", { className: "font-mono text-sm text-neutral-500", children: [
|
|
2305
3373
|
currentSlide + 1,
|
|
2306
3374
|
" / ",
|
|
2307
3375
|
slides.length
|
|
2308
3376
|
] }) }),
|
|
2309
|
-
/* @__PURE__ */
|
|
3377
|
+
/* @__PURE__ */ jsx17(
|
|
2310
3378
|
"button",
|
|
2311
3379
|
{
|
|
2312
3380
|
onClick: advance,
|
|
2313
3381
|
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",
|
|
2314
3382
|
title: "Next step or slide (\u2192 / Space)",
|
|
2315
|
-
children: /* @__PURE__ */
|
|
3383
|
+
children: /* @__PURE__ */ jsx17(ChevronRight, { className: "h-5 w-5" })
|
|
2316
3384
|
}
|
|
2317
3385
|
)
|
|
2318
3386
|
] })
|
|
2319
3387
|
]
|
|
2320
3388
|
}
|
|
2321
3389
|
),
|
|
2322
|
-
|
|
2323
|
-
|
|
3390
|
+
/* @__PURE__ */ jsx17(
|
|
3391
|
+
Inspector,
|
|
2324
3392
|
{
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
3393
|
+
mode: inspectorMode,
|
|
3394
|
+
visible: inspectorVisible,
|
|
3395
|
+
editorAvailable,
|
|
3396
|
+
width: inspectorWidth,
|
|
3397
|
+
overlay: isNarrowViewport,
|
|
3398
|
+
onSelect: requestInspector,
|
|
3399
|
+
onClose: () => requestInspector(null),
|
|
3400
|
+
slideSettings: /* @__PURE__ */ jsx17(
|
|
3401
|
+
LoopSettings,
|
|
3402
|
+
{
|
|
3403
|
+
slides,
|
|
3404
|
+
currentSlide,
|
|
3405
|
+
animationStep,
|
|
3406
|
+
config: loopDraft,
|
|
3407
|
+
onChange: setLoopDraft,
|
|
3408
|
+
onStepChange: goToStep,
|
|
3409
|
+
onSave: saveLoopConfig,
|
|
3410
|
+
onStart: (config) => void startLoop(config),
|
|
3411
|
+
onRevert: () => setLoopDraft(loopConfig),
|
|
3412
|
+
dirty: JSON.stringify(loopDraft) !== JSON.stringify(loopConfig)
|
|
3413
|
+
}
|
|
2331
3414
|
),
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
},
|
|
2335
|
-
children: /* @__PURE__ */ jsx12(
|
|
2336
|
-
"div",
|
|
3415
|
+
comments: /* @__PURE__ */ jsx17(
|
|
3416
|
+
AnnotationPanel,
|
|
2337
3417
|
{
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
onHover: setHoveredAnnotationId,
|
|
2350
|
-
onDelete: effectiveDelete ?? (() => {
|
|
2351
|
-
}),
|
|
2352
|
-
onUpdate: effectiveUpdate ?? (() => {
|
|
2353
|
-
}),
|
|
2354
|
-
onClose: () => {
|
|
2355
|
-
setIsAnnotationMode(false);
|
|
2356
|
-
setShowAnnotationPanel(false);
|
|
2357
|
-
setSelectedAnnotationId(null);
|
|
2358
|
-
setHoveredAnnotationId(null);
|
|
2359
|
-
}
|
|
2360
|
-
}
|
|
2361
|
-
)
|
|
3418
|
+
embedded: true,
|
|
3419
|
+
annotations: getSlideAnnotations(currentSlide),
|
|
3420
|
+
unlinkedAnnotations,
|
|
3421
|
+
selectedId: selectedAnnotationId,
|
|
3422
|
+
onSelect: setSelectedAnnotationId,
|
|
3423
|
+
onHover: setHoveredAnnotationId,
|
|
3424
|
+
onDelete: effectiveDelete ?? (() => {
|
|
3425
|
+
}),
|
|
3426
|
+
onUpdate: effectiveUpdate ?? (() => {
|
|
3427
|
+
}),
|
|
3428
|
+
onClose: () => requestInspector(null)
|
|
2362
3429
|
}
|
|
2363
3430
|
)
|
|
2364
3431
|
}
|
|
@@ -2366,12 +3433,12 @@ function SlideDeck({
|
|
|
2366
3433
|
]
|
|
2367
3434
|
}
|
|
2368
3435
|
),
|
|
2369
|
-
viewMode === "grid" && /* @__PURE__ */
|
|
3436
|
+
viewMode === "grid" && /* @__PURE__ */ jsx17("div", { className: "mx-auto max-w-7xl p-8 pt-16 print:hidden", children: /* @__PURE__ */ jsx17("div", { className: "grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4", children: slides.map((slideConfig, index) => {
|
|
2370
3437
|
const prevSection = index > 0 ? slides[index - 1]?.section : void 0;
|
|
2371
3438
|
const showSectionHeader = slideConfig.section && slideConfig.section !== prevSection;
|
|
2372
|
-
return /* @__PURE__ */
|
|
2373
|
-
showSectionHeader && /* @__PURE__ */
|
|
2374
|
-
/* @__PURE__ */
|
|
3439
|
+
return /* @__PURE__ */ jsxs9("div", { className: "contents", children: [
|
|
3440
|
+
showSectionHeader && /* @__PURE__ */ jsx17("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 }),
|
|
3441
|
+
/* @__PURE__ */ jsxs9(
|
|
2375
3442
|
"button",
|
|
2376
3443
|
{
|
|
2377
3444
|
onClick: () => {
|
|
@@ -2380,15 +3447,15 @@ function SlideDeck({
|
|
|
2380
3447
|
},
|
|
2381
3448
|
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",
|
|
2382
3449
|
children: [
|
|
2383
|
-
/* @__PURE__ */
|
|
2384
|
-
/* @__PURE__ */
|
|
2385
|
-
/* @__PURE__ */
|
|
3450
|
+
/* @__PURE__ */ jsx17(GridThumbnail, { slide: slideConfig, index, total: slides.length }),
|
|
3451
|
+
/* @__PURE__ */ jsx17("div", { className: "absolute inset-0 bg-black/0 transition-colors group-hover:bg-black/20" }),
|
|
3452
|
+
/* @__PURE__ */ jsx17("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 })
|
|
2386
3453
|
]
|
|
2387
3454
|
}
|
|
2388
3455
|
)
|
|
2389
3456
|
] }, slideConfig.id ?? index);
|
|
2390
3457
|
}) }) }),
|
|
2391
|
-
/* @__PURE__ */
|
|
3458
|
+
/* @__PURE__ */ jsx17(
|
|
2392
3459
|
"div",
|
|
2393
3460
|
{
|
|
2394
3461
|
className: cn(
|
|
@@ -2396,7 +3463,7 @@ function SlideDeck({
|
|
|
2396
3463
|
"print:m-0 print:block print:max-w-none print:p-0",
|
|
2397
3464
|
viewMode === "list" ? "block" : "hidden print:block"
|
|
2398
3465
|
),
|
|
2399
|
-
children: /* @__PURE__ */
|
|
3466
|
+
children: /* @__PURE__ */ jsx17("div", { className: "grid grid-cols-1 gap-8 print:block", children: slides.map((slideConfig, index) => /* @__PURE__ */ jsx17(
|
|
2400
3467
|
ListSlide,
|
|
2401
3468
|
{
|
|
2402
3469
|
slide: slideConfig,
|
|
@@ -2420,6 +3487,7 @@ export {
|
|
|
2420
3487
|
EASE_MORPH,
|
|
2421
3488
|
EASE_OUT,
|
|
2422
3489
|
ELEMENT_SLIDE_DISTANCE,
|
|
3490
|
+
LoopConfigProvider,
|
|
2423
3491
|
MORPH_DURATION,
|
|
2424
3492
|
MORPH_TRANSITION,
|
|
2425
3493
|
Morph,
|