diff --git a/lazy_github/lib/github/issues.py b/lazy_github/lib/github/issues.py index 52ee6de..774b35c 100644 --- a/lazy_github/lib/github/issues.py +++ b/lazy_github/lib/github/issues.py @@ -9,7 +9,7 @@ class UpdateIssuePayload(TypedDict): title: str | None body: str | None - status: str | None + state: str | None async def list_issues(repo: Repository, state: IssueStateFilter, owner: IssueOwnerFilter) -> list[Issue]: @@ -52,3 +52,11 @@ async def update_issue(issue_to_update: Issue, **updated_fields: Unpack[UpdateIs response = await github.patch(url, json=updated_fields, headers=github.headers_with_auth_accept()) response.raise_for_status() return Issue(**response.json(), repo=repo) + + +async def create_issue(repo: Repository, title: str, body: str) -> Issue: + url = f"/repos/{repo.owner.login}/{repo.name}/issues" + json_body = {"title": title, "body": body} + response = await github.post(url, json=json_body, headers=github.headers_with_auth_accept()) + response.raise_for_status() + return Issue(**response.json(), repo=repo) diff --git a/lazy_github/ui/screens/new_issue.py b/lazy_github/ui/screens/new_issue.py new file mode 100644 index 0000000..5ca47fe --- /dev/null +++ b/lazy_github/ui/screens/new_issue.py @@ -0,0 +1,85 @@ +from textual import on +from textual.app import ComposeResult +from textual.containers import Container, Horizontal, ScrollableContainer +from textual.screen import ModalScreen +from textual.widgets import Button, Input, Label, Rule, Select, TextArea + +from lazy_github.lib.github import issues +from lazy_github.models.github import Repository + + +class NewIssueContainer(Container): + DEFAULT_CSS = """ + #button_holder { + align: center middle; + } + + ScrollableContainer { + height: 80%; + } + + #new_issue_body { + height: 15; + } + """ + + def __init__(self, repo: Repository, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.repo = repo + + def compose(self) -> ComposeResult: + with ScrollableContainer(): + yield Label("[bold]Title[/bold]") + yield Input(placeholder="Title", id="new_issue_title") + + yield Rule() + + yield Label("[bold]Description[/bold]") + yield TextArea.code_editor(id="new_issue_body", soft_wrap=True) + + with Horizontal(id="button_holder"): + yield Button("Save", id="save_new_issue", variant="success") + yield Button("Cancel", id="cancel_new_issue", variant="error") + + @on(Button.Pressed, "#cancel_new_issue") + def cancel_new_issue(self, _: Button) -> None: + self.app.pop_screen() + + @on(Button.Pressed, "#save_new_issue") + async def submit_new_issue(self, _: Button) -> None: + title = self.query_one("#new_issue_title", Input).value + body = self.query_one("#new_issue_body", TextArea).text + if not str(title): + self.notify("Must have a non-empty issue title!", severity="error") + return + if not str(body): + self.notify("Must have a non-empty issue body!", severity="error") + return + + self.notify("Creating new issue...") + new_issue = await issues.create_issue(self.repo, title, body) + self.notify(f"Successfully updated created issue #{new_issue.number}") + self.app.pop_screen() + + +class NewIssueModal(ModalScreen): + DEFAULT_CSS = """ + NewIssueModal { + align: center middle; + } + + NewIssueContainer { + align: center middle; + height: 30; + width: 100; + border: thick $background 80%; + background: $surface; + } + """ + + def __init__(self, repo: Repository, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.repo = repo + + def compose(self) -> ComposeResult: + yield NewIssueContainer(self.repo) diff --git a/lazy_github/ui/widgets/issues.py b/lazy_github/ui/widgets/issues.py index c75abd8..98ab212 100644 --- a/lazy_github/ui/widgets/issues.py +++ b/lazy_github/ui/widgets/issues.py @@ -11,13 +11,17 @@ from lazy_github.lib.string_utils import link from lazy_github.models.github import Issue, IssueState from lazy_github.ui.screens.edit_issue import EditIssueModal +from lazy_github.ui.screens.new_issue import NewIssueModal from lazy_github.ui.widgets.command_log import log_event from lazy_github.ui.widgets.common import LazyGithubContainer, LazyGithubDataTable, SearchableLazyGithubDataTable from lazy_github.ui.widgets.conversations import IssueCommentContainer class IssuesContainer(LazyGithubContainer): - BINDINGS = [("E", "edit_issue", "Edit Issue")] + BINDINGS = [ + ("E", "edit_issue", "Edit Issue"), + ("N", "new_issue", "New Issue"), + ] issues: Dict[int, Issue] = {} status_column_index = -1 @@ -73,6 +77,11 @@ async def action_edit_issue(self) -> None: issue = await self.get_selected_issue() self.app.push_screen(EditIssueModal(issue)) + async def action_new_issue(self) -> None: + # TODO: This way of getting the repo is really hacky :| + issue = await self.get_selected_issue() + self.app.push_screen(NewIssueModal(issue.repo)) + @on(LazyGithubDataTable.RowSelected, "#issues_table") async def issue_selected(self) -> None: issue = await self.get_selected_issue()