Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "euv"
version = "0.16.1"
version = "0.17.0"
readme = "README.md"
edition = "2024"
authors = ["root@ltpp.vip"]
Expand All @@ -19,13 +19,13 @@ members = ["cli", "core", "engine", "example", "macros", "ui"]
resolver = "3"

[workspace.dependencies]
euv = { path = ".", version = "0.16.1" }
euv-ui = { path = "ui", version = "0.16.1" }
euv-cli = { path = "cli", version = "0.16.1" }
euv-core = { path = "core", version = "0.16.1" }
euv-engine = { path = "engine", version = "0.16.1" }
euv-macros = { path = "macros", version = "0.16.1" }
euv-example = { path = "example", version = "0.16.1" }
euv = { path = ".", version = "0.17.0" }
euv-ui = { path = "ui", version = "0.17.0" }
euv-cli = { path = "cli", version = "0.17.0" }
euv-core = { path = "core", version = "0.17.0" }
euv-engine = { path = "engine", version = "0.17.0" }
euv-macros = { path = "macros", version = "0.17.0" }
euv-example = { path = "example", version = "0.17.0" }

log = "0.4.33"
toml = "0.9.12"
Expand Down
2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "euv-cli"
version = "0.16.1"
version = "0.17.0"
readme = "README.md"
edition = "2024"
authors = ["root@ltpp.vip"]
Expand Down
2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "euv-core"
version = "0.16.1"
version = "0.17.0"
readme = "README.md"
edition = "2024"
authors = ["root@ltpp.vip"]
Expand Down
2 changes: 1 addition & 1 deletion engine/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "euv-engine"
version = "0.16.1"
version = "0.17.0"
readme = "README.md"
edition = "2024"
authors = ["root@ltpp.vip"]
Expand Down
2 changes: 1 addition & 1 deletion example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "euv-example"
version = "0.16.1"
version = "0.17.0"
readme = "README.md"
edition = "2024"
authors = ["root@ltpp.vip"]
Expand Down
7 changes: 6 additions & 1 deletion example/src/component/nav/view/const.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// Navigation item tuple: (icon, label, target).
/// Static navigation route targets — every page in the
/// demo registers here so the sidebar can iterate.
pub(crate) const NAV_ITEMS: &[(&str, &str, &str)] = &[
("ℹ️", "About", "/"),
("🎬", "Animation", "/animation"),
Expand All @@ -14,6 +15,10 @@ pub(crate) const NAV_ITEMS: &[(&str, &str, &str)] = &[
("🏷️", "DynTag", "/dynamic-component"),
("🎯", "Event", "/event"),
("📄", "Form", "/form"),
("⏲️", "Timing", "/hooks-timing"),
("🌐", "Async", "/hooks-async"),
("🛡️", "Protect", "/hooks-protect"),
("🌍", "i18n", "/hooks-i18n"),
("🎮", "Game2D", "/game-2d"),
("🎲", "Game3D", "/game-3d"),
("💚", "KeepAlive", "/keep-alive"),
Expand Down
12 changes: 12 additions & 0 deletions example/src/component/router/view/fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ pub(crate) fn page_router(node: VirtualNode<PageRouterProps>) -> VirtualNode {
"/form" => {
page_form {}
}
"/hooks-timing" => {
page_hooks_timing {}
}
"/hooks-async" => {
page_hooks_async {}
}
"/hooks-protect" => {
page_hooks_protect {}
}
"/hooks-i18n" => {
page_hooks_i18n {}
}
"/game-2d" => {
page_game_2d {}
}
Expand Down
85 changes: 85 additions & 0 deletions example/src/page/hooks_async/hook/fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use super::*;

/// The string value the async page resolves into on `Resolve`.
pub(crate) const HOOKS_ASYNC_RESOLVED_VALUE: &str = "hello from use_async";

/// The error message the async page fails with on `Fail`.
pub(crate) const HOOKS_ASYNC_FAIL_MESSAGE: &str = "demo failure";

/// Returns a click handler that triggers an `Ok("...")` future
/// and writes it into the supplied `UseAsyncHandle`.
pub(crate) fn hooks_async_refetch(handle: UseAsyncHandle<String, ()>) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
handle.set_state(AsyncState::<String, ()>::Ok(String::from(
HOOKS_ASYNC_RESOLVED_VALUE,
)));
}))
}

/// Reads the current `AsyncState` and shapes it into a readable
/// string for the demo card.
pub(crate) fn hooks_async_state_label(handle: UseAsyncHandle<String, ()>) -> String {
match handle.state() {
AsyncState::<String, ()>::Loading(_) => String::from("Loading"),
AsyncState::<String, ()>::Ok(value) => format!("Ok({value:?})"),
AsyncState::<String, ()>::Err(err) => format!("Err({err:?})"),
}
}

/// Returns `true` when the underlying `LazyComponent`'s factory
/// has not yet produced a value.
///
/// Coerces to a `bool` so the call site can use the result
/// directly inside a span / VirtualNode `{}` slot.
pub(crate) fn hooks_async_lazy_is_pending(lazy: &LazyComponent<u32>) -> bool {
lazy.get().is_none()
}

/// Returns the loaded `LazyComponent` value as a `String` for the
/// demo card. Falls back to `"pending"` when the factory has not
/// fired yet.
pub(crate) fn hooks_async_lazy_loaded_label(lazy: &LazyComponent<u32>) -> String {
lazy.loaded()
.map(|value: u32| value.to_string())
.unwrap_or_else(|| String::from("pending"))
}

/// Reads `SuspenseHandle`'s phase and shapes it into a readable
/// string for the demo card.
pub(crate) fn hooks_async_suspense_phase_label(handle: &SuspenseHandle<String>) -> String {
match handle.get_phase().get() {
SuspensePhase::Pending => String::from("Pending"),
SuspensePhase::Resolved(value) => format!("Resolved({value})"),
SuspensePhase::Failed(message) => format!("Failed({message})"),
}
}

/// Builds the click handler that flips the suspense handle to
/// `Resolved`.
pub(crate) fn hooks_async_resolve(
handle: SuspenseHandle<String>,
value: String,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
handle.resolve_sync(value.clone());
}))
}

/// Builds the click handler that flips the suspense handle to
/// `Failed`.
pub(crate) fn hooks_async_fail(
handle: SuspenseHandle<String>,
message: String,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
handle.fail(message.clone());
}))
}

/// Builds the click handler that resets the suspense handle
/// back to `Pending`.
pub(crate) fn hooks_async_reset(handle: SuspenseHandle<String>) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
handle.reset();
}))
}
4 changes: 4 additions & 0 deletions example/src/page/hooks_async/hook/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod r#fn;
pub(crate) use r#fn::*;

use super::*;
6 changes: 6 additions & 0 deletions example/src/page/hooks_async/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mod hook;
mod view;

pub(crate) use {hook::*, view::*};

use super::*;
104 changes: 104 additions & 0 deletions example/src/page/hooks_async/view/fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use super::*;

/// A page demonstrating the async-state primitives
/// ([`UseAsyncHandle`], [`LazyComponent`], [`SuspenseHandle`]).
///
/// The three rows share a single browser timer so the page can
/// drive transitions without spinning up an HTTP server.
#[component]
pub(crate) fn page_hooks_async(node: VirtualNode<PageHooksAsyncProps>) -> VirtualNode {
let PageHooksAsyncProps: PageHooksAsyncProps = node.try_get_props().unwrap_or_default();
let async_handle: UseAsyncHandle<String, ()> = use_async::<String, ()>();
let lazy_value: LazyComponent<u32> = use_lazy_component::<u32, _>(|| 7);
let suspense: SuspenseHandle<String> = use_suspense::<String>();
html! {
div {
class: c_page_container()
euv_header {
icon: "🌐"
title: "Hooks — Async"
subtitle: "AsyncState (use_async), lazy factory (use_lazy_component), and suspense phases (use_suspense)."
}
euv_card {
title: "use_async"
p {
class: c_render_count_text()
"The handle's state exposes an AsyncState machine. The Loading arm carries () by default; the Ok arm carries the resolved value; the Err arm the failure message."
}
div {
class: c_button_controls()
euv_button {
variant: EuvButtonVariant::Primary
label: "Refetch"
onclick: hooks_async_refetch(async_handle)
}
}
p {
class: c_render_count_text()
"state: "
span {
class: c_counter_value()
hooks_async_state_label(async_handle)
}
}
}
euv_card {
title: "use_lazy_component"
p {
class: c_render_count_text()
"The factory is only invoked on first access. After a reset() the next read triggers it again."
}
div {
class: c_counter_row()
div {
"loaded:"
span {
class: c_counter_value()
hooks_async_lazy_loaded_label(&lazy_value)
}
}
div {
"is_pending:"
span {
class: c_counter_value()
hooks_async_lazy_is_pending(&lazy_value)
}
}
}
}
euv_card {
title: "use_suspense"
p {
class: c_render_count_text()
"resolve_sync and fail flip the phase signal — the rendering code branches on the resulting Pending / Resolved / Failed variant."
}
div {
class: c_button_controls()
euv_button {
variant: EuvButtonVariant::Primary
label: "Resolve"
onclick: hooks_async_resolve(suspense, String::from(HOOKS_ASYNC_RESOLVED_VALUE))
}
euv_button {
variant: EuvButtonVariant::Outline
label: "Fail"
onclick: hooks_async_fail(suspense, String::from(HOOKS_ASYNC_FAIL_MESSAGE))
}
euv_button {
variant: EuvButtonVariant::Outline
label: "Reset"
onclick: hooks_async_reset(suspense)
}
}
p {
class: c_render_count_text()
"phase: "
span {
class: c_counter_value()
hooks_async_suspense_phase_label(&suspense)
}
}
}
}
}
}
6 changes: 6 additions & 0 deletions example/src/page/hooks_async/view/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mod r#fn;
mod r#struct;

pub(crate) use {r#fn::*, r#struct::*};

use super::*;
5 changes: 5 additions & 0 deletions example/src/page/hooks_async/view/struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use super::*;

/// Props for the `page_hooks_async` component.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct PageHooksAsyncProps;
51 changes: 51 additions & 0 deletions example/src/page/hooks_i18n/hook/fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use super::*;

/// Default locale tag for the i18n demo.
pub(crate) const HOOKS_I18N_DEFAULT_LOCALE: &str = "en";

/// Secondary locale tag the demo can switch into.
pub(crate) const HOOKS_I18N_OTHER_LOCALE: &str = "zh-CN";

/// English-language button label.
pub(crate) const HOOKS_I18N_LABEL_EN: &str = "English";

/// "Other" (Chinese) language button label.
pub(crate) const HOOKS_I18N_LABEL_OTHER: &str = "中文";

/// Translation key for the greeting message.
pub(crate) const HOOKS_I18N_KEY_GREETING: &str = "greeting";

/// Translation key for the farewell message.
pub(crate) const HOOKS_I18N_KEY_FAREWELL: &str = "farewell";

/// English (`en`) translation table — `&'static` tuples are the
/// only form `i18n_register` can accept without leaking. The
/// underlying `I18n::add_messages` does the
/// `&str -> String` round-trip on the caller's behalf.
pub(crate) const HOOKS_I18N_EN_MESSAGES: [(&str, &str); 2] = [
("greeting", "Hello, world!"),
("farewell", "Goodbye, world!"),
];

/// `zh-CN` translation table.
pub(crate) const HOOKS_I18N_ZH_MESSAGES: [(&str, &str); 2] = [
("greeting", "你好,世界!"),
("farewell", "再见,世界!"),
];

/// Builds a click handler that switches the supplied i18n
/// handle to the supplied locale.
pub(crate) fn hooks_i18n_switch(
handle: I18n,
locale: String,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
handle.change_locale(locale.as_str());
}))
}

/// Returns the translated message for `key` on the supplied
/// handle, falling back to the key itself if missing.
pub(crate) fn hooks_i18n_translate(handle: I18n, key: &'static str) -> String {
handle.t(key)
}
4 changes: 4 additions & 0 deletions example/src/page/hooks_i18n/hook/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod r#fn;
pub(crate) use r#fn::*;

use super::*;
6 changes: 6 additions & 0 deletions example/src/page/hooks_i18n/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mod hook;
mod view;

pub(crate) use {hook::*, view::*};

use super::*;
Loading