@slidev-react/client 0.2.5
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 +42 -0
- package/LICENSE +21 -0
- package/README.md +16 -0
- package/package.json +44 -0
- package/src/addons/AddonProvider.tsx +25 -0
- package/src/addons/g2/G2Chart.tsx +370 -0
- package/src/addons/g2/chartPresets.ts +43 -0
- package/src/addons/g2/chartThemeTokens.ts +124 -0
- package/src/addons/g2/index.ts +36 -0
- package/src/addons/g2/style.css +31 -0
- package/src/addons/insight/Insight.tsx +10 -0
- package/src/addons/insight/InsightAddonProvider.tsx +20 -0
- package/src/addons/insight/SpotlightLayout.tsx +11 -0
- package/src/addons/insight/index.ts +17 -0
- package/src/addons/insight/style.css +34 -0
- package/src/addons/mermaid/MermaidDiagram.tsx +379 -0
- package/src/addons/mermaid/index.ts +10 -0
- package/src/addons/registry.test.ts +28 -0
- package/src/addons/registry.ts +61 -0
- package/src/addons/types.ts +6 -0
- package/src/app/App.tsx +125 -0
- package/src/app/README.md +18 -0
- package/src/app/providers/SlidesNavigationProvider.tsx +82 -0
- package/src/app/usePresentationBootstrap.ts +85 -0
- package/src/features/presentation/PresentationStatus.tsx +514 -0
- package/src/features/presentation/PrintSlidesView.tsx +350 -0
- package/src/features/presentation/browser.ts +5 -0
- package/src/features/presentation/draw/DrawOverlay.tsx +170 -0
- package/src/features/presentation/draw/DrawProvider.tsx +394 -0
- package/src/features/presentation/draw/persistence.test.ts +80 -0
- package/src/features/presentation/draw/persistence.ts +54 -0
- package/src/features/presentation/exportArtifacts.test.ts +48 -0
- package/src/features/presentation/exportArtifacts.ts +6 -0
- package/src/features/presentation/location.test.ts +73 -0
- package/src/features/presentation/location.ts +113 -0
- package/src/features/presentation/navigation/KeyboardController.tsx +73 -0
- package/src/features/presentation/navigation/PresentationNavbar.tsx +162 -0
- package/src/features/presentation/navigation/ShortcutsHelpOverlay.test.tsx +24 -0
- package/src/features/presentation/navigation/ShortcutsHelpOverlay.tsx +111 -0
- package/src/features/presentation/navigation/keyboardShortcuts.test.ts +74 -0
- package/src/features/presentation/navigation/keyboardShortcuts.ts +221 -0
- package/src/features/presentation/navigation/useSlidesNavigation.ts +15 -0
- package/src/features/presentation/overview/NotesOverview.tsx +200 -0
- package/src/features/presentation/overview/QuickOverview.tsx +126 -0
- package/src/features/presentation/path.ts +137 -0
- package/src/features/presentation/presenter/FlowTimelinePreview.test.tsx +54 -0
- package/src/features/presentation/presenter/FlowTimelinePreview.tsx +274 -0
- package/src/features/presentation/presenter/PresenterModeView.tsx +93 -0
- package/src/features/presentation/presenter/PresenterShell.tsx +286 -0
- package/src/features/presentation/presenter/PresenterSidePreview.tsx +68 -0
- package/src/features/presentation/presenter/PresenterTopProgress.tsx +28 -0
- package/src/features/presentation/presenter/SpeakerNotesPanel.tsx +51 -0
- package/src/features/presentation/presenter/StandaloneModeView.tsx +36 -0
- package/src/features/presentation/presenter/persistence.test.ts +26 -0
- package/src/features/presentation/presenter/persistence.ts +31 -0
- package/src/features/presentation/presenter/presentationSyncBridge.test.ts +87 -0
- package/src/features/presentation/presenter/presentationSyncBridge.ts +82 -0
- package/src/features/presentation/presenter/stage.ts +15 -0
- package/src/features/presentation/presenter/types.ts +30 -0
- package/src/features/presentation/presenter/useFullscreen.ts +58 -0
- package/src/features/presentation/presenter/useIdleCursor.ts +37 -0
- package/src/features/presentation/presenter/usePresentationFlowRuntime.ts +238 -0
- package/src/features/presentation/presenter/usePresenterChromeRuntime.ts +358 -0
- package/src/features/presentation/presenter/usePresenterSessionState.ts +226 -0
- package/src/features/presentation/presenter/useWakeLock.ts +110 -0
- package/src/features/presentation/recordingFilename.test.ts +46 -0
- package/src/features/presentation/recordingFilename.ts +56 -0
- package/src/features/presentation/reveal/Reveal.tsx +119 -0
- package/src/features/presentation/reveal/RevealContext.tsx +29 -0
- package/src/features/presentation/reveal/useRevealStep.ts +35 -0
- package/src/features/presentation/session.test.ts +122 -0
- package/src/features/presentation/session.ts +124 -0
- package/src/features/presentation/stage/SlidePreviewSurface.tsx +92 -0
- package/src/features/presentation/stage/SlideStage.tsx +159 -0
- package/src/features/presentation/stage/slideSurface.ts +71 -0
- package/src/features/presentation/stage/slideViewport.tsx +47 -0
- package/src/features/presentation/sync/adapters/broadcastChannelTransport.ts +40 -0
- package/src/features/presentation/sync/adapters/websocketTransport.ts +128 -0
- package/src/features/presentation/sync/model/presence.test.ts +42 -0
- package/src/features/presentation/sync/model/presence.ts +33 -0
- package/src/features/presentation/sync/model/replication.test.ts +72 -0
- package/src/features/presentation/sync/model/replication.ts +113 -0
- package/src/features/presentation/sync/model/status.test.ts +52 -0
- package/src/features/presentation/sync/model/status.ts +33 -0
- package/src/features/presentation/types.ts +1 -0
- package/src/features/presentation/usePresentationRecorder.ts +194 -0
- package/src/features/presentation/usePresentationSync.ts +423 -0
- package/src/index.ts +7 -0
- package/src/main.tsx +12 -0
- package/src/theme/ThemeProvider.test.ts +36 -0
- package/src/theme/ThemeProvider.tsx +79 -0
- package/src/theme/__mocks__/active-theme.ts +3 -0
- package/src/theme/base.css +14 -0
- package/src/theme/components.css +231 -0
- package/src/theme/index.css +11 -0
- package/src/theme/layouts/center.tsx +9 -0
- package/src/theme/layouts/cover.tsx +9 -0
- package/src/theme/layouts/default.tsx +5 -0
- package/src/theme/layouts/defaultLayouts.ts +20 -0
- package/src/theme/layouts/helpers.tsx +12 -0
- package/src/theme/layouts/image-right.tsx +21 -0
- package/src/theme/layouts/immersive.tsx +9 -0
- package/src/theme/layouts/resolveLayout.ts +9 -0
- package/src/theme/layouts/section.tsx +9 -0
- package/src/theme/layouts/statement.tsx +9 -0
- package/src/theme/layouts/two-cols.tsx +21 -0
- package/src/theme/layouts/types.ts +1 -0
- package/src/theme/layouts.css +133 -0
- package/src/theme/mark.css +379 -0
- package/src/theme/print.css +106 -0
- package/src/theme/prose.css +263 -0
- package/src/theme/registry.test.ts +21 -0
- package/src/theme/registry.ts +40 -0
- package/src/theme/tokens.css +148 -0
- package/src/theme/transitions.css +141 -0
- package/src/theme/types.ts +9 -0
- package/src/theme/useResolvedLayout.ts +24 -0
- package/src/types/generated-slides.d.ts +7 -0
- package/src/types/mdx-components.ts +7 -0
- package/src/types/plantuml-encoder.d.ts +7 -0
- package/src/ui/diagrams/PlantUmlDiagram.tsx +33 -0
- package/src/ui/mdx/MagicMoveDemo.tsx +114 -0
- package/src/ui/mdx/index.ts +21 -0
- package/src/ui/primitives/Annotate.test.tsx +64 -0
- package/src/ui/primitives/Annotate.tsx +82 -0
- package/src/ui/primitives/Badge.tsx +5 -0
- package/src/ui/primitives/Callout.tsx +24 -0
- package/src/ui/primitives/ChromeIconButton.tsx +58 -0
- package/src/ui/primitives/ChromePanel.tsx +79 -0
- package/src/ui/primitives/ChromeTag.tsx +70 -0
- package/src/ui/primitives/FormSelect.tsx +51 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { AddonProvider } from "../../../addons/AddonProvider";
|
|
4
|
+
import { DEFAULT_SLIDES_VIEWPORT } from "@slidev-react/core/slides/viewport";
|
|
5
|
+
import { ThemeProvider } from "../../../theme/ThemeProvider";
|
|
6
|
+
import { FlowTimelinePreview } from "./FlowTimelinePreview";
|
|
7
|
+
import type { CompiledSlide } from "./types";
|
|
8
|
+
|
|
9
|
+
const demoSlide: CompiledSlide = {
|
|
10
|
+
id: "timeline-demo",
|
|
11
|
+
component: function DemoSlide() {
|
|
12
|
+
return <div>Timeline Demo</div>;
|
|
13
|
+
},
|
|
14
|
+
meta: {
|
|
15
|
+
title: "Timeline Demo",
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function renderPreview(props?: Partial<React.ComponentProps<typeof FlowTimelinePreview>>) {
|
|
20
|
+
return renderToStaticMarkup(
|
|
21
|
+
<ThemeProvider>
|
|
22
|
+
<AddonProvider>
|
|
23
|
+
<FlowTimelinePreview
|
|
24
|
+
slide={demoSlide}
|
|
25
|
+
currentClicks={1}
|
|
26
|
+
currentClicksTotal={3}
|
|
27
|
+
slidesConfig={{ slidesViewport: DEFAULT_SLIDES_VIEWPORT }}
|
|
28
|
+
onJumpToCue={vi.fn()}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
</AddonProvider>
|
|
32
|
+
</ThemeProvider>,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("FlowTimelinePreview", () => {
|
|
37
|
+
it("renders timeline controls and cue nodes for the current slide", () => {
|
|
38
|
+
const html = renderPreview();
|
|
39
|
+
|
|
40
|
+
expect(html).toContain("Timeline Preview");
|
|
41
|
+
expect(html).toContain("Start");
|
|
42
|
+
expect(html).toContain("Cue 1");
|
|
43
|
+
expect(html).toContain("Cue 3");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("shows an empty-state message when the slide has no cue steps", () => {
|
|
47
|
+
const html = renderPreview({
|
|
48
|
+
currentClicks: 0,
|
|
49
|
+
currentClicksTotal: 0,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(html).toContain("No cue steps detected on this slide yet.");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { X } from "lucide-react";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
import type { SlidesConfig } from "./types";
|
|
4
|
+
import { resolveSlideSurface, resolveSlideSurfaceClassName } from "../stage/slideSurface";
|
|
5
|
+
import { RevealProvider, type RevealContextValue } from "../reveal/RevealContext";
|
|
6
|
+
import { useResolvedLayout } from "../../../theme/useResolvedLayout";
|
|
7
|
+
import { resolveOverviewStageMetrics } from "./stage";
|
|
8
|
+
import type { CompiledSlide } from "./types";
|
|
9
|
+
import { ChromeIconButton } from "../../../ui/primitives/ChromeIconButton";
|
|
10
|
+
import { ChromePanel } from "../../../ui/primitives/ChromePanel";
|
|
11
|
+
import { ChromeTag, chromeTagClassName } from "../../../ui/primitives/ChromeTag";
|
|
12
|
+
|
|
13
|
+
type FlowPreviewMode = "live" | "steps" | "final";
|
|
14
|
+
|
|
15
|
+
function noopCleanup() {}
|
|
16
|
+
|
|
17
|
+
function noopRegisterStep() {
|
|
18
|
+
return noopCleanup;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createRevealContextValue({
|
|
22
|
+
slideId,
|
|
23
|
+
clicks,
|
|
24
|
+
clicksTotal,
|
|
25
|
+
}: {
|
|
26
|
+
slideId: string;
|
|
27
|
+
clicks: number;
|
|
28
|
+
clicksTotal: number;
|
|
29
|
+
}): RevealContextValue {
|
|
30
|
+
return {
|
|
31
|
+
slideId,
|
|
32
|
+
clicks,
|
|
33
|
+
clicksTotal,
|
|
34
|
+
setClicks: () => {},
|
|
35
|
+
registerStep: noopRegisterStep,
|
|
36
|
+
advance: () => {},
|
|
37
|
+
retreat: () => {},
|
|
38
|
+
canAdvance: clicks < clicksTotal,
|
|
39
|
+
canRetreat: clicks > 0,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolvePreviewStep({
|
|
44
|
+
mode,
|
|
45
|
+
currentClicks,
|
|
46
|
+
currentClicksTotal,
|
|
47
|
+
selectedClicks,
|
|
48
|
+
}: {
|
|
49
|
+
mode: FlowPreviewMode;
|
|
50
|
+
currentClicks: number;
|
|
51
|
+
currentClicksTotal: number;
|
|
52
|
+
selectedClicks: number;
|
|
53
|
+
}) {
|
|
54
|
+
if (mode === "final") return currentClicksTotal;
|
|
55
|
+
if (mode === "steps") return selectedClicks;
|
|
56
|
+
|
|
57
|
+
return currentClicks;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function describePreviewStep(step: number, total: number) {
|
|
61
|
+
if (total <= 0) return "Base state";
|
|
62
|
+
if (step <= 0) return "Before cue 1";
|
|
63
|
+
if (step >= total) return `Cue ${total}/${total} • final state`;
|
|
64
|
+
|
|
65
|
+
return `Cue ${step}/${total}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function FlowTimelinePreview({
|
|
69
|
+
slide,
|
|
70
|
+
currentClicks,
|
|
71
|
+
currentClicksTotal,
|
|
72
|
+
slidesConfig,
|
|
73
|
+
onJumpToCue,
|
|
74
|
+
onClose,
|
|
75
|
+
className,
|
|
76
|
+
}: {
|
|
77
|
+
slide: CompiledSlide;
|
|
78
|
+
currentClicks: number;
|
|
79
|
+
currentClicksTotal: number;
|
|
80
|
+
slidesConfig: Pick<SlidesConfig, "slidesViewport" | "slidesLayout" | "slidesBackground">;
|
|
81
|
+
onJumpToCue?: (cueIndex: number) => void;
|
|
82
|
+
onClose?: () => void;
|
|
83
|
+
className?: string;
|
|
84
|
+
}) {
|
|
85
|
+
const { slidesViewport, slidesLayout, slidesBackground } = slidesConfig;
|
|
86
|
+
const [mode, setMode] = useState<FlowPreviewMode>("live");
|
|
87
|
+
const [selectedClicks, setSelectedClicks] = useState(currentClicks);
|
|
88
|
+
const Layout = useResolvedLayout(slide.meta.layout ?? slidesLayout);
|
|
89
|
+
const Slide = slide.component;
|
|
90
|
+
const overviewStage = useMemo(
|
|
91
|
+
() => resolveOverviewStageMetrics(slidesViewport),
|
|
92
|
+
[slidesViewport],
|
|
93
|
+
);
|
|
94
|
+
const cueSteps = useMemo(
|
|
95
|
+
() => Array.from({ length: currentClicksTotal + 1 }, (_, index) => index),
|
|
96
|
+
[currentClicksTotal],
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
setSelectedClicks(currentClicks);
|
|
101
|
+
}, [currentClicks, currentClicksTotal, slide.id]);
|
|
102
|
+
|
|
103
|
+
const previewClicks = resolvePreviewStep({
|
|
104
|
+
mode,
|
|
105
|
+
currentClicks,
|
|
106
|
+
currentClicksTotal,
|
|
107
|
+
selectedClicks,
|
|
108
|
+
});
|
|
109
|
+
const revealContextValue = useMemo(
|
|
110
|
+
() =>
|
|
111
|
+
createRevealContextValue({
|
|
112
|
+
slideId: `${slide.id}:timeline-preview`,
|
|
113
|
+
clicks: previewClicks,
|
|
114
|
+
clicksTotal: currentClicksTotal,
|
|
115
|
+
}),
|
|
116
|
+
[currentClicksTotal, previewClicks, slide.id],
|
|
117
|
+
);
|
|
118
|
+
const surface = resolveSlideSurface({
|
|
119
|
+
meta: slide.meta,
|
|
120
|
+
slidesBackground,
|
|
121
|
+
className: resolveSlideSurfaceClassName({
|
|
122
|
+
layout: slide.meta.layout ?? slidesLayout,
|
|
123
|
+
overflowHidden: true,
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
const previewLabel = describePreviewStep(previewClicks, currentClicksTotal);
|
|
127
|
+
const modeDescription =
|
|
128
|
+
mode === "live"
|
|
129
|
+
? "Mirror the current stage state."
|
|
130
|
+
: mode === "final"
|
|
131
|
+
? "Flatten to the final result."
|
|
132
|
+
: "Inspect one cue position without changing the stage.";
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<ChromePanel className={`flex flex-col ${className ?? ""}`}>
|
|
136
|
+
<div className="mb-3 flex items-center justify-between gap-3">
|
|
137
|
+
<div>
|
|
138
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-500">
|
|
139
|
+
Timeline Preview
|
|
140
|
+
</p>
|
|
141
|
+
<p className="mt-1 text-xs text-slate-500">{modeDescription}</p>
|
|
142
|
+
</div>
|
|
143
|
+
<div className="flex items-center gap-2">
|
|
144
|
+
<ChromeTag>{previewLabel}</ChromeTag>
|
|
145
|
+
{onClose && (
|
|
146
|
+
<ChromeIconButton onClick={onClose} aria-label="Close timeline preview" size="sm">
|
|
147
|
+
<X size={14} />
|
|
148
|
+
</ChromeIconButton>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
<ChromePanel
|
|
153
|
+
as="div"
|
|
154
|
+
tone="solid"
|
|
155
|
+
radius="inset"
|
|
156
|
+
padding="none"
|
|
157
|
+
className="mb-3 flex items-center gap-2 p-1"
|
|
158
|
+
>
|
|
159
|
+
{(["live", "steps", "final"] as const).map((value) => {
|
|
160
|
+
const active = mode === value;
|
|
161
|
+
return (
|
|
162
|
+
<button
|
|
163
|
+
key={value}
|
|
164
|
+
type="button"
|
|
165
|
+
onClick={() => setMode(value)}
|
|
166
|
+
className={`inline-flex flex-1 items-center justify-center rounded-md px-3 py-2 text-xs font-semibold uppercase tracking-[0.16em] transition ${
|
|
167
|
+
active
|
|
168
|
+
? "bg-slate-900 text-white"
|
|
169
|
+
: "text-slate-500 hover:bg-slate-100 hover:text-slate-700"
|
|
170
|
+
}`}
|
|
171
|
+
>
|
|
172
|
+
{value}
|
|
173
|
+
</button>
|
|
174
|
+
);
|
|
175
|
+
})}
|
|
176
|
+
</ChromePanel>
|
|
177
|
+
<ChromePanel
|
|
178
|
+
as="div"
|
|
179
|
+
tone="frame"
|
|
180
|
+
radius="inset"
|
|
181
|
+
padding="none"
|
|
182
|
+
className="mb-3 overflow-hidden"
|
|
183
|
+
>
|
|
184
|
+
<div style={{ height: `${overviewStage.overviewStageHeight}px` }}>
|
|
185
|
+
<div
|
|
186
|
+
className="origin-top-left"
|
|
187
|
+
style={{
|
|
188
|
+
width: `${overviewStage.stageWidth}px`,
|
|
189
|
+
height: `${overviewStage.stageHeight}px`,
|
|
190
|
+
transform: `scale(${overviewStage.overviewStageScale})`,
|
|
191
|
+
transformOrigin: "top left",
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
<RevealProvider value={revealContextValue}>
|
|
195
|
+
<article className={surface.className} style={surface.style}>
|
|
196
|
+
<Layout>
|
|
197
|
+
<Slide />
|
|
198
|
+
</Layout>
|
|
199
|
+
</article>
|
|
200
|
+
</RevealProvider>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</ChromePanel>
|
|
204
|
+
<div className="mb-3 flex items-center justify-between gap-3 text-xs text-slate-500">
|
|
205
|
+
<span>
|
|
206
|
+
Current stage:{" "}
|
|
207
|
+
{currentClicksTotal > 0 ? `${currentClicks}/${currentClicksTotal}` : "base"}
|
|
208
|
+
</span>
|
|
209
|
+
{onJumpToCue && previewClicks !== currentClicks && (
|
|
210
|
+
<button
|
|
211
|
+
onClick={() => onJumpToCue(previewClicks)}
|
|
212
|
+
className={chromeTagClassName({
|
|
213
|
+
tone: "active",
|
|
214
|
+
className: "transition hover:bg-emerald-100",
|
|
215
|
+
})}
|
|
216
|
+
>
|
|
217
|
+
Jump To Stage
|
|
218
|
+
</button>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
{currentClicksTotal > 0 ? (
|
|
222
|
+
<div className="grid grid-cols-2 gap-2 overflow-auto pr-1">
|
|
223
|
+
{cueSteps.map((step) => {
|
|
224
|
+
const selected = previewClicks === step;
|
|
225
|
+
const current = currentClicks === step;
|
|
226
|
+
const label = step === 0 ? "Start" : `Cue ${step}`;
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<button
|
|
230
|
+
key={step}
|
|
231
|
+
type="button"
|
|
232
|
+
onClick={() => {
|
|
233
|
+
setMode("steps");
|
|
234
|
+
setSelectedClicks(step);
|
|
235
|
+
}}
|
|
236
|
+
className={`rounded-md border px-3 py-2 text-left transition ${
|
|
237
|
+
selected
|
|
238
|
+
? "border-slate-900 bg-slate-900 text-white"
|
|
239
|
+
: current
|
|
240
|
+
? "border-emerald-300 bg-emerald-50 text-emerald-800"
|
|
241
|
+
: "border-slate-200 bg-white/90 text-slate-700 hover:border-slate-300 hover:bg-slate-50"
|
|
242
|
+
}`}
|
|
243
|
+
>
|
|
244
|
+
<div className="flex items-center justify-between gap-2">
|
|
245
|
+
<span className="text-sm font-semibold">{label}</span>
|
|
246
|
+
<span
|
|
247
|
+
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.16em] ${
|
|
248
|
+
selected ? "bg-white/18 text-white" : "bg-slate-100 text-slate-500"
|
|
249
|
+
}`}
|
|
250
|
+
>
|
|
251
|
+
reveal
|
|
252
|
+
</span>
|
|
253
|
+
</div>
|
|
254
|
+
<div
|
|
255
|
+
className={`mt-1 text-xs ${
|
|
256
|
+
selected ? "text-white/78" : current ? "text-emerald-700" : "text-slate-500"
|
|
257
|
+
}`}
|
|
258
|
+
>
|
|
259
|
+
{step === 0
|
|
260
|
+
? "Base slide state before cues."
|
|
261
|
+
: `Reveal cue ${step} becomes active.`}
|
|
262
|
+
</div>
|
|
263
|
+
</button>
|
|
264
|
+
);
|
|
265
|
+
})}
|
|
266
|
+
</div>
|
|
267
|
+
) : (
|
|
268
|
+
<ChromePanel as="div" tone="dashed" radius="inset" className="px-4 py-5 text-sm">
|
|
269
|
+
No cue steps detected on this slide yet.
|
|
270
|
+
</ChromePanel>
|
|
271
|
+
)}
|
|
272
|
+
</ChromePanel>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { LayoutName } from "@slidev-react/core/slides/layout"
|
|
2
|
+
import type { TransitionName } from "@slidev-react/core/slides/transition"
|
|
3
|
+
import type { PresentationCursorState } from "../types"
|
|
4
|
+
import { RevealProvider } from "../reveal/RevealContext"
|
|
5
|
+
import { SlideStage } from "../stage/SlideStage"
|
|
6
|
+
import { PresenterSidePreview } from "./PresenterSidePreview"
|
|
7
|
+
import { SpeakerNotesPanel } from "./SpeakerNotesPanel"
|
|
8
|
+
import type { CompiledSlide, SlidesConfig } from "./types"
|
|
9
|
+
import type { PresentationFlowRuntime } from "./usePresentationFlowRuntime"
|
|
10
|
+
import type { usePresenterChromeRuntime } from "./usePresenterChromeRuntime"
|
|
11
|
+
|
|
12
|
+
export function PresenterModeView({
|
|
13
|
+
currentSlide,
|
|
14
|
+
nextSlide,
|
|
15
|
+
slidesConfig,
|
|
16
|
+
canControl,
|
|
17
|
+
remoteCursor,
|
|
18
|
+
localCursor: _localCursor,
|
|
19
|
+
setLocalCursor,
|
|
20
|
+
flow,
|
|
21
|
+
chrome,
|
|
22
|
+
navigation,
|
|
23
|
+
}: {
|
|
24
|
+
currentSlide: CompiledSlide
|
|
25
|
+
nextSlide: CompiledSlide | null
|
|
26
|
+
slidesConfig: SlidesConfig
|
|
27
|
+
canControl: boolean
|
|
28
|
+
remoteCursor: PresentationCursorState | null
|
|
29
|
+
localCursor: PresentationCursorState | null
|
|
30
|
+
setLocalCursor: (cursor: PresentationCursorState | null) => void
|
|
31
|
+
flow: PresentationFlowRuntime
|
|
32
|
+
chrome: ReturnType<typeof usePresenterChromeRuntime>
|
|
33
|
+
navigation: { currentIndex: number; total: number }
|
|
34
|
+
}) {
|
|
35
|
+
const CurrentSlide = currentSlide.component
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
ref={chrome.presenterLayoutRef}
|
|
40
|
+
style={chrome.presenterLayoutStyle}
|
|
41
|
+
className="grid h-full min-h-0 grid-cols-1 gap-0"
|
|
42
|
+
>
|
|
43
|
+
<section className="relative min-h-0 overflow-hidden rounded-md border border-slate-200 bg-white">
|
|
44
|
+
<div className="relative z-0 h-full">
|
|
45
|
+
<RevealProvider value={flow.revealContextValue}>
|
|
46
|
+
<SlideStage
|
|
47
|
+
Slide={CurrentSlide}
|
|
48
|
+
slideId={currentSlide.id}
|
|
49
|
+
meta={currentSlide.meta}
|
|
50
|
+
slidesConfig={slidesConfig}
|
|
51
|
+
remoteCursor={canControl ? null : remoteCursor}
|
|
52
|
+
onCursorChange={canControl ? setLocalCursor : undefined}
|
|
53
|
+
onStageAdvance={
|
|
54
|
+
canControl && !chrome.activeOverlay ? flow.advanceReveal : undefined
|
|
55
|
+
}
|
|
56
|
+
scaleMultiplier={chrome.stageScale}
|
|
57
|
+
/>
|
|
58
|
+
</RevealProvider>
|
|
59
|
+
</div>
|
|
60
|
+
</section>
|
|
61
|
+
<div
|
|
62
|
+
role="separator"
|
|
63
|
+
aria-label="Resize presenter sidebar"
|
|
64
|
+
aria-orientation="vertical"
|
|
65
|
+
tabIndex={0}
|
|
66
|
+
onPointerDown={chrome.handleSidebarResizeStart}
|
|
67
|
+
onKeyDown={chrome.handleSidebarResizeKeyDown}
|
|
68
|
+
className="group relative hidden cursor-col-resize lg:block"
|
|
69
|
+
>
|
|
70
|
+
<div className="absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-slate-200" />
|
|
71
|
+
<div className="absolute inset-y-0 left-1/2 w-1.5 -translate-x-1/2 rounded-sm bg-slate-300 opacity-0 transition-opacity group-hover:opacity-100" />
|
|
72
|
+
</div>
|
|
73
|
+
<aside className="relative z-10 flex min-h-0 min-w-0 flex-col gap-0 text-slate-900">
|
|
74
|
+
<div className="grid min-h-0 flex-1 gap-0 lg:grid-rows-[minmax(220px,0.92fr)_12px_minmax(0,1.08fr)]">
|
|
75
|
+
<PresenterSidePreview
|
|
76
|
+
title="Up Next"
|
|
77
|
+
indexLabel={nextSlide ? String(navigation.currentIndex + 2) : "--"}
|
|
78
|
+
slide={nextSlide}
|
|
79
|
+
slidesConfig={slidesConfig}
|
|
80
|
+
/>
|
|
81
|
+
<div className="flex items-center justify-center px-2" aria-hidden>
|
|
82
|
+
<div className="h-px w-full bg-slate-200" />
|
|
83
|
+
</div>
|
|
84
|
+
<SpeakerNotesPanel
|
|
85
|
+
currentClicks={flow.currentClicks}
|
|
86
|
+
currentClicksTotal={flow.currentClicksTotal}
|
|
87
|
+
notes={currentSlide.meta.notes}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
</aside>
|
|
91
|
+
</div>
|
|
92
|
+
)
|
|
93
|
+
}
|