ai-design-system 0.1.99 → 0.1.100
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/components/composites/ScenePlayer/ScenePlayer.stories.tsx +81 -0
- package/components/composites/ScenePlayer/ScenePlayer.tsx +289 -0
- package/components/composites/ScenePlayer/index.ts +2 -0
- package/components/composites/index.ts +4 -0
- package/components/index.ts +3 -0
- package/dist/index.cjs +278 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +94 -0
- package/dist/index.d.ts +59 -30
- package/dist/index.js +278 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { ScenePlayer, type SceneItem } from "./ScenePlayer";
|
|
3
|
+
import { fn } from "@storybook/test";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ScenePlayer Composite Stories
|
|
7
|
+
*
|
|
8
|
+
* Full-screen segmented video player for scene-by-scene review with progress tracks,
|
|
9
|
+
* mute toggle, step zones, and publish action.
|
|
10
|
+
*/
|
|
11
|
+
const meta = {
|
|
12
|
+
title: "Composites/ScenePlayer",
|
|
13
|
+
component: ScenePlayer,
|
|
14
|
+
tags: ["autodocs"],
|
|
15
|
+
parameters: { layout: "fullscreen" },
|
|
16
|
+
args: {
|
|
17
|
+
onClose: fn(),
|
|
18
|
+
onPublish: fn(),
|
|
19
|
+
onSceneChange: fn(),
|
|
20
|
+
},
|
|
21
|
+
} satisfies Meta<typeof ScenePlayer>;
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
type Story = StoryObj<typeof meta>;
|
|
25
|
+
|
|
26
|
+
const mockScenes: SceneItem[] = [
|
|
27
|
+
{
|
|
28
|
+
id: "001",
|
|
29
|
+
sceneNumber: 1,
|
|
30
|
+
totalScenes: 3,
|
|
31
|
+
start_sec: 0,
|
|
32
|
+
duration_sec: 1.6,
|
|
33
|
+
templateId: "grid2_compare_visual",
|
|
34
|
+
caption: "iOS apps used to mean one",
|
|
35
|
+
scene_clip_url:
|
|
36
|
+
"https://hel1.your-objectstorage.com/waypoint-s3-dev/01M32NJZHXKCX1CQKVEVA49SJT/playground-1790017380397-6ffe0ec0/artifacts/scenes/scene001/clip.mp4",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "002",
|
|
40
|
+
sceneNumber: 2,
|
|
41
|
+
totalScenes: 3,
|
|
42
|
+
start_sec: 1.6,
|
|
43
|
+
duration_sec: 1.5,
|
|
44
|
+
templateId: "full_visual_loop",
|
|
45
|
+
caption: "thing you needed a Mac. And nobody",
|
|
46
|
+
scene_clip_url:
|
|
47
|
+
"https://hel1.your-objectstorage.com/waypoint-s3-dev/01M32NJZHXKCX1CQKVEVA49SJT/playground-1790017380397-6ffe0ec0/artifacts/scenes/scene002/clip.mp4",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "003",
|
|
51
|
+
sceneNumber: 3,
|
|
52
|
+
totalScenes: 3,
|
|
53
|
+
start_sec: 3.1,
|
|
54
|
+
duration_sec: 1.5,
|
|
55
|
+
templateId: "full_talking_head",
|
|
56
|
+
caption: "told the AI, every AI",
|
|
57
|
+
scene_clip_url:
|
|
58
|
+
"https://hel1.your-objectstorage.com/waypoint-s3-dev/01M32NJZHXKCX1CQKVEVA49SJT/playground-1790017380397-6ffe0ec0/artifacts/scenes/scene003/clip.mp4",
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Default usage with multiple scenes
|
|
64
|
+
*/
|
|
65
|
+
export const Default: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
scenes: mockScenes,
|
|
68
|
+
title: "Devin for iOS",
|
|
69
|
+
avatarInitials: "D",
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Empty scenes fallback
|
|
75
|
+
*/
|
|
76
|
+
export const EmptyState: Story = {
|
|
77
|
+
args: {
|
|
78
|
+
scenes: [],
|
|
79
|
+
title: "No Scenes",
|
|
80
|
+
},
|
|
81
|
+
};
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Button } from "@/components/primitives/Button";
|
|
5
|
+
import { Icon } from "@/components/primitives/Icon";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
export interface SceneItem {
|
|
9
|
+
id: string;
|
|
10
|
+
sceneNumber?: number;
|
|
11
|
+
totalScenes?: number;
|
|
12
|
+
start_sec?: number;
|
|
13
|
+
duration_sec?: number;
|
|
14
|
+
templateId?: string;
|
|
15
|
+
caption?: string;
|
|
16
|
+
scene_clip_url: string;
|
|
17
|
+
scene_audio_clip_url?: string;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ScenePlayerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
22
|
+
scenes: SceneItem[];
|
|
23
|
+
title?: string;
|
|
24
|
+
avatarInitials?: string;
|
|
25
|
+
avatarColor?: string;
|
|
26
|
+
initialSceneIndex?: number;
|
|
27
|
+
isMuted?: boolean;
|
|
28
|
+
onClose?: () => void;
|
|
29
|
+
onPublish?: (currentScene: SceneItem) => void;
|
|
30
|
+
publishPlaceholder?: string;
|
|
31
|
+
onSceneChange?: (sceneIndex: number, scene: SceneItem) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
35
|
+
(
|
|
36
|
+
{
|
|
37
|
+
scenes = [],
|
|
38
|
+
title = "Devin for iOS",
|
|
39
|
+
avatarInitials = "D",
|
|
40
|
+
avatarColor = "bg-indigo-600",
|
|
41
|
+
initialSceneIndex = 0,
|
|
42
|
+
isMuted: initialMuted = false,
|
|
43
|
+
onClose,
|
|
44
|
+
onPublish,
|
|
45
|
+
publishPlaceholder = "Ready to publish...",
|
|
46
|
+
onSceneChange,
|
|
47
|
+
className,
|
|
48
|
+
...props
|
|
49
|
+
},
|
|
50
|
+
ref
|
|
51
|
+
) => {
|
|
52
|
+
const [currentSceneIndex, setCurrentSceneIndex] = React.useState<number>(() => {
|
|
53
|
+
if (initialSceneIndex >= 0 && initialSceneIndex < scenes.length) {
|
|
54
|
+
return initialSceneIndex;
|
|
55
|
+
}
|
|
56
|
+
return 0;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const [isMuted, setIsMuted] = React.useState<boolean>(initialMuted);
|
|
60
|
+
const videoRef = React.useRef<HTMLVideoElement | null>(null);
|
|
61
|
+
|
|
62
|
+
const activeScene = scenes[currentSceneIndex] || scenes[0];
|
|
63
|
+
|
|
64
|
+
const handlePrev = React.useCallback(() => {
|
|
65
|
+
if (!scenes.length) return;
|
|
66
|
+
setCurrentSceneIndex((prev) => {
|
|
67
|
+
const nextIdx = prev > 0 ? prev - 1 : scenes.length - 1;
|
|
68
|
+
onSceneChange?.(nextIdx, scenes[nextIdx]);
|
|
69
|
+
return nextIdx;
|
|
70
|
+
});
|
|
71
|
+
}, [scenes, onSceneChange]);
|
|
72
|
+
|
|
73
|
+
const handleNext = React.useCallback(() => {
|
|
74
|
+
if (!scenes.length) return;
|
|
75
|
+
setCurrentSceneIndex((prev) => {
|
|
76
|
+
const nextIdx = prev < scenes.length - 1 ? prev + 1 : 0;
|
|
77
|
+
onSceneChange?.(nextIdx, scenes[nextIdx]);
|
|
78
|
+
return nextIdx;
|
|
79
|
+
});
|
|
80
|
+
}, [scenes, onSceneChange]);
|
|
81
|
+
|
|
82
|
+
const handleSceneEnded = React.useCallback(() => {
|
|
83
|
+
if (!scenes.length) return;
|
|
84
|
+
setCurrentSceneIndex((prev) => {
|
|
85
|
+
const nextIdx = prev < scenes.length - 1 ? prev + 1 : 0;
|
|
86
|
+
onSceneChange?.(nextIdx, scenes[nextIdx]);
|
|
87
|
+
return nextIdx;
|
|
88
|
+
});
|
|
89
|
+
}, [scenes, onSceneChange]);
|
|
90
|
+
|
|
91
|
+
// Autoplay & mute handling
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
const video = videoRef.current;
|
|
94
|
+
if (!video) return;
|
|
95
|
+
|
|
96
|
+
video.muted = isMuted;
|
|
97
|
+
const playPromise = video.play();
|
|
98
|
+
if (playPromise !== undefined) {
|
|
99
|
+
playPromise.catch(() => {
|
|
100
|
+
if (!isMuted) {
|
|
101
|
+
video.muted = true;
|
|
102
|
+
setIsMuted(true);
|
|
103
|
+
video.play().catch(() => {});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}, [activeScene?.scene_clip_url, isMuted]);
|
|
108
|
+
|
|
109
|
+
// Keyboard navigation
|
|
110
|
+
React.useEffect(() => {
|
|
111
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
112
|
+
if (e.key === "ArrowLeft") {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
handlePrev();
|
|
115
|
+
} else if (e.key === "ArrowRight" || e.key === " ") {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
handleNext();
|
|
118
|
+
} else if (e.key === "Escape") {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
onClose?.();
|
|
121
|
+
} else if (e.key === "m" || e.key === "M") {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
setIsMuted((prev) => !prev);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
128
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
129
|
+
}, [handlePrev, handleNext, onClose]);
|
|
130
|
+
|
|
131
|
+
if (!scenes || scenes.length === 0) {
|
|
132
|
+
return (
|
|
133
|
+
<div
|
|
134
|
+
ref={ref}
|
|
135
|
+
className={cn("flex flex-col items-center justify-center w-full h-full bg-black text-white p-6", className)}
|
|
136
|
+
data-testid="screen-scenes"
|
|
137
|
+
{...props}
|
|
138
|
+
>
|
|
139
|
+
<p className="text-white/70 text-sm">No scenes available</p>
|
|
140
|
+
{onClose && (
|
|
141
|
+
<Button
|
|
142
|
+
variant="ghost"
|
|
143
|
+
onClick={onClose}
|
|
144
|
+
className="mt-4 text-white hover:bg-white/20"
|
|
145
|
+
data-testid="btn-close"
|
|
146
|
+
>
|
|
147
|
+
Close
|
|
148
|
+
</Button>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<div
|
|
156
|
+
ref={ref}
|
|
157
|
+
className={cn(
|
|
158
|
+
"flex flex-col w-full h-full min-h-full bg-black text-white relative select-none overflow-hidden",
|
|
159
|
+
className
|
|
160
|
+
)}
|
|
161
|
+
data-testid="screen-scenes"
|
|
162
|
+
{...props}
|
|
163
|
+
>
|
|
164
|
+
{/* Top Segmented Progress Bar */}
|
|
165
|
+
<div className="flex items-center gap-[3px] px-2 pt-3 pb-1.5 z-20 shrink-0">
|
|
166
|
+
{scenes.map((scene, idx) => {
|
|
167
|
+
const isCompleted = idx < currentSceneIndex;
|
|
168
|
+
const isCurrent = idx === currentSceneIndex;
|
|
169
|
+
return (
|
|
170
|
+
<div
|
|
171
|
+
key={scene.id || `scene-${idx}`}
|
|
172
|
+
className="flex-1 h-[2.5px] rounded-[2px] bg-white/35 overflow-hidden"
|
|
173
|
+
>
|
|
174
|
+
<div
|
|
175
|
+
className={cn(
|
|
176
|
+
"h-full w-full transition-all duration-150",
|
|
177
|
+
isCompleted || isCurrent ? "bg-white" : "bg-transparent"
|
|
178
|
+
)}
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
})}
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* Story Header Overlay */}
|
|
186
|
+
<div className="flex items-center justify-between px-4 py-2.5 z-20 shrink-0">
|
|
187
|
+
<div className="flex items-center gap-2.5 min-w-0">
|
|
188
|
+
<div
|
|
189
|
+
className={cn(
|
|
190
|
+
"w-9 h-9 rounded-full flex items-center justify-center font-bold text-sm text-white shrink-0 ring-1.5 ring-white/80 overflow-hidden",
|
|
191
|
+
avatarColor
|
|
192
|
+
)}
|
|
193
|
+
>
|
|
194
|
+
{avatarInitials}
|
|
195
|
+
</div>
|
|
196
|
+
<div className="flex flex-col min-w-0 justify-center">
|
|
197
|
+
<span className="font-bold text-sm text-white truncate max-w-[200px]">
|
|
198
|
+
{title}
|
|
199
|
+
</span>
|
|
200
|
+
<span className="text-xs text-white/75 truncate">
|
|
201
|
+
Scene {activeScene?.sceneNumber ?? currentSceneIndex + 1} of {scenes.length}
|
|
202
|
+
</span>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
207
|
+
{/* Mute/Unmute Toggle Button */}
|
|
208
|
+
<Button
|
|
209
|
+
variant="ghost"
|
|
210
|
+
size="icon"
|
|
211
|
+
data-testid="btn-toggle-mute"
|
|
212
|
+
aria-label={isMuted ? "Unmute audio" : "Mute audio"}
|
|
213
|
+
onClick={() => setIsMuted((prev) => !prev)}
|
|
214
|
+
className="h-8 w-8 rounded-full bg-black/40 hover:bg-white/20 text-white p-0 cursor-pointer"
|
|
215
|
+
>
|
|
216
|
+
<Icon name={isMuted ? "volume-x" : "volume-2"} className="w-4 h-4" />
|
|
217
|
+
</Button>
|
|
218
|
+
|
|
219
|
+
{/* Close Button */}
|
|
220
|
+
{onClose && (
|
|
221
|
+
<Button
|
|
222
|
+
variant="ghost"
|
|
223
|
+
size="icon"
|
|
224
|
+
data-testid="btn-close"
|
|
225
|
+
aria-label="Close scenes view"
|
|
226
|
+
onClick={onClose}
|
|
227
|
+
className="h-8 w-8 rounded-full bg-black/40 hover:bg-white/20 text-white p-0 cursor-pointer"
|
|
228
|
+
>
|
|
229
|
+
<Icon name="x" className="w-4 h-4" />
|
|
230
|
+
</Button>
|
|
231
|
+
)}
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{/* Main Scene Video Player Stage */}
|
|
236
|
+
<div className="flex-1 relative flex items-center justify-center bg-black overflow-hidden min-h-0">
|
|
237
|
+
<video
|
|
238
|
+
ref={videoRef}
|
|
239
|
+
key={activeScene?.scene_clip_url}
|
|
240
|
+
src={activeScene?.scene_clip_url}
|
|
241
|
+
autoPlay
|
|
242
|
+
playsInline
|
|
243
|
+
loop={false}
|
|
244
|
+
muted={isMuted}
|
|
245
|
+
onEnded={handleSceneEnded}
|
|
246
|
+
className="w-full h-full object-contain bg-black"
|
|
247
|
+
/>
|
|
248
|
+
|
|
249
|
+
{/* Invisible Interactive Tap Zones for Stepping Scenes */}
|
|
250
|
+
<div className="absolute inset-x-0 top-[60px] bottom-[70px] flex z-10">
|
|
251
|
+
<Button
|
|
252
|
+
variant="ghost"
|
|
253
|
+
data-testid="btn-prev-scene"
|
|
254
|
+
aria-label="Previous scene"
|
|
255
|
+
onClick={handlePrev}
|
|
256
|
+
className="flex-1 h-full cursor-pointer bg-transparent hover:bg-transparent p-0 rounded-none border-none focus-visible:ring-0 focus-visible:outline-none"
|
|
257
|
+
/>
|
|
258
|
+
<Button
|
|
259
|
+
variant="ghost"
|
|
260
|
+
data-testid="btn-next-scene"
|
|
261
|
+
aria-label="Next scene"
|
|
262
|
+
onClick={handleNext}
|
|
263
|
+
className="flex-1 h-full cursor-pointer bg-transparent hover:bg-transparent p-0 rounded-none border-none focus-visible:ring-0 focus-visible:outline-none"
|
|
264
|
+
/>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
{/* Bottom Action Bar */}
|
|
269
|
+
<div className="flex items-center gap-3 px-4 py-3.5 bg-black z-20 shrink-0">
|
|
270
|
+
<div className="flex-1 h-11 rounded-full bg-white/15 border border-white/25 px-4 flex items-center text-sm text-white/65 select-none truncate">
|
|
271
|
+
{publishPlaceholder}
|
|
272
|
+
</div>
|
|
273
|
+
<Button
|
|
274
|
+
variant="ghost"
|
|
275
|
+
size="icon"
|
|
276
|
+
data-testid="btn-publish-scene"
|
|
277
|
+
aria-label="Publish scene"
|
|
278
|
+
onClick={() => onPublish?.(activeScene)}
|
|
279
|
+
className="w-11 h-11 rounded-full bg-emerald-500 hover:bg-emerald-600 text-white shrink-0 p-0 shadow-md cursor-pointer transition-transform active:scale-95"
|
|
280
|
+
>
|
|
281
|
+
<Icon name="send" className="w-4 h-4 text-white" />
|
|
282
|
+
</Button>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
ScenePlayer.displayName = "ScenePlayer";
|
|
@@ -285,3 +285,7 @@ export type { WorkspaceEmptyStateProps } from './WorkspaceEmptyState'
|
|
|
285
285
|
export { StoriesBar } from './StoriesBar'
|
|
286
286
|
export type { StoriesBarProps, StoryItem } from './StoriesBar'
|
|
287
287
|
|
|
288
|
+
// ScenePlayer Composite
|
|
289
|
+
export { ScenePlayer } from './ScenePlayer'
|
|
290
|
+
export type { ScenePlayerProps, SceneItem } from './ScenePlayer'
|
|
291
|
+
|
package/components/index.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -1497,6 +1497,24 @@ var defaultIcons = {
|
|
|
1497
1497
|
{ type: "path", attrs: { d: "m12.4 3.4 3.1 4" } },
|
|
1498
1498
|
{ type: "path", attrs: { d: "M3 11h18v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z" } }
|
|
1499
1499
|
]
|
|
1500
|
+
},
|
|
1501
|
+
"volume-2": {
|
|
1502
|
+
name: "volume-2",
|
|
1503
|
+
viewBox: "0 0 24 24",
|
|
1504
|
+
elements: [
|
|
1505
|
+
{ type: "polygon", attrs: { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" } },
|
|
1506
|
+
{ type: "path", attrs: { d: "M15.54 8.46a5 5 0 0 1 0 7.07" } },
|
|
1507
|
+
{ type: "path", attrs: { d: "M19.07 4.93a10 10 0 0 1 0 14.14" } }
|
|
1508
|
+
]
|
|
1509
|
+
},
|
|
1510
|
+
"volume-x": {
|
|
1511
|
+
name: "volume-x",
|
|
1512
|
+
viewBox: "0 0 24 24",
|
|
1513
|
+
elements: [
|
|
1514
|
+
{ type: "polygon", attrs: { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" } },
|
|
1515
|
+
{ type: "line", attrs: { x1: "22", x2: "16", y1: "9", y2: "15" } },
|
|
1516
|
+
{ type: "line", attrs: { x1: "16", x2: "22", y1: "9", y2: "15" } }
|
|
1517
|
+
]
|
|
1500
1518
|
}
|
|
1501
1519
|
};
|
|
1502
1520
|
var IconRegistry = class {
|
|
@@ -14548,6 +14566,265 @@ var StoriesBar = React3__namespace.forwardRef(
|
|
|
14548
14566
|
}
|
|
14549
14567
|
);
|
|
14550
14568
|
StoriesBar.displayName = "StoriesBar";
|
|
14569
|
+
var ScenePlayer = React3__namespace.forwardRef(
|
|
14570
|
+
(_a, ref) => {
|
|
14571
|
+
var _b = _a, {
|
|
14572
|
+
scenes = [],
|
|
14573
|
+
title = "Devin for iOS",
|
|
14574
|
+
avatarInitials = "D",
|
|
14575
|
+
avatarColor = "bg-indigo-600",
|
|
14576
|
+
initialSceneIndex = 0,
|
|
14577
|
+
isMuted: initialMuted = false,
|
|
14578
|
+
onClose,
|
|
14579
|
+
onPublish,
|
|
14580
|
+
publishPlaceholder = "Ready to publish...",
|
|
14581
|
+
onSceneChange,
|
|
14582
|
+
className
|
|
14583
|
+
} = _b, props = __objRest(_b, [
|
|
14584
|
+
"scenes",
|
|
14585
|
+
"title",
|
|
14586
|
+
"avatarInitials",
|
|
14587
|
+
"avatarColor",
|
|
14588
|
+
"initialSceneIndex",
|
|
14589
|
+
"isMuted",
|
|
14590
|
+
"onClose",
|
|
14591
|
+
"onPublish",
|
|
14592
|
+
"publishPlaceholder",
|
|
14593
|
+
"onSceneChange",
|
|
14594
|
+
"className"
|
|
14595
|
+
]);
|
|
14596
|
+
var _a2;
|
|
14597
|
+
const [currentSceneIndex, setCurrentSceneIndex] = React3__namespace.useState(() => {
|
|
14598
|
+
if (initialSceneIndex >= 0 && initialSceneIndex < scenes.length) {
|
|
14599
|
+
return initialSceneIndex;
|
|
14600
|
+
}
|
|
14601
|
+
return 0;
|
|
14602
|
+
});
|
|
14603
|
+
const [isMuted, setIsMuted] = React3__namespace.useState(initialMuted);
|
|
14604
|
+
const videoRef = React3__namespace.useRef(null);
|
|
14605
|
+
const activeScene = scenes[currentSceneIndex] || scenes[0];
|
|
14606
|
+
const handlePrev = React3__namespace.useCallback(() => {
|
|
14607
|
+
if (!scenes.length) return;
|
|
14608
|
+
setCurrentSceneIndex((prev) => {
|
|
14609
|
+
const nextIdx = prev > 0 ? prev - 1 : scenes.length - 1;
|
|
14610
|
+
onSceneChange == null ? void 0 : onSceneChange(nextIdx, scenes[nextIdx]);
|
|
14611
|
+
return nextIdx;
|
|
14612
|
+
});
|
|
14613
|
+
}, [scenes, onSceneChange]);
|
|
14614
|
+
const handleNext = React3__namespace.useCallback(() => {
|
|
14615
|
+
if (!scenes.length) return;
|
|
14616
|
+
setCurrentSceneIndex((prev) => {
|
|
14617
|
+
const nextIdx = prev < scenes.length - 1 ? prev + 1 : 0;
|
|
14618
|
+
onSceneChange == null ? void 0 : onSceneChange(nextIdx, scenes[nextIdx]);
|
|
14619
|
+
return nextIdx;
|
|
14620
|
+
});
|
|
14621
|
+
}, [scenes, onSceneChange]);
|
|
14622
|
+
const handleSceneEnded = React3__namespace.useCallback(() => {
|
|
14623
|
+
if (!scenes.length) return;
|
|
14624
|
+
setCurrentSceneIndex((prev) => {
|
|
14625
|
+
const nextIdx = prev < scenes.length - 1 ? prev + 1 : 0;
|
|
14626
|
+
onSceneChange == null ? void 0 : onSceneChange(nextIdx, scenes[nextIdx]);
|
|
14627
|
+
return nextIdx;
|
|
14628
|
+
});
|
|
14629
|
+
}, [scenes, onSceneChange]);
|
|
14630
|
+
React3__namespace.useEffect(() => {
|
|
14631
|
+
const video = videoRef.current;
|
|
14632
|
+
if (!video) return;
|
|
14633
|
+
video.muted = isMuted;
|
|
14634
|
+
const playPromise = video.play();
|
|
14635
|
+
if (playPromise !== void 0) {
|
|
14636
|
+
playPromise.catch(() => {
|
|
14637
|
+
if (!isMuted) {
|
|
14638
|
+
video.muted = true;
|
|
14639
|
+
setIsMuted(true);
|
|
14640
|
+
video.play().catch(() => {
|
|
14641
|
+
});
|
|
14642
|
+
}
|
|
14643
|
+
});
|
|
14644
|
+
}
|
|
14645
|
+
}, [activeScene == null ? void 0 : activeScene.scene_clip_url, isMuted]);
|
|
14646
|
+
React3__namespace.useEffect(() => {
|
|
14647
|
+
const handleKeyDown = (e) => {
|
|
14648
|
+
if (e.key === "ArrowLeft") {
|
|
14649
|
+
e.preventDefault();
|
|
14650
|
+
handlePrev();
|
|
14651
|
+
} else if (e.key === "ArrowRight" || e.key === " ") {
|
|
14652
|
+
e.preventDefault();
|
|
14653
|
+
handleNext();
|
|
14654
|
+
} else if (e.key === "Escape") {
|
|
14655
|
+
e.preventDefault();
|
|
14656
|
+
onClose == null ? void 0 : onClose();
|
|
14657
|
+
} else if (e.key === "m" || e.key === "M") {
|
|
14658
|
+
e.preventDefault();
|
|
14659
|
+
setIsMuted((prev) => !prev);
|
|
14660
|
+
}
|
|
14661
|
+
};
|
|
14662
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
14663
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
14664
|
+
}, [handlePrev, handleNext, onClose]);
|
|
14665
|
+
if (!scenes || scenes.length === 0) {
|
|
14666
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14667
|
+
"div",
|
|
14668
|
+
__spreadProps(__spreadValues({
|
|
14669
|
+
ref,
|
|
14670
|
+
className: cn("flex flex-col items-center justify-center w-full h-full bg-black text-white p-6", className),
|
|
14671
|
+
"data-testid": "screen-scenes"
|
|
14672
|
+
}, props), {
|
|
14673
|
+
children: [
|
|
14674
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-white/70 text-sm", children: "No scenes available" }),
|
|
14675
|
+
onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
14676
|
+
Button2,
|
|
14677
|
+
{
|
|
14678
|
+
variant: "ghost",
|
|
14679
|
+
onClick: onClose,
|
|
14680
|
+
className: "mt-4 text-white hover:bg-white/20",
|
|
14681
|
+
"data-testid": "btn-close",
|
|
14682
|
+
children: "Close"
|
|
14683
|
+
}
|
|
14684
|
+
)
|
|
14685
|
+
]
|
|
14686
|
+
})
|
|
14687
|
+
);
|
|
14688
|
+
}
|
|
14689
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14690
|
+
"div",
|
|
14691
|
+
__spreadProps(__spreadValues({
|
|
14692
|
+
ref,
|
|
14693
|
+
className: cn(
|
|
14694
|
+
"flex flex-col w-full h-full min-h-full bg-black text-white relative select-none overflow-hidden",
|
|
14695
|
+
className
|
|
14696
|
+
),
|
|
14697
|
+
"data-testid": "screen-scenes"
|
|
14698
|
+
}, props), {
|
|
14699
|
+
children: [
|
|
14700
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-[3px] px-2 pt-3 pb-1.5 z-20 shrink-0", children: scenes.map((scene, idx) => {
|
|
14701
|
+
const isCompleted = idx < currentSceneIndex;
|
|
14702
|
+
const isCurrent = idx === currentSceneIndex;
|
|
14703
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14704
|
+
"div",
|
|
14705
|
+
{
|
|
14706
|
+
className: "flex-1 h-[2.5px] rounded-[2px] bg-white/35 overflow-hidden",
|
|
14707
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14708
|
+
"div",
|
|
14709
|
+
{
|
|
14710
|
+
className: cn(
|
|
14711
|
+
"h-full w-full transition-all duration-150",
|
|
14712
|
+
isCompleted || isCurrent ? "bg-white" : "bg-transparent"
|
|
14713
|
+
)
|
|
14714
|
+
}
|
|
14715
|
+
)
|
|
14716
|
+
},
|
|
14717
|
+
scene.id || `scene-${idx}`
|
|
14718
|
+
);
|
|
14719
|
+
}) }),
|
|
14720
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-2.5 z-20 shrink-0", children: [
|
|
14721
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [
|
|
14722
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14723
|
+
"div",
|
|
14724
|
+
{
|
|
14725
|
+
className: cn(
|
|
14726
|
+
"w-9 h-9 rounded-full flex items-center justify-center font-bold text-sm text-white shrink-0 ring-1.5 ring-white/80 overflow-hidden",
|
|
14727
|
+
avatarColor
|
|
14728
|
+
),
|
|
14729
|
+
children: avatarInitials
|
|
14730
|
+
}
|
|
14731
|
+
),
|
|
14732
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-w-0 justify-center", children: [
|
|
14733
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold text-sm text-white truncate max-w-[200px]", children: title }),
|
|
14734
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-white/75 truncate", children: [
|
|
14735
|
+
"Scene ",
|
|
14736
|
+
(_a2 = activeScene == null ? void 0 : activeScene.sceneNumber) != null ? _a2 : currentSceneIndex + 1,
|
|
14737
|
+
" of ",
|
|
14738
|
+
scenes.length
|
|
14739
|
+
] })
|
|
14740
|
+
] })
|
|
14741
|
+
] }),
|
|
14742
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
14743
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14744
|
+
Button2,
|
|
14745
|
+
{
|
|
14746
|
+
variant: "ghost",
|
|
14747
|
+
size: "icon",
|
|
14748
|
+
"data-testid": "btn-toggle-mute",
|
|
14749
|
+
"aria-label": isMuted ? "Unmute audio" : "Mute audio",
|
|
14750
|
+
onClick: () => setIsMuted((prev) => !prev),
|
|
14751
|
+
className: "h-8 w-8 rounded-full bg-black/40 hover:bg-white/20 text-white p-0 cursor-pointer",
|
|
14752
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: isMuted ? "volume-x" : "volume-2", className: "w-4 h-4" })
|
|
14753
|
+
}
|
|
14754
|
+
),
|
|
14755
|
+
onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
14756
|
+
Button2,
|
|
14757
|
+
{
|
|
14758
|
+
variant: "ghost",
|
|
14759
|
+
size: "icon",
|
|
14760
|
+
"data-testid": "btn-close",
|
|
14761
|
+
"aria-label": "Close scenes view",
|
|
14762
|
+
onClick: onClose,
|
|
14763
|
+
className: "h-8 w-8 rounded-full bg-black/40 hover:bg-white/20 text-white p-0 cursor-pointer",
|
|
14764
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "x", className: "w-4 h-4" })
|
|
14765
|
+
}
|
|
14766
|
+
)
|
|
14767
|
+
] })
|
|
14768
|
+
] }),
|
|
14769
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 relative flex items-center justify-center bg-black overflow-hidden min-h-0", children: [
|
|
14770
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14771
|
+
"video",
|
|
14772
|
+
{
|
|
14773
|
+
ref: videoRef,
|
|
14774
|
+
src: activeScene == null ? void 0 : activeScene.scene_clip_url,
|
|
14775
|
+
autoPlay: true,
|
|
14776
|
+
playsInline: true,
|
|
14777
|
+
loop: false,
|
|
14778
|
+
muted: isMuted,
|
|
14779
|
+
onEnded: handleSceneEnded,
|
|
14780
|
+
className: "w-full h-full object-contain bg-black"
|
|
14781
|
+
},
|
|
14782
|
+
activeScene == null ? void 0 : activeScene.scene_clip_url
|
|
14783
|
+
),
|
|
14784
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute inset-x-0 top-[60px] bottom-[70px] flex z-10", children: [
|
|
14785
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14786
|
+
Button2,
|
|
14787
|
+
{
|
|
14788
|
+
variant: "ghost",
|
|
14789
|
+
"data-testid": "btn-prev-scene",
|
|
14790
|
+
"aria-label": "Previous scene",
|
|
14791
|
+
onClick: handlePrev,
|
|
14792
|
+
className: "flex-1 h-full cursor-pointer bg-transparent hover:bg-transparent p-0 rounded-none border-none focus-visible:ring-0 focus-visible:outline-none"
|
|
14793
|
+
}
|
|
14794
|
+
),
|
|
14795
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14796
|
+
Button2,
|
|
14797
|
+
{
|
|
14798
|
+
variant: "ghost",
|
|
14799
|
+
"data-testid": "btn-next-scene",
|
|
14800
|
+
"aria-label": "Next scene",
|
|
14801
|
+
onClick: handleNext,
|
|
14802
|
+
className: "flex-1 h-full cursor-pointer bg-transparent hover:bg-transparent p-0 rounded-none border-none focus-visible:ring-0 focus-visible:outline-none"
|
|
14803
|
+
}
|
|
14804
|
+
)
|
|
14805
|
+
] })
|
|
14806
|
+
] }),
|
|
14807
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-4 py-3.5 bg-black z-20 shrink-0", children: [
|
|
14808
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-11 rounded-full bg-white/15 border border-white/25 px-4 flex items-center text-sm text-white/65 select-none truncate", children: publishPlaceholder }),
|
|
14809
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14810
|
+
Button2,
|
|
14811
|
+
{
|
|
14812
|
+
variant: "ghost",
|
|
14813
|
+
size: "icon",
|
|
14814
|
+
"data-testid": "btn-publish-scene",
|
|
14815
|
+
"aria-label": "Publish scene",
|
|
14816
|
+
onClick: () => onPublish == null ? void 0 : onPublish(activeScene),
|
|
14817
|
+
className: "w-11 h-11 rounded-full bg-emerald-500 hover:bg-emerald-600 text-white shrink-0 p-0 shadow-md cursor-pointer transition-transform active:scale-95",
|
|
14818
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "send", className: "w-4 h-4 text-white" })
|
|
14819
|
+
}
|
|
14820
|
+
)
|
|
14821
|
+
] })
|
|
14822
|
+
]
|
|
14823
|
+
})
|
|
14824
|
+
);
|
|
14825
|
+
}
|
|
14826
|
+
);
|
|
14827
|
+
ScenePlayer.displayName = "ScenePlayer";
|
|
14551
14828
|
var SpecNavigator = React3__namespace.memo(
|
|
14552
14829
|
({ groups, selectedFileId, onFileSelect, className }) => {
|
|
14553
14830
|
var _a, _b;
|
|
@@ -17274,6 +17551,7 @@ exports.ProjectSwitcher = ProjectSwitcher;
|
|
|
17274
17551
|
exports.PromptInput = PromptInput2;
|
|
17275
17552
|
exports.PromptInputProvider = PromptInputProvider;
|
|
17276
17553
|
exports.RefinementPanel = ChatPanel;
|
|
17554
|
+
exports.ScenePlayer = ScenePlayer;
|
|
17277
17555
|
exports.SectionLayout = SectionLayout;
|
|
17278
17556
|
exports.SessionHeader = SessionHeader;
|
|
17279
17557
|
exports.SpecNavigator = SpecNavigator;
|