Compare commits

...

2 Commits

Author SHA1 Message Date
2baa5a930a ... 2025-08-08 08:20:13 +02:00
bb529b9973 ... 2025-08-08 08:14:18 +02:00
12 changed files with 1205 additions and 1 deletions

2
.gitignore vendored
View File

@ -14,3 +14,5 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
dist/

18
Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "web1"
version = "0.1.0"
edition = "2021"
description = "Template for starting a Yew project using Trunk"
readme = "README.md"
repository = "https://github.com/yewstack/yew-trunk-minimal-template"
license = "MIT OR Apache-2.0"
keywords = ["yew", "trunk"]
categories = ["gui", "wasm", "web-programming"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
yew = { version="0.21", features=["csr"] }
web-sys = { version = "0.3", features = ["Document", "HtmlElement", "Window"] }
gloo-utils = "0.1"
gloo-storage = "0.2"
serde = { version = "1.0", features = ["derive"] }

177
LICENSE-APACHE Normal file
View File

@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

25
LICENSE-MIT Normal file
View File

@ -0,0 +1,25 @@
Copyright (c) despiegk <kristof@incubaid.com>
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@ -1,2 +1,75 @@
# webtest
# Yew Trunk Template
This is a fairly minimal template for a Yew app that's built with [Trunk].
## Usage
For a more thorough explanation of Trunk and its features, please head over to the [repository][trunk].
### Installation
If you don't already have it installed, it's time to install Rust: <https://www.rust-lang.org/tools/install>.
The rest of this guide assumes a typical Rust installation which contains both `rustup` and Cargo.
To compile Rust to WASM, we need to have the `wasm32-unknown-unknown` target installed.
If you don't already have it, install it with the following command:
```bash
rustup target add wasm32-unknown-unknown
```
Now that we have our basics covered, it's time to install the star of the show: [Trunk].
Simply run the following command to install it:
```bash
cargo install trunk wasm-bindgen-cli
```
That's it, we're done!
### Running
```bash
trunk serve
```
Rebuilds the app whenever a change is detected and runs a local server to host it.
There's also the `trunk watch` command which does the same thing but without hosting it.
### Release
```bash
trunk build --release
```
This builds the app in release mode similar to `cargo build --release`.
You can also pass the `--release` flag to `trunk serve` if you need to get every last drop of performance.
Unless overwritten, the output will be located in the `dist` directory.
## Using this template
There are a few things you have to adjust when adopting this template.
### Remove example code
The code in [src/main.rs](src/main.rs) specific to the example is limited to only the `view` method.
There is, however, a fair bit of Sass in [index.scss](index.scss) you can remove.
### Update metadata
Update the `version`, `description` and `repository` fields in the [Cargo.toml](Cargo.toml) file.
The [index.html](index.html) file also contains a `<title>` tag that needs updating.
Finally, you should update this very `README` file to be about your app.
### License
The template ships with both the Apache and MIT license.
If you don't want to have your app dual licensed, just remove one (or both) of the files and update the `license` field in `Cargo.toml`.
There are two empty spaces in the MIT license you need to fill out: `` and `despiegk <kristof@incubaid.com>`.
[trunk]: https://github.com/thedodd/trunk

452
ai_instructions.md Normal file
View File

@ -0,0 +1,452 @@
# Yew Framework Documentation for AI Coders: Core Concepts & Coding
This document provides a comprehensive overview of the Yew framework's core coding concepts, designed to equip AI robot coders with the knowledge necessary to build web applications using Rust and WebAssembly.
Yew is a modern Rust framework for building client-side web applications using WebAssembly (Wasm). It enables the development of highly performant web UIs by leveraging Rust's strong type system and rich ecosystem. Yew promotes a component-based architecture for building reusable and maintainable UI elements.
## Fundamental Building Blocks
### The `html!` Macro for UI Composition
Yew employs the `html!` procedural macro for declarative UI construction, drawing inspiration from JSX. This macro is the primary way to define the structure of your component's output.
**Syntax and Features:**
* **Single Root Node:** The `html!` macro always expects a single root HTML node. To render multiple top-level elements without a wrapping container, use the fragment syntax: `<> ... </>`.
* **Embedding Rust Expressions:** Any valid Rust expression can be embedded within the markup using curly braces (`{ expression }`). The expression **must evaluate to a type that implements `Into<Html>`**.
```rust
let header_text = "Welcome to Yew".to_string();
let count = 5;
html! {
<>
<h1>{ header_text }</h1>
<p>{"Current count: "}{ count }</p>
</>
}
```
* **Literals:** String literals are typically enclosed in quotes and then a `{}`, e.g., `{"Hello"}`. They are treated as `Text` nodes, inherently mitigating common HTML injection (XSS) risks.
* **Element / Component Definition:**
* **HTML Elements:** Standard HTML elements are written as `<tagname property="value">child</tagname>` or self-closing `<tagname property="value" />`.
* **Dynamic Tag Names:** For situations where the HTML tag name is determined at runtime, use the `@{expression}` syntax. The expression must be a string.
```rust
let level = 3;
html! { <@{format!("h{}", level)} class="subtitle">{"Dynamic Heading"}</@> }
```
* **Yew Components:** Yew components are instantiated like custom HTML tags, using PascalCase for their names: `<MyComponent property={value} />`.
* **Attributes and Properties:**
* **HTML Attributes:** Set on elements directly: `<div attribute={rust_value} />`.
* **Boolean Attributes:** Set with `true` or `false`. `false` is equivalent to omitting the attribute entirely.
```rust
html! { <input type="checkbox" checked={some_boolean_var} /> }
```
* **String-like Attributes:** Can accept `&str`, `String`, or Yew's optimized `AttrValue` (a cheaply cloneable `Rc<str>` or `&'static str`). `AttrValue` is generally recommended for performance-sensitive scenarios, especially when passing values as properties to other components.
* **Optional Attributes:** Use `Option<T>` for attribute values. If the `Option` is `None`, the attribute will not be rendered in the DOM.
```rust
let maybe_id: Option<&str> = Some("unique-element");
html! { <div id={maybe_id}></div> } // Renders with id or not
```
* **Yew-Specific Properties (Special Props):** These are not directly reflected in the DOM but serve as instructions to Yew's Virtual DOM.
* `ref={node_ref_handle}`: Connects a `NodeRef` to a DOM element, allowing direct programmatic access to the underlying DOM node (e.g., for canvas manipulation, scrolling, form input values).
* `key={unique_key}`: Provides a unique identifier for elements within a list. Crucial for performance optimization, as Yew uses keys to efficiently reconcile list items during updates, preventing unnecessary re-renders or DOM manipulations. Keys must be unique *within their immediate siblings* (the list itself) and should be stable/deterministic, not based on item position.
* **Conditional Rendering:** Use standard Rust `if` and `if let` control flow structures directly within the `html!` macro to conditionally render content.
```rust
let show_message = true;
html! {
if show_message {
<p>{"This message is visible."}</p>
} else {
<p>{"Message hidden."}</p>
}
}
```
* **List Rendering / Iteration:**
* Use `for` syntax directly in `html!`: `{ for collection.iter() }`. This expects the iterator items to be renderable `Html`.
* Alternatively, map the collection to `Html` elements and use `.collect::<Html>()`.
* **Always use `key` for list items** where the order or presence of items can change, as this drastically improves reconciliation performance.
```rust
let items = vec!["Apple", "Banana", "Cherry"];
html! {
<ul>
{ for items.iter().map(|item| html! { <li key={item.to_string()}>{item}</li> }) }
</ul>
}
```
### Components: Function Components
Yew applications are built from components, which encapsulate UI logic and presentation. Function components are the recommended and most common way to define components in modern Yew.
* **Definition:** Declare a function component using the `#[function_component]` attribute on a `fn` that returns `Html`. By convention, component names are PascalCase.
```rust
use yew::prelude::*;
#[function_component]
fn MySimpleComponent() -> Html {
html! { <p>{"Hello from a component!"}</p> }
}
```
* **Properties (Props):** Data is passed from parent to child components using "props". Props are defined by a struct that must implement `Properties` (usually via `#[derive(Properties)]`) and `PartialEq`. The function component accepts an `&Props` reference as its single argument.
```rust
#[derive(Properties, PartialEq)]
pub struct GreetProps {
pub name: String,
#[prop_or_default] // Field attribute for optional props with default value
pub greeting_text: String,
}
#[function_component]
fn Greeter(props: &GreetProps) -> Html {
let greeting = if props.greeting_text.is_empty() {
"Hello".to_string()
} else {
props.greeting_text.clone()
};
html! { <p>{ format!("{}, {}!", greeting, props.name) }</p> }
}
// Usage: html! { <Greeter name="Alice" /> } or html! { <Greeter name="Bob" greeting_text="Hi" /> }
```
* **Reactive Nature of Props:** Yew automatically re-renders a component when its props change (detected via `PartialEq`).
* **`props!` Macro:** Allows building `Properties` structs programmatically.
```rust
use yew::props;
let my_props = props! { GreetProps { name: "Charlie".to_string() } };
html! { <Greeter ..my_props /> }
```
* **Children (Special Prop):** If a component's `Props` struct includes a `pub children: Html` field, it can accept nested `html!` content.
```rust
#[derive(Properties, PartialEq)]
pub struct CardProps {
pub title: String,
pub children: Html, // This field name is special
}
#[function_component]
fn Card(props: &CardProps) -> Html {
html! {
<div class="card">
<h2>{ &props.title }</h2>
<div class="card-content">
{ props.children.clone() }
</div>
</div>
}
}
// Usage: html! { <Card title="My Card"> <p>Some content here.</p> </Card> }
```
* **Generic Components:** Function components can be generic over types, provided the generic type parameters meet the necessary trait bounds (e.g., `PartialEq`).
```rust
#[derive(Properties, PartialEq)]
pub struct ItemDisplayProps<T: PartialEq + ToHtml> {
pub item: T,
}
#[function_component]
pub fn ItemDisplay<T>(props: &ItemDisplayProps<T>) -> Html
where
T: PartialEq + ToHtml + 'static, // 'static for use in VDOM
{
html! { <p>{ &props.item }</p> }
}
// Usage: html! { <ItemDisplay<i32> item=123 /> }
```
* **Pure Components:** A function component is "pure" if its output `Html` is solely determined by its props, and it has no side effects or internal mutable state. Yew's reconciliation benefits from pure components.
* Simple pure components with no hooks can sometimes be implemented as regular functions returning `Html` to reduce overhead.
* **Communication Patterns:**
* **Parent to Child:** Via [Props](#properties-props).
* **Child to Parent:** Via [Callbacks](#callbacks). The parent passes a `Callback` to the child via props, and the child calls `emit()` on it to send data back up.
## State Management and Interactivity
Yew provides "hooks" to manage mutable state and side effects within function components.
### Hooks
Hooks are functions that allow "hooking into" the lifecycle and state management capabilities of function components.
**Rules of Hooks:**
1. **Naming Convention:** Hook function names **must start with `use_`**.
2. **Top-Level Calls Only:** Hooks can **only be called at the top level of a function component or another hook**, and not inside loops, conditionals without `if let`, or nested functions *unless* within the scrutinee of a top-level `if` or `match` expression.
3. **Consistent Call Order:** Hooks must be called in the exact same order on every render. This enables Yew to correctly associate state with hook calls.
4. **No Early Return:** A component using hooks cannot `return` early before all hooks are called unless using [Suspense](#suspense).
**Common Pre-defined Hooks:**
* **`use_state<T>() -> UseStateHandle<T>`:** Manages local component state. Returns a handle that can be dereferenced to get the current value and has a `.set(new_value)` method to update the state. Updating state triggers a re-render.
```rust
use yew::prelude::*;
#[function_component]
fn ClickCounter() -> Html {
let count = use_state(|| 0); // Initializes with 0
let onclick = {
let count = count.clone(); // Clone the handle for the closure
move |_| {
count.set(*count + 1); // Update the state
}
};
html! { <button {onclick}>{"Clicked "}{*count}{" times"}</button> }
}
```
* **`use_state_eq<T: PartialEq>() -> UseStateHandle<T>`:** Similar to `use_state`, but only triggers a re-render if the new value is *not* equal to the current value (using `PartialEq`).
* **`use_memo<T, D>(f: impl FnOnce(D) -> T, deps: D) -> Rc<T>`:** Memoizes an expensive computation. The `f` closure is only re-executed if `deps` (dependencies) change.
* **`use_callback<IN, OUT, F>(f: F) -> Callback<IN, OUT>`:** Memoizes a `Callback` instance. The `Callback` is only recreated if its dependencies (captured variables) change.
* **`use_mut_ref<T>() -> Rc<RefCell<T>>`:** Provides a mutable reference (`RefCell`) that persists across re-renders without triggering them. Useful for mutable data that doesn't directly affect rendering or for interop with mutable JS APIs.
* **`use_node_ref() -> NodeRef`:** Creates a `NodeRef` handle, used to get a direct reference to a rendered DOM element.
```rust
use yew::prelude::*;
use web_sys::HtmlInputElement;
#[function_component]
fn MyInput() -> Html {
let input_ref = use_node_ref();
let current_value = use_state(|| String::new());
let on_input_change = {
let input_ref = input_ref.clone();
let current_value = current_value.clone();
move |_| {
if let Some(input) = input_ref.cast::<HtmlInputElement>() {
current_value.set(input.value());
}
}
};
html! {
<div>
<input ref={input_ref} type="text" oninput={on_input_change} />
<p>{"Input Value: "}{&*current_value}</p>
</div>
}
}
```
* **`use_reducer<R>() -> UseReducerHandle<R>`:** Manages more complex state using a reducer pattern (similar to Redux), allowing state updates based on dispatched "actions". The state must implement the `Reducible` trait.
* **`use_reducer_eq<R: PartialEq>() -> UseReducerHandle<R>`:** Similar to `use_reducer`, but dispatches only if the new state differs from the old.
* **`use_effect(f: impl FnOnce() -> impl FnOnce())`:** Runs a side effect after every render. Can return a cleanup closure that runs before the next effect or when the component is unmounted.
* **`use_effect_with<D>(deps: D, f: impl FnOnce(D) -> impl FnOnce())`:** Runs a side effect only when `deps` (dependencies) change.
* **`use_context<T: Clone + PartialEq>() -> Option<Rc<T>>`:** Accesses a context value provided by an ancestor `ContextProvider` component.
* **`use_force_update()`:** Returns a callback that, when emitted, forces a re-render of the component. Use sparingly.
**Custom Hooks:** Components can extract reusable stateful logic into custom hooks by defining functions starting with `use_` and marking them with `#[hook]`. Custom hooks compose existing hooks.
### Callbacks
`Callback` is a crucial type for event handling and child-to-parent communication. It wraps an `Fn` closure in an `Rc`, making it cheaply clonable.
* **`Callback::from(closure)`:** Creates a `Callback` from a closure.
* **`callback.emit(value)`:** Invokes the wrapped closure with the given value.
* **DOM Events:** Event handlers in `html!` (e.g., `onclick`, `oninput`) expect a `Callback` that takes the corresponding `web_sys` event type as an argument.
```rust
use yew::prelude::*;
use web_sys::MouseEvent;
#[function_component]
fn MyButton() -> Html {
let onclick_handler = Callback::from(move |e: MouseEvent| {
// Access event properties:
log::info!("Click event at: ({}, {})", e.client_x(), e.client_y());
// More complex logic...
});
html! { <button onclick={onclick_handler}>{"Click Me"}</button> }
}
```
* **`TargetCast` Trait:** Provided by Yew (within `yew::prelude::*`), this trait extends `web_sys::Event` to safely cast the event target to a specific HTML element type (e.g., `HtmlInputElement`).
* `event.target_dyn_into::<HtmlElementType>() -> Option<HtmlElementType>` (safe, checked)
* `event.target_unchecked_into::<HtmlElementType>() -> HtmlElementType` (unchecked, use with caution when type is guaranteed)
### Contexts
Contexts provide a way to pass data deeply through the component tree without manually "prop drilling" at every level.
* **Provider (`ContextProvider`):** An ancestor component wraps its children with `ContextProvider<T>`, providing a value of type `T`. `T` must implement `Clone` and `PartialEq`.
```rust
// Define your context data
#[derive(Clone, Debug, PartialEq)]
struct Theme {
foreground: String,
background: String,
}
#[function_component]
fn ThemeProvider(props: &ChildrenProps) -> Html { // ChildrenProps from yew::html
let theme = use_state(|| Theme {
foreground: "#000".to_string(),
background: "#eee".to_string(),
});
html! {
<yew::ContextProvider<Theme> context={(*theme).clone()}>
{ props.children.clone() }
</yew::ContextProvider<Theme>>
}
}
// Usage: html! { <ThemeProvider> <ArbitraryDeepComponent /> </ThemeProvider> }
```
* **Consumer (`use_context` hook):** Descendant function components use `use_context::<T>()` to retrieve the provided value.
```rust
#[function_component]
fn ThemedText() -> Html {
let theme = use_context::<Theme>() // Retrieve the Theme context
.expect("Theme context not provided!");
html! {
<p style={format!("color: {}; background: {};", theme.foreground, theme.background)}>
{"This text is themed."}
</p>
}
}
```
* **Mutable Contexts:** To allow children to modify a context value, combine `ContextProvider` with `use_reducer` for a predictable state update mechanism.
### Event Handling and Delegation
Yew integrates with `web-sys` for DOM events. Yew's event system employs event delegation: listeners are not directly attached to individual elements but are handled by a single delegate at the application's root. Events then "bubble up" through Yew's Virtual DOM hierarchy.
* **Event Listener Names:** In `html!`, event listeners start with `on` followed by the event name (e.g., `onclick`, `oninput`).
* **Manual Event Listeners:** For events not directly supported by `html!` or for fine-grained control, use `use_effect_with` and `gloo-events` (`EventListener`) to manually attach/detach event listeners to `NodeRef`-obtained DOM elements.
```rust
use gloo::events::EventListener;
use yew::prelude::*;
use web_sys::HtmlElement;
#[function_component]
fn CustomEventHandler() -> Html {
let div_ref = use_node_ref();
use_effect_with(div_ref.clone(), {
let div_ref_clone = div_ref.clone();
move |_| {
let mut listener_obj: Option<EventListener> = None;
if let Some(div_element) = div_ref_clone.cast::<HtmlElement>() {
let listener = EventListener::new(&div_element, "custom-event", move |event| {
log::info!("Custom event received!");
// event.dyn_into::<web_sys::CustomEvent>() for richer data
});
listener_obj = Some(listener);
}
move || drop(listener_obj) // Cleanup when effect re-runs or component unmounts
}
});
html! { <div ref={div_ref}>{"Div with custom event listener"}</div> }
}
```
## Advanced Topics
### Interacting with JavaScript (JS) and Web APIs
Yew compiles to Wasm, but direct interaction with browser APIs often involves `wasm-bindgen` and related crates.
* **`wasm-bindgen`:** Bridges calls between Rust and JavaScript. Used for defining FFI (Foreign Function Interface) to import/export functions.
* **`web-sys`:** Provides Rust bindings for all Web APIs (DOM, Fetch, etc.). This is the primary way to interact with the browser from Rust.
* **Features:** `web-sys` is heavily feature-gated; enable only the necessary features in `Cargo.toml` to avoid bloat (e.g., `features = ["Document", "HtmlElement", "Window"]`).
* **Inheritance:** `web-sys` types simulate JavaScript inheritance using Rust's `Deref` and `AsRef` traits. For instance, an `HtmlElement` can deref to `Element`, then `Node`, then `EventTarget`, finally `JsValue`. This allows calling methods from ancestor types.
* **`JsCast` Trait:** Crucial for downcasting `JsValue` or generic `EventTarget` (from `web_sys`) to specific, more concrete types (e.g., `HtmlInputElement`). Provides `dyn_into` (checked, returns `Result`) and `unchecked_into` (unchecked, faster).
* **`js-sys`:** Provides Rust bindings for JavaScript's standard, built-in objects (e.g., `Date`, `Object`).
* **`wasm-bindgen-futures`:** Bridges Rust `Future`s with JavaScript `Promise`s.
* **`spawn_local(future)`:** Spawns a Rust `Future` to run on the current thread's event loop, enabling asynchronous operations (e.g., `async fetch()` calls).
### Asynchronous Operations and Suspense
Yew's `Suspense` component allows suspending component rendering while waiting for an asynchronous task (e.g., data fetching) to complete, showing a fallback UI in the interim. This enables a "Render-as-You-Fetch" pattern.
* **`Suspense` Component:** Wraps children components that might "suspend". Requires a `fallback` prop, which is the `Html` to render during suspension.
* **Suspending Hooks:** A hook can signal suspension by returning `Err(Suspension)`.
* **`Suspension::new()`:** Creates a `Suspension` and a `SuspensionHandle`. When `handle.resume()` is called (or `handle` is dropped), the suspended component re-renders.
```rust
use yew::prelude::*;
use yew::suspense::{Suspension, SuspensionResult};
// A hypothetical async data loading function
async fn fetch_user_data() -> String { "AI Robot Coder".to_string() }
// This hook will suspend rendering until user data is fetched
#[hook]
fn use_current_user() -> SuspensionResult<String> {
let (suspension, handle) = Suspension::new(); // Create suspension
let user_data_state = use_state(|| None ); // Local state for fetched data
// If data is already there, return Ok.
if let Some(data) = &*user_data_state {
return Ok(data.clone());
}
// If not, spawn an async task and signal suspension.
let user_data_state_clone = user_data_state.clone();
wasm_bindgen_futures::spawn_local(async move {
let data = fetch_user_data().await;
user_data_state_clone.set(Some(data)); // Update state
handle.resume(); // Signal the suspension to resume
});
Err(suspension) // Signal that the component should suspend
}
#[function_component]
fn UserDisplay() -> HtmlResult { // HtmlResult instead of Html for suspending components
let user_name = use_current_user()?; // The '?' operator propagates the suspension
Ok(html! { <p>{ format!("Hello, {}!", user_name) }</p> })
}
#[function_component]
fn App() -> Html {
html! {
<Suspense fallback={html!{<p>{"Loading user..."}</p>}}>
<UserDisplay />
</Suspense>
}
}
```
### Routing (`yew-router` crate)
`yew-router` provides client-side routing for Single Page Applications (SPAs).
* **`Routable` Enum:** Define routes as an enum deriving `Routable`. Each variant maps to a URL path using `#[at("/path")]`. `#[not_found]` designates the fallback route.
* **Path Segments:** Define dynamic segments with `:segment_name` (for single segments) or `*wildcard` (for multi-segment wildcards). These become fields in the enum variant.
* **`BrowserRouter`:** The main router component that manages browser history. All `<Switch />` and `<Link />` components must be descendants of `BrowserRouter`.
* **`Switch` Component:** Takes an `Routable` enum and a `render` function. The `render` function receives the matched route variant and returns the `Html` to display.
* **`Link` Component:** A component that renders an `<a>` tag but performs client-side navigation (`pushState`) instead of a full page reload.
* **Navigation API (`use_navigator()`):** Provides programmatic navigation via `navigator.push(&Route)` or `navigator.replace(&Route)`.
* **Query Parameters:** Can be included in navigation by passing a serializable struct/map to `push_with_query()` or retrieved from `location.query()`.
* **Nested Routers:** Allows for modular routing within sub-sections of the application, often using a `*` wildcard in the parent router to delegate to a child router.
* **Basename:** Configure a common prefix for all routes, useful when the application is served from a sub-path.
### Web Workers and Agents
Yew `Agents` are a mechanism for offloading tasks to Web Workers, enabling concurrent processing and preventing UI unresponsiveness.
* **Types of Agents:**
* `Public`: A single instance shared across all bridges in a web worker.
* `Private`: A new instance spawned for each bridge connection.
* **Communication:**
* `Bridges`: Bi-directional communication between a component and an agent, or between agents.
* `Dispatchers`: Uni-directional communication from a component to an agent.
* **Overhead:** Agents introduce serialization/deserialization overhead for messages between threads (using `bincode`), making them suitable for chunky computations rather than frequent, small messages.
## Deployment Considerations
* **Release Build:** Use `trunk build --release` for optimized, production-ready builds.
* **Server Configuration:**
* **SPA Fallback:** Configure the HTTP server to serve `index.html` as a fallback for any unmatched URL paths, allowing the `yew-router` to handle client-side routing.
* **MIME-type:** Ensure `.wasm` files are served with the `application/wasm` MIME-type.
* **Relative Paths:** Use `<base data-trunk-public-url />` in `index.html` and `trunk build --public-url /your/path/` to deploy the app under a sub-path.
* **Environment Variables:** Use `std::env!("VAR_NAME")` to embed environment variables at **compile time** (runtime access in browser is not direct).
## Debugging and Testing
### Debugging
* **Panics:** Yew automatically logs Rust panics to the browser's developer console.
* **Console Logging:**
* `wasm-logger`: Integrates Rust's `log` crate with the browser console.
* `gloo-console`: Provides `log!` macro for direct `JsValue` logging to console.
* `tracing-web`: Integrates `tracing` framework with browser console and performance API.
* **Component Lifecycles:** Use `tracing` to gain insights into component re-renders and hook execution.
* **Source Maps:** Limited support available; requires specific configuration.
### Testing
* **`wasm_bindgen_test`:** Enables running Rust tests directly in a browser environment.
* **Snapshot Testing:** Yew provides utilities (`yew::tests::layout_tests`) for snapshot testing component output.
* **Shallow Rendering:** Work in progress for testing components in isolation without rendering their full subtree.
---

0
env.sh Normal file
View File

22
index.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Yew Bootstrap App - Modern Rust WebAssembly</title>
<meta name="description" content="A modern web application built with Yew, Rust, and Bootstrap 5" />
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.css">
<!-- Custom SCSS -->
<link data-trunk rel="sass" href="index.scss" />
</head>
<body>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</body>
</html>

254
index.scss Normal file
View File

@ -0,0 +1,254 @@
// CSS Custom Properties for theming
:root {
--bs-body-bg: #ffffff;
--bs-body-color: #212529;
--bs-navbar-bg: #f8f9fa;
--bs-navbar-color: rgba(0, 0, 0, 0.65);
--bs-navbar-active-color: rgba(0, 0, 0, 0.9);
--bs-navbar-brand-color: #0d6efd;
--bs-hero-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--bs-hero-color: #ffffff;
--bs-card-bg: #ffffff;
--bs-card-border: rgba(0, 0, 0, 0.125);
--bs-feature-bg: #f8f9fa;
--bs-footer-bg: #e9ecef;
--bs-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--bs-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
}
// Dark theme variables
.dark-theme {
--bs-body-bg: #0d1117;
--bs-body-color: #f0f6fc;
--bs-navbar-bg: #161b22;
--bs-navbar-color: rgba(240, 246, 252, 0.65);
--bs-navbar-active-color: rgba(240, 246, 252, 0.9);
--bs-navbar-brand-color: #58a6ff;
--bs-hero-bg: linear-gradient(135deg, #1f2937 0%, #374151 100%);
--bs-hero-color: #f0f6fc;
--bs-card-bg: #21262d;
--bs-card-border: rgba(240, 246, 252, 0.125);
--bs-feature-bg: #161b22;
--bs-footer-bg: #0d1117;
--bs-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.3);
--bs-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.5);
}
// Global styles
* {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
body {
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
}
// Navbar styling
.navbar {
background-color: var(--bs-navbar-bg) !important;
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--bs-card-border);
box-shadow: var(--bs-shadow);
.navbar-brand {
color: var(--bs-navbar-brand-color) !important;
font-weight: 700;
font-size: 1.5rem;
&:hover {
transform: scale(1.05);
transition: transform 0.2s ease;
}
}
.navbar-nav .nav-link {
color: var(--bs-navbar-color) !important;
font-weight: 500;
padding: 0.5rem 1rem !important;
border-radius: 0.375rem;
margin: 0 0.25rem;
&:hover {
background-color: rgba(var(--bs-primary-rgb), 0.1);
color: var(--bs-navbar-active-color) !important;
}
&.active {
background-color: rgba(var(--bs-primary-rgb), 0.15);
color: var(--bs-navbar-active-color) !important;
}
}
}
// Hero section
.hero-section {
background: var(--bs-hero-bg);
color: var(--bs-hero-color);
min-height: 60vh;
display: flex;
align-items: center;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="50" cy="50" r="1" fill="white" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
opacity: 0.3;
}
.container {
position: relative;
z-index: 1;
}
h1 {
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
margin-bottom: 1.5rem;
}
.lead {
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
opacity: 0.95;
}
.btn {
box-shadow: var(--bs-shadow-lg);
border: none;
font-weight: 600;
&:hover {
transform: translateY(-2px);
box-shadow: 0 1.5rem 4rem rgba(0, 0, 0, 0.2);
}
}
}
// Card styling
.card {
background-color: var(--bs-card-bg);
border: 1px solid var(--bs-card-border);
box-shadow: var(--bs-shadow);
transition: all 0.3s ease;
&:hover {
transform: translateY(-5px);
box-shadow: var(--bs-shadow-lg);
}
.feature-icon {
transition: transform 0.3s ease;
}
&:hover .feature-icon {
transform: scale(1.1) rotate(5deg);
}
}
// Theme toggle styling
.form-check-input {
&:checked {
background-color: #0d6efd;
border-color: #0d6efd;
}
}
// Background sections
.bg-body-secondary {
background-color: var(--bs-feature-bg) !important;
}
.bg-body-tertiary {
background-color: var(--bs-footer-bg) !important;
}
// Animations
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeInUp 0.6s ease forwards;
&:nth-child(1) { animation-delay: 0.1s; }
&:nth-child(2) { animation-delay: 0.2s; }
&:nth-child(3) { animation-delay: 0.3s; }
}
// Responsive improvements
@media (max-width: 768px) {
.hero-section {
min-height: 50vh;
text-align: center;
h1 {
font-size: 2.5rem;
}
.lead {
font-size: 1.1rem;
}
}
.navbar-brand {
font-size: 1.25rem !important;
}
.feature-icon {
width: 3rem !important;
height: 3rem !important;
}
}
// Custom scrollbar for webkit browsers
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--bs-body-bg);
}
::-webkit-scrollbar-thumb {
background: rgba(var(--bs-primary-rgb), 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(var(--bs-primary-rgb), 0.5);
}
// Focus styles for accessibility
.btn:focus,
.form-check-input:focus,
.nav-link:focus {
box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25);
}
// Print styles
@media print {
.navbar,
.form-check,
footer {
display: none !important;
}
.hero-section {
background: none !important;
color: black !important;
}
}

169
src/app.rs Normal file
View File

@ -0,0 +1,169 @@
use yew::prelude::*;
use gloo_utils::document;
use gloo_storage::{LocalStorage, Storage};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
enum Theme {
Light,
Dark,
}
impl Theme {
fn as_str(&self) -> &'static str {
match self {
Theme::Light => "light-theme",
Theme::Dark => "dark-theme",
}
}
}
#[function_component(App)]
pub fn app() -> Html {
let theme = use_state(|| {
LocalStorage::get("theme").unwrap_or(Theme::Light)
});
let onclick_theme_toggle = {
let theme = theme.clone();
Callback::from(move |_| {
let new_theme = if *theme == Theme::Light { Theme::Dark } else { Theme::Light };
theme.set(new_theme);
})
};
use_effect_with(theme.clone(), move |theme| {
let body = document().body().expect("body element not found");
body.set_class_name(theme.as_str());
LocalStorage::set("theme", **theme).unwrap();
});
let navbar_class = if *theme == Theme::Dark { "navbar navbar-expand-lg navbar-dark" } else { "navbar navbar-expand-lg navbar-light" };
html! {
<>
<nav class={navbar_class}>
<div class="container-fluid">
<a class="navbar-brand fw-bold" href="#">{"🦀 Yew Bootstrap App"}</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">
<i class="bi bi-house-fill me-1"></i>{"Home"}
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<i class="bi bi-star-fill me-1"></i>{"Features"}
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<i class="bi bi-currency-dollar me-1"></i>{"Pricing"}
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<i class="bi bi-envelope-fill me-1"></i>{"Contact"}
</a>
</li>
</ul>
<div class="form-check form-switch d-flex align-items-center">
<i class="bi bi-sun-fill me-2 text-warning"></i>
<input class="form-check-input me-2" type="checkbox" id="flexSwitchCheckDefault" onclick={onclick_theme_toggle} checked={*theme == Theme::Dark} />
<i class="bi bi-moon-stars-fill text-info"></i>
</div>
</div>
</div>
</nav>
<div class="hero-section jumbotron">
<div class="container py-5 text-center">
<div class="row justify-content-center">
<div class="col-lg-8">
<h1 class="display-4 fw-bold mb-4">{"🚀 Welcome to Yew Bootstrap!"}</h1>
<p class="lead fs-5 mb-4">{"Experience the power of Rust and WebAssembly with this modern Yew application featuring Bootstrap 5 integration, responsive design, and seamless dark mode switching."}</p>
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
<button class="btn btn-primary btn-lg px-4 me-md-2" type="button">
<i class="bi bi-rocket-takeoff me-2"></i>{"Get Started"}
</button>
<button class="btn btn-outline-secondary btn-lg px-4" type="button">
<i class="bi bi-github me-2"></i>{"View Source"}
</button>
</div>
</div>
</div>
</div>
</div>
<div class="container my-5">
<div class="row g-4">
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body text-center">
<div class="feature-icon bg-primary bg-gradient text-white rounded-3 mb-3 mx-auto" style="width: 4rem; height: 4rem; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-lightning-charge fs-2"></i>
</div>
<h3 class="card-title">{"⚡ Fast Performance"}</h3>
<p class="card-text">{"Built with Rust and WebAssembly for blazing fast performance. Experience near-native speed in your web applications."}</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body text-center">
<div class="feature-icon bg-success bg-gradient text-white rounded-3 mb-3 mx-auto" style="width: 4rem; height: 4rem; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-shield-check fs-2"></i>
</div>
<h3 class="card-title">{"🛡️ Type Safety"}</h3>
<p class="card-text">{"Rust's powerful type system ensures memory safety and prevents common programming errors at compile time."}</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 shadow-sm">
<div class="card-body text-center">
<div class="feature-icon bg-info bg-gradient text-white rounded-3 mb-3 mx-auto" style="width: 4rem; height: 4rem; display: flex; align-items: center; justify-content: center;">
<i class="bi bi-phone fs-2"></i>
</div>
<h3 class="card-title">{"📱 Responsive Design"}</h3>
<p class="card-text">{"Fully responsive design that works perfectly on desktop, tablet, and mobile devices with Bootstrap 5."}</p>
</div>
</div>
</div>
</div>
</div>
<div class="bg-body-secondary py-5">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6">
<h2 class="display-6 fw-bold mb-3">{"🎨 Modern UI Components"}</h2>
<p class="lead">{"This application showcases modern UI patterns with smooth theme transitions, interactive components, and beautiful typography."}</p>
<ul class="list-unstyled">
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Dark/Light theme switching"}</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Responsive navigation"}</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Bootstrap 5 integration"}</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i>{"Custom CSS properties"}</li>
</ul>
</div>
<div class="col-lg-6 text-center">
<div class="p-4">
<i class="bi bi-palette2 display-1 text-primary"></i>
</div>
</div>
</div>
</div>
</div>
<footer class="bg-body-tertiary py-4 mt-5">
<div class="container text-center">
<p class="mb-0">{"Built with ❤️ using Yew, Rust, and Bootstrap 5"}</p>
</div>
</footer>
</>
}
}

6
src/main.rs Normal file
View File

@ -0,0 +1,6 @@
mod app;
use app::App;
fn main() {
yew::Renderer::<App>::new().render();
}

6
start.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
set -ex
cd "$(dirname "$0")"
trunk serve --open -p 9999