From 89ae287feda46b703d29d9df7ac0f008b349e1d3 Mon Sep 17 00:00:00 2001 From: Jeremy Arnold Date: Wed, 14 Feb 2024 13:56:26 -0800 Subject: [PATCH 1/3] add weather_app to examples, update for 0.5 --- Cargo.toml | 4 + examples/weather_app.rs | 259 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 examples/weather_app.rs diff --git a/Cargo.toml b/Cargo.toml index 5fcbca6e8..08c471ec0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -171,3 +171,7 @@ required-features = ["http"] [[example]] name = "suspense" required-features = ["http"] + +[[example]] +name = "weather_app" +required-features = ["http"] diff --git a/examples/weather_app.rs b/examples/weather_app.rs new file mode 100644 index 000000000..9615c8d49 --- /dev/null +++ b/examples/weather_app.rs @@ -0,0 +1,259 @@ +#![allow(non_snake_case)] + +use dioxus::prelude::*; +use serde::{Deserialize, Serialize}; + +fn main() { + launch(app); +} + +fn app() -> Element { + let country = use_signal(|| WeatherLocation { + name: "Berlin".to_string(), + country: "Germany".to_string(), + latitude: 52.5244, + longitude: 13.4105, + id: 2950159, + }); + + let current_weather = + use_resource(move || async move { get_weather(&country.read().clone()).await }); + + rsx! { + link { + rel: "stylesheet", + href: "https://unpkg.com/tailwindcss@^2.0/dist/tailwind.min.css" + } + div { class: "mx-auto p-4 bg-gray-100 h-screen flex justify-center", + div { class: "flex items-center justify-center flex-row", + div { class: "flex items-start justify-center flex-row", + SearchBox { country: country } + div { class: "flex flex-wrap w-full px-2", + div { class: "bg-gray-900 text-white relative min-w-0 break-words rounded-lg overflow-hidden shadow-sm mb-4 w-full bg-white dark:bg-gray-600", + div { class: "px-6 py-6 relative", + if let Some(Ok(weather)) = current_weather.read().as_ref() { + CountryData { + country: country.read().clone(), + weather: weather.clone(), + } + Forecast { + weather: weather.clone(), + } + + } else { + p { + "Loading.." + } + } + } + } + } + } + } + } + } +} + +#[allow(non_snake_case)] +#[component] +fn CountryData(weather: WeatherResponse, country: WeatherLocation) -> Element { + let today = "Today"; + let max_temp = weather.daily.temperature_2m_max.first().unwrap(); + let min_temp = weather.daily.temperature_2m_min.first().unwrap(); + + rsx! { + div { class: "flex mb-4 justify-between items-center", + div { + h5 { class: "mb-0 font-medium text-xl", "{country.name} 🏞️" } + h6 { class: "mb-0", "{today}" } + } + div { + div { class: "flex items-center", + span { "Temp min" } + span { class: "px-2 inline-block", "πŸ‘‰ {min_temp}Β°" } + } + div { class: "flex items-center", + span { "Temp max" } + span { class: "px-2 inline-block ", "πŸ‘‰ {max_temp}ΒΊ" } + } + } + } + } +} + +#[allow(non_snake_case)] +#[component] +fn Forecast(weather: WeatherResponse) -> Element { + let today = (weather.daily.temperature_2m_max.first().unwrap() + + weather.daily.temperature_2m_max.first().unwrap()) + / 2.0; + let tomorrow = (weather.daily.temperature_2m_max.get(1).unwrap() + + weather.daily.temperature_2m_max.get(1).unwrap()) + / 2.0; + let past_tomorrow = (weather.daily.temperature_2m_max.get(2).unwrap() + + weather.daily.temperature_2m_max.get(2).unwrap()) + / 2.0; + rsx! { + div { class: "px-6 pt-4 relative", + div { class: "w-full h-px bg-gray-100 mb-4" } + div { p { class: "text-center w-full mb-4", "πŸ‘‡ Forecast πŸ“†" } } + div { class: "text-center justify-between items-center flex", + div { class: "text-center mb-0 flex items-center justify-center flex-col mx-4 w-16", + span { class: "block my-1", "Today" } + span { class: "block my-1", "{today}Β°" } + } + div { class: "text-center mb-0 flex items-center justify-center flex-col mx-8 w-16", + span { class: "block my-1", "Tomorrow" } + span { class: "block my-1", "{tomorrow}Β°" } + } + div { class: "text-center mb-0 flex items-center justify-center flex-col mx-2 w-30", + span { class: "block my-1", "Past Tomorrow" } + span { class: "block my-1", "{past_tomorrow}Β°" } + } + } + } + } +} + +#[component] +fn SearchBox(mut country: Signal) -> Element { + let mut input = use_signal(|| "".to_string()); + + let locations = use_resource(move || async move { + let current_location = input.read().clone(); + get_locations(¤t_location).await + }); + + rsx! { + div { + div { class: "inline-flex flex-col justify-center relative text-gray-500", + div { class: "relative", + input { + class: "p-2 pl-8 rounded-lg border border-gray-200 bg-gray-200 focus:bg-white focus:outline-none focus:ring-2 focus:ring-yellow-600 focus:border-transparent", + placeholder: "Country name", + "type": "text", + autofocus: true, + oninput: move |e| input.set(e.value()) + } + svg { + class: "w-4 h-4 absolute left-2.5 top-3.5", + "viewBox": "0 0 24 24", + fill: "none", + stroke: "currentColor", + xmlns: "http://www.w3.org/2000/svg", + path { + d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z", + "stroke-linejoin": "round", + "stroke-linecap": "round", + "stroke-width": "2" + } + } + } + ul { class: "bg-white border border-gray-100 w-full mt-2 max-h-72 overflow-auto", + { + if let Some(Ok(locs)) = locations.read().as_ref() { + rsx! { + { + locs.iter().cloned().map(move |wl| { + rsx! { + li { class: "pl-8 pr-2 py-1 border-b-2 border-gray-100 relative cursor-pointer hover:bg-yellow-50 hover:text-gray-900", + onclick: move |_| country.set(wl.clone()), + MapIcon {} + b { + "{wl.name}" + } + " Β· {wl.country}" + } + } + }) + } + } + } else { + rsx! { "loading locations..." } + } + } + } + } + } + } +} + +fn MapIcon() -> Element { + rsx! { + svg { + class: "stroke-current absolute w-4 h-4 left-2 top-2", + stroke: "currentColor", + xmlns: "http://www.w3.org/2000/svg", + "viewBox": "0 0 24 24", + fill: "none", + path { + "stroke-linejoin": "round", + "stroke-width": "2", + "stroke-linecap": "round", + d: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" + } + path { + "stroke-linecap": "round", + "stroke-linejoin": "round", + d: "M15 11a3 3 0 11-6 0 3 3 0 016 0z", + "stroke-width": "2" + } + } + } +} + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)] +struct WeatherLocation { + id: usize, + name: String, + latitude: f32, + longitude: f32, + country: String, +} + +type WeatherLocations = Vec; + +#[derive(Debug, Default, Serialize, Deserialize)] +struct SearchResponse { + results: WeatherLocations, +} + +async fn get_locations(input: &str) -> reqwest::Result { + let res = reqwest::get(&format!( + "https://geocoding-api.open-meteo.com/v1/search?name={input}" + )) + .await? + .json::() + .await?; + + Ok(res.results) +} + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)] +struct WeatherResponse { + daily: DailyWeather, + hourly: HourlyWeather, +} + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)] +struct HourlyWeather { + time: Vec, + temperature_2m: Vec, +} + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)] +struct DailyWeather { + temperature_2m_min: Vec, + temperature_2m_max: Vec, +} + +async fn get_weather(location: &WeatherLocation) -> reqwest::Result { + let res = reqwest::get(&format!("https://api.open-meteo.com/v1/forecast?latitude={}&longitude={}&hourly=temperature_2m&daily=temperature_2m_max,temperature_2m_min,apparent_temperature_max,apparent_temperature_min&timezone=GMT", location.latitude, location.longitude)) + .await + ? + .json::() + .await + ?; + + Ok(res) +} From 6e2b0e5a55bc921e6920bbf877b2753ac1117756 Mon Sep 17 00:00:00 2001 From: Jeremy Arnold Date: Wed, 14 Feb 2024 15:21:32 -0800 Subject: [PATCH 2/3] image_generator_open_ai example project --- Cargo.toml | 4 + examples/image_generator_openai.rs | 150 +++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 examples/image_generator_openai.rs diff --git a/Cargo.toml b/Cargo.toml index 08c471ec0..7a73e0002 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -175,3 +175,7 @@ required-features = ["http"] [[example]] name = "weather_app" required-features = ["http"] + +[[example]] +name = "image_generator_openai" +required-features = ["http"] \ No newline at end of file diff --git a/examples/image_generator_openai.rs b/examples/image_generator_openai.rs new file mode 100644 index 000000000..8b551889c --- /dev/null +++ b/examples/image_generator_openai.rs @@ -0,0 +1,150 @@ +use dioxus::prelude::*; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Error}; + +fn main() { + launch(app) +} + +fn app() -> Element { + let mut api = use_signal(|| "".to_string()); + let mut prompt = use_signal(|| "".to_string()); + let mut n_image = use_signal(|| 1.to_string()); + let mut image = use_signal(|| ImageResponse { + created: 0, + data: Vec::new(), + }); + let mut loading = use_signal(|| "".to_string()); + + let mut generate_images = use_resource(move || async move { + let api_key = api.peek().clone(); + let prompt = prompt.peek().clone(); + let number_of_images = n_image.peek().clone(); + + if (api_key.is_empty() || prompt.is_empty() || number_of_images.is_empty()) { + return; + } + + loading.set("is-loading".to_string()); + let images = request(api_key, prompt, number_of_images).await; + match images { + Ok(imgz) => { + image.set(imgz); + } + Err(e) => { + println!("Error: {:?}", e); + } + } + loading.set("".to_string()); + }); + + rsx! { + head { + link { + rel: "stylesheet", + href: "https://unpkg.com/bulma@0.9.0/css/bulma.min.css", + } + } + div { class: "container", + div { class: "columns", + div { class: "column", + input { class: "input is-primary mt-4", + value:"{api}", + r#type: "text", + placeholder: "API", + oninput: move |evt| { + api.set(evt.value().clone()); + }, + } + + input { class: "input is-primary mt-4", + placeholder: "MAX 1000 Dgts", + r#type: "text", + value:"{prompt}", + oninput: move |evt| { + prompt.set(evt.value().clone()); + }, + } + + input { class: "input is-primary mt-4", + r#type: "number", + min:"1", + max:"10", + value:"{n_image}", + oninput: move |evt| { + n_image.set(evt.value().clone()); + }, + } + } + } + + button { class: "button is-primary {loading}", + onclick: move |_| { + generate_images.restart(); + }, + "Generate image" + } + br { + } + } + {image.read().data.iter().map(|image| { + rsx!( + section { class: "is-flex", + div { class: "container is-fluid", + div { class: "container has-text-centered", + div { class: "is-justify-content-center", + div { class: "level", + div { class: "level-item", + figure { class: "image", + img { + alt: "", + src: "{image.url}", + } + } + } + } + } + } + } + } + ) + }) + } } +} +async fn request(api: String, prompt: String, n_image: String) -> Result { + let client = reqwest::Client::new(); + let body = json!({ + "prompt": prompt, + "n":n_image.parse::().unwrap_or(1), + "size":"1024x1024", + }); + + let mut authorization = "Bearer ".to_string(); + authorization.push_str(&api); + + let res = client + .post("https://api.openai.com/v1/images/generations") + .body(body.to_string()) + .header("Content-Type", "application/json") + .header("Authorization", authorization) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + + let deserialized: ImageResponse = serde_json::from_str(&res)?; + Ok(deserialized) +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Props, Clone)] +struct UrlImage { + url: String, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Props, Clone)] +struct ImageResponse { + created: i32, + data: Vec, +} From b7cc7438eb599db380676e369480a0fc0dbafeba Mon Sep 17 00:00:00 2001 From: Jeremy Arnold Date: Wed, 14 Feb 2024 15:26:52 -0800 Subject: [PATCH 3/3] update readme to remove dead examples --- examples/README.md | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/examples/README.md b/examples/README.md index a20ffe60e..23bfb1ac7 100644 --- a/examples/README.md +++ b/examples/README.md @@ -30,16 +30,10 @@ cargo run --example hello_world ### Props -[borrowed](./borrowed.rs) - Borrowed props - -[inlineprops](./inlineprops.rs) - Demo of `inline_props` macro - [optional_props](./optional_props.rs) - Optional props ### CSS -[all_css](./all_css.rs) - You can specify any CSS attribute - [tailwind](./tailwind/) - You can use a library for styling ## Input Handling @@ -58,12 +52,6 @@ cargo run --example hello_world ### State Management -[fermi](./fermi.rs) - Fermi library for state management - -[pattern_reducer](./pattern_reducer.rs) - The reducer pattern with `use_state` - -[rsx_compile_fail](./rsx_compile_fail.rs) - ### Async [login_form](./login_form.rs) - Login endpoint example @@ -74,22 +62,8 @@ cargo run --example hello_world ### SVG -[svg_basic](./svg_basic.rs) - [svg](./svg.rs) -### Performance - -[framework_benchmark](./framework_benchmark.rs) - Renders a huge list - -> Note: The benchmark should be run in release mode: -> ->```shell -> cargo run --example framework_benchmark --release ->``` - -[heavy_compute](./heavy_compute.rs) - How to deal with expensive operations - ## Server-side rendering [ssr](./ssr.rs) - Rendering RSX server-side @@ -120,8 +94,6 @@ cargo run --example hello_world [calculator](./calculator.rs) - Simple calculator -[pattern_model](./pattern_model.rs) - Simple calculator, but using a custom struct as the model - [crm](./crm.rs) - Toy multi-page customer management app [dog_app](./dog_app.rs) - Accesses dog API