mirror of
https://seed.radicle.dev/rad:z39mP9rQAaGmERfUMPULfPUi473tY.git
synced 2026-11-16 00:10:38 +08:00
Show / hide open issue form
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,6 +1,5 @@
|
||||
pub mod container;
|
||||
pub mod context;
|
||||
pub mod form;
|
||||
pub mod label;
|
||||
pub mod list;
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user