feat(web): add AES-CBC page with file encryption and UI improvements

This commit is contained in:
Kristofers Solo 2025-12-31 05:45:42 +02:00
parent 651651780f
commit 721c712ba3
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
4 changed files with 73 additions and 53 deletions

View File

@ -1,4 +1,3 @@
use crate::components::radio_button::RadioButton;
use cipher_factory::prelude::*; use cipher_factory::prelude::*;
use leptos::{prelude::*, tachys::dom::event_target_value}; use leptos::{prelude::*, tachys::dom::event_target_value};
use std::str::FromStr; use std::str::FromStr;
@ -11,7 +10,7 @@ pub fn ConfigurationSection(
set_mode: WriteSignal<OperationMode>, set_mode: WriteSignal<OperationMode>,
output_fmt: ReadSignal<OutputFormat>, output_fmt: ReadSignal<OutputFormat>,
update_output: impl Fn(OutputFormat) + Copy + Send + 'static, update_output: impl Fn(OutputFormat) + Copy + Send + 'static,
) -> AnyView { ) -> 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();
@ -39,39 +38,49 @@ pub fn ConfigurationSection(
view! { view! {
<div class="form-group"> <div class="form-group">
<label>"Configuration"</label> <label>"Mode"</label>
<div class="controls-row"> <div class="controls-row">
<div class="radio-group"> <div class="mode-toggle">
<RadioButton value=OperationMode::Encrypt current=mode set_current=set_mode /> <button
<RadioButton value=OperationMode::Decrypt current=mode set_current=set_mode /> type="button"
class=move || if mode.get() == OperationMode::Encrypt { "mode-btn active" } else { "mode-btn" }
on:click=move |_| set_mode(OperationMode::Encrypt)
>
"Encrypt"
</button>
<button
type="button"
class=move || if mode.get() == OperationMode::Decrypt { "mode-btn active" } else { "mode-btn" }
on:click=move |_| set_mode(OperationMode::Decrypt)
>
"Decrypt"
</button>
</div> </div>
{move || { {move || {
if mode.get() != OperationMode::Decrypt { if mode.get() != OperationMode::Decrypt {
return view! { <span></span> }.into_any(); return view! { <span></span> }.into_any();
} }
view! { view! {
<div class="format-controls-box"> <div class="format-select">
<div class="format-controls"> <label>"Output:"</label>
<label>"Output format:"</label> <select
<select on:wheel=handle_format_wheel
on:wheel=handle_format_wheel on:change=handle_format_change
on:change=handle_format_change prop:value=move || output_fmt.get().to_string()
prop:value=move || output_fmt.get().to_string() >
> {OutputFormat::iter()
{OutputFormat::iter() .map(|fmt| {
.map(|fmt| { view! {
view! { <option value=fmt.to_string()>{fmt.to_string()}</option>
<option value=fmt.to_string()>{fmt.to_string()}</option> }
} })
}) .collect_view()}
.collect_view()} </select>
</select>
</div>
</div> </div>
} }
.into_any() .into_any()
}} }}
</div> </div>
</div> </div>
}.into_any() }
} }

View File

@ -6,5 +6,4 @@ pub mod file_input;
pub mod iv_input; pub mod iv_input;
pub mod key_input; pub mod key_input;
pub mod output_box; pub mod output_box;
pub mod radio_button;
pub mod text_input; pub mod text_input;

View File

@ -1,25 +0,0 @@
use cipher_factory::prelude::OperationMode;
use leptos::prelude::*;
#[component]
pub fn RadioButton(
value: OperationMode,
current: ReadSignal<OperationMode>,
set_current: WriteSignal<OperationMode>,
) -> AnyView {
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>
}
.into_any()
}

View File

@ -701,11 +701,13 @@ main {
} }
} }
// Input mode toggle (Text/File switcher) // Mode toggle buttons (shared style for encrypt/decrypt and text/file)
.mode-toggle,
.input-mode-toggle { .input-mode-toggle {
display: flex; display: flex;
gap: 4px; gap: 4px;
background: var(--bg-input); background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 6px; border-radius: 6px;
padding: 2px; padding: 2px;
@ -713,10 +715,10 @@ main {
background: transparent; background: transparent;
border: none; border: none;
color: var(--text-muted); color: var(--text-muted);
padding: 4px 12px; padding: 6px 14px;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-size: 0.8rem; font-size: 0.85rem;
font-weight: 600; font-weight: 600;
transition: all 0.2s; transition: all 0.2s;
@ -731,6 +733,41 @@ main {
} }
} }
// Format selector (appears next to mode toggle in decrypt mode)
.format-select {
display: flex;
align-items: center;
gap: 8px;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: 6px;
padding: 2px 10px;
animation: fadeIn 0.2s ease-in-out;
label {
margin: 0;
color: var(--text-muted);
font-weight: 500;
font-size: 0.85rem;
text-transform: none;
letter-spacing: normal;
}
select {
background: var(--bg-body);
border: none;
border-radius: 4px;
color: var(--text-main);
padding: 6px 8px;
font-size: 0.85rem;
cursor: pointer;
&:focus {
outline: none;
}
}
}
// Textarea wrapper // Textarea wrapper
.textarea-wrapper { .textarea-wrapper {
position: relative; position: relative;