1use leptos::prelude::*;
6
7use crate::components::editor::{
8 CanvasBackendView, DopeSheet, EditorDropZoneCanvas, EditorShell, InspectorPanel, PlayState,
9 PlayerControls, PropertyControlView, PropertyRowView, PropertySection, PropertySectionView,
10 TimelineSkeleton, WispCanvasHost,
11};
12use crate::components::primitives::{Card, CardBody, CardHeader};
13use crate::fixtures::editor::{
14 sample_dope_sheet_dense, sample_dope_sheet_tracks, sample_editor_drop_zone,
15 sample_editor_drop_zone_no_recent, sample_editor_shell, sample_editor_shell_export_disabled,
16 sample_inspector_cursor_tab, sample_inspector_disabled_section, sample_inspector_style_tab,
17 sample_timeline_skeleton, sample_timeline_skeleton_empty, sample_timeline_skeleton_playing,
18};
19
20use super::{Story, StoryViewport, render};
21
22fn fixed(width: u16, height: u16) -> StoryViewport {
23 StoryViewport::Fixed { width, height }
24}
25
26fn s(
27 id: &'static str,
28 category: &'static str,
29 title: &'static str,
30 viewport: StoryViewport,
31 render: fn() -> String,
32) -> Story {
33 Story {
34 id,
35 category,
36 title,
37 viewport,
38 render,
39 }
40}
41
42fn legacy_editor_stories() -> Vec<Story> {
43 let auto = StoryViewport::Auto;
44 vec![
45 s(
46 "dope-sheet-basic",
47 "Editor",
48 "Dope sheet — multi-track",
49 auto,
50 render_dope_sheet_basic,
51 ),
52 s(
53 "dope-sheet-dense",
54 "Editor",
55 "Dope sheet — dense keyframes",
56 auto,
57 render_dope_sheet_dense_story,
58 ),
59 s(
60 "card-with-dope-sheet",
61 "Compositions",
62 "Editor panel — card wrapping dope sheet",
63 auto,
64 render_editor_panel,
65 ),
66 s(
67 "player-controls-paused",
68 "Player",
69 "Player controls — paused at start",
70 auto,
71 render_player_paused,
72 ),
73 s(
74 "player-controls-playing",
75 "Player",
76 "Player controls — playing mid-clip",
77 auto,
78 render_player_playing,
79 ),
80 s(
81 "player-controls-near-end",
82 "Player",
83 "Player controls — near end of clip",
84 auto,
85 render_player_near_end,
86 ),
87 s(
88 "editor-mock",
89 "Compositions",
90 "Editor mock — drop zone result + player + dope sheet",
91 auto,
92 render_editor_mock,
93 ),
94 ]
95}
96
97fn editor_shell_stories() -> Vec<Story> {
98 let shell = fixed(960, 600);
99 vec![
100 s(
101 "editor-shell-empty",
102 "Editor",
103 "Editor shell — empty (no clip)",
104 shell,
105 render_editor_shell_empty,
106 ),
107 s(
108 "editor-shell-clip-loaded",
109 "Editor",
110 "Editor shell — clip loaded",
111 shell,
112 render_editor_shell_loaded,
113 ),
114 s(
115 "editor-toolbar-states",
116 "Editor",
117 "Editor toolbar — selected + disabled mix",
118 fixed(720, 80),
119 render_editor_toolbar_states,
120 ),
121 s(
122 "editor-shell-export-disabled",
123 "Editor",
124 "Editor shell — export disabled",
125 shell,
126 render_editor_shell_export_disabled,
127 ),
128 ]
129}
130
131fn timeline_stories() -> Vec<Story> {
132 let strip = fixed(960, 200);
133 vec![
134 s(
135 "timeline-empty",
136 "Editor",
137 "Timeline — empty (no placeholders)",
138 strip,
139 render_timeline_empty,
140 ),
141 s(
142 "timeline-with-placeholders",
143 "Editor",
144 "Timeline — with placeholders",
145 strip,
146 render_timeline_with_placeholders,
147 ),
148 s(
149 "timeline-playing",
150 "Editor",
151 "Timeline — playing",
152 strip,
153 render_timeline_playing,
154 ),
155 s(
156 "timeline-selected-track",
157 "Editor",
158 "Timeline — selected video track",
159 strip,
160 render_timeline_selected,
161 ),
162 ]
163}
164
165fn inspector_stories() -> Vec<Story> {
166 let panel = fixed(320, 480);
167 vec![
168 s(
169 "inspector-style-tab",
170 "Inspector",
171 "Inspector — Style tab",
172 panel,
173 render_inspector_style,
174 ),
175 s(
176 "inspector-cursor-tab",
177 "Inspector",
178 "Inspector — Cursor tab",
179 panel,
180 render_inspector_cursor,
181 ),
182 s(
183 "property-row-slider",
184 "Inspector",
185 "Property row — slider",
186 fixed(320, 80),
187 render_row_slider,
188 ),
189 s(
190 "property-row-toggle",
191 "Inspector",
192 "Property row — toggle",
193 fixed(320, 80),
194 render_row_toggle,
195 ),
196 s(
197 "property-row-color-swatches",
198 "Inspector",
199 "Property row — color swatches",
200 fixed(320, 80),
201 render_row_swatches,
202 ),
203 s(
204 "inspector-disabled-section",
205 "Inspector",
206 "Inspector — disabled rows",
207 panel,
208 render_inspector_disabled,
209 ),
210 ]
211}
212
213fn editor_canvas_stories() -> Vec<Story> {
214 let drop_zone = fixed(760, 520);
215 let canvas = fixed(600, 360);
216 vec![
217 s(
218 "editor-drop-zone-empty",
219 "Editor",
220 "Editor drop zone — empty",
221 drop_zone,
222 render_drop_zone_empty,
223 ),
224 s(
225 "editor-drop-zone-drag-active",
226 "Editor",
227 "Editor drop zone — drag active",
228 drop_zone,
229 render_drop_zone_drag,
230 ),
231 s(
232 "editor-drop-zone-with-recent",
233 "Editor",
234 "Editor drop zone — with recent clips",
235 drop_zone,
236 render_drop_zone_with_recent,
237 ),
238 s(
239 "wisp-canvas-host-fallback",
240 "Editor",
241 "Wisp canvas host — CSS fallback",
242 canvas,
243 render_canvas_host_fallback,
244 ),
245 s(
246 "wisp-canvas-host-asset",
247 "Editor",
248 "Wisp canvas host — runtime unavailable",
249 canvas,
250 render_canvas_host_unavailable,
251 ),
252 ]
253}
254
255#[must_use]
257pub fn stories() -> Vec<Story> {
258 let mut out = legacy_editor_stories();
259 out.extend(editor_shell_stories());
260 out.extend(editor_canvas_stories());
261 out.extend(inspector_stories());
262 out.extend(timeline_stories());
263 out
264}
265
266fn render_dope_sheet_basic() -> String {
267 render(view! {
268 <DopeSheet
269 tracks=sample_dope_sheet_tracks()
270 duration_seconds=8.0
271 playhead_seconds=3.4
272 />
273 })
274}
275
276fn render_dope_sheet_dense_story() -> String {
277 render(view! {
278 <DopeSheet
279 tracks=sample_dope_sheet_dense()
280 duration_seconds=8.0
281 playhead_seconds=5.1
282 />
283 })
284}
285
286fn render_editor_panel() -> String {
287 render(view! {
288 <Card>
289 <CardHeader title="Timeline" subtitle="4 tracks · 8.0s" />
290 <CardBody>
291 <DopeSheet
292 tracks=sample_dope_sheet_tracks()
293 duration_seconds=8.0
294 playhead_seconds=2.0
295 />
296 </CardBody>
297 </Card>
298 })
299}
300
301fn render_player_paused() -> String {
302 render(view! {
303 <PlayerControls state=PlayState::Paused position=0.0 duration_seconds=84.0 />
304 })
305}
306
307fn render_player_playing() -> String {
308 render(view! {
309 <PlayerControls state=PlayState::Playing position=0.32 duration_seconds=84.0 />
310 })
311}
312
313fn render_player_near_end() -> String {
314 render(view! {
315 <PlayerControls state=PlayState::Playing position=0.94 duration_seconds=84.0 />
316 })
317}
318
319fn render_editor_shell_empty() -> String {
320 render(view! { <EditorShell view=sample_editor_shell(false) /> })
321}
322
323fn render_editor_shell_loaded() -> String {
324 render(view! { <EditorShell view=sample_editor_shell(true) /> })
325}
326
327fn render_editor_shell_export_disabled() -> String {
328 render(view! { <EditorShell view=sample_editor_shell_export_disabled() /> })
329}
330
331fn render_editor_toolbar_states() -> String {
332 use crate::components::editor::EditorToolbar;
333 let v = sample_editor_shell(true);
334 render(view! {
335 <EditorToolbar actions=v.toolbar_actions export_enabled=true share_enabled=false />
336 })
337}
338
339fn render_drop_zone_empty() -> String {
340 render(view! { <EditorDropZoneCanvas view=sample_editor_drop_zone_no_recent() /> })
341}
342
343fn render_drop_zone_drag() -> String {
344 render(view! { <EditorDropZoneCanvas view=sample_editor_drop_zone(true) /> })
345}
346
347fn render_drop_zone_with_recent() -> String {
348 render(view! { <EditorDropZoneCanvas view=sample_editor_drop_zone(false) /> })
349}
350
351fn render_canvas_host_fallback() -> String {
352 render(view! { <WispCanvasHost backend=CanvasBackendView::CssFallback /> })
353}
354
355fn render_canvas_host_unavailable() -> String {
356 render(view! { <WispCanvasHost backend=CanvasBackendView::WispRuntimeUnavailable /> })
357}
358
359fn render_inspector_style() -> String {
360 render(view! { <InspectorPanel view=sample_inspector_style_tab() /> })
361}
362
363fn render_inspector_cursor() -> String {
364 render(view! { <InspectorPanel view=sample_inspector_cursor_tab() /> })
365}
366
367fn render_inspector_disabled() -> String {
368 render(view! { <InspectorPanel view=sample_inspector_disabled_section() /> })
369}
370
371fn render_row_slider() -> String {
372 let section = PropertySectionView {
373 title: "EXAMPLE",
374 rows: vec![PropertyRowView {
375 label: "Auto-zoom",
376 value: Some("2.0×"),
377 disabled: false,
378 control: PropertyControlView::SliderPercent { percent: 55 },
379 }],
380 };
381 render(view! { <PropertySection section=section /> })
382}
383
384fn render_row_toggle() -> String {
385 let section = PropertySectionView {
386 title: "EXAMPLE",
387 rows: vec![PropertyRowView {
388 label: "Drop shadow",
389 value: None,
390 disabled: false,
391 control: PropertyControlView::Toggle { on: true },
392 }],
393 };
394 render(view! { <PropertySection section=section /> })
395}
396
397fn render_timeline_empty() -> String {
398 render(view! { <TimelineSkeleton view=sample_timeline_skeleton_empty() /> })
399}
400
401fn render_timeline_with_placeholders() -> String {
402 render(view! { <TimelineSkeleton view=sample_timeline_skeleton() /> })
403}
404
405fn render_timeline_playing() -> String {
406 render(view! { <TimelineSkeleton view=sample_timeline_skeleton_playing() /> })
407}
408
409fn render_timeline_selected() -> String {
410 let mut v = sample_timeline_skeleton();
411 if let Some(t) = v.tracks.first_mut() {
412 t.selected = true;
413 }
414 render(view! { <TimelineSkeleton view=v /> })
415}
416
417fn render_row_swatches() -> String {
418 let section = PropertySectionView {
419 title: "EXAMPLE",
420 rows: vec![PropertyRowView {
421 label: "Color",
422 value: None,
423 disabled: false,
424 control: PropertyControlView::ColorSwatches {
425 swatches: vec!["#fafafa", "#facc15", "#38bdf8", "#a78bfa", "#f97316"],
426 selected: 2,
427 },
428 }],
429 };
430 render(view! { <PropertySection section=section /> })
431}
432
433fn render_editor_mock() -> String {
434 render(view! {
435 <div class="editor-mock">
436 <Card>
437 <CardHeader title="Recording 02" subtitle="Captured 2026-05-09 · 1m 24s" />
438 <CardBody>
439 <div class="editor-mock-preview">
440 <div class="editor-mock-screen">
441 <div class="editor-mock-screen-label">"Player surface"</div>
442 </div>
443 </div>
444 <PlayerControls
445 state=PlayState::Playing
446 position=0.42
447 duration_seconds=84.0
448 />
449 </CardBody>
450 </Card>
451 <Card>
452 <CardHeader title="Timeline" subtitle="4 tracks · 8.0s window" />
453 <CardBody>
454 <DopeSheet
455 tracks=sample_dope_sheet_tracks()
456 duration_seconds=8.0
457 playhead_seconds=3.4
458 />
459 </CardBody>
460 </Card>
461 </div>
462 })
463}