use fib_rs::Fib;
use leptos::prelude::*;
use leptos_use::{UseToggleReturn, use_toggle, use_window};
fn main() {
mount_to_body(App);
}
#[component]
fn App() -> impl IntoView {
let UseToggleReturn {
toggle,
value,
set_value: _,
} = use_toggle(true);
let (result, set_result) = signal(Vec::<String>::new());
view! {
<main class="app-container">
<h1 class="app-title">"Fibonacci Calculator"</h1>
<div class="mode-toggle">
<div class="toggle-label" class:toggle-active=move || value.get()>
"Single"
</div>
<button
class="toggle-button"
aria-label="Toggle calculation mode"
on:click=move |_| toggle()
>
<div
class="toggle-thumb"
class:toggle-thumb-left=move || value.get()
class:toggle-thumb-right=move || !value.get()
></div>
</button>
<div class="toggle-label" class:toggle-active=move || !value.get()>
"Range"
</div>
</div>
<Calculator set_result=set_result is_single_mode=value />
<div class="result-container">
<For each=move || result.get() key=|line| line.clone() let:line>
<p class="result-line">{line}</p>
</For>
</div>
</main>
}
}
#[component]
fn Calculator(set_result: WriteSignal<Vec<String>>, is_single_mode: Signal<bool>) -> impl IntoView {
let (value, set_value) = signal(Ok(0u128));
let (start, set_start) = signal(Ok(0u128));
let (end, set_end) = signal(Ok(0u128));
let (elapsed, set_elapsed) = signal(None::<f64>);
let format_num = |result: Result<u128, _>| -> String {
match result {
Ok(0) | Err(_) => String::new(),
Ok(n) => n.to_string(),
}
};
let value_display = move || format_num(value.get());
let start_display = move || format_num(start.get());
let end_display = move || format_num(end.get());
let perf = use_window()
.as_ref()
.and_then(leptos::web_sys::Window::performance);
let calculate = move |_| {
let start_time = perf.as_ref().map(leptos::web_sys::Performance::now);
if is_single_mode.get() {
match value.get() {
Ok(n) => {
let result = Fib::single(n);
set_result.set(vec![format!("F({}) = {}", n, result)]);
}
Err(_) => set_result.set(vec!["Please enter a valid number".to_string()]),
}
} else {
match (start.get(), end.get()) {
(Ok(start_val), Ok(end_val)) if start_val > end_val => {
set_result.set(vec!["Invalid range: end < start".to_string()]);
}
(Ok(start_val), Ok(end_val)) => {
let results = Fib::range(start_val, end_val);
let formatted = results
.into_iter()
.enumerate()
.map(|(i, v)| format!("F({}) = {}", start_val + i as u128, v))
.collect();
set_result.set(formatted);
}
_ => {
set_result.set(vec!["Invalid input(s).".to_string()]);
}
}
}
let end_time = perf.as_ref().map(leptos::web_sys::Performance::now);
if let (Some(start), Some(end)) = (start_time, end_time) {
set_elapsed.set(Some((end - start) / 1000.0));
}
};
view! {
<Show
when=move || is_single_mode.get()
fallback=move || {
view! {
<div class="range-inputs">
<input
class="number-input"
type="number"
placeholder="Start"
prop:value=start_display
on:input=move |ev| {
set_start.set(event_target_value(&ev).parse::<u128>());
}
/>
<input
class="number-input"
type="number"
placeholder="End"
prop:value=end_display
on:input=move |ev| {
set_end.set(event_target_value(&ev).parse::<u128>());
}
/>
</div>
}
}
>
<input
class="number-input"
type="number"
placeholder="Enter a number"
prop:value=value_display
on:input=move |ev| { set_value.set(event_target_value(&ev).parse::<u128>()) }
/>
</Show>
<div class="calculate-row">
<button on:click=calculate>"Calculate"</button>
<Show when=move || elapsed.get().is_some()>
<span class="timer">{move || elapsed.get().map(|t| format!("{t:.3}s"))}</span>
</Show>
</div>
}
}