Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify UI #93

Merged
merged 13 commits into from
Oct 26, 2023
1 change: 1 addition & 0 deletions crates/nix_rs/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ impl MacOSArch {
}
}

// The [Display] instance affects how [OS] is displayed to the app user
impl Display for OS {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expand Down
6 changes: 5 additions & 1 deletion crates/nix_rs/src/flake/url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ impl FlakeUrl {
vec![
FlakeUrl::default(),
"github:srid/emanote".into(),
"github:srid/nixos-config".into(),
"github:juspay/nix-browser".into(),
"github:nixos/nixpkgs".into(),
"github:juspay/nix-dev-home".into(),
// Commented out until we figure out rendering performance and/or
// search filtering/limit.
// "github:nixos/nixpkgs".into(),
]
}

Expand Down
16 changes: 5 additions & 11 deletions src/app/flake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@ use nix_rs::flake::{
};

use crate::{
app::widget::{FolderDialogButton, RefreshButton},
app::{
state::{self, AppState},
Route,
},
app::widget::FolderDialogButton,
app::{state::AppState, widget::Loader, Route},
};

#[component]
Expand All @@ -25,7 +22,7 @@ pub fn Flake(cx: Scope) -> Element {
let flake = state.flake.read();
let busy = (*flake).is_loading_or_refreshing();
render! {
h1 { class: "text-5xl font-bold", "Flake dashboard" }
h1 { class: "text-5xl font-bold", "Flake browser" }
div { class: "p-2 my-1 flex w-full",
input {
class: "flex-1 w-full p-1 mb-4 font-mono",
Expand All @@ -49,11 +46,8 @@ pub fn Flake(cx: Scope) -> Element {
}
}
}
RefreshButton {
busy: busy,
handler: move |_| {
state.act(state::Action::RefreshFlake);
}
if flake.is_loading_or_refreshing() {
render! { Loader {} }
}
flake.render_with(cx, |v| render! { FlakeView { flake: v.clone() } })
}
Expand Down
12 changes: 3 additions & 9 deletions src/app/health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
use dioxus::prelude::*;
use nix_health::traits::{Check, CheckResult};

use crate::{
app::state::AppState,
app::{state::Action, widget::RefreshButton},
};
use crate::{app::state::AppState, app::widget::Loader};

/// Nix health checks
pub fn Health(cx: Scope) -> Element {
Expand All @@ -15,11 +12,8 @@ pub fn Health(cx: Scope) -> Element {
let title = "Nix Health";
render! {
h1 { class: "text-5xl font-bold", title }
RefreshButton {
busy: (*health_checks).is_loading_or_refreshing(),
handler: move |_event| {
state.act(Action::GetNixInfo);
}
if health_checks.is_loading_or_refreshing() {
render! { Loader {} }
}
health_checks.render_with(cx, |checks| render! {
div { class: "flex flex-col items-stretch justify-start space-y-8 text-left",
Expand Down
12 changes: 3 additions & 9 deletions src/app/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ use std::fmt::Display;
use dioxus::prelude::*;
use nix_rs::{config::NixConfig, env::NixEnv, info::NixInfo, version::NixVersion};

use crate::{
app::state::AppState,
app::{state::Action, widget::RefreshButton},
};
use crate::{app::state::AppState, app::widget::Loader};

/// Nix information
#[component]
Expand All @@ -18,11 +15,8 @@ pub fn Info(cx: Scope) -> Element {
let nix_info = state.nix_info.read();
render! {
h1 { class: "text-5xl font-bold", title }
RefreshButton {
busy: (*nix_info).is_loading_or_refreshing(),
handler: move |_event| {
state.act(Action::GetNixInfo);
}
if nix_info.is_loading_or_refreshing() {
render! { Loader {} }
}
div { class: "flex items-center justify-center",
nix_info.render_with(cx, |v| render! { NixInfoView { info: v.clone() } })
Expand Down
196 changes: 91 additions & 105 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ mod widget;

use dioxus::prelude::*;
use dioxus_router::prelude::*;
use nix_rs::flake::url::FlakeUrl;

use crate::app::{
flake::{Flake, FlakeRaw},
health::Health,
info::Info,
state::AppState,
widget::{Loader, Scrollable},
widget::{Loader, RefreshButton},
};

#[derive(Routable, PartialEq, Debug, Clone)]
Expand All @@ -26,8 +27,6 @@ enum Route {
#[layout(Wrapper)]
#[route("/")]
Dashboard {},
#[route("/about")]
About {},
#[route("/flake")]
Flake {},
#[route("/flake/raw")]
Expand All @@ -42,136 +41,123 @@ enum Route {
pub fn App(cx: Scope) -> Element {
AppState::provide_state(cx);
render! {
body {
div { class: "flex flex-col text-center justify-between w-full overflow-hidden h-screen bg-base-200",
Router::<Route> {}
}
}
body { class: "bg-base-100 overflow-hidden", Router::<Route> {} }
}
}

fn Wrapper(cx: Scope) -> Element {
render! {
Nav {}
Scrollable {
div { class: "m-2 py-2", Outlet::<Route> {} }
div { class: "flex flex-col text-center justify-between w-full h-screen",
TopBar {}
div { class: "m-2 py-2 overflow-auto", Outlet::<Route> {} }
Footer {}
}
Footer {}
}
}

#[component]
fn Footer(cx: Scope) -> Element {
fn TopBar(cx: Scope) -> Element {
let state = AppState::use_state(cx);
let health_checks = state.health_checks.read();
let nix_info = state.nix_info.read();
render! {
footer { class: "flex flex-row justify-center w-full p-2 bg-primary-100",
a { href: "https://github.com/juspay/nix-browser", img { src: "images/128x128.png", class: "h-6" } }
div { class: "flex justify-between items-center w-full p-2 bg-primary-100 shadow",
div { class: "flex space-x-2",
Link { to: Route::Dashboard {}, "🏠" }
}
div { class: "flex space-x-2",
ViewRefreshButton {}
Link { to: Route::Health {},
span { title: "Nix Health Status",
match (*health_checks).current_value() {
Some(Ok(checks)) => render! {
if checks.iter().all(|check| check.result.green()) {
"✅"
} else {
"❌"
}
},
Some(Err(err)) => render! { "{err}" },
None => render! { Loader {} },
}
}
}
Link { to: Route::Info {},
span {
"Nix "
match (*nix_info).current_value() {
Some(Ok(info)) => render! {
"{info.nix_version} on {info.nix_env.os}"
},
Some(Err(err)) => render! { "{err}" },
None => render! { Loader {} },
}
}
}
}
}
}
}

// Home page
fn Dashboard(cx: Scope) -> Element {
tracing::debug!("Rendering Dashboard page");
/// Intended to refresh the data behind the current route.
#[component]
fn ViewRefreshButton(cx: Scope) -> Element {
let state = AppState::use_state(cx);
let health_checks = state.health_checks.read();
// A Card component
#[component]
fn Card<'a>(cx: Scope, href: Route, children: Element<'a>) -> Element<'a> {
render! {
Link {
to: "{href}",
class: "flex items-center justify-center w-48 h-48 p-2 m-2 border-2 rounded-lg shadow border-base-400 active:shadow-none bg-base-100 hover:bg-primary-200",
span { class: "text-3xl text-base-800", children }
}
}
}
let (busy, action) = match use_route(cx).unwrap() {
Route::Flake {} => Some((
state.flake.read().is_loading_or_refreshing(),
state::Action::RefreshFlake,
)),
Route::Health {} => Some((
state.health_checks.read().is_loading_or_refreshing(),
state::Action::GetNixInfo,
)),
Route::Info {} => Some((
state.nix_info.read().is_loading_or_refreshing(),
state::Action::GetNixInfo,
)),
_ => None,
}?;
render! {
div {
id: "cards",
class: "flex flex-row justify-center items-center flex-wrap",
Card { href: Route::Health {},
"Health "
match (*health_checks).current_value() {
Some(Ok(checks)) => render! {
if checks.iter().all(|check| check.result.green()) {
"✅"
} else {
"❌"
}
},
Some(Err(err)) => render! { "{err}" },
None => render! { Loader {} },
}
RefreshButton {
busy: busy,
handler: move |_| {
state.act(action);
}
Card { href: Route::Info {}, "Info ℹ️" }
Card { href: Route::Flake {}, "Flake ❄️️" }
}
}
}

/// Navigation bar
///
/// TODO Switch to breadcrumbs, as it simplifes the design overall.
fn Nav(cx: Scope) -> Element {
// Common class for all tabs
let class = "flex-grow block py-1.5 mx-1 text-center rounded-t-md";

// Active tab styling: Highlighted background and pronounced text color
let active_class = "bg-primary-200 font-bold text-black";

// Inactive tab styling: Muted background and text color
let inactive_class = "bg-gray-200 text-gray-600";

#[component]
fn Footer(cx: Scope) -> Element {
render! {
nav { class: "flex flex-row w-full bg-gray-100 border-b border-gray-300 pt-2",

Link {
to: Route::Dashboard {},
class: "{class} {inactive_class}",
active_class: active_class,
"Dashboard"
}
Link {
to: Route::Flake {},
class: "{class} {inactive_class}",
active_class: active_class,
"Flake"
}
Link {
to: Route::Health {},
class: "{class} {inactive_class}",
active_class: active_class,
"Nix Health"
}
Link {
to: Route::Info {},
class: "{class} {inactive_class}",
active_class: active_class,
"Nix Info"
}
Link {
to: Route::About {},
class: "{class} {inactive_class}",
active_class: active_class,
"About"
}
div { class: "flex-grow font-bold text-end px-3 py-1", "nix-browser" }
footer { class: "flex flex-row justify-center w-full bg-primary-100 p-2",
a { href: "https://github.com/juspay/nix-browser", img { src: "images/128x128.png", class: "h-4" } }
}
}
}

/// About page
fn About(cx: Scope) -> Element {
// Home page
fn Dashboard(cx: Scope) -> Element {
tracing::debug!("Rendering Dashboard page");
// TODO: Store and show user's recent flake visits
let suggestions = FlakeUrl::suggestions();
render! {
h1 { class: "text-5xl font-bold", "About" }
p {
"nix-browser is still work in progress. Track its development "
a {
href: "https://github.com/juspay/nix-browser",
class: "underline text-primary-500 hover:no-underline",
rel: "external",
target: "_blank",
"on Github"
div { class: "pl-4",
h2 { class: "text-2xl", "We have hand-picked some flakes for you to try out:" }
div { class: "flex flex-col",
for flake in suggestions {
a {
onclick: move |_| {
let state = AppState::use_state(cx);
let nav = use_navigator(cx);
state.flake_url.set(flake.clone());
nav.replace(Route::Flake {});
},
class: "cursor-pointer text-primary-600 underline hover:no-underline",
"{flake.clone()}"
}
}
}
}
}
Expand Down
Loading