Skip to content

Commit

Permalink
Implement status filter
Browse files Browse the repository at this point in the history
  • Loading branch information
lusingander committed Apr 14, 2024
1 parent 475c2b1 commit e69e523
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 9 deletions.
175 changes: 167 additions & 8 deletions internal/ui/prs_list_all.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,63 @@
package ui

import (
"fmt"
"sort"
"strings"

"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/lusingander/ghcv-cli/internal/gh"
"github.com/lusingander/kasane"
)

var (
pullRequestsListAllDialogBodyStyle = lipgloss.NewStyle().
Padding(0, 2)

pullRequestsListAllDialogStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder())

pullRequestsListAllDialogSelectedStyle = lipgloss.NewStyle().
Foreground(selectedColor1)

pullRequestsListAllDialogNotSelectedStyle = lipgloss.NewStyle()
)

type pullRequestListAllSortType int

const (
pullRequestListAllSortByCreatedAtDesc pullRequestListAllSortType = iota
pullRequestListAllSortByCreatedAtAsc
)

type pullRequestStatus struct {
name string
count int
}

type pullRequestsListAllModel struct {
prs *gh.UserPullRequests

list list.Model
delegateKeys pullRequestsListAllDelegateKeyMap
list list.Model
originalItems []list.Item
delegateKeys pullRequestsListAllDelegateKeyMap
filterStatusDialogDelegateKeys pullRequestsListAllFilterStatusDialogDelegateKeyMap

selectedUser string
width, height int

pullRequestListAllSortType

statuses []*pullRequestStatus
statusIdx int
statusDialogOpened bool
}

type pullRequestsListAllDelegateKeyMap struct {
stat key.Binding
open key.Binding
back key.Binding
tog key.Binding
Expand All @@ -28,6 +66,10 @@ type pullRequestsListAllDelegateKeyMap struct {

func newPullRequestsListAllDelegateKeyMap() pullRequestsListAllDelegateKeyMap {
return pullRequestsListAllDelegateKeyMap{
stat: key.NewBinding(
key.WithKeys("T"),
key.WithHelp("T", "filter by status"),
),
open: key.NewBinding(
key.WithKeys("x"),
key.WithHelp("x", "open in browser"),
Expand All @@ -47,9 +89,33 @@ func newPullRequestsListAllDelegateKeyMap() pullRequestsListAllDelegateKeyMap {
}
}

type pullRequestsListAllFilterStatusDialogDelegateKeyMap struct {
next key.Binding
prev key.Binding
close key.Binding
}

func newPullRequestsListAllFilterStatusDialogDelegateKeyMap() pullRequestsListAllFilterStatusDialogDelegateKeyMap {
return pullRequestsListAllFilterStatusDialogDelegateKeyMap{
next: key.NewBinding(
key.WithKeys("j"),
key.WithHelp("j", "select next"),
),
prev: key.NewBinding(
key.WithKeys("k"),
key.WithHelp("k", "select prev"),
),
close: key.NewBinding(
key.WithKeys("T", "esc", "enter"),
key.WithHelp("T", "close dialog"),
),
}
}

func newPullRequestsListAllModel() *pullRequestsListAllModel {
delegateKeys := newPullRequestsListAllDelegateKeyMap()
delegate := newPullRequestsListAllDelegate(delegateKeys)
filterStatusDialogDelegateKeys := newPullRequestsListAllFilterStatusDialogDelegateKeyMap()

l := list.New(nil, delegate, 0, 0)
l.KeyMap.Quit = delegateKeys.quit
Expand All @@ -58,8 +124,9 @@ func newPullRequestsListAllModel() *pullRequestsListAllModel {
l.SetShowStatusBar(false)

return &pullRequestsListAllModel{
list: l,
delegateKeys: delegateKeys,
list: l,
delegateKeys: delegateKeys,
filterStatusDialogDelegateKeys: filterStatusDialogDelegateKeys,
}
}

Expand All @@ -75,7 +142,9 @@ func (m *pullRequestsListAllModel) SetUser(id string) {

func (m *pullRequestsListAllModel) updatePrs(prs *gh.UserPullRequests) {
m.prs = prs

items := make([]list.Item, 0)
statusesMap := make(map[string]int)
for _, owner := range m.prs.Owners {
for _, repo := range owner.Repositories {
for _, pr := range repo.PullRequests {
Expand All @@ -99,19 +168,61 @@ func (m *pullRequestsListAllModel) updatePrs(prs *gh.UserPullRequests) {
},
}
items = append(items, item)
statusesMap[pr.State] += 1
}
}
}
m.list.SetItems(items)
m.originalItems = items
m.sortItems()

m.statuses = []*pullRequestStatus{
{name: "All", count: len(items)},
{name: "OPEN", count: statusesMap["OPEN"]},
{name: "MERGED", count: statusesMap["MERGED"]},
{name: "CLOSED", count: statusesMap["CLOSED"]},
}
m.statusIdx = 0
}

func (m *pullRequestsListAllModel) sortItems() {
items := m.list.Items()
sort.Slice(items, func(i, j int) bool {
return items[i].(pullRequestsListAllItem).createdAt.After(items[j].(pullRequestsListAllItem).createdAt)
})
switch m.pullRequestListAllSortType {
case pullRequestListAllSortByCreatedAtDesc:
sort.Slice(items, func(i, j int) bool {
return items[i].(pullRequestsListAllItem).createdAt.After(items[j].(pullRequestsListAllItem).createdAt)
})
case pullRequestListAllSortByCreatedAtAsc:
sort.Slice(items, func(i, j int) bool {
return items[i].(pullRequestsListAllItem).createdAt.Before(items[j].(pullRequestsListAllItem).createdAt)
})
}
m.list.SetItems(items)
}

func (m *pullRequestsListAllModel) updateStatusIdx(reverse bool) {
n := len(m.statuses)
if reverse {
m.statusIdx = ((m.statusIdx-1)%n + n) % n
} else {
m.statusIdx = (m.statusIdx + 1) % n
}
}

func (m *pullRequestsListAllModel) filterItems() {
if m.statuses[m.statusIdx].name == "All" {
m.list.SetItems(m.originalItems)
m.sortItems()
return
}
items := make([]list.Item, 0)
for _, i := range m.originalItems {
if i.(pullRequestsListAllItem).status == m.statuses[m.statusIdx].name {
items = append(items, i)
}
}
m.list.SetItems(items)
m.sortItems()
}

func (m pullRequestsListAllModel) Init() tea.Cmd {
Expand All @@ -131,7 +242,25 @@ func (m pullRequestsListAllModel) Update(msg tea.Msg) (pullRequestsListAllModel,
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
if m.statusDialogOpened {
switch {
case key.Matches(msg, m.filterStatusDialogDelegateKeys.close):
m.statusDialogOpened = false
case key.Matches(msg, m.filterStatusDialogDelegateKeys.next):
m.list.ResetSelected()
m.updateStatusIdx(false)
m.filterItems()
case key.Matches(msg, m.filterStatusDialogDelegateKeys.prev):
m.list.ResetSelected()
m.updateStatusIdx(true)
m.filterItems()
}
return m, nil
}
switch {
case key.Matches(msg, m.delegateKeys.stat):
m.statusDialogOpened = true
return m, nil
case key.Matches(msg, m.delegateKeys.open):
item := m.list.SelectedItem().(pullRequestsListAllItem)
return m, m.openPullRequestPageInBrowser(item)
Expand All @@ -152,7 +281,37 @@ func (m pullRequestsListAllModel) Update(msg tea.Msg) (pullRequestsListAllModel,
}

func (m pullRequestsListAllModel) View() string {
return titleView(m.breadcrumb()) + listView(m.list)
ret := titleView(m.breadcrumb()) + listView(m.list)
if m.statusDialogOpened {
return m.withStatusDialogView(ret)
}
return ret
}

func (m pullRequestsListAllModel) withStatusDialogView(base string) string {
title := repositoriesDialogTitleStyle.Render("Status")

ivs := make([]string, len(m.statuses))
for i, s := range m.statuses {
ivs[i] = m.statusKeySelectItemView(s)
}
body := strings.Join(ivs, "\n")
body = pullRequestsListAllDialogBodyStyle.Render(body)

dialog := pullRequestsListAllDialogStyle.Render(lipgloss.JoinVertical(lipgloss.Left, title, body))

dw, dh := lipgloss.Size(dialog)
top := (m.height / 2) - (dh / 2)
left := (m.width / 2) - (dw / 2)
return kasane.OverlayString(base, dialog, top, left, kasane.WithPadding(m.width))
}

func (m pullRequestsListAllModel) statusKeySelectItemView(status *pullRequestStatus) string {
if m.statuses[m.statusIdx].name == status.name {
return pullRequestsListAllDialogSelectedStyle.Render(fmt.Sprintf("> %s (%d)", status.name, status.count))
} else {
return pullRequestsListAllDialogNotSelectedStyle.Render(fmt.Sprintf(" %s (%d)", status.name, status.count))
}
}

func (m pullRequestsListAllModel) breadcrumb() []string {
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/prs_list_all_delegate.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func newPullRequestsListAllDelegate(delegateKeys pullRequestsListAllDelegateKeyM
return []key.Binding{delegateKeys.back, delegateKeys.tog}
}
fullHelpFunc := func() [][]key.Binding {
return [][]key.Binding{{delegateKeys.open, delegateKeys.back, delegateKeys.tog}}
return [][]key.Binding{{delegateKeys.stat, delegateKeys.open, delegateKeys.back, delegateKeys.tog}}
}
return pullRequestsListAllDelegate{
shortHelpFunc: shortHelpFunc,
Expand Down

0 comments on commit e69e523

Please sign in to comment.