Skip to content

Commit

Permalink
feat: begin implementation of cassette page
Browse files Browse the repository at this point in the history
  • Loading branch information
HoKim98 committed Jul 11, 2024
1 parent f89f8b6 commit aa0893b
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 41 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ gloo-net = { version = "0.5", default-features = false, features = [
"json",
] }
gloo-utils = { version = "0.2", default-features = false }
inflector = { package = "Inflector", version = "0.11" }
k8s-openapi = { version = "0.22", features = ["latest", "schemars"] }
kube = { version = "0.91", default-features = false }
patternfly-yew = { version = "0.6", features = [
Expand Down
5 changes: 5 additions & 0 deletions crates/cassette-core/src/cassette.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ pub struct CassetteSpec {
#[garde(length(min = 1, max = 1024))]
#[serde(default)]
pub description: Option<String>,
#[garde(length(min = 1, max = 1024))]
#[serde(default)]
pub group: Option<String>,
#[garde(skip)]
#[serde(default)]
pub priority: Option<u32>,
Expand All @@ -51,6 +54,8 @@ pub struct CassetteRef<Component = Uuid> {
pub component: Component,
pub name: String,
#[serde(default)]
pub group: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub priority: Option<u32>,
Expand Down
3 changes: 3 additions & 0 deletions crates/cassette-gateway/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ impl CassetteDBInner {
id,
component: cr.spec.component,
name,
group: cr.spec.group,
description: cr.spec.description,
priority: cr.spec.priority,
};
Expand Down Expand Up @@ -118,6 +119,7 @@ impl CassetteDBInner {
id,
component,
name,
group,
description,
priority,
} = cassette;
Expand All @@ -132,6 +134,7 @@ impl CassetteDBInner {
id,
component,
name,
group,
description,
priority,
})
Expand Down
1 change: 1 addition & 0 deletions crates/cassette/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ cassette-plugin-kubernetes = { path = "../cassette-plugin-kubernetes", optional

browser-panic-hook = { workspace = true }
gloo-utils = { workspace = true }
inflector = { workspace = true }
patternfly-yew = { workspace = true }
regex = { workspace = true }
tracing = { workspace = true }
Expand Down
36 changes: 28 additions & 8 deletions crates/cassette/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use patternfly_yew::prelude::*;
use cassette_core::net::fetch::FetchState;
use inflector::Inflector;
use patternfly_yew::prelude::{Switch as ToggleSwitch, *};
use yew::prelude::*;
use yew_nested_router::prelude::{Switch as RouterSwitch, *};

use crate::{
hooks::redirect::{use_open, OpenTarget},
hooks::{
gateway::use_cassette_list,
redirect::{use_open, OpenTarget},
},
route::AppRoute,
};

Expand All @@ -29,19 +34,34 @@ pub struct AppPageProps {

#[function_component(AppPage)]
pub fn app_page(props: &AppPageProps) -> Html {
let sidebar = AppRoute::side_bar();
let cassette_list = use_cassette_list();
let cassette_list = match &*cassette_list {
FetchState::Pending | FetchState::Fetching => Err(html! { <p>{ "Loading..." }</p> }),
FetchState::Completed(list) => Ok(list.as_slice()),
FetchState::Error(error) => Err(html! { <p>{ format!("Error: {error}") }</p> }),
};

let sidebar = AppRoute::use_side_bar(cassette_list);

let title = env!("CARGO_PKG_NAME")
.to_screaming_snake_case()
.replace('_', " ");
let brand = html! (
<MastheadBrand>
<Brand src="assets/images/icons/logo.webp" alt="Main Logo" style="--pf-v5-c-brand--Height: 36px;"/>
<div style="display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; user-select: none;">
<Brand src="assets/images/icons/logo.webp" alt="Main Logo" style="--pf-v5-c-brand--Height: 36px;"/>
<Title size={Size::XLarge} >
{ title }
</Title>
</div>
</MastheadBrand>
);

let callback_github = use_open(env!("CARGO_PKG_REPOSITORY"), OpenTarget::Blank);

// track dark mode state
let darkmode = use_state_eq(|| {
gloo_utils::window()
::gloo_utils::window()
.match_media("(prefers-color-scheme: dark)")
.ok()
.flatten()
Expand All @@ -51,8 +71,8 @@ pub fn app_page(props: &AppPageProps) -> Html {

// apply dark mode
use_effect_with(*darkmode, |state| match state {
true => gloo_utils::document_element().set_class_name("pf-v5-theme-dark"),
false => gloo_utils::document_element().set_class_name(""),
true => ::gloo_utils::document_element().set_class_name("pf-v5-theme-dark"),
false => ::gloo_utils::document_element().set_class_name(""),
});

// toggle dark mode
Expand All @@ -66,7 +86,7 @@ pub fn app_page(props: &AppPageProps) -> Html {
variant={GroupVariant::IconButton}
>
<ToolbarItem>
<patternfly_yew::prelude::Switch checked={*darkmode} onchange={onthemeswitch} label="Dark Theme" />
<ToggleSwitch checked={*darkmode} onchange={onthemeswitch} label="Dark Theme" />
</ToolbarItem>
<ToolbarItem>
<Button variant={ButtonVariant::Plain} icon={Icon::Github} onclick={callback_github}/>
Expand Down
2 changes: 1 addition & 1 deletion crates/cassette/src/hooks/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub fn use_cassette_list() -> UseStateHandle<FetchState<Vec<CassetteRef>>> {
let namespace = use_namespace();
use_fetch(move || FetchRequest {
method: Method::GET,
name: "cassette list",
name: "list",
url: format!("/c/{namespace}/"),
})
}
42 changes: 42 additions & 0 deletions crates/cassette/src/pages/cassette.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use patternfly_yew::prelude::*;
use uuid::Uuid;
use yew::prelude::*;

#[derive(Clone, Debug, PartialEq, Properties)]
pub struct Props {
pub id: Uuid,
}

#[function_component(Cassette)]
pub fn cassette(props: &Props) -> Html {
match () {
() => html! { <CassetteFallback /> },
}
}

#[derive(Clone, Debug, PartialEq, Properties)]
struct FallbackProps {
#[prop_or_default]
pub error: Option<String>,
}

#[function_component(CassetteFallback)]
fn cassette_fallback(props: &FallbackProps) -> Html {
let title = "A Cassette";
let subtitle = "Loading...";

let error = match props.error.as_deref() {
Some(error) => html! {
<Alert inline=true title="Error" r#type={AlertType::Danger}>
{ error }
</Alert>
},
None => html! {},
};

html! {
<super::PageBody {title} {subtitle} >
{ error }
</super::PageBody>
}
}
26 changes: 5 additions & 21 deletions crates/cassette/src/pages/home.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,16 @@
use cassette_core::{cassette::CassetteRef, net::fetch::FetchState};
use inflector::Inflector;
use patternfly_yew::prelude::*;
use yew::prelude::*;

use crate::hooks::gateway::use_cassette_list;

#[function_component(Home)]
pub fn home() -> Html {
let title = "Cassette";
let title = env!("CARGO_PKG_NAME").to_title_case();
let subtitle = env!("CARGO_PKG_DESCRIPTION");

let cassette_list = match &*use_cassette_list() {
FetchState::Pending | FetchState::Fetching => html! { <p>{ "Loading..." }</p> },
FetchState::Completed(list) => render_cassette_list(list),
FetchState::Error(error) => html! { <p>{ format!("Error: {error}") }</p> },
};

html! {
<super::PageBody {title} {subtitle} >
<div class="home">
<img class="logo" src="/assets/images/icons/logo.webp" alt="Cassette logo" style="
width: 5vw;
height: 5vw;
"/>
{ cassette_list }
</div>
<Content>
</Content>
</super::PageBody>
}
}

fn render_cassette_list(list: &[CassetteRef]) -> Html {
html! { <p>{ format!("{list:?}") }</p> }
}
15 changes: 9 additions & 6 deletions crates/cassette/src/pages/license.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use patternfly_yew::prelude::*;
use yew::prelude::*;

#[function_component(License)]
Expand All @@ -6,12 +7,14 @@ pub fn license() -> Html {

html! {
<super::PageBody {title} >
<div style="
text-align: left;
white-space: pre-wrap;
">
{ include_str!("../../../../LICENSE") }
</div>
<Content>
<div style="
text-align: left;
white-space: pre-wrap;
">
{ include_str!("../../../../LICENSE") }
</div>
</Content>
</super::PageBody>
}
}
71 changes: 66 additions & 5 deletions crates/cassette/src/route.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use cassette_core::{cassette::CassetteRef, net::gateway::use_namespace};
use inflector::Inflector;
use patternfly_yew::prelude::*;
use uuid::Uuid;
use yew::{prelude::*, virtual_dom::VChild};
Expand Down Expand Up @@ -25,26 +27,49 @@ impl Default for AppRoute {
}

impl AppRoute {
pub fn side_bar() -> VChild<PageSidebar> {
#[hook]
pub fn use_side_bar(cassettes: Result<&[CassetteRef], Html>) -> VChild<PageSidebar> {
let cassettes_home = cassettes
.as_ref()
.map(|list| render_cassette_list(list, "home", false))
.unwrap_or_else(|_| html! {});
let nav_home = html! {
<NavExpandable title="Basics">
<NavExpandable title={ env!("CARGO_PKG_NAME").to_title_case() }>
<NavRouterItem<AppRoute> to={AppRoute::Home}>{"Home"}</NavRouterItem<AppRoute>>
{ cassettes_home }
</NavExpandable>
};

let namespace = use_namespace();
let cassettes_namespaced = cassettes
.as_ref()
.map(|list| render_cassette_list(list, &namespace, true))
.unwrap_or_else(render_cassette_fallback);
let nav_namespaced = html! {
<NavExpandable title={ namespace.to_title_case() }>
{ cassettes_namespaced }
</NavExpandable>
};

let cassettes_about = cassettes
.as_ref()
.map(|list| render_cassette_list(list, "about", false))
.unwrap_or_else(|_| html! {});
let nav_about = html! {
<NavExpandable title="About">
<NavRouterItem<AppRoute> to={AppRoute::Profile}>{"Profile"}</NavRouterItem<AppRoute>>
<NavRouterItem<AppRoute> to={AppRoute::License}>{"License"}</NavRouterItem<AppRoute>>
{ cassettes_about }
</NavExpandable>
};

html_nested! {
<PageSidebar>
<Nav>
<NavList>
{nav_home}
{nav_about}
{ nav_home }
{ nav_namespaced }
{ nav_about }
</NavList>
</Nav>
</PageSidebar>
Expand All @@ -54,7 +79,7 @@ impl AppRoute {
pub fn switch(self) -> Html {
let page = match self {
Self::Home => html! { <crate::pages::home::Home /> },
Self::Cassette { id } => html! { <crate::pages::home::Home /> },
Self::Cassette { id } => html! { <crate::pages::cassette::Cassette {id} /> },
Self::License => html! { <crate::pages::license::License /> },
Self::Error(route) => route.switch(),
Self::Profile => html! { <crate::pages::profile::Profile /> },
Expand All @@ -67,6 +92,42 @@ impl AppRoute {
}
}

fn render_cassette_list(cassettes: &[CassetteRef], group: &str, is_default: bool) -> Html {
let items = cassettes
.iter()
.filter(|cassette| {
cassette
.group
.as_deref()
.map(|name| name == group)
.unwrap_or(is_default)
})
.map(render_cassette);
html! { for items }
}

fn render_cassette(cassette: &CassetteRef) -> Html {
let CassetteRef {
id: _,
component,
name,
group: _,
description: _,
priority: _,
} = cassette;

let id = *component;
let name = name.to_title_case();

html! {
<NavRouterItem<AppRoute> to={AppRoute::Cassette { id }}>{ name }</NavRouterItem<AppRoute>>
}
}

fn render_cassette_fallback(child: &Html) -> Html {
child.clone()
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Target)]
pub enum Errors {
#[target(rename = "404")]
Expand Down

0 comments on commit aa0893b

Please sign in to comment.