Handle change project. Start CLI

This commit is contained in:
Adrian Wozniak 2020-05-28 10:30:07 +02:00
parent f6c4c7c9a1
commit c0fcd62b69
11 changed files with 237 additions and 11 deletions

1
Cargo.lock generated
View File

@ -1587,6 +1587,7 @@ dependencies = [
"actix", "actix",
"actix-rt", "actix-rt",
"clap", "clap",
"termion",
"tui", "tui",
] ]

View File

@ -9,7 +9,8 @@ name = "jirs"
path = "./src/main.rs" path = "./src/main.rs"
[dependencies] [dependencies]
tui = { version = "0.9.5" } tui = { version = "0.9.5", features = ["termion"] }
clap = { version = "2.33.0" } clap = { version = "2.33.0" }
actix = { version = "0.9.0" } actix = { version = "0.9.0" }
actix-rt = { version = "1.0.0" } actix-rt = { version = "1.0.0" }
termion = { version = "*" }

View File

@ -1,3 +1,181 @@
pub fn main() { use std::sync::atomic::{AtomicBool, Ordering};
// tui::backend::CrosstermBackend::new(stdout()); use std::sync::{mpsc, Arc};
use std::time::Duration;
use std::{error::Error, io, thread};
use termion::input::TermRead;
use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen};
use tui::{
backend::TermionBackend,
layout::{Constraint, Direction, Layout},
style::{Color, Style},
widgets::{Block, Borders, Tabs},
Terminal,
};
#[derive(Debug, Clone, Copy)]
pub struct Config {
pub exit_key: Key,
pub tick_rate: Duration,
}
impl Default for Config {
fn default() -> Config {
Config {
exit_key: Key::Char('q'),
tick_rate: Duration::from_millis(250),
}
}
}
pub enum Event<I> {
Input(I),
Tick,
}
/// A small event handler that wrap termion input and tick events. Each event
/// type is handled in its own thread and returned to a common `Receiver`
pub struct Events {
rx: mpsc::Receiver<Event<Key>>,
input_handle: thread::JoinHandle<()>,
ignore_exit_key: Arc<AtomicBool>,
tick_handle: thread::JoinHandle<()>,
}
impl Events {
pub fn new() -> Events {
Events::with_config(Config::default())
}
pub fn with_config(config: Config) -> Events {
let (tx, rx) = mpsc::channel();
let ignore_exit_key = Arc::new(AtomicBool::new(false));
let input_handle = {
let tx = tx.clone();
let ignore_exit_key = ignore_exit_key.clone();
thread::spawn(move || {
let stdin = io::stdin();
for evt in stdin.keys() {
if let Ok(key) = evt {
if let Err(err) = tx.send(Event::Input(key)) {
eprintln!("{}", err);
return;
}
if !ignore_exit_key.load(Ordering::Relaxed) && key == config.exit_key {
return;
}
}
}
})
};
let tick_handle = {
thread::spawn(move || loop {
tx.send(Event::Tick).unwrap();
thread::sleep(config.tick_rate);
})
};
Events {
rx,
ignore_exit_key,
input_handle,
tick_handle,
}
}
pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
self.rx.recv()
}
pub fn disable_exit_key(&mut self) {
self.ignore_exit_key.store(true, Ordering::Relaxed);
}
pub fn enable_exit_key(&mut self) {
self.ignore_exit_key.store(false, Ordering::Relaxed);
}
}
pub struct TabsState<'a> {
pub titles: Vec<&'a str>,
pub index: usize,
}
impl<'a> TabsState<'a> {
pub fn new(titles: Vec<&'a str>) -> TabsState {
TabsState { titles, index: 0 }
}
pub fn next(&mut self) {
self.index = (self.index + 1) % self.titles.len();
}
pub fn previous(&mut self) {
if self.index > 0 {
self.index -= 1;
} else {
self.index = self.titles.len() - 1;
}
}
}
struct App<'a> {
tabs: TabsState<'a>,
}
fn main() -> Result<(), Box<dyn Error>> {
// Terminal initialization
let stdout = io::stdout().into_raw_mode()?;
let stdout = MouseTerminal::from(stdout);
let stdout = AlternateScreen::from(stdout);
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
terminal.hide_cursor()?;
let events = Events::new();
// App
let mut app = App {
tabs: TabsState::new(vec!["Tab0", "Tab1", "Tab2", "Tab3"]),
};
// Main loop
loop {
terminal.draw(|mut f| {
let size = f.size();
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(5)
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
.split(size);
let block = Block::default().style(Style::default().bg(Color::White));
f.render_widget(block, size);
let tabs = Tabs::default()
.block(Block::default().borders(Borders::ALL).title("Tabs"))
.titles(&app.tabs.titles)
.select(app.tabs.index)
.style(Style::default().fg(Color::Cyan))
.highlight_style(Style::default().fg(Color::Yellow));
f.render_widget(tabs, chunks[0]);
let inner = match app.tabs.index {
0 => Block::default().title("Inner 0").borders(Borders::ALL),
1 => Block::default().title("Inner 1").borders(Borders::ALL),
2 => Block::default().title("Inner 2").borders(Borders::ALL),
3 => Block::default().title("Inner 3").borders(Borders::ALL),
_ => unreachable!(),
};
f.render_widget(inner, chunks[1]);
})?;
if let Event::Input(input) = events.next()? {
match input {
Key::Char('q') => {
break;
}
Key::Right => app.tabs.next(),
Key::Left => app.tabs.previous(),
_ => {}
}
}
}
Ok(())
} }

View File

@ -1,10 +1,13 @@
use crate::model::{Model, Page, PageContent, ProfilePage};
use crate::ws::{enqueue_ws_msg, send_ws_msg};
use crate::{FieldId, Msg, PageChanged, ProfilePageChange, WebSocketChanged};
use jirs_data::{UsersFieldId, WsMsg};
use seed::prelude::{Method, Orders, Request}; use seed::prelude::{Method, Orders, Request};
use web_sys::FormData; use web_sys::FormData;
use jirs_data::{UsersFieldId, WsMsg};
use crate::model::{Model, Page, PageContent, ProfilePage};
use crate::shared::styled_select::StyledSelectChange;
use crate::ws::{enqueue_ws_msg, send_ws_msg};
use crate::{FieldId, Msg, PageChanged, ProfilePageChange, WebSocketChanged};
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) { pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
match msg { match msg {
Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(..))) Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(..)))
@ -65,6 +68,22 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
orders, orders,
); );
} }
Msg::StyledSelectChanged(
FieldId::Profile(UsersFieldId::CurrentProject),
StyledSelectChange::Changed(id),
) => {
if let Some(up) = model
.user_projects
.iter()
.find(|up| up.project_id == id as i32)
{
send_ws_msg(
WsMsg::UserProjectSetCurrent(up.id),
model.ws.as_ref(),
orders,
);
}
}
_ => (), _ => (),
} }
} }

View File

@ -1,9 +1,11 @@
use seed::prelude::Orders;
use jirs_data::{Issue, IssueFieldId, WsMsg};
use crate::model::{ModalType, Model, Page, PageContent, ProjectPage}; use crate::model::{ModalType, Model, Page, PageContent, ProjectPage};
use crate::shared::styled_select::StyledSelectChange; use crate::shared::styled_select::StyledSelectChange;
use crate::ws::{enqueue_ws_msg, send_ws_msg}; use crate::ws::{enqueue_ws_msg, send_ws_msg};
use crate::{BoardPageChange, EditIssueModalSection, FieldId, Msg, PageChanged, WebSocketChanged}; use crate::{BoardPageChange, EditIssueModalSection, FieldId, Msg, PageChanged, WebSocketChanged};
use jirs_data::{Issue, IssueFieldId, WsMsg};
use seed::prelude::Orders;
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) { pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
if model.user.is_none() { if model.user.is_none() {
@ -26,6 +28,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
match msg { match msg {
Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(..))) Msg::WebSocketChange(WebSocketChanged::WsMsg(WsMsg::AuthorizeLoaded(..)))
| Msg::ProjectChanged(Some(..))
| Msg::ChangePage(Page::Project) | Msg::ChangePage(Page::Project)
| Msg::ChangePage(Page::AddIssue) | Msg::ChangePage(Page::AddIssue)
| Msg::ChangePage(Page::EditIssue(..)) => { | Msg::ChangePage(Page::EditIssue(..)) => {

View File

@ -0,0 +1,5 @@
pub use update::update;
pub use view::view;
mod update;
mod view;

View File

View File

View File

@ -105,6 +105,16 @@ pub fn update(msg: &WsMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
model.current_user_project = v.iter().find(|up| up.is_current).cloned(); model.current_user_project = v.iter().find(|up| up.is_current).cloned();
init_current_project(model, orders); init_current_project(model, orders);
} }
WsMsg::UserProjectCurrentChanged(user_project) => {
let mut old = vec![];
std::mem::swap(&mut old, &mut model.user_projects);
for mut up in old {
up.is_current = up.id == user_project.id;
model.user_projects.push(up);
}
model.current_user_project = Some(user_project.clone());
init_current_project(model, orders);
}
// issues // issues
WsMsg::ProjectIssuesLoaded(v) => { WsMsg::ProjectIssuesLoaded(v) => {

View File

@ -21,7 +21,7 @@ use crate::ws::issue_statuses::*;
use crate::ws::issues::*; use crate::ws::issues::*;
use crate::ws::messages::*; use crate::ws::messages::*;
use crate::ws::projects::*; use crate::ws::projects::*;
use crate::ws::user_projects::LoadUserProjects; use crate::ws::user_projects::{LoadUserProjects, SetCurrentUserProject};
use crate::ws::users::*; use crate::ws::users::*;
pub mod auth; pub mod auth;
@ -128,6 +128,12 @@ impl WebSocketActor {
// user projects // user projects
WsMsg::UserProjectsLoad => self.handle_msg(LoadUserProjects, ctx)?, WsMsg::UserProjectsLoad => self.handle_msg(LoadUserProjects, ctx)?,
WsMsg::UserProjectSetCurrent(user_project_id) => self.handle_msg(
SetCurrentUserProject {
id: user_project_id,
},
ctx,
)?,
// auth // auth
WsMsg::AuthorizeRequest(uuid) => { WsMsg::AuthorizeRequest(uuid) => {

View File

@ -38,7 +38,10 @@ impl WsHandler<SetCurrentUserProject> for WebSocketActor {
user_id, user_id,
id: msg.id, id: msg.id,
})) { })) {
Ok(Ok(user_project)) => Ok(Some(WsMsg::UserProjectCurrentChanged(user_project))), Ok(Ok(user_project)) => {
self.current_user_project = Some(user_project.clone());
Ok(Some(WsMsg::UserProjectCurrentChanged(user_project)))
}
Ok(Err(e)) => { Ok(Err(e)) => {
error!("{:?}", e); error!("{:?}", e);
return Ok(None); return Ok(None);