ui_storybook/components/shell/
status_bar.rs1use leptos::prelude::*;
8
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
10pub enum StatusKind {
11 #[default]
12 Ready,
13 Busy,
14 Error,
15}
16
17impl StatusKind {
18 fn css(self) -> &'static str {
19 match self {
20 StatusKind::Ready => "status-pill-ready",
21 StatusKind::Busy => "status-pill-busy",
22 StatusKind::Error => "status-pill-error",
23 }
24 }
25
26 fn label(self) -> &'static str {
27 match self {
28 StatusKind::Ready => "Ready",
29 StatusKind::Busy => "Working",
30 StatusKind::Error => "Error",
31 }
32 }
33}
34
35#[component]
36pub fn StatusBar(
37 #[prop(optional)]
39 fps: f32,
40 #[prop(optional, into)]
42 encoder: String,
43 #[prop(optional)]
45 file_bytes: u64,
46 #[prop(optional)]
48 kind: StatusKind,
49 #[prop(optional, into)]
51 detail: String,
52) -> impl IntoView {
53 let fps_value = fps.max(0.0).round();
54 #[allow(
55 clippy::cast_possible_truncation,
56 clippy::cast_sign_loss,
57 reason = "fps is positive and bounded; rendered FPS comfortably fits in u32"
58 )]
59 let fps_int = fps_value as u32;
60 let encoder = if encoder.is_empty() {
61 "—".to_string()
62 } else {
63 encoder
64 };
65 let size = format_bytes(file_bytes);
66 let pill_class = format!("status-pill {}", kind.css());
67 let pill_label = kind.label();
68 let has_detail = !detail.is_empty();
69
70 view! {
71 <div class="status-bar" role="contentinfo" aria-label="Engine status">
72 <div class="status-cell">
73 <span class="status-key">"FPS"</span>
74 <span class="status-value">{fps_int}</span>
75 </div>
76 <div class="status-cell">
77 <span class="status-key">"Encoder"</span>
78 <span class="status-value">{encoder}</span>
79 </div>
80 <div class="status-cell">
81 <span class="status-key">"Size"</span>
82 <span class="status-value">{size}</span>
83 </div>
84 <div class="status-spacer"></div>
85 <Show when=move || has_detail>
86 <span class="status-detail">{detail.clone()}</span>
87 </Show>
88 <span class=pill_class>
89 <span class="status-pill-dot"></span>
90 {pill_label}
91 </span>
92 </div>
93 }
94}
95
96fn format_bytes(bytes: u64) -> String {
97 const KB: u64 = 1024;
98 const MB: u64 = 1024 * KB;
99 const GB: u64 = 1024 * MB;
100 if bytes == 0 {
101 return "0 B".into();
102 }
103 if bytes >= GB {
104 #[allow(
105 clippy::cast_precision_loss,
106 reason = "display value, fractional precision sufficient"
107 )]
108 let v = bytes as f64 / GB as f64;
109 format!("{v:.2} GB")
110 } else if bytes >= MB {
111 #[allow(
112 clippy::cast_precision_loss,
113 reason = "display value, fractional precision sufficient"
114 )]
115 let v = bytes as f64 / MB as f64;
116 format!("{v:.1} MB")
117 } else if bytes >= KB {
118 #[allow(
119 clippy::cast_precision_loss,
120 reason = "display value, fractional precision sufficient"
121 )]
122 let v = bytes as f64 / KB as f64;
123 format!("{v:.0} KB")
124 } else {
125 format!("{bytes} B")
126 }
127}