Skip to content

Commit

Permalink
refactor: split state.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
srid committed Nov 7, 2023
1 parent 04e764d commit 2a1e8df
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 105 deletions.
6 changes: 3 additions & 3 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ fn ViewRefreshButton(cx: Scope) -> Element {
let (busy, action) = match use_route(cx).unwrap() {
Route::Flake {} => Some((
state.flake.read().is_loading_or_refreshing(),
state::Action::RefreshFlake,
state::action::Action::RefreshFlake,
)),
Route::Health {} => Some((
state.health_checks.read().is_loading_or_refreshing(),
state::Action::GetNixInfo,
state::action::Action::GetNixInfo,
)),
Route::Info {} => Some((
state.nix_info.read().is_loading_or_refreshing(),
state::Action::GetNixInfo,
state::action::Action::GetNixInfo,
)),
_ => None,
}?;
Expand Down
56 changes: 56 additions & 0 deletions src/app/state/action.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use dioxus::prelude::Scope;
use dioxus_signals::{use_signal, Signal};

/// An action to be performed on [AppState]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Action {
/// Refresh the [AppState::flake] signal using [AppState::flake_url] signal's current value
RefreshFlake,

/// Refresh [AppState::nix_info] signal
#[default]
GetNixInfo,
}

impl Action {
/// Return a [Signal] containing only the given [Action]
///
/// The signal value will be the [Action]'s index in the original signal.
pub fn signal_for<F>(cx: Scope, sig: Signal<(usize, Action)>, f: F) -> Signal<usize>
where
F: Fn(Action) -> bool + 'static,
{
signal_filter_map(
cx,
sig,
0,
move |(idx, action)| {
if f(action) {
Some(idx)
} else {
None
}
},
)
}
}

/// Like [std::iter::Iterator::filter_map] but applied on a dioxus [Signal]
///
/// Since `Signal`s always have a value, an `initial` value must be provided.
///
/// Upstream issue: https://github.com/DioxusLabs/dioxus/issues/1555
fn signal_filter_map<T, U, F>(cx: Scope, sig: Signal<T>, initial: U, f: F) -> Signal<U>
where
F: Fn(T) -> Option<U> + 'static,
T: Copy,
{
let res: Signal<U> = use_signal(cx, || initial);
dioxus_signals::use_effect(cx, move || {
let value = *sig.read();
if let Some(value) = f(value) {
res.set(value);
}
});
res
}
19 changes: 19 additions & 0 deletions src/app/state/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::fmt::Display;

/// Catch all error to use in UI components
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SystemError {
pub message: String,
}

impl Display for SystemError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}

impl From<String> for SystemError {
fn from(message: String) -> Self {
Self { message }
}
}
137 changes: 35 additions & 102 deletions src/app/state.rs → src/app/state/mod.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
//! Application state
pub mod action;
mod datum;

use std::fmt::Display;
mod error;

use dioxus::prelude::{use_context, use_context_provider, use_future, Scope};
use dioxus_signals::{use_signal, Signal};
use dioxus_signals::Signal;
use dioxus_std::storage::{storage, LocalStorage};
use nix_health::NixHealth;
use nix_rs::flake::{url::FlakeUrl, Flake};
use nix_rs::{
flake::{url::FlakeUrl, Flake},
info::NixInfo,
};

use self::datum::Datum;
use self::{action::Action, datum::Datum, error::SystemError};

/// Our dioxus application state is a struct of [Signal]
/// Our dioxus application state is a struct of [Signal]s that store app state.
///
/// They use [Datum] which is a glorified [Option] to distinguis between initial
/// They use [Datum] which is a glorified [Option] to distinguish between initial
/// loading and subsequent refreshing.
///
/// Use [Action] to mutate this state, in addition to [Signal::set].
#[derive(Default, Clone, Copy, Debug, PartialEq)]
pub struct AppState {
pub nix_info: Signal<Datum<Result<nix_rs::info::NixInfo, SystemError>>>,
pub nix_info: Signal<Datum<Result<NixInfo, SystemError>>>,
pub health_checks: Signal<Datum<Result<Vec<nix_health::traits::Check>, SystemError>>>,

/// User selected [FlakeUrl]
Expand All @@ -34,40 +37,6 @@ pub struct AppState {
pub action: Signal<(usize, Action)>,
}

/// An action to be performed on [AppState]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Action {
/// Refresh the [AppState::flake] signal using [AppState::flake_url] signal's current value
RefreshFlake,

/// Refresh [AppState::nix_info] signal
#[default]
GetNixInfo,
}

impl Action {
/// Return a [Signal] containing only the given [Action]
///
/// The signal value will be the [Action]'s index in the original signal.
pub fn signal_for<F>(cx: Scope, sig: Signal<(usize, Action)>, f: F) -> Signal<usize>
where
F: Fn(Action) -> bool + 'static,
{
signal_filter_map(
cx,
sig,
0,
move |(idx, action)| {
if f(action) {
Some(idx)
} else {
None
}
},
)
}
}

impl AppState {
fn new(cx: Scope) -> Self {
tracing::debug!("🔨 Creating AppState default value");
Expand All @@ -79,6 +48,18 @@ impl AppState {
}
}

/// Get the [AppState] from context
pub fn use_state(cx: Scope) -> Self {
*use_context::<Self>(cx).unwrap()
}

pub fn provide_state(cx: Scope) {
tracing::debug!("🏗️ Providing AppState");
let state = *use_context_provider(cx, || Self::new(cx));
// FIXME: Can we avoid calling build_network multiple times?
state.build_network(cx);
}

/// Perform an [Action] on the state
///
/// This eventuates an update on the appropriate signals the state holds.
Expand All @@ -89,18 +70,21 @@ impl AppState {
});
}

/// Get the [AppState] from context
pub fn use_state(cx: Scope) -> Self {
*use_context::<Self>(cx).unwrap()
/// Return the [String] representation of the current [AppState::flake_url] value. If there is none, return empty string.
pub fn get_flake_url_string(&self) -> String {
self.flake_url
.read()
.clone()
.map_or("".to_string(), |url| url.to_string())
}

pub fn provide_state(cx: Scope) {
tracing::debug!("🏗️ Providing AppState");
let state = *use_context_provider(cx, || Self::new(cx));
// FIXME: Can we avoid calling build_network multiple times?
state.build_network(cx);
pub fn set_flake_url(&self, url: FlakeUrl) {
tracing::info!("setting flake url to {}", &url);
self.flake_url.set(Some(url));
}
}

impl AppState {
/// Build the Signal network
///
/// If a signal's value is dependent on another signal's value, you must
Expand Down Expand Up @@ -165,7 +149,7 @@ impl AppState {
use_future(cx, (&idx,), |(idx,)| async move {
tracing::info!("Updating nix info [{}] ...", idx);
Datum::refresh_with(self.nix_info, async {
nix_rs::info::NixInfo::from_nix(&nix_rs::command::NixCmd::default())
NixInfo::from_nix(&nix_rs::command::NixCmd::default())
.await
.map_err(|e| SystemError {
message: format!("Error getting nix info: {:?}", e),
Expand All @@ -175,57 +159,6 @@ impl AppState {
});
}
}

/// Return the [String] representation of the current [AppState::flake_url] value. If there is none, return empty string.
pub fn get_flake_url_string(&self) -> String {
self.flake_url
.read()
.clone()
.map_or("".to_string(), |url| url.to_string())
}

pub fn set_flake_url(&self, url: FlakeUrl) {
tracing::info!("setting flake url to {}", &url);
self.flake_url.set(Some(url));
}
}

/// Catch all error to use in UI components
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SystemError {
pub message: String,
}

impl Display for SystemError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}

impl From<String> for SystemError {
fn from(message: String) -> Self {
Self { message }
}
}

/// Like [std::iter::Iterator::filter_map] but applied on a dioxus [Signal]
///
/// Since `Signal`s always have a value, an `initial` value must be provided.
///
/// Upstream issue: https://github.com/DioxusLabs/dioxus/issues/1555
fn signal_filter_map<T, U, F>(cx: Scope, sig: Signal<T>, initial: U, f: F) -> Signal<U>
where
F: Fn(T) -> Option<U> + 'static,
T: Copy,
{
let res: Signal<U> = use_signal(cx, || initial);
dioxus_signals::use_effect(cx, move || {
let value = *sig.read();
if let Some(value) = f(value) {
res.set(value);
}
});
res
}

/// Push an item to the front of a vector
Expand Down

0 comments on commit 2a1e8df

Please sign in to comment.