use cipher_factory::prelude::*; use leptos::{prelude::*, tachys::dom::event_target_value}; use std::{str::FromStr, time::Duration}; use strum::IntoEnumIterator; use web_sys::WheelEvent; #[component] pub fn CipherForm(algorithm: Algorithm) -> impl IntoView { let (mode, set_mode) = signal(OperationMode::Encrypt); let (output_fmt, set_output_fmt) = signal(OutputFormat::Hex); let (key_input, set_key_input) = signal(String::new()); let (text_input, set_text_input) = signal(String::new()); let (output, set_output) = signal(String::new()); let (error_msg, set_error_msg) = signal(String::new()); let (copy_feedback, set_copy_feedback) = signal(false); let handle_submit = move || { set_error_msg(String::new()); set_output(String::new()); let key = key_input.get(); let raw_text = text_input.get(); if key.is_empty() || raw_text.is_empty() { set_error_msg("Please enter both key and input text.".to_string()); return; } let final_text = if mode.get() == OperationMode::Decrypt { format!("0x{raw_text}") } else { raw_text }; let context = CipherContext::new(algorithm, mode.get(), key, final_text, output_fmt.get()); match context.process() { Ok(out) => set_output(out), Err(e) => set_error_msg(e.to_string()), } }; let copy_to_clipboard = move |content: String| { let clipboard = window().navigator().clipboard(); let _ = clipboard.write_text(&content); set_copy_feedback(true); set_timeout(move || set_copy_feedback(false), Duration::from_secs(2)); }; let update_output = move |fmt| { set_output_fmt(fmt); if !output.get().is_empty() { handle_submit(); } }; view! {

{algorithm.to_string()}

} } #[component] fn RadioButton( value: OperationMode, current: ReadSignal, set_current: WriteSignal, ) -> impl IntoView { view! {
} } #[component] fn ConfigurationSection( mode: ReadSignal, set_mode: WriteSignal, output_fmt: ReadSignal, update_output: impl Fn(OutputFormat) + Copy + Send + 'static, ) -> impl IntoView { let handle_format_change = move |ev| { let val = event_target_value(&ev); let fmt = OutputFormat::from_str(&val).unwrap_or_default(); update_output(fmt); }; let handle_format_wheel = move |ev: WheelEvent| { ev.prevent_default(); let formats = OutputFormat::iter().collect::>(); let current_idx = formats .iter() .position(|f| *f == output_fmt.get()) .unwrap_or(2); let next_idx = if ev.delta_y() > 0.0 { (current_idx + 1) % formats.len() } else if current_idx == 0 { formats.len() - 1 } else { current_idx - 1 }; update_output(formats[next_idx]); }; view! {
{move || { if mode.get() != OperationMode::Decrypt { return view! { }.into_any(); } view! {
} .into_any() }}
} } fn clean_hex_input(input: String) -> String { input .chars() .filter(|ch| ch.is_ascii_hexdigit()) .collect::() } #[component] fn KeyInput(set_key_input: WriteSignal) -> impl IntoView { view! {
} } #[component] fn TextInput( mode: ReadSignal, text_input: ReadSignal, set_text_input: WriteSignal, ) -> impl IntoView { let handle_hex_input = move |ev| { let val = event_target_value(&ev); let cleaned = clean_hex_input(val); set_text_input(cleaned); }; let handle_text_input = move |ev| { set_text_input(event_target_value(&ev)); }; view! {
{move || { match mode.get() { OperationMode::Encrypt => { view! {
} .into_any() } OperationMode::Decrypt => { view! {
"0x"
} .into_any() } } }}
} } #[component] fn OutputBox( output: ReadSignal, output_fmt: ReadSignal, copy_to_clipboard: impl Fn(String) + Copy + Send + 'static, copy_feedback: ReadSignal, ) -> impl IntoView { view! { {move || { if output.get().is_empty() { return view! { }.into_any(); } view! {
"Output ("{output_fmt.get().to_string()}")"
{output.get()}
} .into_any() }} } } #[component] fn ErrorBox(error_msg: ReadSignal) -> impl IntoView { view! { {move || { if error_msg.get().is_empty() { return view! { }.into_any(); } view! {
{error_msg.get()}
}.into_any() }} } }