Skip to main content

app_ui/
export_bar.rs

1//! Export bar — start / progress / cancel for the edited export (ED.22).
2//!
3//! The cutting room's "strike a print" button. One control that drives the
4//! whole export lifecycle off the [`ExportUiState`] the
5//! [export event bridge](crate::editor_ipc::install_editor_export_listeners)
6//! feeds: an **Export** button when idle (or after a finished / failed run),
7//! a **progress bar + Cancel** while a print is being struck. The heavy
8//! lifting is the backend `editor_export` command; this is the thin,
9//! reactive surface over it.
10
11use edit::EditProject;
12use leptos::prelude::*;
13
14use crate::editor_ipc::{self, ExportUiState, export_percent};
15
16type ProjectSignal = Option<RwSignal<Option<EditProject>>>;
17
18/// Idle / done / error view: Export + Save buttons plus optional status +
19/// last-saved lines.
20fn idle_view(
21    project: ProjectSignal,
22    state: RwSignal<ExportUiState>,
23    status: Option<String>,
24    saved: Option<String>,
25) -> AnyView {
26    view! {
27        <div class="export-bar-row">
28            <button
29                class="export-btn"
30                on:click=move |_| {
31                    if let Some(p) = project
32                        && let Some(proj) = p.get_untracked()
33                    {
34                        state.set(ExportUiState::Running {
35                            done: 0,
36                            total: proj.project_duration(),
37                        });
38                        editor_ipc::editor_export(&proj, "mp4");
39                    }
40                }
41            >
42                "Export mp4"
43            </button>
44            <button
45                class="export-save"
46                on:click=move |_| {
47                    if let Some(p) = project
48                        && let Some(proj) = p.get_untracked()
49                    {
50                        editor_ipc::editor_save_project(&proj);
51                    }
52                }
53            >
54                "Save"
55            </button>
56            {status.map(|msg| view! { <span class="export-status">{msg}</span> })}
57            {saved.map(|path| view! { <span class="export-status">{format!("Saved: {path}")}</span> })}
58        </div>
59    }
60    .into_any()
61}
62
63/// Running view: a progress bar + percent + Cancel.
64fn running_view(done: u64, total: u64) -> AnyView {
65    let pct = export_percent(done, total);
66    view! {
67        <div class="export-bar-row">
68            <div class="export-progress">
69                <div class="export-progress-fill" style=format!("width:{pct}%")></div>
70            </div>
71            <span class="export-pct">{format!("{pct}%")}</span>
72            <button class="export-cancel" on:click=move |_| editor_ipc::editor_export_cancel()>
73                "Cancel"
74            </button>
75        </div>
76    }
77    .into_any()
78}
79
80/// The export bar. Reads the project + export state from context.
81#[component]
82pub fn ExportBar() -> impl IntoView {
83    let project = use_context::<RwSignal<Option<EditProject>>>();
84    let state = use_context::<RwSignal<ExportUiState>>()
85        .unwrap_or_else(|| RwSignal::new(ExportUiState::Idle));
86    let saved = use_context::<RwSignal<Option<String>>>();
87    view! {
88        <div class="export-bar">
89            <h3 class="clip-inspector-title">"Export"</h3>
90            {move || {
91                let saved_now = saved.and_then(|s| s.get());
92                match state.get() {
93                    ExportUiState::Idle => idle_view(project, state, None, saved_now),
94                    ExportUiState::Running { done, total } => running_view(done, total),
95                    ExportUiState::Done { path } => {
96                        idle_view(project, state, Some(format!("✓ Exported: {path}")), saved_now)
97                    }
98                    ExportUiState::Error { message } => {
99                        idle_view(project, state, Some(format!("✗ {message}")), saved_now)
100                    }
101                }
102            }}
103        </div>
104    }
105}