Add dashboard view

This commit is contained in:
edouard 2022-08-19 09:31:47 +02:00
parent 55373eadeb
commit 88bfe974d0
7 changed files with 225 additions and 12 deletions

View File

@ -1,4 +1,5 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Menu {
Home,
Settings,
}

View File

@ -1,14 +1,12 @@
use minisafe::config::Config as DaemonConfig;
use crate::app::{error::Error, menu::Menu};
use crate::app::{error::Error, view};
#[derive(Debug, Clone)]
#[derive(Debug)]
pub enum Message {
Reload,
Tick,
Event(iced_native::Event),
Clipboard(String),
Menu(Menu),
View(view::Message),
LoadDaemonConfig(Box<DaemonConfig>),
DaemonConfigLoaded(Result<(), Error>),
}

View File

@ -3,6 +3,7 @@ pub mod context;
pub mod menu;
pub mod message;
pub mod state;
pub mod view;
mod error;
@ -76,12 +77,12 @@ impl App {
let res = self.context.load_daemon_config(*cfg);
self.update(Message::DaemonConfigLoaded(res))
}
Message::Menu(menu) => {
Message::View(view::Message::Menu(menu)) => {
self.context.menu = menu;
self.state = new_state(&self.context);
self.state.load(&self.context)
}
Message::Clipboard(text) => clipboard::write(text),
Message::View(view::Message::Clipboard(text)) => clipboard::write(text),
Message::Event(Event::Window(window::Event::CloseRequested)) => {
self.stop();
Command::none()
@ -91,6 +92,6 @@ impl App {
}
pub fn view(&self) -> Element<Message> {
self.state.view(&self.context)
self.state.view(&self.context).map(Message::View)
}
}

View File

@ -1,10 +1,10 @@
use iced::pure::{column, Element};
use iced::{Command, Subscription};
use super::{context::Context, message::Message};
use super::{context::Context, message::Message, view};
pub trait State {
fn view(&self, ctx: &Context) -> Element<Message>;
fn view<'a>(&self, ctx: &'a Context) -> Element<'a, view::Message>;
fn update(&mut self, ctx: &Context, message: Message) -> Command<Message>;
fn subscription(&self) -> Subscription<Message> {
Subscription::none()
@ -17,8 +17,8 @@ pub trait State {
pub struct Home {}
impl State for Home {
fn view(&self, _ctx: &Context) -> Element<Message> {
column().into()
fn view<'a>(&self, ctx: &'a Context) -> Element<'a, view::Message> {
view::dashboard(&ctx.menu, None, column())
}
fn update(&mut self, _ctx: &Context, _message: Message) -> Command<Message> {
Command::none()

View File

@ -0,0 +1,8 @@
use crate::app::menu::Menu;
#[derive(Debug, Clone)]
pub enum Message {
Reload,
Clipboard(String),
Menu(Menu),
}

108
gui/src/app/view/mod.rs Normal file
View File

@ -0,0 +1,108 @@
mod message;
mod warning;
pub use message::Message;
use warning::warn;
use iced::{
pure::{column, container, row, scrollable, widget, Element},
Length,
};
use crate::ui::{
color,
component::{button, separation, text::*},
icon::{home_icon, settings_icon},
};
use crate::app::{error::Error, menu::Menu};
pub fn sidebar(menu: &Menu) -> widget::Container<Message> {
let home_button = if *menu == Menu::Home {
button::primary(Some(home_icon()), "Home")
.on_press(Message::Reload)
.width(iced::Length::Units(200))
} else {
button::transparent(Some(home_icon()), "Home")
.on_press(Message::Menu(Menu::Home))
.width(iced::Length::Units(200))
};
let settings_button = if *menu == Menu::Settings {
button::primary(Some(settings_icon()), "Settings")
.on_press(Message::Menu(Menu::Settings))
.width(iced::Length::Units(200))
} else {
button::transparent(Some(settings_icon()), "Settings")
.on_press(Message::Menu(Menu::Settings))
.width(iced::Length::Units(200))
};
container(
column()
.padding(10)
.push(
column()
.push(
column()
.push(container(text("Minisafe").bold()).padding(10))
.push(separation().width(Length::Units(200)))
.spacing(10),
)
.push(home_button)
.spacing(20)
.height(Length::Fill),
)
.push(container(settings_button).height(Length::Shrink)),
)
.style(SidebarStyle)
}
pub struct SidebarStyle;
impl widget::container::StyleSheet for SidebarStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
background: color::FOREGROUND.into(),
border_width: 1.0,
border_color: color::SECONDARY,
..widget::container::Style::default()
}
}
}
pub fn dashboard<'a, T: Into<Element<'a, Message>>>(
menu: &'a Menu,
warning: Option<&Error>,
content: T,
) -> Element<'a, Message> {
row()
.push(sidebar(menu).width(Length::Shrink).height(Length::Fill))
.push(
column().push(warn(warning)).push(
main_section(container(scrollable(content)))
.width(Length::Fill)
.height(Length::Fill),
),
)
.width(iced::Length::Fill)
.height(iced::Length::Fill)
.into()
}
fn main_section<'a, T: 'a>(menu: widget::Container<'a, T>) -> widget::Container<'a, T> {
container(menu.max_width(1500))
.padding(20)
.style(MainSectionStyle)
.center_x()
.width(Length::Fill)
.height(Length::Fill)
}
pub struct MainSectionStyle;
impl widget::container::StyleSheet for MainSectionStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
background: color::BACKGROUND.into(),
..widget::container::Style::default()
}
}
}

View File

@ -0,0 +1,97 @@
use std::convert::From;
use iced::{
pure::{column, container, row, widget},
Length,
};
use crate::{
app::error::Error,
daemon::{client::error::RpcErrorCode, DaemonError},
ui::{color, component::text::*, icon},
};
/// Simple warning message displayed to non technical user.
pub struct WarningMessage(String);
impl From<&Error> for WarningMessage {
fn from(error: &Error) -> WarningMessage {
match error {
Error::Config(e) => WarningMessage(e.to_owned()),
Error::Daemon(e) => match e {
DaemonError::Rpc(code, _) => {
if *code == RpcErrorCode::JSONRPC2_INVALID_PARAMS as i32 {
WarningMessage("Some fields are invalid".to_string())
} else {
WarningMessage("Internal error".to_string())
}
}
DaemonError::Unexpected(_) => WarningMessage("Unknown error".to_string()),
DaemonError::Start(_) => WarningMessage("Daemon failed to start".to_string()),
DaemonError::NoAnswer | DaemonError::Transport(..) => {
WarningMessage("Communication with Daemon failed".to_string())
}
},
Error::Unexpected(_) => WarningMessage("Unknown error".to_string()),
}
}
}
impl std::fmt::Display for WarningMessage {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
pub fn warn<'a, T: 'a>(error: Option<&Error>) -> widget::Container<'a, T> {
if let Some(w) = error {
let message: WarningMessage = w.into();
warning(&message.to_string(), &w.to_string()).width(Length::Fill)
} else {
container(column()).width(Length::Fill)
}
}
pub fn warning<'a, T: 'a>(message: &str, error: &str) -> widget::Container<'a, T> {
container(
widget::Tooltip::new(
row()
.push(icon::warning_icon())
.push(text(message))
.spacing(20),
error,
widget::tooltip::Position::Bottom,
)
.style(TooltipWarningStyle),
)
.padding(15)
.center_x()
.style(WarningStyle)
.width(Length::Fill)
}
struct WarningStyle;
impl widget::container::StyleSheet for WarningStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
border_radius: 0.0,
text_color: iced::Color::BLACK.into(),
background: color::WARNING.into(),
border_color: color::WARNING,
..widget::container::Style::default()
}
}
}
struct TooltipWarningStyle;
impl widget::container::StyleSheet for TooltipWarningStyle {
fn style(&self) -> widget::container::Style {
widget::container::Style {
border_radius: 0.0,
border_width: 1.0,
text_color: color::WARNING.into(),
background: color::FOREGROUND.into(),
border_color: color::WARNING,
}
}
}