From 36e2766bb99b98a71045cedac31315853b859576 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Mon, 5 Jun 2023 12:54:07 +0200 Subject: [PATCH] feat: add feature slicing Signed-off-by: kjuulh --- Cargo.lock | 38 +++- Cargo.toml | 10 +- input.css | 11 ++ leptosfmt.toml | 3 + scripts/refresh:schema.sh | 10 +- src/api/graphql/schema/schema.json | 2 +- src/app.rs | 61 +++--- src/common.rs | 2 + src/common/graphql/mod.rs | 1 + src/common/layout.rs | 46 +++++ src/features.rs | 1 + src/features/navbar_projects.rs | 4 + src/features/navbar_projects/gen/mod.rs | 1 + src/features/navbar_projects/gen/queries.rs | 43 +++++ .../navbar_projects/graphql/queries.graphql | 6 + .../navbar_projects/navbar_projects.rs | 60 ++++++ src/lib.rs | 4 + src/routes.rs | 4 + src/routes/dash.rs | 1 + src/routes/dash/home.rs | 12 ++ src/routes/features_view.rs | 44 +++++ src/routes/home.rs | 27 +++ style/output.css | 175 ++++++++++++++++++ tailwind.config.js | 1 + 24 files changed, 526 insertions(+), 41 deletions(-) create mode 100644 leptosfmt.toml create mode 100644 src/common.rs create mode 100644 src/common/graphql/mod.rs create mode 100644 src/common/layout.rs create mode 100644 src/features.rs create mode 100644 src/features/navbar_projects.rs create mode 100644 src/features/navbar_projects/gen/mod.rs create mode 100644 src/features/navbar_projects/gen/queries.rs create mode 100644 src/features/navbar_projects/graphql/queries.graphql create mode 100644 src/features/navbar_projects/navbar_projects.rs create mode 100644 src/routes.rs create mode 100644 src/routes/dash.rs create mode 100644 src/routes/dash/home.rs create mode 100644 src/routes/features_view.rs create mode 100644 src/routes/home.rs diff --git a/Cargo.lock b/Cargo.lock index b97cb56..8970762 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,8 +404,9 @@ dependencies = [ "leptos_meta", "leptos_router", "log", - "reqwest", + "reqwasm", "serde", + "serde_json", "simple_logger", "thiserror", "tokio", @@ -800,6 +801,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-net" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2899cb1a13be9020b010967adc6b2a8a343b6f1428b90238c9d53ca24decc6db" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "gloo-net" version = "0.2.6" @@ -1356,7 +1377,7 @@ dependencies = [ "cached", "cfg-if", "common_macros", - "gloo-net", + "gloo-net 0.2.6", "js-sys", "lazy_static", "leptos", @@ -1927,6 +1948,15 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +[[package]] +name = "reqwasm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b89870d729c501fa7a68c43bf4d938bbb3a8c156d333d90faa0e8b3e3212fb" +dependencies = [ + "gloo-net 0.1.0", +] + [[package]] name = "reqwest" version = "0.11.18" @@ -2155,7 +2185,7 @@ checksum = "e4f115508f94ea61b6e9ed5f4ecd1926ad283b143680183c65dc450c20c8520a" dependencies = [ "ciborium", "const_format", - "gloo-net", + "gloo-net 0.2.6", "js-sys", "once_cell", "proc-macro2", @@ -2767,6 +2797,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", + "serde", + "serde_json", "wasm-bindgen-macro", ] diff --git a/Cargo.toml b/Cargo.toml index b050c3e..cea2abf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,13 +35,14 @@ tracing-subscriber = { version = "0.3.16", optional = true, features = [ "env-filter", ] } tracing = { version = "0.1.37", features = ["log"], optional = true } -anyhow = { version = "1.0.71", optional = true } +anyhow = { version = "1.0.71" } serde = { workspace = true } chrono = { workspace = true } uuid = { workspace = true, features = ["v4", "wasm-bindgen", "js", "serde"] } -graphql_client = { version = "0.13.0", optional = true, features = ["reqwest"] } -reqwest = { version = "0.11.18", optional = true } +graphql_client = { version = "0.13.0", features = ["reqwest"] } +reqwasm = "0.5.0" +serde_json = "1.0.96" [features] hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] @@ -56,9 +57,6 @@ ssr = [ "dep:leptos_axum", "dep:tracing-subscriber", "dep:tracing", - "dep:graphql_client", - "dep:reqwest", - "dep:anyhow", ] [package.metadata.leptos] diff --git a/input.css b/input.css index b5c61c9..11d9dbe 100644 --- a/input.css +++ b/input.css @@ -1,3 +1,14 @@ @tailwind base; @tailwind components; @tailwind utilities; + +html, body { + scroll-behavior: smooth; + min-height: 100%; + + @apply bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100; +} + +.feature-case { + @apply m-8 border-blue-700 border-2 rounded-lg p-4; +} diff --git a/leptosfmt.toml b/leptosfmt.toml new file mode 100644 index 0000000..2c9d1d4 --- /dev/null +++ b/leptosfmt.toml @@ -0,0 +1,3 @@ +max_width = 100 +tab_spaces = 4 +attr_value_brace_style = "WhenRequired" # "Always", "AlwaysUnlessLit", "WhenRequired" or "Preserve" diff --git a/scripts/refresh:schema.sh b/scripts/refresh:schema.sh index a7e15be..22ccd7b 100755 --- a/scripts/refresh:schema.sh +++ b/scripts/refresh:schema.sh @@ -7,10 +7,6 @@ graphql-client introspect-schema \ graphql-client generate \ --schema-path src/api/graphql/schema/schema.json \ - src/api/graphql/schema/mutations.graphql \ - --output-directory src/api/graphql/gen - -graphql-client generate \ - --schema-path src/api/graphql/schema/schema.json \ - src/api/graphql/schema/queries.graphql \ - --output-directory src/api/graphql/gen + src/features/navbar_projects/graphql/queries.graphql \ + --output-directory src/features/navbar_projects/gen \ + --custom-scalars-module='crate::common::graphql' diff --git a/src/api/graphql/schema/schema.json b/src/api/graphql/schema/schema.json index ae2ab54..c01c432 100644 --- a/src/api/graphql/schema/schema.json +++ b/src/api/graphql/schema/schema.json @@ -1735,4 +1735,4 @@ ] } } -} +} \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index e3f985d..ad7752d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,41 +2,54 @@ use leptos::*; use leptos_meta::*; use leptos_router::*; +use crate::common::layout::DashboardLayout; +use crate::routes::dash::home::DashHomePage; +use crate::routes::features_view::FeaturesView; +use crate::routes::home::HomePage; + #[component] pub fn App(cx: Scope) -> impl IntoView { // Provides context that manages stylesheets, titles, meta tags, etc. provide_meta_context(cx); - view! { - cx, - - // injects a stylesheet into the document - // id=leptos means cargo-leptos will hot-reload this stylesheet + view! { cx, - - // sets the document title - - - // content for this welcome page <Router> <main> <Routes> - <Route path="" view=|cx| view! { cx, <HomePage/> }/> + <Route + path="" + view=|cx| { + view! { cx, <HomePage/> } + } + /> + <Route + path="/dash" + view=|cx| { + view! { cx, <DashboardLayout/> } + } + > + <Route + path="" + view=|cx| { + view! { cx, <DashHomePage/> } + } + /> + <Route + path="home" + view=|cx| { + view! { cx, <DashHomePage/> } + } + /> + </Route> + <Route + path="/features" + view=|cx| { + view! { cx, <FeaturesView/> } + } + /> </Routes> </main> </Router> } } - -/// Renders the home page of your application. -#[component] -fn HomePage(cx: Scope) -> impl IntoView { - // Creates a reactive value to update the button - let (count, set_count) = create_signal(cx, 0); - let on_click = move |_| set_count.update(|count| *count += 1); - - view! { cx, - <h1 class="text-xl text-red-50">"Welcome to Leptos!"</h1> - <button on:click=on_click>"Click Me: " {count}</button> - } -} diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..3dea3b8 --- /dev/null +++ b/src/common.rs @@ -0,0 +1,2 @@ +pub mod layout; +pub mod graphql; diff --git a/src/common/graphql/mod.rs b/src/common/graphql/mod.rs new file mode 100644 index 0000000..bfce685 --- /dev/null +++ b/src/common/graphql/mod.rs @@ -0,0 +1 @@ +pub type UUID = uuid::Uuid; diff --git a/src/common/layout.rs b/src/common/layout.rs new file mode 100644 index 0000000..677074e --- /dev/null +++ b/src/common/layout.rs @@ -0,0 +1,46 @@ +use leptos::*; +use leptos_router::*; + +use crate::features::navbar_projects::NavbarProjects; + +#[component] +pub fn DashNav(cx: Scope) -> impl IntoView { + view! { cx, + <nav class="absolute min-w-[200px] p-4 space-y-4 h-screen sticky top-0 select-none"> + <div> + <a href="/dash/home" class="text-xl"> + "como" + </a> + </div> + <div> + <a href="/dash/current" class=""> + "inbox" + </a> + </div> + <div> + <p class="text-sm mb-0.5 dark:text-gray-500">"Favorites"</p> + <a href="/dash/current" class="dark:text-gray-300 pl-2"> + "inbox" + </a> + </div> + <div> + <p class="text-sm mb-0.5 dark:text-gray-500">"Projects"</p> + <div class="pl-2 dark:text-gray-300"> + <NavbarProjects/> + </div> + </div> + </nav> + } +} + +#[component] +pub fn DashboardLayout(cx: Scope) -> impl IntoView { + view! { cx, + <div class="flex flex-row"> + <DashNav/> + <div id="content" class="p-2"> + <Outlet/> + </div> + </div> + } +} diff --git a/src/features.rs b/src/features.rs new file mode 100644 index 0000000..94d764e --- /dev/null +++ b/src/features.rs @@ -0,0 +1 @@ +pub mod navbar_projects; diff --git a/src/features/navbar_projects.rs b/src/features/navbar_projects.rs new file mode 100644 index 0000000..1f6bf39 --- /dev/null +++ b/src/features/navbar_projects.rs @@ -0,0 +1,4 @@ +pub mod gen; +pub mod navbar_projects; + +pub use navbar_projects::*; diff --git a/src/features/navbar_projects/gen/mod.rs b/src/features/navbar_projects/gen/mod.rs new file mode 100644 index 0000000..84c032e --- /dev/null +++ b/src/features/navbar_projects/gen/mod.rs @@ -0,0 +1 @@ +pub mod queries; diff --git a/src/features/navbar_projects/gen/queries.rs b/src/features/navbar_projects/gen/queries.rs new file mode 100644 index 0000000..eda784f --- /dev/null +++ b/src/features/navbar_projects/gen/queries.rs @@ -0,0 +1,43 @@ +#![allow(clippy::all, warnings)] +pub struct GetProjectsListView; +pub mod get_projects_list_view { + #![allow(dead_code)] + use std::result::Result; + pub const OPERATION_NAME: &str = "GetProjectsListView"; + pub const QUERY: &str = + "query GetProjectsListView {\n getProjects {\n id\n name\n }\n}\n"; + use super::*; + use serde::{Deserialize, Serialize}; + #[allow(dead_code)] + type Boolean = bool; + #[allow(dead_code)] + type Float = f64; + #[allow(dead_code)] + type Int = i64; + #[allow(dead_code)] + type ID = String; + type UUID = crate::common::graphql::UUID; + #[derive(Serialize)] + pub struct Variables; + #[derive(Deserialize)] + pub struct ResponseData { + #[serde(rename = "getProjects")] + pub get_projects: Vec<GetProjectsListViewGetProjects>, + } + #[derive(Deserialize)] + pub struct GetProjectsListViewGetProjects { + pub id: UUID, + pub name: String, + } +} +impl graphql_client::GraphQLQuery for GetProjectsListView { + type Variables = get_projects_list_view::Variables; + type ResponseData = get_projects_list_view::ResponseData; + fn build_query(variables: Self::Variables) -> ::graphql_client::QueryBody<Self::Variables> { + graphql_client::QueryBody { + variables, + query: get_projects_list_view::QUERY, + operation_name: get_projects_list_view::OPERATION_NAME, + } + } +} diff --git a/src/features/navbar_projects/graphql/queries.graphql b/src/features/navbar_projects/graphql/queries.graphql new file mode 100644 index 0000000..9217250 --- /dev/null +++ b/src/features/navbar_projects/graphql/queries.graphql @@ -0,0 +1,6 @@ +query GetProjectsListView { + getProjects { + id + name + } +} diff --git a/src/features/navbar_projects/navbar_projects.rs b/src/features/navbar_projects/navbar_projects.rs new file mode 100644 index 0000000..0ded118 --- /dev/null +++ b/src/features/navbar_projects/navbar_projects.rs @@ -0,0 +1,60 @@ +use graphql_client::{GraphQLQuery, Response}; +use leptos::*; +use leptos_router::*; + +use crate::features::navbar_projects::gen::queries::get_projects_list_view::{ + ResponseData, Variables, +}; +use crate::features::navbar_projects::gen::queries::GetProjectsListView; + +use super::gen::queries::get_projects_list_view::GetProjectsListViewGetProjects; + +pub async fn get_projects_list() -> anyhow::Result<Vec<GetProjectsListViewGetProjects>> { + let request_body = GetProjectsListView::build_query(Variables {}); + let payload = serde_json::to_string(&request_body)?; + let res = reqwasm::http::Request::post("http://localhost:3001/graphql") + .credentials(reqwasm::http::RequestCredentials::Include) + .body(payload) + .send() + .await?; + let response_body: Response<ResponseData> = res.json().await?; + Ok(response_body.data.unwrap().get_projects) +} + +#[component] +pub fn NavbarProjectsView( + cx: Scope, + projects: Resource<(), Vec<GetProjectsListViewGetProjects>>, +) -> impl IntoView { + let projects_view = move || { + projects.with(cx, |projects| { + + if projects.is_empty() { + return vec![view! { cx, <div class="project-item">"No projects"</div> }.into_any()]; + } + + projects + .into_iter() + .map(|project| { + view! { cx, + <a href=format!("/dash/project/{}", & project.id) class="project-item"> + <div class="project-item-name hover:dark:bg-blue-700 rounded-md p-0.5 px-2"> + {&project.name} + </div> + </a> + }.into_any() + }) + .collect::<Vec<_>>() + }) + }; + + view! { cx, <div class="project-items space-y-1">{projects_view}</div> } +} + +#[component] +pub fn NavbarProjects(cx: Scope) -> impl IntoView { + let projects = + create_local_resource(cx, || (), |_| async { get_projects_list().await.unwrap() }); + + view! { cx, <NavbarProjectsView projects=projects/> } +} diff --git a/src/lib.rs b/src/lib.rs index c2e99ae..84cdeb9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,10 @@ pub mod api; pub mod app; +pub mod common; pub mod fallback; +pub mod features; +pub mod routes; + use cfg_if::cfg_if; cfg_if! { diff --git a/src/routes.rs b/src/routes.rs new file mode 100644 index 0000000..1f0e63d --- /dev/null +++ b/src/routes.rs @@ -0,0 +1,4 @@ +pub mod dash; +pub mod features_view; +pub mod home; + diff --git a/src/routes/dash.rs b/src/routes/dash.rs new file mode 100644 index 0000000..9b86bcf --- /dev/null +++ b/src/routes/dash.rs @@ -0,0 +1 @@ +pub mod home; diff --git a/src/routes/dash/home.rs b/src/routes/dash/home.rs new file mode 100644 index 0000000..fd8f1ef --- /dev/null +++ b/src/routes/dash/home.rs @@ -0,0 +1,12 @@ +use leptos::*; + +use crate::features::navbar_projects::NavbarProjects; + +#[component] +pub fn DashHomePage(cx: Scope) -> impl IntoView { + view! { cx, + <div class="home-dash"> + <NavbarProjects/> + </div> + } +} diff --git a/src/routes/features_view.rs b/src/routes/features_view.rs new file mode 100644 index 0000000..94e3a22 --- /dev/null +++ b/src/routes/features_view.rs @@ -0,0 +1,44 @@ +use leptos::*; +use uuid::Uuid; + +use crate::features::navbar_projects::gen::queries::get_projects_list_view::GetProjectsListViewGetProjects; +use crate::features::navbar_projects::NavbarProjectsView; + +#[component] +pub fn FeaturesView(cx: Scope) -> impl IntoView { + let projects = create_local_resource( + cx, + || (), + |_| async { + vec![ + GetProjectsListViewGetProjects { + id: Uuid::new_v4(), + name: "some-name".to_string(), + }, + GetProjectsListViewGetProjects { + id: Uuid::new_v4(), + name: "some-other-name".to_string(), + }, + ] + }, + ); + + let emptyProjects: Resource<(), Vec<GetProjectsListViewGetProjects>> = + create_local_resource(cx, || (), |_| async { Vec::new() }); + + view! { cx, + <div> + <div class="space-y-5"> + <h1>"NavbarProjects"</h1> + <h2>"Projects"</h2> + <div class="feature-case"> + <NavbarProjectsView projects=projects/> + </div> + <h2>"no projects"</h2> + <div class="feature-case"> + <NavbarProjectsView projects=emptyProjects/> + </div> + </div> + </div> + } +} diff --git a/src/routes/home.rs b/src/routes/home.rs new file mode 100644 index 0000000..9cc8cd6 --- /dev/null +++ b/src/routes/home.rs @@ -0,0 +1,27 @@ +use leptos::*; + +#[component] +pub fn Navbar(cx: Scope) -> impl IntoView { + view! { cx, + <div class="flex flex-row justify-between items-center bg-gray-800 p-4"> + <div class="flex flex-row items-center"> + <div class="text-2xl text-white font-bold">"Como - Todo"</div> + </div> + <div class="flex flex-row items-center space-x-4"> + <div class="text-xl text-white font-bold cursor-pointer"> + <a href="http://localhost:3001/auth/zitadel?return_url=http://localhost:3000/dash/home"> + "Enter" + </a> + </div> + </div> + </div> + } +} + +#[component] +pub fn HomePage(cx: Scope) -> impl IntoView { + view! { cx, + <Navbar/> + <h1 class="text-xl text-red-50">"Welcome to Leptos!"</h1> + } +} diff --git a/style/output.css b/style/output.css index dca72e1..5a24d7e 100644 --- a/style/output.css +++ b/style/output.css @@ -522,16 +522,191 @@ video { --tw-backdrop-sepia: ; } +.absolute { + position: absolute; +} + .relative { position: relative; } +.sticky { + position: sticky; +} + +.top-0 { + top: 0px; +} + +.mb-0 { + margin-bottom: 0px; +} + +.mb-0\.5 { + margin-bottom: 0.125rem; +} + +.flex { + display: flex; +} + +.h-screen { + height: 100vh; +} + +.min-w-\[200px\] { + min-width: 200px; +} + +.cursor-pointer { + cursor: pointer; +} + +.select-none { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.flex-row { + flex-direction: row; +} + +.items-center { + align-items: center; +} + +.justify-between { + justify-content: space-between; +} + +.space-x-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1rem * var(--tw-space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} + +.space-y-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.space-y-5 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.25rem * var(--tw-space-y-reverse)); +} + +.rounded-md { + border-radius: 0.375rem; +} + +.bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +.p-0 { + padding: 0px; +} + +.p-0\.5 { + padding: 0.125rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-4 { + padding: 1rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.pl-2 { + padding-left: 0.5rem; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + .text-xl { font-size: 1.25rem; line-height: 1.75rem; } +.font-bold { + font-weight: 700; +} + .text-red-50 { --tw-text-opacity: 1; color: rgb(254 242 242 / var(--tw-text-opacity)); } + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +html, body { + scroll-behavior: smooth; + min-height: 100%; + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +@media (prefers-color-scheme: dark) { + html, body { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(243 244 246 / var(--tw-text-opacity)); + } +} + +.feature-case { + margin: 2rem; + border-radius: 0.5rem; + border-width: 2px; + --tw-border-opacity: 1; + border-color: rgb(29 78 216 / var(--tw-border-opacity)); + padding: 1rem; +} + +@media (prefers-color-scheme: dark) { + .dark\:text-gray-300 { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity)); + } + + .dark\:text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); + } + + .hover\:dark\:bg-blue-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); + } +} diff --git a/tailwind.config.js b/tailwind.config.js index 74fc54a..ad0b797 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,5 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { + darkMode: 'media', content: { files: ["*.html", "./src/**/*.rs"], },