Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Create issue
Erik Kundt committed 2 years ago
commit 12ee3fb8b1cedcb178f6ffa28b33a7e3ba943001
parent c79caff79ade0ac8e4ed43812c926834b785c1d1
7 files changed +145 -38
modified src/app.rs
@@ -48,7 +48,7 @@ pub enum IssueCid {
    List,
    Details,
    Context,
-
    NewForm,
+
    Form,
    Shortcuts,
}

@@ -67,13 +67,24 @@ pub enum Cid {
pub enum HomeMessage {}

#[derive(Clone, Debug, Eq, PartialEq)]
+
pub enum IssueCobMessage {
+
    Create {
+
        title: String,
+
        tags: String,
+
        assignees: String,
+
        description: String,
+
    },
+
}
+

+
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum IssueMessage {
    Show(IssueId),
    Changed(IssueId),
    Focus(IssueCid),
-
    OpenPopup(IssueCid),
-
    ClosePopup(IssueCid),
-
    New(String, String, String, String),
+
    Created(IssueId),
+
    Cob(IssueCobMessage),
+
    OpenForm,
+
    HideForm,
    Leave,
}

@@ -194,7 +205,27 @@ impl App {
                    _ => Ok(Some(Message::Batch(results))),
                }
            }
-
            Message::Issue(IssueMessage::New(_title, _tags, _assignees, _description)) => Ok(None),
+
            Message::Issue(IssueMessage::Cob(IssueCobMessage::Create {
+
                title,
+
                tags,
+
                assignees,
+
                description,
+
            })) => match self.create_issue(title, description, tags, assignees) {
+
                Ok(id) => {
+
                    self.context.reload();
+

+
                    Ok(Some(Message::Batch(vec![
+
                        Message::Issue(IssueMessage::HideForm),
+
                        Message::Issue(IssueMessage::Created(id)),
+
                    ])))
+
                }
+
                Err(err) => {
+
                    let error = format!("{:?}", err);
+
                    self.show_error_popup(app, &theme, &error)?;
+

+
                    Ok(None)
+
                }
+
            },
            Message::Issue(IssueMessage::Show(id)) => {
                self.view_issue(app, id, &theme)?;
                Ok(None)
@@ -283,6 +314,29 @@ impl App {

        Ok(())
    }
+

+
    fn create_issue(
+
        &mut self,
+
        title: String,
+
        description: String,
+
        tags: String,
+
        assignees: String,
+
    ) -> Result<IssueId> {
+
        let repository = self.context.repository();
+
        let signer = self.context.signer();
+

+
        let tags = cob::parse_tags(tags);
+
        let assignees = cob::parse_assigness(assignees);
+

+
        cob::issue::create(
+
            repository,
+
            signer,
+
            title,
+
            description,
+
            tags.as_slice(),
+
            assignees.as_slice(),
+
        )
+
    }
}

impl Tui<Cid, Message> for App {
modified src/app/event.rs
@@ -14,7 +14,7 @@ use radicle_tui::ui::widget::{issue, patch};

use radicle_tui::ui::widget::Widget;

-
use super::{IssueCid, IssueMessage, Message, PatchMessage, PopupMessage};
+
use super::{IssueCid, IssueMessage, Message, PatchMessage, PopupMessage, IssueCobMessage};

/// Since the framework does not know the type of messages that are being
/// passed around in the app, the following handlers need to be implemented for
@@ -81,10 +81,9 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<issue::LargeList> {
                code: Key::Enter, ..
            }) => Some(Message::Issue(IssueMessage::Focus(IssueCid::Details))),
            Event::Keyboard(KeyEvent {
-
                code: Key::Char('n'), ..
-
            }) => {
-
                Some(Message::Issue(IssueMessage::OpenPopup(IssueCid::NewForm)))
-
            }
+
                code: Key::Char('n'),
+
                ..
+
            }) => Some(Message::Issue(IssueMessage::OpenForm)),
            _ => None,
        }
    }
@@ -157,7 +156,7 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<issue::NewForm> {
            }
            Event::Keyboard(KeyEvent {
                code: Key::Char('s'),
-
                modifiers: KeyModifiers::ALT,
+
                modifiers: KeyModifiers::CONTROL,
            }) => {
                match self.perform(Cmd::Submit) {
                    CmdResult::Submit(State::Map(inputs)) => {
@@ -196,19 +195,19 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<issue::NewForm> {
                            let error = format!("Missing fields: {:?}", missing_values);
                            Some(Message::Popup(PopupMessage::Error(error)))
                        } else {
-
                            Some(Message::Issue(IssueMessage::New(
-
                                title.unwrap(),
-
                                tags.unwrap(),
-
                                assignees.unwrap(),
-
                                description.unwrap(),
-
                            )))
+
                            Some(Message::Issue(IssueMessage::Cob(IssueCobMessage::Create {
+
                                title: title.unwrap(),
+
                                tags: tags.unwrap(),
+
                                assignees: assignees.unwrap(),
+
                                description: description.unwrap(),
+
                            })))
                        }
                    }
                    _ => None,
                }
            }
            Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
-
                Some(Message::Issue(IssueMessage::ClosePopup(IssueCid::NewForm)))
+
                Some(Message::Issue(IssueMessage::HideForm))
            }
            Event::Keyboard(KeyEvent {
                code: Key::BackTab, ..
modified src/app/page.rs
@@ -330,13 +330,13 @@ impl IssuePage {
                ),
            ),
            (
-
                IssueCid::NewForm,
+
                IssueCid::Form,
                widget::common::shortcuts(
                    theme,
                    vec![
                        widget::common::shortcut(theme, "esc", "back"),
                        widget::common::shortcut(theme, "shift + tab / tab", "navigate"),
-
                        widget::common::shortcut(theme, "alt + s", "submit"),
+
                        widget::common::shortcut(theme, "ctrl + s", "submit"),
                    ],
                ),
            ),
@@ -439,8 +439,8 @@ impl ViewPage for IssuePage {
        app.umount(&Cid::Issue(IssueCid::List))?;
        app.umount(&Cid::Issue(IssueCid::Details))?;
        app.umount(&Cid::Issue(IssueCid::Context))?;
-
        if app.mounted(&Cid::Issue(IssueCid::NewForm)) {
-
            app.umount(&Cid::Issue(IssueCid::NewForm))?;
+
        if app.mounted(&Cid::Issue(IssueCid::Form)) {
+
            app.umount(&Cid::Issue(IssueCid::Form))?;
        }
        app.umount(&Cid::Issue(IssueCid::Shortcuts))?;
        Ok(())
@@ -454,6 +454,25 @@ impl ViewPage for IssuePage {
        message: Message,
    ) -> Result<Option<Message>> {
        match message {
+
            Message::Issue(IssueMessage::Created(id)) => {
+
                let repo = context.repository();
+

+
                if let Some(issue) = cob::issue::find(repo, &id)? {
+
                    let list = widget::issue::list(context, theme, (id, issue.clone())).to_boxed();
+
                    let comments = issue.comments().collect::<Vec<_>>();
+

+
                    let details = widget::issue::details(
+
                        context,
+
                        theme,
+
                        (id, issue.clone()),
+
                        comments.first().copied(),
+
                    )
+
                    .to_boxed();
+

+
                    app.remount(Cid::Issue(IssueCid::List), list, vec![])?;
+
                    app.remount(Cid::Issue(IssueCid::Details), details, vec![])?;
+
                }
+
            }
            Message::Issue(IssueMessage::Changed(id)) => {
                let repo = context.repository();
                if let Some(issue) = cob::issue::find(repo, &id)? {
@@ -472,20 +491,20 @@ 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(cid.clone()), new_form, vec![])?;
-
                    app.active(&Cid::Issue(cid.clone()))?;
+
            Message::Issue(IssueMessage::OpenForm) => {
+
                let new_form = widget::issue::new_form(context, theme).to_boxed();

-
                    app.unsubscribe(&Cid::GlobalListener, subscription::global_clause())?;
+
                app.remount(Cid::Issue(IssueCid::Form), new_form, vec![])?;
+
                app.active(&Cid::Issue(IssueCid::Form))?;

-
                    self.update_shortcuts(app, cid)?;
-
                }
+
                app.unsubscribe(&Cid::GlobalListener, subscription::global_clause())?;
+

+
                self.activate(app, IssueCid::Form)?;
+
                self.update_shortcuts(app, IssueCid::Form)?;
            }
-
            Message::Issue(IssueMessage::ClosePopup(cid)) => {
+
            Message::Issue(IssueMessage::HideForm) => {
                app.blur()?;
-
                app.umount(&Cid::Issue(cid))?;
+
                app.umount(&Cid::Issue(IssueCid::Form))?;

                app.subscribe(
                    &Cid::GlobalListener,
@@ -510,8 +529,8 @@ impl ViewPage for IssuePage {
        app.view(&Cid::Issue(IssueCid::Header), frame, layout.header);
        app.view(&Cid::Issue(IssueCid::List), frame, layout.left);

-
        if app.mounted(&Cid::Issue(IssueCid::NewForm)) {
-
            app.view(&Cid::Issue(IssueCid::NewForm), frame, layout.right);
+
        if app.mounted(&Cid::Issue(IssueCid::Form)) {
+
            app.view(&Cid::Issue(IssueCid::Form), frame, layout.right);
        } else {
            app.view(&Cid::Issue(IssueCid::Details), frame, layout.right);
        }
modified src/cob.rs
@@ -1,2 +1,12 @@
+
use radicle::cob::{ActorId, Tag};
+

pub mod issue;
pub mod patch;
+

+
pub fn parse_tags(tags: String) -> Vec<Tag> {
+
    vec![]
+
}
+

+
pub fn parse_assigness(assignees: String) -> Vec<ActorId> {
+
    vec![]
+
}
modified src/cob/issue.rs
@@ -1,5 +1,7 @@
use anyhow::Result;
use radicle::cob::issue::{Issue, IssueId, Issues};
+
use radicle::cob::{ActorId, Tag};
+
use radicle::prelude::Signer;
use radicle::storage::git::Repository;

pub fn all(repository: &Repository) -> Result<Vec<(IssueId, Issue)>> {
@@ -17,3 +19,17 @@ pub fn find(repository: &Repository, id: &IssueId) -> Result<Option<Issue>> {
    let issues = Issues::open(repository)?;
    Ok(issues.get(id)?)
}
+

+
pub fn create<G: Signer>(
+
    repository: &Repository,
+
    signer: &G,
+
    title: String,
+
    description: String,
+
    tags: &[Tag],
+
    assignees: &[ActorId],
+
) -> Result<IssueId> {
+
    let mut issues = Issues::open(repository)?;
+
    let issue = issues.create(title, description.trim(), tags, assignees, signer)?;
+

+
    Ok(*issue.id())
+
}
modified src/ui/context.rs
@@ -60,4 +60,9 @@ impl Context {
    pub fn signer(&self) -> &Box<dyn Signer> {
        &self.signer
    }
+

+
    pub fn reload(&mut self) {
+
        self.issues = crate::cob::issue::all(&self.repository).unwrap_or_default();
+
        self.patches = crate::cob::patch::all(&self.repository).unwrap_or_default();
+
    }
}
modified src/ui/widget/issue.rs
@@ -33,8 +33,9 @@ pub struct LargeList {
impl LargeList {
    pub fn new(context: &Context, theme: &Theme, selected: Option<(IssueId, Issue)>) -> Self {
        let repo = context.repository();
-
        let issues = crate::cob::issue::all(repo).unwrap_or_default();
-
        let mut items = issues
+

+
        let mut items = context
+
            .issues()
            .iter()
            .map(|(id, issue)| IssueItem::from((context.profile(), repo, *id, issue.clone())))
            .collect::<Vec<_>>();
@@ -263,8 +264,11 @@ impl NewForm {
        use tuirealm::props::Layout;

        let title = Widget::new(TextInput::new(theme.clone(), "Title"));
-
        let tags = Widget::new(TextInput::new(theme.clone(), "Tags"));
-
        let assignees = Widget::new(TextInput::new(theme.clone(), "Assignees"));
+
        let tags = Widget::new(TextInput::new(theme.clone(), "Tags (tag1, tag2, ...)"));
+
        let assignees = Widget::new(TextInput::new(
+
            theme.clone(),
+
            "Assignees (z6MkvAdxCp1oLVVTsqYvev9YrhSN3gBQNUSM45hhy4pgkexk, ...)",
+
        ));
        let description = Widget::new(TextInput::new(theme.clone(), "Description"))
            .custom(TextInput::PROP_MULTILINE, AttrValue::Flag(true));