Compare commits
2 Commits
05a9669fec
...
47752720b4
Author | SHA1 | Date | |
---|---|---|---|
|
47752720b4 | ||
|
a7f509b165 |
@ -26,5 +26,5 @@ pub struct Opt {
|
|||||||
fn main() {
|
fn main() {
|
||||||
//parse parameter
|
//parse parameter
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
ui::Ui::run(&opt);
|
ui::create_ui(&opt);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use std::sync::mpsc;
|
|
||||||
use std::{io, thread};
|
use std::{io, thread};
|
||||||
|
|
||||||
|
use crate::Opt;
|
||||||
use termion::event::Key;
|
use termion::event::Key;
|
||||||
use termion::input::TermRead;
|
|
||||||
use termion::raw::IntoRawMode;
|
use termion::raw::IntoRawMode;
|
||||||
use tui::backend::TermionBackend;
|
use tui::backend::TermionBackend;
|
||||||
use tui::layout::{Constraint, Direction, Layout};
|
use tui::layout::{Constraint, Direction, Layout};
|
||||||
@ -12,7 +11,6 @@ use crate::widget::info;
|
|||||||
use crate::widget::repo_entry;
|
use crate::widget::repo_entry;
|
||||||
use crate::widget::service_switcher;
|
use crate::widget::service_switcher;
|
||||||
use crate::widget::tag_list;
|
use crate::widget::tag_list;
|
||||||
use crate::Opt;
|
|
||||||
|
|
||||||
pub struct Ui {
|
pub struct Ui {
|
||||||
state: State,
|
state: State,
|
||||||
@ -56,7 +54,7 @@ impl Ui {
|
|||||||
state: State::SelectService,
|
state: State::SelectService,
|
||||||
repo: repo_entry::RepoEntry::new(repo_id),
|
repo: repo_entry::RepoEntry::new(repo_id),
|
||||||
tags: tag_list::TagList::with_status("Tags are empty"),
|
tags: tag_list::TagList::with_status("Tags are empty"),
|
||||||
services: service_switcher::ServiceSwitcher::new(&opt.config),
|
services: service_switcher::ServiceSwitcher::new(&opt.config).unwrap(),
|
||||||
info: info::Info::new("Select image of edit Repository"),
|
info: info::Info::new("Select image of edit Repository"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -70,7 +68,7 @@ impl Ui {
|
|||||||
let mut terminal = Terminal::new(backend).unwrap();
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
|
|
||||||
//setup input thread
|
//setup input thread
|
||||||
let receiver = ui.spawn_stdin_channel();
|
let receiver = super::spawn_stdin_channel();
|
||||||
|
|
||||||
//core interaction loop
|
//core interaction loop
|
||||||
'core: loop {
|
'core: loop {
|
||||||
@ -90,10 +88,10 @@ impl Ui {
|
|||||||
)
|
)
|
||||||
.split(rect.size());
|
.split(rect.size());
|
||||||
|
|
||||||
let (list, state) = ui.services.render(&ui.state);
|
let (list, state) = ui.services.render(ui.state == State::SelectService);
|
||||||
rect.render_stateful_widget(list, chunks[0], state);
|
rect.render_stateful_widget(list, chunks[0], state);
|
||||||
rect.render_widget(ui.repo.render(&ui.state), chunks[1]);
|
rect.render_widget(ui.repo.render(ui.state == State::EditRepo), chunks[1]);
|
||||||
let (list, state) = ui.tags.render(&ui.state);
|
let (list, state) = ui.tags.render(ui.state == State::SelectTag);
|
||||||
rect.render_stateful_widget(list, chunks[2], state);
|
rect.render_stateful_widget(list, chunks[2], state);
|
||||||
rect.render_widget(ui.info.render(), chunks[3]);
|
rect.render_widget(ui.info.render(), chunks[3]);
|
||||||
})
|
})
|
||||||
@ -145,22 +143,24 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
Ok(Key::Char(key)) => {
|
Ok(Key::Char(key)) => match ui.state {
|
||||||
if ui.state == State::EditRepo {
|
State::SelectService => (),
|
||||||
|
State::EditRepo => {
|
||||||
ui.info.set_info("Editing Repository");
|
ui.info.set_info("Editing Repository");
|
||||||
|
ui.repo.handle_input(Key::Char(key));
|
||||||
}
|
}
|
||||||
ui.repo.handle_input(&ui.state, Key::Char(key));
|
State::SelectTag => (),
|
||||||
ui.tags.handle_input(&ui.state, Key::Char(key));
|
},
|
||||||
}
|
Ok(Key::Backspace) => match ui.state {
|
||||||
Ok(Key::Backspace) => {
|
State::SelectService => (),
|
||||||
if ui.state == State::EditRepo {
|
State::EditRepo => {
|
||||||
ui.info.set_info("Editing Repository");
|
ui.info.set_info("Editing Repository");
|
||||||
|
ui.repo.handle_input(Key::Backspace);
|
||||||
}
|
}
|
||||||
ui.repo.handle_input(&ui.state, Key::Backspace);
|
State::SelectTag => (),
|
||||||
ui.tags.handle_input(&ui.state, Key::Backspace);
|
},
|
||||||
}
|
Ok(Key::Up) => match ui.state {
|
||||||
Ok(Key::Up) => {
|
State::SelectService if ui.services.find_previous_match() => {
|
||||||
if ui.state == State::SelectService && ui.services.find_previous_match() {
|
|
||||||
match ui.services.extract_repo() {
|
match ui.services.extract_repo() {
|
||||||
Err(e) => ui.info.set_info(&format!("{}", e)),
|
Err(e) => ui.info.set_info(&format!("{}", e)),
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
@ -176,9 +176,10 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui.tags.handle_input(&ui.state, Key::Up);
|
State::SelectService => (),
|
||||||
ui.repo.handle_input(&ui.state, Key::Up);
|
State::EditRepo => (),
|
||||||
}
|
State::SelectTag => ui.tags.handle_input(Key::Up),
|
||||||
|
},
|
||||||
Ok(Key::Down) => match ui.state {
|
Ok(Key::Down) => match ui.state {
|
||||||
State::SelectService if ui.services.find_next_match() => {
|
State::SelectService if ui.services.find_next_match() => {
|
||||||
match ui.services.extract_repo() {
|
match ui.services.extract_repo() {
|
||||||
@ -196,15 +197,10 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
State::SelectService => (),
|
||||||
ui.tags.handle_input(&ui.state, Key::Down);
|
State::EditRepo => (),
|
||||||
ui.repo.handle_input(&ui.state, Key::Down);
|
State::SelectTag => ui.tags.handle_input(Key::Down),
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Ok(key) => {
|
|
||||||
ui.repo.handle_input(&ui.state, Key::Down);
|
|
||||||
ui.tags.handle_input(&ui.state, key);
|
|
||||||
}
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,18 +210,4 @@ impl Ui {
|
|||||||
|
|
||||||
terminal.clear().unwrap();
|
terminal.clear().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// create a thread for catching input and send them to core loop
|
|
||||||
pub fn spawn_stdin_channel(&self) -> mpsc::Receiver<termion::event::Key> {
|
|
||||||
let (tx, rx) = mpsc::channel::<termion::event::Key>();
|
|
||||||
|
|
||||||
thread::spawn(move || loop {
|
|
||||||
let stdin = io::stdin();
|
|
||||||
for c in stdin.keys() {
|
|
||||||
tx.send(c.unwrap()).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread::sleep(std::time::Duration::from_millis(64));
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
}
|
}
|
32
src/ui/mod.rs
Normal file
32
src/ui/mod.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
mod default;
|
||||||
|
mod no_yaml;
|
||||||
|
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::{io, thread};
|
||||||
|
|
||||||
|
use crate::Opt;
|
||||||
|
use termion::input::TermRead;
|
||||||
|
|
||||||
|
use crate::widget::service_switcher;
|
||||||
|
|
||||||
|
pub fn create_ui(opt: &Opt) {
|
||||||
|
let service_result = service_switcher::ServiceSwitcher::new(&opt.config);
|
||||||
|
match service_result {
|
||||||
|
None => no_yaml::NoYaml::run(opt),
|
||||||
|
Some(_) => default::Ui::run(opt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// create a thread for catching input and send them to core loop
|
||||||
|
pub fn spawn_stdin_channel() -> mpsc::Receiver<termion::event::Key> {
|
||||||
|
let (tx, rx) = mpsc::channel::<termion::event::Key>();
|
||||||
|
|
||||||
|
thread::spawn(move || loop {
|
||||||
|
let stdin = io::stdin();
|
||||||
|
for c in stdin.keys() {
|
||||||
|
tx.send(c.unwrap()).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread::sleep(std::time::Duration::from_millis(64));
|
||||||
|
rx
|
||||||
|
}
|
138
src/ui/no_yaml.rs
Normal file
138
src/ui/no_yaml.rs
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
use std::{io, thread};
|
||||||
|
|
||||||
|
use termion::event::Key;
|
||||||
|
use termion::raw::IntoRawMode;
|
||||||
|
use tui::backend::TermionBackend;
|
||||||
|
use tui::layout::{Constraint, Direction, Layout};
|
||||||
|
use tui::Terminal;
|
||||||
|
|
||||||
|
use crate::widget::info;
|
||||||
|
use crate::widget::repo_entry;
|
||||||
|
use crate::widget::tag_list;
|
||||||
|
use crate::Opt;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone)]
|
||||||
|
pub enum State {
|
||||||
|
EditRepo,
|
||||||
|
SelectTag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::iter::Iterator for State {
|
||||||
|
type Item = Self;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self {
|
||||||
|
State::EditRepo => *self = State::SelectTag,
|
||||||
|
State::SelectTag => *self = State::EditRepo,
|
||||||
|
}
|
||||||
|
Some(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NoYaml {
|
||||||
|
state: State,
|
||||||
|
repo: repo_entry::RepoEntry,
|
||||||
|
tags: tag_list::TagList,
|
||||||
|
info: info::Info,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoYaml {
|
||||||
|
pub fn run(opt: &Opt) {
|
||||||
|
let (repo, load_repo) = match &opt.repo {
|
||||||
|
None => (
|
||||||
|
repo_entry::RepoEntry::new(
|
||||||
|
"enter a repository or select one from docker-compose.yml",
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
Some(repo_id) => (repo_entry::RepoEntry::new(repo_id), true),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ui = NoYaml {
|
||||||
|
state: State::EditRepo,
|
||||||
|
repo,
|
||||||
|
tags: tag_list::TagList::with_status("Tags are empty"),
|
||||||
|
info: info::Info::new("edit the Repository widget to load tags"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// load tags if a repository was given thorugh paramter
|
||||||
|
if load_repo {
|
||||||
|
ui.tags = tag_list::TagList::with_repo(ui.repo.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
//setup tui
|
||||||
|
let stdout = io::stdout().into_raw_mode().unwrap();
|
||||||
|
let backend = TermionBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
|
|
||||||
|
//setup input thread
|
||||||
|
let receiver = super::spawn_stdin_channel();
|
||||||
|
|
||||||
|
//core interaction loop
|
||||||
|
'core: loop {
|
||||||
|
//draw
|
||||||
|
terminal
|
||||||
|
.draw(|rect| {
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Min(7),
|
||||||
|
Constraint::Length(2),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(rect.size());
|
||||||
|
|
||||||
|
rect.render_widget(ui.repo.render(ui.state == State::EditRepo), chunks[0]);
|
||||||
|
let (list, state) = ui.tags.render(ui.state == State::SelectTag);
|
||||||
|
rect.render_stateful_widget(list, chunks[1], state);
|
||||||
|
rect.render_widget(ui.info.render(), chunks[2]);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
//handle input
|
||||||
|
match receiver.try_recv() {
|
||||||
|
Ok(Key::Ctrl('q')) => break 'core,
|
||||||
|
Ok(Key::Char('\t')) => {
|
||||||
|
ui.state.next();
|
||||||
|
}
|
||||||
|
Ok(Key::Ctrl('r')) => {
|
||||||
|
ui.repo.confirm();
|
||||||
|
ui.tags = tag_list::TagList::with_repo(ui.repo.get());
|
||||||
|
}
|
||||||
|
Ok(Key::Ctrl('n')) => match ui.tags.next_page() {
|
||||||
|
Err(e) => ui.info.set_info(&format!("{}", e)),
|
||||||
|
Ok(_) => (),
|
||||||
|
},
|
||||||
|
Ok(Key::Ctrl('p')) => match ui.tags.prev_page() {
|
||||||
|
Err(e) => ui.info.set_info(&format!("{}", e)),
|
||||||
|
Ok(_) => (),
|
||||||
|
},
|
||||||
|
Ok(Key::Char('\n')) => match ui.state {
|
||||||
|
State::EditRepo => {
|
||||||
|
ui.repo.confirm();
|
||||||
|
ui.tags = tag_list::TagList::with_repo(ui.repo.get());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
Ok(Key::Char(key)) => match ui.state {
|
||||||
|
State::EditRepo => {
|
||||||
|
ui.info.set_info("Editing Repository");
|
||||||
|
ui.repo.handle_input(Key::Char(key));
|
||||||
|
}
|
||||||
|
State::SelectTag => {
|
||||||
|
ui.tags.handle_input(Key::Char(key));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
//sleep for 32ms (30 fps)
|
||||||
|
thread::sleep(std::time::Duration::from_millis(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.clear().unwrap();
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,6 @@ use tui::layout::Alignment;
|
|||||||
use tui::style::{Color, Style};
|
use tui::style::{Color, Style};
|
||||||
use tui::widgets::{Block, Borders, Paragraph};
|
use tui::widgets::{Block, Borders, Paragraph};
|
||||||
|
|
||||||
use crate::ui::State;
|
|
||||||
|
|
||||||
pub struct RepoEntry {
|
pub struct RepoEntry {
|
||||||
text: String,
|
text: String,
|
||||||
old_text: String,
|
old_text: String,
|
||||||
@ -29,13 +27,13 @@ impl RepoEntry {
|
|||||||
self.old_text = entry;
|
self.old_text = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&self, state: &crate::ui::State) -> Paragraph {
|
pub fn render(&self, colored: bool) -> Paragraph {
|
||||||
let title = match self.changed {
|
let title = match self.changed {
|
||||||
true => "Repository*",
|
true => "Repository*",
|
||||||
false => "Repository",
|
false => "Repository",
|
||||||
};
|
};
|
||||||
|
|
||||||
let border_style = if state == &crate::ui::State::EditRepo {
|
let border_style = if colored {
|
||||||
Style::default().fg(Color::Green)
|
Style::default().fg(Color::Green)
|
||||||
} else {
|
} else {
|
||||||
Style::default().fg(Color::Gray)
|
Style::default().fg(Color::Gray)
|
||||||
@ -52,11 +50,7 @@ impl RepoEntry {
|
|||||||
.alignment(Alignment::Left)
|
.alignment(Alignment::Left)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_input(&mut self, state: &State, key: termion::event::Key) {
|
pub fn handle_input(&mut self, key: termion::event::Key) {
|
||||||
if state != &State::EditRepo {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match key {
|
match key {
|
||||||
// Key::Char('\n') => self.confirm(), //handled in Ui
|
// Key::Char('\n') => self.confirm(), //handled in Ui
|
||||||
Key::Char(c) => {
|
Key::Char(c) => {
|
||||||
|
@ -9,7 +9,6 @@ use tui::style::{Color, Style};
|
|||||||
use tui::widgets::{Block, Borders, List, ListState};
|
use tui::widgets::{Block, Borders, List, ListState};
|
||||||
|
|
||||||
use crate::repo;
|
use crate::repo;
|
||||||
use crate::ui::State;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -34,7 +33,7 @@ pub struct ServiceSwitcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ServiceSwitcher {
|
impl ServiceSwitcher {
|
||||||
pub fn new(file: &Option<PathBuf>) -> Self {
|
pub fn new(file: &Option<PathBuf>) -> Option<Self> {
|
||||||
let mut file_list = vec![
|
let mut file_list = vec![
|
||||||
PathBuf::from("docker-compose.yml"),
|
PathBuf::from("docker-compose.yml"),
|
||||||
PathBuf::from("docker-compose.yaml"),
|
PathBuf::from("docker-compose.yaml"),
|
||||||
@ -55,25 +54,20 @@ impl ServiceSwitcher {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return Self {
|
return Some(Self {
|
||||||
list,
|
list,
|
||||||
state: ListState::default(),
|
state: ListState::default(),
|
||||||
changed: false,
|
changed: false,
|
||||||
opened_file: file,
|
opened_file: file,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//could not find docker-compose file
|
//could not find docker-compose file
|
||||||
Self {
|
None
|
||||||
list: vec![format!("No docker-compose file found")],
|
|
||||||
state: ListState::default(),
|
|
||||||
changed: false,
|
|
||||||
opened_file: PathBuf::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self, state: &State) -> (List, &mut ListState) {
|
pub fn render(&mut self, colored: bool) -> (List, &mut ListState) {
|
||||||
let border_style = if state == &State::SelectService {
|
let border_style = if colored {
|
||||||
Style::default().fg(Color::Green)
|
Style::default().fg(Color::Green)
|
||||||
} else {
|
} else {
|
||||||
Style::default().fg(Color::Gray)
|
Style::default().fg(Color::Gray)
|
||||||
|
@ -5,7 +5,6 @@ use tui::style::{Color, Style};
|
|||||||
use tui::widgets::{Block, Borders, List, ListState};
|
use tui::widgets::{Block, Borders, List, ListState};
|
||||||
|
|
||||||
use crate::tags;
|
use crate::tags;
|
||||||
use crate::ui::State;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -109,8 +108,8 @@ impl TagList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self, state: &State) -> (List, &mut ListState) {
|
pub fn render(&mut self, colored: bool) -> (List, &mut ListState) {
|
||||||
let border_style = if state == &State::SelectTag {
|
let border_style = if colored {
|
||||||
Style::default().fg(Color::Green)
|
Style::default().fg(Color::Green)
|
||||||
} else {
|
} else {
|
||||||
Style::default().fg(Color::Gray)
|
Style::default().fg(Color::Gray)
|
||||||
@ -144,11 +143,7 @@ impl TagList {
|
|||||||
(items, &mut self.state)
|
(items, &mut self.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_input(&mut self, state: &State, key: termion::event::Key) {
|
pub fn handle_input(&mut self, key: termion::event::Key) {
|
||||||
if state != &State::SelectTag {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match key {
|
match key {
|
||||||
Key::Down => self.next(),
|
Key::Down => self.next(),
|
||||||
Key::Up => self.previous(),
|
Key::Up => self.previous(),
|
||||||
|
Loading…
Reference in New Issue
Block a user