Add dashboard view
This commit is contained in:
parent
55373eadeb
commit
88bfe974d0
@ -1,4 +1,5 @@
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Menu {
|
pub enum Menu {
|
||||||
Home,
|
Home,
|
||||||
|
Settings,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
use minisafe::config::Config as DaemonConfig;
|
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 {
|
pub enum Message {
|
||||||
Reload,
|
|
||||||
Tick,
|
Tick,
|
||||||
Event(iced_native::Event),
|
Event(iced_native::Event),
|
||||||
Clipboard(String),
|
View(view::Message),
|
||||||
Menu(Menu),
|
|
||||||
LoadDaemonConfig(Box<DaemonConfig>),
|
LoadDaemonConfig(Box<DaemonConfig>),
|
||||||
DaemonConfigLoaded(Result<(), Error>),
|
DaemonConfigLoaded(Result<(), Error>),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ pub mod context;
|
|||||||
pub mod menu;
|
pub mod menu;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
pub mod view;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
|
|
||||||
@ -76,12 +77,12 @@ impl App {
|
|||||||
let res = self.context.load_daemon_config(*cfg);
|
let res = self.context.load_daemon_config(*cfg);
|
||||||
self.update(Message::DaemonConfigLoaded(res))
|
self.update(Message::DaemonConfigLoaded(res))
|
||||||
}
|
}
|
||||||
Message::Menu(menu) => {
|
Message::View(view::Message::Menu(menu)) => {
|
||||||
self.context.menu = menu;
|
self.context.menu = menu;
|
||||||
self.state = new_state(&self.context);
|
self.state = new_state(&self.context);
|
||||||
self.state.load(&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)) => {
|
Message::Event(Event::Window(window::Event::CloseRequested)) => {
|
||||||
self.stop();
|
self.stop();
|
||||||
Command::none()
|
Command::none()
|
||||||
@ -91,6 +92,6 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(&self) -> Element<Message> {
|
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::pure::{column, Element};
|
||||||
use iced::{Command, Subscription};
|
use iced::{Command, Subscription};
|
||||||
|
|
||||||
use super::{context::Context, message::Message};
|
use super::{context::Context, message::Message, view};
|
||||||
|
|
||||||
pub trait State {
|
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 update(&mut self, ctx: &Context, message: Message) -> Command<Message>;
|
||||||
fn subscription(&self) -> Subscription<Message> {
|
fn subscription(&self) -> Subscription<Message> {
|
||||||
Subscription::none()
|
Subscription::none()
|
||||||
@ -17,8 +17,8 @@ pub trait State {
|
|||||||
pub struct Home {}
|
pub struct Home {}
|
||||||
|
|
||||||
impl State for Home {
|
impl State for Home {
|
||||||
fn view(&self, _ctx: &Context) -> Element<Message> {
|
fn view<'a>(&self, ctx: &'a Context) -> Element<'a, view::Message> {
|
||||||
column().into()
|
view::dashboard(&ctx.menu, None, column())
|
||||||
}
|
}
|
||||||
fn update(&mut self, _ctx: &Context, _message: Message) -> Command<Message> {
|
fn update(&mut self, _ctx: &Context, _message: Message) -> Command<Message> {
|
||||||
Command::none()
|
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