Add dashboard view
This commit is contained in:
parent
55373eadeb
commit
88bfe974d0
@ -1,4 +1,5 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Menu {
|
||||
Home,
|
||||
Settings,
|
||||
}
|
||||
|
||||
@ -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>),
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
8
gui/src/app/view/message.rs
Normal file
8
gui/src/app/view/message.rs
Normal 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
108
gui/src/app/view/mod.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
97
gui/src/app/view/warning.rs
Normal file
97
gui/src/app/view/warning.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user