use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Clear, List, ListItem, Paragraph, Wrap};
use crate::collection::Collection;
use crate::environment::Environment;
use crate::i18n::Strings;
use crate::save_flow::{SaveFlow, SaveSource, SaveTargetKind, Step};
use super::app::{MouseHitTarget, MouseLayer, TuiApp};
use super::draw::*;
use super::editor::*;
use super::theme::*;
use crate::remote_flow::WorkspaceGitOrigin;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum GitSaveStage {
Connect,
ChoosePaths,
ChooseTarget,
CommitMessage,
Pushing,
Done,
Error,
}
pub(crate) struct GitSaveWizard {
pub(crate) flow: SaveFlow,
pub(crate) env: Option<Environment>,
pub(crate) url: Editor,
pub(crate) token: Editor,
pub(crate) collection_path: Editor,
pub(crate) env_path: Editor,
pub(crate) target_name: Editor,
pub(crate) commit_msg: Editor,
pub(crate) field: u8,
pub(crate) sel: Option<usize>,
}
impl GitSaveWizard {
pub(crate) fn new(ci: usize, col: &Collection, env: Option<Environment>) -> Self {
let flow = SaveFlow::for_collection(ci, col, env.as_ref());
Self::from_flow(flow, env)
}
pub(crate) fn new_workspace(ci: usize, col: &Collection, origin: &WorkspaceGitOrigin) -> Self {
Self::from_flow(SaveFlow::for_workspace(ci, col, origin), None)
}
pub(crate) fn new_report(report_idx: usize, report: &crate::report::Report) -> Self {
Self::from_flow(SaveFlow::for_report(report_idx, report), None)
}
fn from_flow(flow: SaveFlow, env: Option<Environment>) -> Self {
Self {
url: Editor::new(&flow.url, false),
token: Editor::blank(),
collection_path: Editor::new(&flow.path, false),
env_path: Editor::new(&flow.env_path, false),
target_name: Editor::new(&flow.target_name, false),
commit_msg: Editor::new(&flow.message, false),
field: 0,
sel: None,
env,
flow,
}
}
pub(crate) fn sync(&mut self) {
self.flow.url = self.url.text();
self.flow.token = self.token.text();
self.flow.path = self.collection_path.text();
self.flow.env_path = self.env_path.text();
self.flow.target_name = self.target_name.text();
self.flow.message = self.commit_msg.text();
}
pub(crate) fn stage(&self) -> GitSaveStage {
match self.flow.step() {
Step::Failed(_) => GitSaveStage::Error,
Step::Done => GitSaveStage::Done,
_ if self.flow.is_busy() && !self.on_ref_step() => GitSaveStage::Pushing,
Step::Connect => GitSaveStage::Connect,
Step::ChoosePaths => GitSaveStage::ChoosePaths,
Step::ChooseTarget => GitSaveStage::ChooseTarget,
Step::CommitMessage => GitSaveStage::CommitMessage,
}
}
fn on_ref_step(&self) -> bool {
matches!(self.flow.step(), Step::ChooseTarget)
}
pub(crate) fn has_env(&self) -> bool {
self.env.is_some() && self.flow.source.can_include_env()
}
pub(crate) fn error_text(&self) -> &str {
self.flow.error().unwrap_or_default()
}
}
pub(crate) fn draw_git_save_wizard_with_hits(
f: &mut Frame,
w: &GitSaveWizard,
s: &Strings,
th: &Theme,
app: Option<&TuiApp>,
) {
if let Some(app) = app {
app.set_mouse_layer(MouseLayer::Overlay);
}
let title = s.git_save_title;
match w.stage() {
GitSaveStage::Connect => {
let field = w.field;
let fields = [
(s.git_url_label, s.git_url_hint, &w.url, false),
(s.git_token_label, s.git_token_hint, &w.token, true),
];
let label_w = label_column(fields.iter().map(|(l, _, _, _)| *l));
let area = centered_rect(74, 4, f.area());
f.render_widget(Clear, area);
let block = panel_hinted(title.to_string(), s.git_connect_hint, th);
let inner = block.inner(area);
f.render_widget(block, area);
let rows =
Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).split(inner);
for (i, (label, placeholder, ed, mask)) in fields.iter().enumerate() {
let cols = Layout::horizontal([Constraint::Length(label_w), Constraint::Min(1)])
.split(rows[i]);
f.render_widget(
Paragraph::new(Span::styled(*label, Style::default().fg(th.accent))),
cols[0],
);
render_line_field_hinted(f, cols[1], ed, field == i as u8, *mask, placeholder, th);
if let Some(app) = app {
app.push_mouse_hit(
MouseLayer::Overlay,
rows[i],
MouseHitTarget::GitSaveWizardRow(i),
);
}
}
}
GitSaveStage::ChoosePaths => {
let field = w.field;
let rows_n: u16 = if w.has_env() { 8 } else { 4 };
let area = centered_rect(76, rows_n + 3, f.area());
f.render_widget(Clear, area);
let block = panel_hinted(title.to_string(), s.git_save_step_hint, th);
let inner = block.inner(area);
f.render_widget(block, area);
let mut constraints = vec![Constraint::Length(1), Constraint::Length(1)]; if w.has_env() {
constraints.push(Constraint::Length(1)); constraints.push(Constraint::Length(1)); if w.flow.include_env {
constraints.push(Constraint::Length(1)); constraints.push(Constraint::Length(1)); }
}
let rows = Layout::vertical(constraints).split(inner);
f.render_widget(
Paragraph::new(Span::styled(
if matches!(w.flow.source, SaveSource::Report { .. }) {
s.git_save_report_path_label
} else {
s.git_save_collection_path_label
},
Style::default().fg(th.accent),
)),
rows[0],
);
render_line_field(f, rows[1], &w.collection_path, field == 0, false, th);
if let Some(app) = app {
app.push_mouse_hit(
MouseLayer::Overlay,
rows[1],
MouseHitTarget::GitSaveWizardRow(0),
);
}
if w.has_env() {
let mark = if w.flow.include_env { "[x]" } else { "[ ]" };
let cb_style = if field == 1 {
Style::default()
.bg(th.accent)
.fg(th.bg)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(th.text)
};
f.render_widget(
Paragraph::new(Line::styled(
format!("{mark} {}", s.git_save_include_env_label),
cb_style,
)),
rows[3],
);
if let Some(app) = app {
app.push_mouse_hit(
MouseLayer::Overlay,
rows[3],
MouseHitTarget::GitSaveWizardRow(1),
);
}
if w.flow.include_env {
f.render_widget(
Paragraph::new(Span::styled(
s.git_save_env_path_label,
Style::default().fg(th.accent),
)),
rows[4],
);
render_line_field(f, rows[5], &w.env_path, field == 2, false, th);
if let Some(app) = app {
app.push_mouse_hit(
MouseLayer::Overlay,
rows[5],
MouseHitTarget::GitSaveWizardRow(2),
);
}
}
}
}
GitSaveStage::ChooseTarget => {
let sel = w.sel;
let refs = w.flow.refs();
let branches = &refs.branches;
let dropdown_rows = if sel.is_some() {
branches.len().min(5) as u16
} else {
0
};
let area = centered_rect(74, 5 + dropdown_rows, f.area());
f.render_widget(Clear, area);
let block = panel_hinted(title.to_string(), s.git_save_target_hint, th);
let inner = block.inner(area);
f.render_widget(block, area);
let rows = Layout::vertical([
Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), Constraint::Length(dropdown_rows), ])
.split(inner);
let (branch_style, tag_style) = if w.flow.target_kind == SaveTargetKind::Branch {
(
Style::default()
.bg(th.accent)
.fg(th.bg)
.add_modifier(Modifier::BOLD),
Style::default().fg(th.dim),
)
} else {
(
Style::default().fg(th.dim),
Style::default()
.bg(th.accent)
.fg(th.bg)
.add_modifier(Modifier::BOLD),
)
};
f.render_widget(
Paragraph::new(Line::from(vec![
Span::styled(format!(" {} ", s.git_save_branch_label), branch_style),
Span::raw(" "),
Span::styled(format!(" {} ", s.git_save_tag_label), tag_style),
])),
rows[0],
);
if let Some(app) = app {
app.push_mouse_hit(
MouseLayer::Overlay,
rows[0],
MouseHitTarget::GitSaveWizardRow(0),
);
}
f.render_widget(Paragraph::new(Span::raw("")), rows[1]);
render_line_field(f, rows[2], &w.target_name, sel.is_none(), false, th);
if let Some(app) = app {
app.push_mouse_hit(
MouseLayer::Overlay,
rows[2],
MouseHitTarget::GitSaveWizardRow(1),
);
}
if dropdown_rows > 0 {
let items: Vec<ListItem> = branches
.iter()
.take(5)
.enumerate()
.map(|(i, b)| {
let style = if sel == Some(i) {
Style::default()
.bg(th.accent)
.fg(th.bg)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(th.dim)
};
ListItem::new(Line::styled(b.clone(), style))
})
.collect();
f.render_widget(List::new(items), rows[3]);
if let Some(app) = app {
for i in 0..branches.len().min(5) {
app.push_mouse_hit(
MouseLayer::Overlay,
Rect::new(rows[3].x, rows[3].y + i as u16, rows[3].width, 1),
MouseHitTarget::GitSaveWizardRow(10 + i),
);
}
}
}
}
GitSaveStage::CommitMessage => {
let area = centered_rect(74, 3, f.area());
f.render_widget(Clear, area);
let block = panel_hinted(title.to_string(), s.git_save_commit_msg_hint, th);
let inner = block.inner(area);
f.render_widget(block, area);
let label_w = label_column([s.git_save_commit_msg_label]);
let cols =
Layout::horizontal([Constraint::Length(label_w), Constraint::Min(1)]).split(inner);
f.render_widget(
Paragraph::new(Span::styled(
s.git_save_commit_msg_label,
Style::default().fg(th.accent),
)),
cols[0],
);
render_line_field(f, cols[1], &w.commit_msg, true, false, th);
if let Some(app) = app {
app.push_mouse_hit(
MouseLayer::Overlay,
inner,
MouseHitTarget::GitSaveWizardRow(0),
);
}
}
GitSaveStage::Pushing => {
let width = (s.git_save_pushing.len() as u16 + 4).min(f.area().width);
let area = centered_rect(width, 3, f.area());
f.render_widget(Clear, area);
let block = panel(title.to_string(), true, th);
let inner = block.inner(area);
f.render_widget(block, area);
f.render_widget(
Paragraph::new(Span::styled(
s.git_save_pushing,
Style::default().fg(th.text).add_modifier(Modifier::BOLD),
)),
inner,
);
}
GitSaveStage::Done => {
let width = (s.git_save_success.len() as u16 + 4).min(f.area().width);
let area = centered_rect(width, 3, f.area());
f.render_widget(Clear, area);
let block = panel(title.to_string(), true, th);
let inner = block.inner(area);
f.render_widget(block, area);
f.render_widget(
Paragraph::new(Span::styled(
s.git_save_success,
Style::default().fg(th.text).add_modifier(Modifier::BOLD),
)),
inner,
);
}
GitSaveStage::Error => {
let e = w.error_text();
let width = (f.area().width * 6 / 10).max(40);
let area = centered_rect(width, 7, f.area());
f.render_widget(Clear, area);
let block = panel_hinted(title.to_string(), s.git_error_hint, th);
let inner = block.inner(area);
f.render_widget(block, area);
f.render_widget(
Paragraph::new(e.to_string())
.style(Style::default().fg(th.err))
.wrap(Wrap { trim: true }),
inner,
);
}
}
}