AT Protocol Terminal Interface Explorer
at main 191 lines 4.4 kB view raw
1package ui 2 3import ( 4 "fmt" 5 "strings" 6 7 comatproto "github.com/bluesky-social/indigo/api/atproto" 8 "github.com/bluesky-social/indigo/atproto/identity" 9 "github.com/charmbracelet/bubbles/list" 10 tea "github.com/charmbracelet/bubbletea" 11 "github.com/charmbracelet/lipgloss" 12 "github.com/treethought/attie/at" 13) 14 15var ( 16 headerStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("205")) 17 labelStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")) 18 valueStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("255")) 19 collectionStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("42")) 20 dimStyle = lipgloss.NewStyle().Faint(true) 21) 22 23type CollectionList struct { 24 list list.Model 25} 26 27type CollectionListItem struct { 28 Name string 29} 30 31func (c CollectionListItem) FilterValue() string { 32 return c.Name 33} 34func (c CollectionListItem) Title() string { 35 return c.Name 36 // return collectionStyle.Render(c.Name) 37} 38func (c CollectionListItem) Description() string { 39 return "" 40} 41 42func NewCollectionList(collections []string) *CollectionList { 43 items := make([]list.Item, len(collections)) 44 for i, col := range collections { 45 ci := CollectionListItem{Name: col} 46 items[i] = list.Item(ci) 47 } 48 del := list.DefaultDelegate{ 49 ShowDescription: false, 50 Styles: list.NewDefaultItemStyles(), 51 } 52 del.SetHeight(1) 53 54 l := list.New(items, del, 80, 20) 55 l.SetShowTitle(false) 56 l.SetShowStatusBar(false) 57 l.SetFilteringEnabled(true) 58 return &CollectionList{ 59 list: l, 60 } 61} 62 63func (cl *CollectionList) Init() tea.Cmd { 64 return nil 65} 66 67func (cl *CollectionList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 68 69 if !cl.list.SettingFilter() { 70 switch msg := msg.(type) { 71 case tea.KeyMsg: 72 switch msg.String() { 73 case "enter": 74 if item, ok := cl.list.SelectedItem().(CollectionListItem); ok { 75 return cl, func() tea.Msg { 76 return selectCollectionMsg{collection: item.Name} 77 } 78 } 79 } 80 } 81 } 82 83 var cmd tea.Cmd 84 cl.list, cmd = cl.list.Update(msg) 85 return cl, cmd 86} 87 88func (cl *CollectionList) View() string { 89 if len(cl.list.Items()) == 0 { 90 return dimStyle.Render("No collections found") 91 } 92 return cl.list.View() 93} 94 95type RepoView struct { 96 identity *identity.Identity 97 repo *comatproto.RepoDescribeRepo_Output 98 clist *CollectionList 99 header string 100 width int 101 height int 102} 103 104func NewRepoView() *RepoView { 105 return &RepoView{ 106 clist: NewCollectionList([]string{}), 107 width: 80, 108 height: 24, 109 } 110} 111 112func (r *RepoView) buildHeader() string { 113 if r.repo == nil { 114 return "" 115 } 116 var s strings.Builder 117 118 s.WriteString(headerStyle.Render("📦 Repository")) 119 s.WriteString("\n\n") 120 121 s.WriteString(labelStyle.Render("Handle: ")) 122 s.WriteString(valueStyle.Render(r.repo.Handle)) 123 s.WriteString(" ") 124 s.WriteString(labelStyle.Render("Valid: ")) 125 if r.repo.HandleIsCorrect { 126 s.WriteString(valueStyle.Render("✓")) 127 } else { 128 s.WriteString(dimStyle.Render("✗")) 129 } 130 s.WriteString("\n") 131 132 s.WriteString(labelStyle.Render("DID: ")) 133 s.WriteString(dimStyle.Render(r.repo.Did)) 134 s.WriteString("\n\n") 135 136 s.WriteString(labelStyle.Render("PDS: ")) 137 s.WriteString(valueStyle.Render(r.identity.PDSEndpoint())) 138 s.WriteString("\n") 139 140 // Collections section header 141 s.WriteString(headerStyle.Render("Collections ")) 142 s.WriteString(dimStyle.Render(fmt.Sprintf("(%d)", len(r.repo.Collections)))) 143 s.WriteString("\n") 144 145 // add bottom border 146 return lipgloss.NewStyle().BorderBottom(true).Render(s.String()) 147 148} 149 150func (r *RepoView) SetRepo(repo *at.RepoWithIdentity) tea.Cmd { 151 r.identity = repo.Identity 152 r.repo = repo.Repo 153 r.header = r.buildHeader() 154 r.clist = NewCollectionList(repo.Repo.Collections) 155 return r.clist.Init() 156} 157 158func (r *RepoView) Init() tea.Cmd { 159 return nil 160} 161 162func (r *RepoView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 163 clist, cmd := r.clist.Update(msg) 164 r.clist = clist.(*CollectionList) 165 return r, cmd 166} 167 168// updateListSize calculates and sets the list size to fill remaining space 169func (r *RepoView) SetSize(w, h int) { 170 r.width = w 171 r.height = h 172 if r.clist == nil { 173 return 174 } 175 headerHeight := lipgloss.Height(r.header) 176 177 // List gets all remaining space 178 listHeight := r.height - headerHeight 179 if listHeight < 5 { 180 listHeight = 5 181 } 182 183 r.clist.list.SetSize(r.width, listHeight) 184} 185 186func (r *RepoView) View() string { 187 if r.repo == nil { 188 return "No repository loaded" 189 } 190 return lipgloss.JoinVertical(lipgloss.Left, r.header, r.clist.View()) 191}