@kywk/open-slide-core 2.3.1 → 2.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{build-CZ6Qtrng.js → build-46dO14h1.js} +1 -1
- package/dist/cli/bin.js +4 -4
- package/dist/{config-CpmGqpQi.d.ts → config-DJ8ZOe7C.d.ts} +6 -2
- package/dist/{config-B5LArqpp.js → config-KyYvWqq-.js} +2061 -355
- package/dist/{dev-CCgdakbZ.js → dev-C0n-o0-c.js} +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +73 -32
- package/dist/locale/index.d.ts +1 -1
- package/dist/locale/index.js +1 -1
- package/dist/{preview-AwAqvG17.js → preview-CDWAGSfB.js} +1 -1
- package/dist/{types-CvAqFqsY.d.ts → types-tdaXSyah.d.ts} +111 -0
- package/dist/{update-C_RlnKu4.js → update-CqZqQRQk.js} +128 -7
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +1 -1
- package/dist/{zh-tw-B7_Oqv2q.js → zh-tw-DXcqQTrA.js} +540 -0
- package/package.json +10 -3
- package/src/agent/prompt.ts +141 -0
- package/src/agent/provider.ts +123 -0
- package/src/agent/types.ts +151 -0
- package/src/app/components/agent/agent-dock.tsx +35 -0
- package/src/app/components/agent/agent-markdown.tsx +45 -0
- package/src/app/components/agent/agent-panel.tsx +692 -0
- package/src/app/components/agent/agent-presence.tsx +74 -0
- package/src/app/components/agent/composer.tsx +120 -0
- package/src/app/components/agent/connect-view.tsx +184 -0
- package/src/app/components/agent/history-view.tsx +135 -0
- package/src/app/components/agent/provider-logo.tsx +34 -0
- package/src/app/components/agent/transcript.tsx +609 -0
- package/src/app/components/command/slide-command-menu.tsx +10 -0
- package/src/app/components/inline-svg.tsx +52 -21
- package/src/app/components/inspector/comment-layer.tsx +20 -1
- package/src/app/components/inspector/image-crop-dialog.tsx +1 -5
- package/src/app/components/inspector/inspector-panel.tsx +1 -1
- package/src/app/components/mermaid.tsx +42 -11
- package/src/app/components/panel/editor-sidebar.tsx +85 -16
- package/src/app/components/panel/panel-shell.tsx +2 -2
- package/src/app/components/panel/resize-handle.tsx +129 -0
- package/src/app/components/player.tsx +105 -23
- package/src/app/components/present/curtain.tsx +52 -0
- package/src/app/components/present/last-slide-hint.tsx +36 -0
- package/src/app/components/slide-canvas.tsx +1 -1
- package/src/app/components/thumbnail-rail.tsx +1 -1
- package/src/app/lib/agent/use-agent.ts +543 -0
- package/src/app/lib/use-auto-grow.ts +15 -0
- package/src/app/lib/utils.ts +4 -0
- package/src/app/routes/home.tsx +51 -7
- package/src/app/routes/slide.tsx +172 -116
- package/src/app/styles.css +170 -0
- package/src/editing/placeholder-id.ts +1 -0
- package/src/locale/en.ts +107 -0
- package/src/locale/es.ts +109 -0
- package/src/locale/ja.ts +100 -0
- package/src/locale/types.ts +110 -0
- package/src/locale/zh-cn.ts +97 -0
- package/src/locale/zh-tw.ts +97 -0
|
@@ -12,9 +12,16 @@ import { usePrefersReducedMotion } from '../lib/use-prefers-reduced-motion';
|
|
|
12
12
|
import { OverviewGrid } from './overview-grid';
|
|
13
13
|
import { PresentBlackoutOverlay } from './present/blackout-overlay';
|
|
14
14
|
import { PresentControlBar } from './present/control-bar';
|
|
15
|
+
import {
|
|
16
|
+
afterPaint,
|
|
17
|
+
CURTAIN_FADE_MS,
|
|
18
|
+
PresentCurtain,
|
|
19
|
+
whenFullscreenSettled,
|
|
20
|
+
} from './present/curtain';
|
|
15
21
|
import { PresentHelpOverlay } from './present/help-overlay';
|
|
16
22
|
import { PresentJumpInput } from './present/jump-input';
|
|
17
23
|
import { PresentLaserPointer } from './present/laser-pointer';
|
|
24
|
+
import { PresentLastSlideHint } from './present/last-slide-hint';
|
|
18
25
|
import { PresentProgressBar } from './present/progress-bar';
|
|
19
26
|
import { useIdle } from './present/use-idle';
|
|
20
27
|
import { usePointerNearBottom } from './present/use-pointer-near-bottom';
|
|
@@ -48,6 +55,8 @@ type Props = {
|
|
|
48
55
|
* without entering fullscreen. Defaults to true for back-compat.
|
|
49
56
|
*/
|
|
50
57
|
fullscreen?: boolean;
|
|
58
|
+
/** Fade in from black on mount and fade to black before calling `onExit`. */
|
|
59
|
+
curtain?: boolean;
|
|
51
60
|
};
|
|
52
61
|
|
|
53
62
|
export function Player({
|
|
@@ -62,9 +71,13 @@ export function Player({
|
|
|
62
71
|
slideId,
|
|
63
72
|
onSwitchSlide,
|
|
64
73
|
fullscreen = true,
|
|
74
|
+
curtain = false,
|
|
65
75
|
}: Props) {
|
|
66
76
|
const isMobile = useIsMobile();
|
|
67
77
|
const prefersReducedMotion = usePrefersReducedMotion();
|
|
78
|
+
const fade = curtain && !prefersReducedMotion;
|
|
79
|
+
const [curtainOpaque, setCurtainOpaque] = useState(fade);
|
|
80
|
+
const exitingRef = useRef(false);
|
|
68
81
|
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
69
82
|
// Mirrored as state so descendants portaling *into* the player subtree
|
|
70
83
|
// (tooltips, popovers — the body is outside the fullscreen tree) re-render
|
|
@@ -93,6 +106,28 @@ export function Player({
|
|
|
93
106
|
const canPrev = index > 0;
|
|
94
107
|
const canNext = index < pages.length - 1;
|
|
95
108
|
|
|
109
|
+
// Stepping past either end nudges the slide like a rubber band so the
|
|
110
|
+
// press visibly registered; past the last slide a hint says how to leave.
|
|
111
|
+
const nudgeRef = useRef<HTMLDivElement | null>(null);
|
|
112
|
+
const [lastSlideHintAt, setLastSlideHintAt] = useState(0);
|
|
113
|
+
const bumpEdge = useCallback(
|
|
114
|
+
(direction: 'start' | 'end') => {
|
|
115
|
+
if (!controls) return;
|
|
116
|
+
if (direction === 'end') setLastSlideHintAt(Date.now());
|
|
117
|
+
if (prefersReducedMotion) return;
|
|
118
|
+
const offset = direction === 'end' ? -14 : 14;
|
|
119
|
+
nudgeRef.current?.animate(
|
|
120
|
+
[
|
|
121
|
+
{ transform: 'translateX(0)' },
|
|
122
|
+
{ transform: `translateX(${offset}px)`, offset: 0.3 },
|
|
123
|
+
{ transform: 'translateX(0)' },
|
|
124
|
+
],
|
|
125
|
+
{ duration: 360, easing: 'cubic-bezier(0.2, 0.8, 0.3, 1)' },
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
[controls, prefersReducedMotion],
|
|
129
|
+
);
|
|
130
|
+
|
|
96
131
|
const stepControllerRef = useRef<StepController | null>(null);
|
|
97
132
|
const [entryDirection, setEntryDirection] = useState<EntryDirection>('jump');
|
|
98
133
|
const [stepAggregate, setStepAggregate] = useState<StepAggregate>({
|
|
@@ -116,14 +151,34 @@ export function Player({
|
|
|
116
151
|
[index, onIndexChange],
|
|
117
152
|
);
|
|
118
153
|
|
|
154
|
+
const exitTimerRef = useRef<number | null>(null);
|
|
155
|
+
useEffect(
|
|
156
|
+
() => () => {
|
|
157
|
+
if (exitTimerRef.current !== null) window.clearTimeout(exitTimerRef.current);
|
|
158
|
+
},
|
|
159
|
+
[],
|
|
160
|
+
);
|
|
161
|
+
const requestExit = useCallback(() => {
|
|
162
|
+
if (!fade) {
|
|
163
|
+
onExit();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (exitingRef.current) return;
|
|
167
|
+
exitingRef.current = true;
|
|
168
|
+
setCurtainOpaque(true);
|
|
169
|
+
exitTimerRef.current = window.setTimeout(onExit, CURTAIN_FADE_MS);
|
|
170
|
+
}, [fade, onExit]);
|
|
171
|
+
|
|
119
172
|
const goPrev = useCallback(() => {
|
|
120
173
|
if (stepControllerRef.current?.retreat()) return;
|
|
121
174
|
if (index > 0) handleIndexChange(index - 1);
|
|
122
|
-
|
|
175
|
+
else bumpEdge('start');
|
|
176
|
+
}, [index, handleIndexChange, bumpEdge]);
|
|
123
177
|
const goNext = useCallback(() => {
|
|
124
178
|
if (stepControllerRef.current?.advance()) return;
|
|
125
179
|
if (index < pages.length - 1) handleIndexChange(index + 1);
|
|
126
|
-
|
|
180
|
+
else bumpEdge('end');
|
|
181
|
+
}, [index, pages.length, handleIndexChange, bumpEdge]);
|
|
127
182
|
|
|
128
183
|
const overlayActive = controls && (overviewOpen || helpOpen);
|
|
129
184
|
const overlayActiveRef = useRef(overlayActive);
|
|
@@ -178,8 +233,8 @@ export function Player({
|
|
|
178
233
|
useClickPageNavigation({
|
|
179
234
|
ref: rootRef,
|
|
180
235
|
enabled: !overlayActive,
|
|
181
|
-
canPrev,
|
|
182
|
-
canNext,
|
|
236
|
+
canPrev: canPrev || controls,
|
|
237
|
+
canNext: canNext || controls,
|
|
183
238
|
onPrev: goPrev,
|
|
184
239
|
onNext: goNext,
|
|
185
240
|
onViewportClick: controls && isMobile ? handleMobileViewportClick : undefined,
|
|
@@ -216,11 +271,33 @@ export function Player({
|
|
|
216
271
|
useEffect(() => {
|
|
217
272
|
if (!allowExit) return;
|
|
218
273
|
const onFsChange = () => {
|
|
219
|
-
if (!document.fullscreenElement && !windowedRef.current)
|
|
274
|
+
if (!document.fullscreenElement && !windowedRef.current) requestExit();
|
|
220
275
|
};
|
|
221
276
|
document.addEventListener('fullscreenchange', onFsChange);
|
|
222
277
|
return () => document.removeEventListener('fullscreenchange', onFsChange);
|
|
223
|
-
}, [
|
|
278
|
+
}, [requestExit, allowExit]);
|
|
279
|
+
|
|
280
|
+
// Wait out the browser's own fullscreen animation before revealing the
|
|
281
|
+
// first slide, otherwise the fade plays while the screen is still resizing.
|
|
282
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: runs once on mount
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
if (!fade) return;
|
|
285
|
+
let cancelPaint = () => {};
|
|
286
|
+
const reveal = () => {
|
|
287
|
+
cancelPaint = afterPaint(() => {
|
|
288
|
+
if (!exitingRef.current) setCurtainOpaque(false);
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
if (!fullscreen || document.fullscreenElement) {
|
|
292
|
+
reveal();
|
|
293
|
+
return () => cancelPaint();
|
|
294
|
+
}
|
|
295
|
+
const cancelWait = whenFullscreenSettled(reveal);
|
|
296
|
+
return () => {
|
|
297
|
+
cancelWait();
|
|
298
|
+
cancelPaint();
|
|
299
|
+
};
|
|
300
|
+
}, []);
|
|
224
301
|
|
|
225
302
|
const toggleFullscreen = useCallback(() => {
|
|
226
303
|
setWindowed((w) => !w);
|
|
@@ -296,7 +373,7 @@ export function Player({
|
|
|
296
373
|
setBlackout(null);
|
|
297
374
|
return;
|
|
298
375
|
}
|
|
299
|
-
if (allowExit)
|
|
376
|
+
if (allowExit) requestExit();
|
|
300
377
|
return;
|
|
301
378
|
}
|
|
302
379
|
|
|
@@ -364,7 +441,7 @@ export function Player({
|
|
|
364
441
|
helpOpen,
|
|
365
442
|
blackout,
|
|
366
443
|
allowExit,
|
|
367
|
-
|
|
444
|
+
requestExit,
|
|
368
445
|
goNext,
|
|
369
446
|
goPrev,
|
|
370
447
|
handleIndexChange,
|
|
@@ -403,26 +480,29 @@ export function Player({
|
|
|
403
480
|
controls && (hideCursor ? 'cursor-none' : 'cursor-default'),
|
|
404
481
|
)}
|
|
405
482
|
>
|
|
406
|
-
<
|
|
407
|
-
|
|
483
|
+
<div ref={nudgeRef} className="h-full w-full">
|
|
484
|
+
<SlideCanvas flat design={design}>
|
|
485
|
+
{/* Keyed per deck so a presenter-driven deck switch cuts instead of
|
|
408
486
|
animating a transition between two unrelated decks. */}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
487
|
+
<SlideTransitionLayer
|
|
488
|
+
key={slideId}
|
|
489
|
+
pages={pages}
|
|
490
|
+
index={index}
|
|
491
|
+
total={pages.length}
|
|
492
|
+
moduleTransition={transition}
|
|
493
|
+
disabled={prefersReducedMotion}
|
|
494
|
+
stepControllerRef={stepControllerRef}
|
|
495
|
+
entryDirection={entryDirection}
|
|
496
|
+
onStepAggregateChange={handleAggregateChange}
|
|
497
|
+
/>
|
|
498
|
+
</SlideCanvas>
|
|
499
|
+
</div>
|
|
421
500
|
|
|
422
501
|
{controls && (
|
|
423
502
|
<div data-osd-chrome style={{ display: 'contents' }}>
|
|
424
503
|
<PresentProgressBar index={index} total={pages.length} visible={chromeVisible} />
|
|
425
504
|
<PresentBlackoutOverlay mode={blackout} />
|
|
505
|
+
<PresentLastSlideHint shownAt={lastSlideHintAt} />
|
|
426
506
|
<PresentJumpInput pageCount={pages.length} onJump={handleIndexChange} />
|
|
427
507
|
<PresentLaserPointer enabled={laser} />
|
|
428
508
|
<PresentControlBar
|
|
@@ -444,7 +524,7 @@ export function Player({
|
|
|
444
524
|
onPresenter={() => slideId && openPresenterWindow(slideId)}
|
|
445
525
|
onToggleFullscreen={toggleFullscreen}
|
|
446
526
|
onHelp={() => setHelpOpen(true)}
|
|
447
|
-
onExit={
|
|
527
|
+
onExit={requestExit}
|
|
448
528
|
/>
|
|
449
529
|
<OverviewGrid
|
|
450
530
|
pages={pages}
|
|
@@ -460,6 +540,8 @@ export function Player({
|
|
|
460
540
|
<PresentHelpOverlay open={helpOpen} onOpenChange={setHelpOpen} container={rootEl} />
|
|
461
541
|
</div>
|
|
462
542
|
)}
|
|
543
|
+
|
|
544
|
+
{fade && <PresentCurtain opaque={curtainOpaque} className="absolute z-[100]" />}
|
|
463
545
|
</div>
|
|
464
546
|
);
|
|
465
547
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils';
|
|
2
|
+
|
|
3
|
+
export const CURTAIN_FADE_MS = 400;
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
opaque: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function PresentCurtain({ opaque, className }: Props) {
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
aria-hidden
|
|
14
|
+
className={cn(
|
|
15
|
+
'pointer-events-none inset-0 bg-black transition-opacity ease-in-out',
|
|
16
|
+
opaque ? 'opacity-100' : 'opacity-0',
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
style={{ transitionDuration: `${CURTAIN_FADE_MS}ms` }}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Two frames so the opaque state is painted before the fade starts;
|
|
25
|
+
// a single rAF can land in the same style recalc and skip the transition.
|
|
26
|
+
export function afterPaint(cb: () => void): () => void {
|
|
27
|
+
let inner = 0;
|
|
28
|
+
const outer = requestAnimationFrame(() => {
|
|
29
|
+
inner = requestAnimationFrame(cb);
|
|
30
|
+
});
|
|
31
|
+
return () => {
|
|
32
|
+
cancelAnimationFrame(outer);
|
|
33
|
+
cancelAnimationFrame(inner);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function whenFullscreenSettled(cb: () => void, maxWaitMs = 800): () => void {
|
|
38
|
+
let fired = false;
|
|
39
|
+
const timer = window.setTimeout(done, maxWaitMs);
|
|
40
|
+
document.addEventListener('fullscreenchange', done);
|
|
41
|
+
function done() {
|
|
42
|
+
if (fired) return;
|
|
43
|
+
fired = true;
|
|
44
|
+
cleanup();
|
|
45
|
+
cb();
|
|
46
|
+
}
|
|
47
|
+
function cleanup() {
|
|
48
|
+
window.clearTimeout(timer);
|
|
49
|
+
document.removeEventListener('fullscreenchange', done);
|
|
50
|
+
}
|
|
51
|
+
return cleanup;
|
|
52
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { usePanelMount } from '@/components/panel/panel-shell';
|
|
3
|
+
import { useLocale } from '@/lib/use-locale';
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
|
|
6
|
+
const VISIBLE_MS = 1800;
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
shownAt: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function PresentLastSlideHint({ shownAt }: Props) {
|
|
13
|
+
const t = useLocale();
|
|
14
|
+
const [open, setOpen] = useState(false);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!shownAt) return;
|
|
17
|
+
setOpen(true);
|
|
18
|
+
const id = window.setTimeout(() => setOpen(false), VISIBLE_MS);
|
|
19
|
+
return () => window.clearTimeout(id);
|
|
20
|
+
}, [shownAt]);
|
|
21
|
+
const { mounted, animVisible } = usePanelMount(open);
|
|
22
|
+
if (!mounted) return null;
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
role="status"
|
|
26
|
+
className={cn(
|
|
27
|
+
'pointer-events-none absolute bottom-10 left-1/2 z-30 -translate-x-1/2 rounded-full bg-black/70 px-3.5 py-1.5 text-[12px] text-white/85 shadow-lg backdrop-blur-md motion-safe:transition-[opacity,translate]',
|
|
28
|
+
animVisible
|
|
29
|
+
? 'opacity-100 motion-safe:duration-200'
|
|
30
|
+
: 'translate-y-1 opacity-0 motion-safe:duration-200',
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
{t.present.lastSlideHint}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -51,7 +51,7 @@ export function SlideCanvas({
|
|
|
51
51
|
<div ref={containerRef} className={cn('relative h-full w-full', flat && 'overflow-hidden')}>
|
|
52
52
|
<div
|
|
53
53
|
className={cn(
|
|
54
|
-
'overflow-hidden bg-white text-black',
|
|
54
|
+
'overflow-hidden bg-white text-start text-black',
|
|
55
55
|
!flat && 'rounded-[6px] shadow-floating',
|
|
56
56
|
)}
|
|
57
57
|
style={
|
|
@@ -568,7 +568,7 @@ function HorizontalVirtualThumbList({
|
|
|
568
568
|
aria-label={format(t.thumbnailRail.goToPageAria, { n: i + 1 })}
|
|
569
569
|
aria-current={active ? 'page' : undefined}
|
|
570
570
|
className={cn(
|
|
571
|
-
'group/thumb relative flex shrink-0 flex-col items-center gap-1.5 rounded-[6px] outline-none motion-safe:transition-[scale] motion-safe:duration-100 active:scale-[0.985]',
|
|
571
|
+
'group/thumb relative flex shrink-0 flex-col items-center gap-1.5 rounded-[6px] text-left outline-none motion-safe:transition-[scale] motion-safe:duration-100 active:scale-[0.985]',
|
|
572
572
|
'focus-visible:ring-1 focus-visible:ring-brand focus-visible:ring-offset-1 focus-visible:ring-offset-sidebar',
|
|
573
573
|
)}
|
|
574
574
|
>
|