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! {
}
}
#[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! {
}
}
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! {
}
}
#[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()
}}
}
}