refactor(web): make smaller components

This commit is contained in:
Kristofers Solo 2025-11-26 05:07:02 +02:00
parent 486f8957eb
commit bb7ef246f8
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
2 changed files with 170 additions and 126 deletions

View File

@ -4,15 +4,15 @@ use thiserror::Error;
#[derive(Debug, Error, Clone, PartialEq, Eq)] #[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum CipherError { pub enum CipherError {
/// Invalid key size for the cipher /// Invalid key size for the cipher
#[error("Invalid key size: expected {expected} bytes, got {actual}")] #[error("Invalid key size: expected {expected} bytes, got {actual}.")]
InvalidKeySize { expected: usize, actual: usize }, InvalidKeySize { expected: usize, actual: usize },
/// Input data doesn't match the cipher's block size /// Input data doesn't match the cipher's block size
#[error("Invalid block size: expected {expected} bytes, got {actual}")] #[error("Invalid block size: expected {expected} bytes, got {actual}.")]
InvalidBlockSize { expected: usize, actual: usize }, InvalidBlockSize { expected: usize, actual: usize },
/// Error parsing block from string /// Error parsing block from string
#[error("Error parsing block from string: {0}")] #[error("{0}")]
BlockParseError(#[from] BlockError), BlockParseError(#[from] BlockError),
} }

View File

@ -50,6 +50,65 @@ pub fn CipherForm(algorithm: Algorithm) -> impl IntoView {
} }
}; };
view! {
<div class="cipher-card">
<div class="card-header">
<h2>{algorithm.to_string()}</h2>
</div>
<ConfigurationSection
mode=mode
set_mode=set_mode
output_fmt=output_fmt
update_output=update_output
/>
<KeyInput set_key_input=set_key_input />
<TextInput mode=mode set_text_input=set_text_input />
<button class="btn-primary" on:click=move |_| handle_submit()>
{move || format!("{} using {algorithm}", mode.get())}
</button>
<OutputBox
output=output
output_fmt=output_fmt
copy_to_clipboard=copy_to_clipboard
copy_feedback=copy_feedback
/>
<ErrorBox error_msg=error_msg />
</div>
}
}
#[component]
fn RadioButton(
value: OperationMode,
current: ReadSignal<OperationMode>,
set_current: WriteSignal<OperationMode>,
) -> impl IntoView {
view! {
<div class="radio-button">
<label>
<input
type="radio"
name="crypto-mode"
value=value.to_string()
prop:checked=move || current.get() == value
on:change=move |_| set_current.set(value)
/>
{value.to_string()}
</label>
</div>
}
}
#[component]
fn ConfigurationSection(
mode: ReadSignal<OperationMode>,
set_mode: WriteSignal<OperationMode>,
output_fmt: ReadSignal<OutputFormat>,
update_output: impl Fn(OutputFormat) + Copy + Send + 'static,
) -> impl IntoView {
let handle_format_change = move |ev| { let handle_format_change = move |ev| {
let val = event_target_value(&ev); let val = event_target_value(&ev);
let fmt = OutputFormat::from_str(&val).unwrap_or_default(); let fmt = OutputFormat::from_str(&val).unwrap_or_default();
@ -76,137 +135,122 @@ pub fn CipherForm(algorithm: Algorithm) -> impl IntoView {
}; };
view! { view! {
<div class="cipher-card"> <div class="form-group">
<div class="card-header"> <label>"Configuration"</label>
<h2>{algorithm.to_string()}</h2> <div class="controls-row">
</div> <div class="radio-group">
<RadioButton value=OperationMode::Encrypt current=mode set_current=set_mode />
<div class="form-group"> <RadioButton value=OperationMode::Decrypt current=mode set_current=set_mode />
<label>"Configuration"</label>
<div class="controls-row">
<div class="radio-group">
<RadioButton
value=OperationMode::Encrypt
current=mode
set_current=set_mode
/>
<RadioButton
value=OperationMode::Decrypt
current=mode
set_current=set_mode
/>
</div>
{move || {
if mode.get() != OperationMode::Decrypt {
return view! { <span></span> }.into_any();
}
view! {
<div class="format-controls-box">
<div class="format-controls">
<label>"Output format:"</label>
<select
on:wheel=handle_format_wheel
on:change=handle_format_change
prop:value=move || output_fmt.get().to_string()
>
{OutputFormat::iter()
.map(|fmt| {
view! {
<option value=fmt.to_string()>{fmt.to_string()}</option>
}
})
.collect_view()}
</select>
</div>
</div>
}
.into_any()
}}
</div> </div>
</div> {move || {
<div class="form-group"> if mode.get() != OperationMode::Decrypt {
<label>"Secret Key"</label> return view! { <span></span> }.into_any();
<input }
type="text" view! {
prop:key_input <div class="format-controls-box">
placeholder="Enter key..." <div class="format-controls">
on:input=move |ev| set_key_input(event_target_value(&ev)) <label>"Output format:"</label>
/> <select
</div> on:wheel=handle_format_wheel
<div class="form-group"> on:change=handle_format_change
<label> prop:value=move || output_fmt.get().to_string()
{move || { >
match mode.get() { {OutputFormat::iter()
OperationMode::Encrypt => "Plaintext Input", .map(|fmt| {
OperationMode::Decrypt => "Ciphertext (Hex) Input", view! {
} <option value=fmt.to_string()>{fmt.to_string()}</option>
}} }
</label> })
<input .collect_view()}
type="text" </select>
prop:text_input </div>
placeholder="Enter text..."
on:input=move |ev| set_text_input(event_target_value(&ev))
/>
</div>
<button class="btn-primary" on:click=move |_| handle_submit()>
{move || format!("{} using {algorithm}", mode.get())}
</button>
// Output Section
{move || {
if output.get().is_empty() {
return view! { <span></span> }.into_any();
}
view! {
<div class="result-box">
<div class="result-toolbar">
<strong>"Output ("{output_fmt.get().to_string()}")"</strong>
<button
class="btn-copy"
on:click=move |_| copy_to_clipboard(output.get())
>
{move || {
if copy_feedback.get() { "✔️ Copied" } else { "📋 Copy" }
}}
</button>
</div> </div>
<code>{output.get()}</code> }
</div> .into_any()
} }}
.into_any() </div>
}}
// Error Section
{move || {
if error_msg.get().is_empty() {
return view! { <span></span> }.into_any();
}
view! { <div class="error-box">{error_msg.get()}</div> }.into_any()
}}
</div> </div>
} }
} }
#[component] #[component]
fn RadioButton( fn KeyInput(set_key_input: WriteSignal<String>) -> impl IntoView {
value: OperationMode,
current: ReadSignal<OperationMode>,
set_current: WriteSignal<OperationMode>,
) -> impl IntoView {
view! { view! {
<div class="radio-button"> <div class="form-group">
<label> <label>"Secret Key"</label>
<input <input
type="radio" type="text"
name="crypto-mode" prop:key_input
value=value.to_string() placeholder="Enter key..."
prop:checked=move || current.get() == value on:input=move |ev| set_key_input(event_target_value(&ev))
on:change=move |_| set_current.set(value) />
/>
{value.to_string()}
</label>
</div> </div>
} }
} }
#[component]
fn TextInput(
mode: ReadSignal<OperationMode>,
set_text_input: WriteSignal<String>,
) -> impl IntoView {
view! {
<div class="form-group">
<label>
{move || {
match mode.get() {
OperationMode::Encrypt => "Plaintext Input",
OperationMode::Decrypt => "Ciphertext (Hex) Input",
}
}}
</label>
<input
type="text"
prop:text_input
placeholder="Enter text..."
on:input=move |ev| set_text_input(event_target_value(&ev))
/>
</div>
}
}
#[component]
fn OutputBox(
output: ReadSignal<String>,
output_fmt: ReadSignal<OutputFormat>,
copy_to_clipboard: impl Fn(String) + Copy + Send + 'static,
copy_feedback: ReadSignal<bool>,
) -> impl IntoView {
view! {
{move || {
if output.get().is_empty() {
return view! { <span></span> }.into_any();
}
view! {
<div class="result-box">
<div class="result-toolbar">
<strong>"Output ("{output_fmt.get().to_string()}")"</strong>
<button class="btn-copy" on:click=move |_| copy_to_clipboard(output.get())>
{move || {
if copy_feedback.get() { "✔️ Copied" } else { "📋 Copy" }
}}
</button>
</div>
<code>{output.get()}</code>
</div>
}
.into_any()
}}
}
}
#[component]
fn ErrorBox(error_msg: ReadSignal<String>) -> impl IntoView {
view! {
{move || {
if error_msg.get().is_empty() {
return view! { <span></span> }.into_any();
}
view! { <div class="error-box">{error_msg.get()}</div> }.into_any()
}}
}
}