diff --git a/smart-house-web/Cargo.toml b/smart-house-web/Cargo.toml index dfc365c..7d13ab9 100644 --- a/smart-house-web/Cargo.toml +++ b/smart-house-web/Cargo.toml @@ -1,15 +1,18 @@ [workspace] +members = [ + "backend", + "frontend", +] resolver = "3" -members = ["backend"] + +[workspace.dependencies] [profile.release] opt-level = "z" -strip = "symbols" lto = "fat" -panic = "abort" codegen-units = 1 -overflow-checks = false debug-assertions = false +panic = "abort" +overflow-checks = false incremental = false - -[workspace.dependencies] +strip = "symbols" diff --git a/smart-house-web/backend/Cargo.toml b/smart-house-web/backend/Cargo.toml index 666690e..fdb8186 100644 --- a/smart-house-web/backend/Cargo.toml +++ b/smart-house-web/backend/Cargo.toml @@ -12,4 +12,4 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" [dev-dependencies] -reqwest = { version = "0.13.3", features = ["json"] } +reqwest = { version = "0.13", features = ["json"] } diff --git a/smart-house-web/backend/src/lib.rs b/smart-house-web/backend/src/lib.rs index 9bd7010..082cf83 100644 --- a/smart-house-web/backend/src/lib.rs +++ b/smart-house-web/backend/src/lib.rs @@ -67,7 +67,7 @@ pub fn build_runtime() -> tokio::runtime::Runtime { } mod server; -pub use server::server_main; +pub use server::{axum_app, server_main}; mod house; pub use house::{Device, House, PowerSocket, Room, Thermometer}; diff --git a/smart-house-web/backend/src/main.rs b/smart-house-web/backend/src/main.rs index ce2b9a4..fda7766 100644 --- a/smart-house-web/backend/src/main.rs +++ b/smart-house-web/backend/src/main.rs @@ -1,7 +1,8 @@ -use backend::{build_runtime, init_logger, server_main}; +use backend::{axum_app, build_runtime, init_logger, server_main}; fn main() { init_logger(); let runtime = build_runtime(); - runtime.block_on(server_main()); + let app = axum_app(); + runtime.block_on(server_main(app)); } diff --git a/smart-house-web/backend/src/server.rs b/smart-house-web/backend/src/server.rs index cb31124..f48a28d 100644 --- a/smart-house-web/backend/src/server.rs +++ b/smart-house-web/backend/src/server.rs @@ -1,4 +1,7 @@ -use axum::routing::{delete, get, post, put}; +use axum::{ + Router, + routing::{delete, get, post, put}, +}; use std::{process::exit, sync::Arc}; use tokio::sync::RwLock; use tracing::{error, info}; @@ -8,11 +11,9 @@ use crate::House; /// Тип состояния type ServerState = Arc>; -/// Основная функция сервера -pub async fn server_main() { +pub fn axum_app() -> Router { let state: ServerState = Arc::new(RwLock::new(House::default())); - - let app = axum::Router::new() + axum::Router::new() // Тестовый эндпоинт для экспериментов .route("/debug", get(debug::debug)) // API дома @@ -29,7 +30,11 @@ pub async fn server_main() { .route("/room/{name}/device/{name}", delete(device::delete_device)) // Состояние и роут по-умолчанию .with_state(state) - .fallback(fallback); + .fallback(fallback) +} + +/// Основная функция сервера +pub async fn server_main(app: Router) { let addr = "127.0.0.1:8080"; let listener = match tokio::net::TcpListener::bind(addr).await { Ok(listener) => listener, diff --git a/smart-house-web/backend/tests/api_smoke_test.rs b/smart-house-web/backend/tests/api_smoke_test.rs index c71801b..f6094f4 100644 --- a/smart-house-web/backend/tests/api_smoke_test.rs +++ b/smart-house-web/backend/tests/api_smoke_test.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use backend::{Device, Room, init_logger, server_main}; +use backend::{Device, Room, axum_app, init_logger, server_main}; use reqwest::{Client, Response, StatusCode}; use serde_json::json; use tokio::spawn; @@ -11,7 +11,7 @@ type RqResult = Result; #[tokio::test(flavor = "current_thread")] async fn smoke() -> RqResult<()> { init_logger(); - spawn(server_main()); + spawn(server_main(axum_app())); let client = Client::new(); diff --git a/smart-house-web/frontend/Cargo.toml b/smart-house-web/frontend/Cargo.toml new file mode 100644 index 0000000..fc2f171 --- /dev/null +++ b/smart-house-web/frontend/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "frontend" +version = "0.1.0" +edition = "2021" + +[dependencies] +dioxus = { version = "0.7", features = ["fullstack", "router"] } +reqwest = { version = "0.13", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } + +[features] +default = ["web"] +web = ["dioxus/web"] +desktop = ["dioxus/desktop"] +mobile = ["dioxus/mobile"] +server = ["dioxus/server"] diff --git a/smart-house-web/frontend/Dioxus.toml b/smart-house-web/frontend/Dioxus.toml new file mode 100644 index 0000000..e16d5f2 --- /dev/null +++ b/smart-house-web/frontend/Dioxus.toml @@ -0,0 +1,21 @@ +[application] + +[web.app] + +# HTML title tag content +title = "Smart House" + +# include `assets` in web platform +[web.resource] + +# Additional CSS style files +style = [] + +# Additional JavaScript files +script = [] + +[web.resource.dev] + +# Javascript code file +# serve: [dev-server] only +script = [] diff --git a/smart-house-web/frontend/assets/favicon.ico b/smart-house-web/frontend/assets/favicon.ico new file mode 100644 index 0000000..eed0c09 Binary files /dev/null and b/smart-house-web/frontend/assets/favicon.ico differ diff --git a/smart-house-web/frontend/assets/header.svg b/smart-house-web/frontend/assets/header.svg new file mode 100644 index 0000000..59c96f2 --- /dev/null +++ b/smart-house-web/frontend/assets/header.svg @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/smart-house-web/frontend/assets/main.css b/smart-house-web/frontend/assets/main.css new file mode 100644 index 0000000..972a921 --- /dev/null +++ b/smart-house-web/frontend/assets/main.css @@ -0,0 +1,155 @@ +html, +body { + background-color: #0e0e0e; + color: white; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + height: 100%; + width: 100%; + overflow: hidden; + margin: 0; +} + +#main { + display: flex; + flex-direction: column; + height: 100%; + justify-content: space-between; +} + +#dogview { + max-height: 80vh; + flex-grow: 1; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +#dogview img { + display: block; + max-width: 50%; + max-height: 50%; + transform: scale(1.8); + border-radius: 5px; + border: 1px solid rgb(233, 233, 233); + box-shadow: 0px 0px 5px 1px rgb(216, 216, 216, 0.5); +} + +#title { + text-align: center; + padding-top: 10px; + border-bottom: 1px solid #a8a8a8; + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: center; +} + +#title a { + text-decoration: none; + color: white; +} + +a#heart { + background-color: white; + color: red; + padding: 5px; + border-radius: 5px; +} + +#title span { + width: 20px; +} + +#title h1 { + margin: 0.25em; + font-style: italic; +} + +#buttons { + display: flex; + flex-direction: row; + justify-content: center; + gap: 20px; + /* padding-top: 20px; */ + padding-bottom: 20px; +} + +#skip { + background-color: gray; +} +#save { + background-color: green; +} + +#skip, +#save { + padding: 5px 30px 5px 30px; + border-radius: 3px; + font-size: 2rem; + font-weight: bold; + color: rgb(230, 230, 230); +} + +#navbar { + border: 1px solid rgb(233, 233, 233); + border-width: 1px 0px 0px 0px; + display: flex; + flex-direction: row; + justify-content: space-evenly; + padding: 20px; + gap: 20px; +} + +#navbar a { + background-color: #a8a8a8; + border-radius: 5px; + border: 1px solid black; + text-decoration: none; + color: black; + padding: 10px 30px 10px 30px; +} + +#favorites { + flex-grow: 1; + overflow: hidden; + display: flex; + flex-direction: column; + padding: 10px; +} + +#favorites-container { + overflow-y: auto; + overflow-x: hidden; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + gap: 10px; + padding: 10px; +} + +.favorite-dog { + max-height: 180px; + max-width: 60%; + position: relative; +} + +.favorite-dog img { + max-height: 150px; + border-radius: 5px; + margin: 5px; +} + +.favorite-dog:hover button { + display: block; +} + +.favorite-dog button { + display: none; + position: absolute; + bottom: 10px; + left: 10px; + z-index: 10; +} diff --git a/smart-house-web/frontend/clippy.toml b/smart-house-web/frontend/clippy.toml new file mode 100644 index 0000000..20d3251 --- /dev/null +++ b/smart-house-web/frontend/clippy.toml @@ -0,0 +1,8 @@ +await-holding-invalid-types = [ + "generational_box::GenerationalRef", + { path = "generational_box::GenerationalRef", reason = "Reads should not be held over an await point. This will cause any writes to fail while the await is pending since the read borrow is still active." }, + "generational_box::GenerationalRefMut", + { path = "generational_box::GenerationalRefMut", reason = "Write should not be held over an await point. This will cause any reads or writes to fail while the await is pending since the write borrow is still active." }, + "dioxus_signals::WriteLock", + { path = "dioxus_signals::WriteLock", reason = "Write should not be held over an await point. This will cause any reads or writes to fail while the await is pending since the write borrow is still active." }, +] diff --git a/smart-house-web/frontend/src/backend.rs b/smart-house-web/frontend/src/backend.rs new file mode 100644 index 0000000..e69de29 diff --git a/smart-house-web/frontend/src/components/favorites.rs b/smart-house-web/frontend/src/components/favorites.rs new file mode 100644 index 0000000..60bd50b --- /dev/null +++ b/smart-house-web/frontend/src/components/favorites.rs @@ -0,0 +1,6 @@ +use dioxus::prelude::*; + +#[component] +pub fn Favorites() -> Element { + rsx! { "favorites!" } +} diff --git a/smart-house-web/frontend/src/components/mod.rs b/smart-house-web/frontend/src/components/mod.rs new file mode 100644 index 0000000..a3f4b20 --- /dev/null +++ b/smart-house-web/frontend/src/components/mod.rs @@ -0,0 +1,7 @@ +mod favorites; +mod nav; +mod view; + +pub use favorites::*; +pub use nav::*; +pub use view::*; diff --git a/smart-house-web/frontend/src/components/nav.rs b/smart-house-web/frontend/src/components/nav.rs new file mode 100644 index 0000000..5ed7855 --- /dev/null +++ b/smart-house-web/frontend/src/components/nav.rs @@ -0,0 +1,15 @@ +use crate::Route; +use dioxus::prelude::*; + +#[component] +pub fn NavBar() -> Element { + rsx! { + div { id: "title", + Link { to: Route::DogView, + h1 { "🌭 HotDog! " } + } + Link { to: Route::Favorites, id: "heart", "♥️" } + } + Outlet:: {} + } +} diff --git a/smart-house-web/frontend/src/components/view.rs b/smart-house-web/frontend/src/components/view.rs new file mode 100644 index 0000000..e69de29 diff --git a/smart-house-web/frontend/src/main.rs b/smart-house-web/frontend/src/main.rs new file mode 100644 index 0000000..7f853bf --- /dev/null +++ b/smart-house-web/frontend/src/main.rs @@ -0,0 +1,74 @@ +use dioxus::prelude::*; + +use crate::components::{Favorites, NavBar}; + +const FAVICON: Asset = asset!("/assets/favicon.ico"); +const MAIN_CSS: Asset = asset!("/assets/main.css"); + +mod backend; +mod components; + +fn main() { + dioxus::launch(App); +} + +#[derive(Clone)] +struct TitleState(String); + +#[component] +fn App() -> Element { + use_context_provider(|| TitleState("HotDog".to_string())); + rsx! { + document::Link { rel: "icon", href: FAVICON } + document::Stylesheet { href: MAIN_CSS } + Router:: { } + } +} + +#[component] +fn Title() -> Element { + let title = use_context::(); + rsx! { + div { id: "title", + h1 { "{title.0}! 🌭" } + } + } +} + +#[derive(serde::Deserialize)] +struct DogApi { + message: String, +} + +#[component] +fn DogView() -> Element { + let mut img_src = use_resource(|| async move { + reqwest::get("https://dog.ceo/api/breeds/image/random") + .await + .unwrap() + .json::() + .await + .unwrap() + .message + }); + + rsx! { + div { id: "dogview", + img { src: img_src.cloned().unwrap_or_default() } + } + div { id: "buttons", + button { onclick: move |_| img_src.restart(), id: "skip", "skip" } + button { onclick: move |_| img_src.restart(), id: "save", "save!" } + } + } +} + +#[derive(Routable, Clone, PartialEq)] +enum Route { + #[layout(NavBar)] + #[route("/")] + DogView, + + #[route("/favorites")] + Favorites, +}