refactor(web): use AnyView return type for all components

This commit is contained in:
Kristofers Solo 2025-12-31 16:09:20 +02:00
parent 6acf98bbae
commit 4c3d620250
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
5 changed files with 62 additions and 46 deletions

View File

@ -1,48 +1,51 @@
# Compute recipe
FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef
WORKDIR /app
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
# Install tools and build dependencies
FROM rustlang/rust:nightly-bookworm AS cacher
WORKDIR /app
# Install cargo-binstall
RUN curl -L https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xz -C /usr/local/bin
RUN curl -LO https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz
RUN tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz
RUN cp cargo-binstall /usr/local/cargo/bin
RUN cargo binstall cargo-leptos cargo-chef -y
RUN apt-get update -y && apt-get install -y --no-install-recommends clang
RUN rustup target add wasm32-unknown-unknown
WORKDIR /app
# Cook dependencies
COPY --from=chef /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
# Actual build
FROM rustlang/rust:nightly-bookworm AS builder
RUN wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz
RUN tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz
RUN cp cargo-binstall /usr/local/cargo/bin
RUN cargo binstall cargo-leptos -y
RUN apt-get update -y && apt-get install -y --no-install-recommends clang
RUN rustup target add wasm32-unknown-unknown
WORKDIR /app
# Copy the tools from the cacher stage
COPY --from=cacher /usr/local/rustup /usr/local/rustup
COPY --from=cacher /usr/local/cargo /usr/local/cargo
RUN apt-get update && apt-get install -y --no-install-recommends clang
# Bring in the cooked dependencies
COPY --from=cacher /app/target target
COPY . .
# Build the Leptos app
RUN cargo leptos build --release -vv
# Runtime
FROM debian:bookworm-slim AS runtime
WORKDIR /app
RUN apt-get update -y \
&& apt-get install -y --no-install-recommends openssl ca-certificates \
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends openssl ca-certificates \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Copy binaries and assets
COPY --from=builder /app/target/release/web /app/
COPY --from=builder /app/target/site /app/site
COPY --from=builder /app/Cargo.toml /app/
EXPOSE 8080
CMD ["/app/web"]

View File

@ -10,7 +10,7 @@ use leptos::prelude::*;
use std::time::Duration;
#[component]
pub fn CipherForm(algorithm: Algorithm) -> impl IntoView {
pub fn CipherForm(algorithm: Algorithm) -> AnyView {
let (mode, set_mode) = signal(OperationMode::Encrypt);
let (output_fmt, set_output_fmt) = signal(OutputFormat::Hex);
@ -101,4 +101,5 @@ pub fn CipherForm(algorithm: Algorithm) -> impl IntoView {
</div>
}
.into_any()
}

View File

@ -13,7 +13,7 @@ use wasm_bindgen::JsCast;
use web_sys::{Blob, Url};
#[component]
pub fn CipherFormCbc() -> impl IntoView {
pub fn CipherFormCbc() -> AnyView {
let (mode, set_mode) = signal(OperationMode::Encrypt);
let (output_fmt, set_output_fmt) = signal(OutputFormat::Hex);
@ -56,7 +56,7 @@ pub fn CipherFormCbc() -> impl IntoView {
let formatted_iv = format!("0x{iv}");
// Get input data
let input_data: Vec<u8> = match input_mode.get() {
let input_data = match input_mode.get() {
InputMode::Text => {
let text = text_content.get();
if text.is_empty() {
@ -192,7 +192,10 @@ pub fn CipherFormCbc() -> impl IntoView {
<div class="result-toolbar">
<strong>"Output ("{output_fmt.get().to_string()}")"</strong>
<div class="result-actions">
<button class="btn-copy" on:click=move |_| copy_to_clipboard(output.get())>
<button
class="btn-copy"
on:click=move |_| copy_to_clipboard(output.get())
>
{move || if copy_feedback.get() { "Copied" } else { "Copy" }}
</button>
{move || {
@ -201,7 +204,8 @@ pub fn CipherFormCbc() -> impl IntoView {
<button class="btn-download" on:click=download_output>
"Download"
</button>
}.into_any()
}
.into_any()
} else {
view! { <span></span> }.into_any()
}
@ -209,21 +213,25 @@ pub fn CipherFormCbc() -> impl IntoView {
</div>
</div>
<div class="result-content">
<code>{move || {
<code>
{move || {
let out = output.get();
if out.len() > 1000 {
format!("{}... ({} chars total)", &out[..1000], out.len())
} else {
out
}
}}</code>
}}
</code>
</div>
</div>
}.into_any()
}
.into_any()
}}
<ErrorBox error_msg=error_msg />
</div>
}
.into_any()
}
fn parse_hex_string(s: &str) -> Result<Vec<u8>, String> {
@ -234,7 +242,7 @@ fn parse_hex_string(s: &str) -> Result<Vec<u8>, String> {
.unwrap_or(trimmed);
// Remove whitespace and newlines
let s: String = s.chars().filter(|c| !c.is_whitespace()).collect();
let s = s.chars().filter(|c| !c.is_whitespace()).collect::<String>();
if !s.len().is_multiple_of(2) {
return Err("Hex string must have even length".to_string());
@ -282,10 +290,7 @@ fn download_bytes(bytes: &[u8], filename: &str) {
return;
};
let Some(window) = web_sys::window() else {
return;
};
let Some(document) = window.document() else {
let Some(document) = window().document() else {
return;
};
let Some(a) = document.create_element("a").ok() else {
@ -295,7 +300,7 @@ fn download_bytes(bytes: &[u8], filename: &str) {
let _ = a.set_attribute("href", &url);
let _ = a.set_attribute("download", filename);
let a: web_sys::HtmlElement = a.unchecked_into();
let a = a.unchecked_into::<web_sys::HtmlElement>();
a.click();
let _ = Url::revoke_object_url(&url);

View File

@ -10,7 +10,7 @@ pub fn ConfigurationSection(
set_mode: WriteSignal<OperationMode>,
output_fmt: ReadSignal<OutputFormat>,
update_output: impl Fn(OutputFormat) + Copy + Send + 'static,
) -> impl IntoView {
) -> AnyView {
let handle_format_change = move |ev| {
let val = event_target_value(&ev);
let fmt = OutputFormat::from_str(&val).unwrap_or_default();
@ -83,4 +83,5 @@ pub fn ConfigurationSection(
</div>
</div>
}
.into_any()
}

View File

@ -41,7 +41,7 @@ fn read_file(
fn InputModeToggle(
input_mode: ReadSignal<InputMode>,
set_input_mode: WriteSignal<InputMode>,
) -> impl IntoView {
) -> AnyView {
view! {
<div class="input-mode-toggle">
<button
@ -64,6 +64,7 @@ fn InputModeToggle(
</button>
</div>
}
.into_any()
}
#[component]
@ -71,7 +72,7 @@ fn TextAreaInput(
text_content: ReadSignal<String>,
set_text_content: WriteSignal<String>,
is_decrypt_mode: Memo<bool>,
) -> impl IntoView {
) -> AnyView {
let handle_text_change = move |ev: Event| {
if let Some(target) = ev.target() {
let textarea: web_sys::HtmlTextAreaElement = target.unchecked_into();
@ -99,6 +100,7 @@ fn TextAreaInput(
</div>
</div>
}
.into_any()
}
#[component]
@ -107,7 +109,7 @@ fn FileDropZone(
set_file_data: WriteSignal<Option<Vec<u8>>>,
file_name: ReadSignal<Option<String>>,
set_file_name: WriteSignal<Option<String>>,
) -> impl IntoView {
) -> AnyView {
let (is_dragging, set_is_dragging) = signal(false);
let handle_file_change = move |ev: Event| {
@ -193,6 +195,7 @@ fn FileDropZone(
</label>
</div>
}
.into_any()
}
#[component]
@ -201,7 +204,7 @@ fn FileSelected(
file_data: ReadSignal<Option<Vec<u8>>>,
set_file_name: WriteSignal<Option<String>>,
set_file_data: WriteSignal<Option<Vec<u8>>>,
) -> impl IntoView {
) -> AnyView {
let clear_file = move |ev: web_sys::MouseEvent| {
ev.prevent_default();
ev.stop_propagation();
@ -221,16 +224,18 @@ fn FileSelected(
</button>
</div>
}
.into_any()
}
#[component]
fn FilePlaceholder() -> impl IntoView {
fn FilePlaceholder() -> AnyView {
view! {
<div class="file-placeholder">
<span class="upload-icon">"[+]"</span>
<span>"Click to select a file or drag and drop"</span>
</div>
}
.into_any()
}
#[component]
@ -244,7 +249,7 @@ pub fn FileTextInput(
file_name: ReadSignal<Option<String>>,
set_file_name: WriteSignal<Option<String>>,
is_decrypt_mode: Memo<bool>,
) -> impl IntoView {
) -> AnyView {
view! {
<div class="form-group">
<div class="label-header">
@ -281,4 +286,5 @@ pub fn FileTextInput(
}}
</div>
}
.into_any()
}