diff --git a/.gitignore b/.gitignore index 4d0f0c7..cc460e8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ target static pkg ./Cargo.lock +package-lock.json diff --git a/src/components/layout.rs b/src/components/layout.rs index d8550d8..87044d7 100644 --- a/src/components/layout.rs +++ b/src/components/layout.rs @@ -1,26 +1,5 @@ use sycamore::prelude::*; -#[component] -pub fn Layout<'a, G: Html>( - cx: Scope<'a>, - LayoutProps { title, children }: LayoutProps<'a, G>, -) -> View { - let children = children.call(cx); - - view! { cx, - // These elements are styled with bright colors for demonstration purposes - header(style = "background-color: red; color: white; padding: 1rem") { - p { (title.to_string()) } - } - main(style = "padding: 1rem") { - (children) - } - footer(style = "background-color: black; color: white; padding: 1rem") { - p { "Hey there, I'm a footer!" } - } - } -} - #[derive(Prop)] pub struct LayoutProps<'a, G: Html> { /// The title of the page, which will be displayed in the header. @@ -28,3 +7,56 @@ pub struct LayoutProps<'a, G: Html> { /// The content to put inside the layout. pub children: Children<'a, G>, } + +#[component] +pub fn Layout<'a, G: Html>( + cx: Scope<'a>, + LayoutProps { title, children }: LayoutProps<'a, G>, +) -> View { + let children = children.call(cx); + + // example params + // p { (title.to_string()) } + + view! { cx, + // These elements are styled with bright colors for demonstration purposes + header { + div (class = "flex items-center justify-between") { + div (class = "w-full text-gray-700 md:text-center text-2xl font-semibold") { + "Pool Elo - Season 1" + } + } + + div (class = "container mx-auto px-6 py-3") { + nav (class = "sm:flex sm:justify-center sm:items-center mt-4 hidden") { + div (class = "flex flex-col sm:flex-row"){ + a(href = "add-game-form", + class = "mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" + ) { "Add game result" } + a(href = "one-v-one-board", + class = "mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" + ) { "1v1 Leaderboard" } + a(href = "overall-board", + class = "mt-3 text-gray-600 hover:underline sm:mx-3 sm:mt-0" + ) { "Overall Leaderboard" } + } + } + } + } + + main(style = "my-8") { + div(class = "container mx-auto px-6") { + div(class = "md:flex mt-8 md:-mx-4") { + div(class = "h-64 rounded-md overflow-hidden bg-cover bg-center") { + (children) + } + } + } + } + footer(class = "bg-gray-200") { + p(class = "container mx-auto px-6 py-3 flex justify-between items-center"){ + "Hey there, I'm a footer!" + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 1046da3..b0daf1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,18 +12,20 @@ pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template()) - .template(crate::templates::long::get_template()) + .template(crate::templates::add_game_form::get_template()) + .template(crate::templates::one_v_one_board::get_template()) + .template(crate::templates::overall_board::get_template()) .error_views(crate::error_views::get_error_views()) .index_view(|cx| { view! { cx, - html { + html (class = "flex w-full h-full"){ head { meta(charset = "UTF-8") meta(name = "viewport", content = "width=device-width, initial-scale=1.0") // Perseus automatically resolves `/.perseus/static/` URLs to the contents of the `static/` directory at the project root link(rel = "stylesheet", href = ".perseus/static/style.css") } - body { + body (class = "w-full"){ // Quirk: this creates a wrapper `
` around the root `
` by necessity PerseusRoot() } diff --git a/src/templates/add_game_form.rs b/src/templates/add_game_form.rs new file mode 100644 index 0000000..1f371ea --- /dev/null +++ b/src/templates/add_game_form.rs @@ -0,0 +1,48 @@ +use crate::components::layout::Layout; +use perseus::prelude::*; +use serde::{Deserialize, Serialize}; +use std::fs; +use sycamore::prelude::*; + +// Reactive page + +#[derive(Serialize, Deserialize, Clone, ReactiveState)] +#[rx(alias = "PageStateRx")] +struct PageState { + +} + +fn add_game_form_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStateRx) -> View { + view! { cx, + Layout(title = "Add Game Results") { + // Anything we put in here will be rendered inside the `
` block of the layout + p { "Results" } + } + } +} + +#[engine_only_fn] +async fn get_request_state( + _info: StateGeneratorInfo<()>, + req: Request, +) -> Result> { + Ok(PageState {}) +} + +#[engine_only_fn] +fn head(cx: Scope) -> View { + view! { cx, + title { "Add Game Form" } + } +} + + +// Template + +pub fn get_template() -> Template { + Template::build("add-game-form") + .request_state_fn(get_request_state) + .view_with_state(add_game_form_page) + .head(head) + .build() +} diff --git a/src/templates/index.rs b/src/templates/index.rs index 9f2c7ec..dda860d 100644 --- a/src/templates/index.rs +++ b/src/templates/index.rs @@ -1,7 +1,6 @@ use crate::components::layout::Layout; use perseus::prelude::*; use sycamore::prelude::*; -use crate::templates::get_path; fn index_page(cx: Scope) -> View { view! { cx, @@ -9,7 +8,6 @@ fn index_page(cx: Scope) -> View { // Anything we put in here will be rendered inside the `
` block of the layout p { "Hello World!" } br {} - a(href = "long") { "Long page" } } } } @@ -22,7 +20,7 @@ fn head(cx: Scope) -> View { } pub fn get_template() -> Template { - Template::build(get_path("").as_str()) + Template::build("") .view(index_page) .head(head) .build() diff --git a/src/templates/long.rs b/src/templates/long.rs deleted file mode 100644 index a1d1c7d..0000000 --- a/src/templates/long.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::components::layout::Layout; -use perseus::prelude::*; -use serde::{Deserialize, Serialize}; -use std::fs; -use sycamore::prelude::*; -use crate::templates::get_path; - -// Reactive page - -#[derive(Serialize, Deserialize, Clone, ReactiveState)] -#[rx(alias = "PageStateRx")] -struct PageState { - ip: String, - text: String, -} - -fn request_state_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStateRx) -> View { - view! { cx, - p { - ( - format!("Your IP address is {}.", state.ip.get()) - ) - } - p { - ( - state.text.get().repeat(5000) - ) - } - } -} - -#[engine_only_fn] -async fn get_request_state( - _info: StateGeneratorInfo<()>, - req: Request, -) -> Result> { - let text = fs::read_to_string("static/test.txt") - .unwrap_or("~".to_string()) - .parse() - .unwrap(); - - Ok(PageState { - ip: format!( - "{:?}", - req.headers() - // NOTE: This header can be trivially spoofed, and may well not be the user's actual - // IP address - .get("X-Forwarded-For") - .unwrap_or(&perseus::http::HeaderValue::from_str("hidden from view!").unwrap()) - ), - text, - }) -} - -// Regular page - -fn long_page(cx: Scope) -> View { - view! { cx, - Layout(title = "Long") { - // Anything we put in here will be rendered inside the `
` block of the layout - a(href = "") { "Index" } - br {} - p { - ("This is a test. ".repeat(5000)) - } - } - } -} - -#[engine_only_fn] -fn head(cx: Scope) -> View { - view! { cx, - title { "Long Page" } - } -} - -// Template - -pub fn get_template() -> Template { - // Template::build(get_full_path("long")).view(long_page).head(head).build() - Template::build(get_path("long").as_str()) - .request_state_fn(get_request_state) - .view_with_state(request_state_page) - .head(head) - .build() -} diff --git a/src/templates/mod.rs b/src/templates/mod.rs index e61db68..697dd20 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -1,17 +1,4 @@ -use std::env; - pub mod index; -pub mod long; - -pub fn get_path(path: &str) -> String { - // Get base path - match env::var("PERSEUS_BASE_PATH") { - Ok(env_path) => { - // Strip the slash on both sides for directory consistency - // let stripped_env = env_path.trim_start_matches("/").trim_end_matches("/"); - // format!("{stripped_env}/{path}").to_string().trim_end_matches("/").to_owned() - path.to_owned() - } - Err(_) => path.to_owned(), - } -} +pub mod add_game_form; +pub mod one_v_one_board; +pub mod overall_board; diff --git a/src/templates/one_v_one_board.rs b/src/templates/one_v_one_board.rs new file mode 100644 index 0000000..3a9ac4c --- /dev/null +++ b/src/templates/one_v_one_board.rs @@ -0,0 +1,48 @@ +use crate::components::layout::Layout; +use perseus::prelude::*; +use serde::{Deserialize, Serialize}; +use std::fs; +use sycamore::prelude::*; + +// Reactive page + +#[derive(Serialize, Deserialize, Clone, ReactiveState)] +#[rx(alias = "PageStateRx")] +struct PageState { + +} + +fn one_v_one_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStateRx) -> View { + view! { cx, + Layout(title = "1v1 Leaderboard") { + // Anything we put in here will be rendered inside the `
` block of the layout + p { "leaderboard" } + } + } +} + +#[engine_only_fn] +async fn get_request_state( + _info: StateGeneratorInfo<()>, + req: Request, +) -> Result> { + Ok(PageState {}) +} + +#[engine_only_fn] +fn head(cx: Scope) -> View { + view! { cx, + title { "1v1 Leaderboard" } + } +} + + +// Template + +pub fn get_template() -> Template { + Template::build("one-v-one-board") + .request_state_fn(get_request_state) + .view_with_state(one_v_one_board_page) + .head(head) + .build() +} diff --git a/src/templates/overall_board.rs b/src/templates/overall_board.rs new file mode 100644 index 0000000..7eee3be --- /dev/null +++ b/src/templates/overall_board.rs @@ -0,0 +1,48 @@ +use crate::components::layout::Layout; +use perseus::prelude::*; +use serde::{Deserialize, Serialize}; +use std::fs; +use sycamore::prelude::*; + +// Reactive page + +#[derive(Serialize, Deserialize, Clone, ReactiveState)] +#[rx(alias = "PageStateRx")] +struct PageState { + +} + +fn overall_board_page<'a, G: Html>(cx: BoundedScope<'_, 'a>, state: &'a PageStateRx) -> View { + view! { cx, + Layout(title = "Overall Leaderboard") { + // Anything we put in here will be rendered inside the `
` block of the layout + p { "leaderboard" } + } + } +} + +#[engine_only_fn] +async fn get_request_state( + _info: StateGeneratorInfo<()>, + req: Request, +) -> Result> { + Ok(PageState {}) +} + +#[engine_only_fn] +fn head(cx: Scope) -> View { + view! { cx, + title { "Overall leaderboard" } + } +} + + +// Template + +pub fn get_template() -> Template { + Template::build("overall-board") + .request_state_fn(get_request_state) + .view_with_state(overall_board_page) + .head(head) + .build() +} diff --git a/style/tailwind.css b/style/tailwind.css index 08d6ef3..a90f074 100644 --- a/style/tailwind.css +++ b/style/tailwind.css @@ -2,47 +2,3 @@ @tailwind components; @tailwind utilities; -/* This removes the margins inserted by default around pages */ -* { - margin: 0; -} - -/* This makes all the elements that wrap our code take up the whole page, so that we can put things at the bottom. - * Without this, the footer would be just beneath the content if the content doesn't fill the whole page (try disabling this). -*/ -html, body, #root { - height: 100%; -} -/* This makes the `
` that wraps our whole app use CSS Grid to display three sections: the header, content, and footer. */ -#root { - display: grid; - grid-template-columns: 1fr; - /* The header will be automatically sized, the footer will be as small as possible, and the content will take up the rest of the space in the middle */ - grid-template-rows: auto 1fr min-content; - grid-template-areas: - 'header' - 'main' - 'footer'; -} -header { - /* Put this in the right place in the grid */ - grid-area: header; - /* Make this float over the content so it persists as we scroll */ - position: fixed; - top: 0; - z-index: 99; - /* Make this span the whole page */ - width: 100%; -} -main { - /* Put this in the right place in the grid */ - grid-area: main; - /* The header is positioned 'floating' over the content, so we have to make sure this doesn't go behind the header, or it would be invisible. - * You may need to adjust this based on screen size, depending on how the header expands. - */ - margin-top: 5rem; -} -footer { - /* Put this in the right place in the grid */ - grid-area: footer; -}