소스 검색

Merge branch 'master' into jk/upgrade-axum-drop-others

Jonathan Kelley 1 년 전
부모
커밋
1ac69ae148
4개의 변경된 파일417개의 추가작업 그리고 28개의 파일을 삭제
  1. 8 0
      Cargo.toml
  2. 0 28
      examples/README.md
  3. 150 0
      examples/image_generator_openai.rs
  4. 259 0
      examples/weather_app.rs

+ 8 - 0
Cargo.toml

@@ -185,3 +185,11 @@ required-features = ["http"]
 [[example]]
 name = "suspense"
 required-features = ["http"]
+
+[[example]]
+name = "weather_app"
+required-features = ["http"]
+
+[[example]]
+name = "image_generator_openai"
+required-features = ["http"]

+ 0 - 28
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

+ 150 - 0
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<ImageResponse, Error> {
+    let client = reqwest::Client::new();
+    let body = json!({
+        "prompt":  prompt,
+        "n":n_image.parse::<i32>().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<UrlImage>,
+}

+ 259 - 0
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<WeatherLocation>) -> Element {
+    let mut input = use_signal(|| "".to_string());
+
+    let locations = use_resource(move || async move {
+        let current_location = input.read().clone();
+        get_locations(&current_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<WeatherLocation>;
+
+#[derive(Debug, Default, Serialize, Deserialize)]
+struct SearchResponse {
+    results: WeatherLocations,
+}
+
+async fn get_locations(input: &str) -> reqwest::Result<WeatherLocations> {
+    let res = reqwest::get(&format!(
+        "https://geocoding-api.open-meteo.com/v1/search?name={input}"
+    ))
+    .await?
+    .json::<SearchResponse>()
+    .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<String>,
+    temperature_2m: Vec<f32>,
+}
+
+#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone)]
+struct DailyWeather {
+    temperature_2m_min: Vec<f32>,
+    temperature_2m_max: Vec<f32>,
+}
+
+async fn get_weather(location: &WeatherLocation) -> reqwest::Result<WeatherResponse> {
+    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::<WeatherResponse>()
+        .await
+        ?;
+
+    Ok(res)
+}