Show / hide open issue form

This commit is contained in:
Erik Kundt
2023-08-11 11:43:54 +02:00
parent cb2490745e
commit d50f02f46b
7 changed files with 178 additions and 30 deletions
+3
View File
@@ -47,6 +47,7 @@ pub enum IssueCid {
List,
Details,
Context,
NewForm,
Shortcuts,
}
@@ -69,6 +70,8 @@ pub enum IssueMessage {
Show(IssueId),
Changed(IssueId),
Focus(IssueCid),
OpenPopup(IssueCid),
ClosePopup(IssueCid),
Leave,
}
+16
View File
@@ -78,6 +78,11 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<issue::LargeList> {
Event::Keyboard(KeyEvent {
code: Key::Enter, ..
}) => Some(Message::Issue(IssueMessage::Focus(IssueCid::Details))),
Event::Keyboard(KeyEvent {
code: Key::Char('o'), ..
}) => {
Some(Message::Issue(IssueMessage::OpenPopup(IssueCid::NewForm)))
}
_ => None,
}
}
@@ -104,6 +109,17 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<issue::IssueDetails> {
}
}
impl tuirealm::Component<Message, NoUserEvent> for Widget<issue::NewForm> {
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
match event {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
Some(Message::Issue(IssueMessage::ClosePopup(IssueCid::NewForm)))
}
_ => None,
}
}
}
impl tuirealm::Component<Message, NoUserEvent> for Widget<PatchBrowser> {
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
match event {
+19 -1
View File
@@ -427,6 +427,7 @@ impl ViewPage for IssuePage {
app.umount(&Cid::Issue(IssueCid::List))?;
app.umount(&Cid::Issue(IssueCid::Details))?;
app.umount(&Cid::Issue(IssueCid::Context))?;
app.umount(&Cid::Issue(IssueCid::NewForm))?;
app.umount(&Cid::Issue(IssueCid::Shortcuts))?;
Ok(())
}
@@ -457,6 +458,17 @@ impl ViewPage for IssuePage {
self.activate(app, cid)?;
self.update_shortcuts(app, self.active_component.clone())?;
}
Message::Issue(IssueMessage::OpenPopup(cid)) => {
if cid == IssueCid::NewForm {
let new_form = widget::issue::new_form(context, theme).to_boxed();
app.remount(Cid::Issue(IssueCid::NewForm), new_form, vec![])?;
app.active(&Cid::Issue(IssueCid::NewForm))?;
}
}
Message::Issue(IssueMessage::ClosePopup(cid)) => {
app.blur()?;
app.umount(&Cid::Issue(cid))?;
}
_ => {}
}
@@ -472,7 +484,13 @@ impl ViewPage for IssuePage {
app.view(&Cid::Issue(IssueCid::Header), frame, layout.header);
app.view(&Cid::Issue(IssueCid::List), frame, layout.left);
app.view(&Cid::Issue(IssueCid::Details), frame, layout.right);
if app.mounted(&Cid::Issue(IssueCid::NewForm)) {
app.view(&Cid::Issue(IssueCid::NewForm), frame, layout.right);
} else {
app.view(&Cid::Issue(IssueCid::Details), frame, layout.right);
}
app.view(&Cid::Issue(IssueCid::Context), frame, layout.context);
app.view(&Cid::Issue(IssueCid::Shortcuts), frame, layout.shortcuts);
}
+48
View File
@@ -83,3 +83,51 @@ impl From<&ItemState> for ListState {
state
}
}
#[derive(Clone)]
pub struct FormState {
focus: Option<usize>,
len: usize,
}
impl FormState {
pub fn new(focus: Option<usize>, len: usize) -> Self {
Self { focus, len }
}
pub fn focus(&self) -> Option<usize> {
self.focus
}
pub fn focus_previous(&mut self) -> Option<usize> {
let old_index = self.focus();
let new_index = match old_index {
Some(focus) if focus == 0 => Some(0),
Some(focus) => Some(focus.saturating_sub(1)),
None => Some(0),
};
if old_index != new_index {
self.focus = new_index;
self.focus()
} else {
None
}
}
pub fn focus_next(&mut self) -> Option<usize> {
let old_index = self.focus();
let new_index = match old_index {
Some(focus) if focus >= self.len.saturating_sub(1) => Some(self.len.saturating_sub(1)),
Some(focus) => Some(focus.saturating_add(1)),
None => Some(0),
};
if old_index != new_index {
self.focus = new_index;
self.focus()
} else {
None
}
}
}
-1
View File
@@ -1,6 +1,5 @@
pub mod container;
pub mod context;
pub mod form;
pub mod label;
pub mod list;
-28
View File
@@ -1,28 +0,0 @@
use std::collections::HashMap;
use tuirealm::command::{CmdResult, Cmd};
use tuirealm::tui::layout::Rect;
use tuirealm::{MockComponent, Props, Frame, State};
use crate::ui::widget::WidgetComponent;
struct Form {
inputs: HashMap<String, Box<dyn MockComponent>>,
}
impl Form {
}
impl WidgetComponent for Form {
fn view(&mut self, _properties: &Props, _frame: &mut Frame, _area: Rect) {}
fn state(&self) -> State {
State::None
}
fn perform(&mut self, _properties: &Props, _cmd: Cmd) -> CmdResult {
CmdResult::None
}
}
+92
View File
@@ -3,6 +3,10 @@ use radicle::cob::thread::CommentId;
use radicle::cob::issue::Issue;
use radicle::cob::issue::IssueId;
use tui_realm_stdlib::Input;
use tuirealm::props::BorderType;
use tuirealm::props::Borders;
use tuirealm::props::Style;
use tuirealm::tui::layout::Constraint;
use tuirealm::tui::layout::Direction;
use tuirealm::tui::layout::Layout;
@@ -19,6 +23,7 @@ use super::Widget;
use crate::ui::cob;
use crate::ui::cob::IssueItem;
use crate::ui::context::Context;
use crate::ui::state::FormState;
use crate::ui::theme::Theme;
use super::*;
@@ -244,6 +249,88 @@ impl WidgetComponent for CommentBody {
}
}
pub struct NewForm {
///
_issue: Issue,
// This form's fields: title, tags, assignees, description.
inputs: Vec<Input>,
/// State that holds the current focus etc.
state: FormState,
}
impl NewForm {
pub fn new(_context: &Context, theme: &Theme) -> Self {
let foreground = theme.colors.default_fg;
let placeholder_style = Style::default().fg(theme.colors.container_border_fg);
let inactive_style = Style::default().fg(theme.colors.container_border_fg);
let borders = Borders::default()
.modifiers(BorderType::Rounded)
.color(theme.colors.container_border_focus_fg);
let title = Input::default()
.foreground(foreground)
.borders(borders.clone())
.inactive(inactive_style)
.placeholder("Title", placeholder_style);
let tags = Input::default()
.foreground(foreground)
.borders(borders.clone())
.inactive(inactive_style)
.placeholder("Tags", placeholder_style);
let assignees = Input::default()
.foreground(foreground)
.borders(borders.clone())
.inactive(inactive_style)
.placeholder("Assignees", placeholder_style);
let description = Input::default()
.foreground(foreground)
.borders(borders)
.inactive(inactive_style)
.placeholder("Description", placeholder_style);
let state = FormState::new(Some(0), 4);
Self {
_issue: Issue::default(),
inputs: vec![title, tags, assignees, description],
state,
}
}
}
impl WidgetComponent for NewForm {
fn view(&mut self, _properties: &Props, frame: &mut Frame, area: Rect) {
let focus = self.state.focus().unwrap_or(0);
if let Some(input) = self.inputs.get_mut(focus) {
input.attr(Attribute::Focus, AttrValue::Flag(true));
}
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Min(3),
])
.split(area);
for (index, area) in layout.iter().enumerate().take(self.inputs.len()) {
if let Some(input) = self.inputs.get_mut(index) {
input.view(frame, *area);
}
}
}
fn state(&self) -> State {
State::None
}
fn perform(&mut self, _properties: &Props, _cmd: Cmd) -> CmdResult {
CmdResult::None
}
}
pub fn list(context: &Context, theme: &Theme, issue: (IssueId, Issue)) -> Widget<LargeList> {
let list = LargeList::new(context, theme, Some(issue));
@@ -264,6 +351,11 @@ pub fn description(
Widget::new(body)
}
pub fn new_form(context: &Context, theme: &Theme) -> Widget<NewForm> {
let form = NewForm::new(context, theme);
Widget::new(form)
}
pub fn details(
context: &Context,
theme: &Theme,