AT Protocol Terminal Interface Explorer

better search sizing

+127 -76
+18 -6
at/client.go
··· 13 13 "github.com/bluesky-social/indigo/atproto/syntax" 14 14 ) 15 15 16 + // response wrappers with identity for easier navigation of views 17 + 18 + type RepoWithIdentity struct { 19 + Identity *identity.Identity 20 + Repo *comatproto.RepoDescribeRepo_Output 21 + } 22 + 23 + type RecordsWithIdentity struct { 24 + Identity *identity.Identity 25 + Records []*agnostic.RepoListRecords_Record 26 + } 27 + 28 + type RecordWithIdentity struct { 29 + Identity *identity.Identity 30 + Record *agnostic.RepoGetRecord_Output 31 + } 32 + 16 33 type Client struct { 17 34 dir identity.Directory 18 35 c *atclient.APIClient ··· 43 60 log.WithFields(log.Fields{ 44 61 "handle": idd.Handle, 45 62 "DID": idd.DID, 46 - "PDS": idd.PDSEndpoint(), 63 + "PDS": idd.PDSEndpoint(), 47 64 }).Info("identifier resolved") 48 65 return idd, nil 49 66 } ··· 54 71 return nil, fmt.Errorf("failed to lookup identifier: %w", err) 55 72 } 56 73 return atclient.NewAPIClient(idd.PDSEndpoint()), nil 57 - } 58 - 59 - type RepoWithIdentity struct { 60 - Identity *identity.Identity 61 - Repo *comatproto.RepoDescribeRepo_Output 62 74 } 63 75 64 76 func (c *Client) GetRepo(ctx context.Context, repo string) (*RepoWithIdentity, error) {
+5 -70
ui/app.go
··· 2 2 3 3 import ( 4 4 "context" 5 - "fmt" 6 5 7 6 log "github.com/sirupsen/logrus" 8 7 ··· 10 9 "github.com/bluesky-social/indigo/atproto/identity" 11 10 "github.com/bluesky-social/indigo/atproto/syntax" 12 11 "github.com/charmbracelet/bubbles/spinner" 13 - "github.com/charmbracelet/bubbles/textinput" 14 12 tea "github.com/charmbracelet/bubbletea" 15 13 "github.com/treethought/goatie/at" 16 14 ) ··· 95 93 switch msg.String() { 96 94 case "ctrl+c", "q": 97 95 return a, tea.Quit 96 + case "ctrl+k": 97 + a.active = a.search 98 + a.search.loading = false 99 + return a, a.search.Init() 98 100 case "esc": 99 101 switch a.active { 100 102 case a.repoView: 101 103 a.active = a.search 102 104 a.search.loading = false 103 - return a, nil 105 + return a, a.search.Init() 104 106 case a.rlist: 105 107 a.active = a.repoView 106 108 return a, nil ··· 249 251 err error 250 252 } 251 253 252 - type CommandPallete struct { 253 - ti textinput.Model 254 - err string 255 - loading bool 256 - spinner spinner.Model 257 - } 258 - 259 - func (c *CommandPallete) Init() tea.Cmd { 260 - c.ti = textinput.New() 261 - c.ti.Placeholder = "Enter handle, DID, or AT URI" 262 - c.ti.Focus() 263 - c.spinner = spinner.New() 264 - c.spinner.Spinner = spinner.Dot 265 - return textinput.Blink 266 - } 267 - 268 - func (c *CommandPallete) SetSize(w, h int) { 269 - c.ti.Width = w - 2 270 - } 271 - 272 - func (c *CommandPallete) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 273 - 274 - switch msg := msg.(type) { 275 - case tea.KeyMsg: 276 - switch msg.String() { 277 - case "enter": 278 - val := c.ti.Value() 279 - if val == "" { 280 - c.err = "Input cannot be empty" 281 - return c, nil 282 - } 283 - id, err := syntax.ParseAtIdentifier(val) 284 - if err != nil { 285 - c.err = fmt.Sprintf("Must use handle, DID or AT URI: %s", err.Error()) 286 - return c, nil 287 - } 288 - c.err = "" 289 - c.loading = true 290 - return c, func() tea.Msg { 291 - log.Printf("Looking up identifier: %s", id.String()) 292 - return searchSubmitMsg{identifier: id} 293 - } 294 - } 295 - 296 - } 297 - 298 - var cmds []tea.Cmd 299 - ti, tcmd := c.ti.Update(msg) 300 - c.ti = ti 301 - cmds = append(cmds, tcmd) 302 - 303 - sp, scmd := c.spinner.Update(msg) 304 - c.spinner = sp 305 - cmds = append(cmds, scmd) 306 - 307 - return c, tea.Batch(cmds...) 308 - } 309 - 310 - func (c *CommandPallete) View() string { 311 - s := fmt.Sprint("Search:\n", c.ti.View()) 312 - if c.err != "" { 313 - s += fmt.Sprintf("\nError: %s", c.err) 314 - } else if c.loading { 315 - s += "\nLoading... " + c.spinner.View() 316 - } 317 - return s 318 - }
+104
ui/search.go
··· 1 + package ui 2 + 3 + import ( 4 + "fmt" 5 + 6 + log "github.com/sirupsen/logrus" 7 + 8 + "github.com/bluesky-social/indigo/atproto/syntax" 9 + "github.com/charmbracelet/bubbles/spinner" 10 + "github.com/charmbracelet/bubbles/textinput" 11 + tea "github.com/charmbracelet/bubbletea" 12 + "github.com/charmbracelet/lipgloss" 13 + ) 14 + 15 + type CommandPallete struct { 16 + ti textinput.Model 17 + err string 18 + loading bool 19 + spinner spinner.Model 20 + width int 21 + height int 22 + } 23 + 24 + func (c *CommandPallete) Init() tea.Cmd { 25 + c.ti = textinput.New() 26 + c.ti.Placeholder = "Enter handle, DID, or AT URI" 27 + c.ti.Focus() 28 + c.ti.Width = 60 29 + c.spinner = spinner.New() 30 + c.spinner.Spinner = spinner.Dot 31 + return textinput.Blink 32 + } 33 + 34 + func (c *CommandPallete) SetSize(w, h int) { 35 + c.width = w 36 + c.height = h 37 + 38 + // Account for border (2 chars) + padding (4 chars) = 6 total 39 + maxWidth := 128 40 + availableWidth := w - 6 41 + 42 + if availableWidth < maxWidth { 43 + if availableWidth < 20 { 44 + availableWidth = 20 // Minimum width 45 + } 46 + c.ti.Width = availableWidth 47 + } else { 48 + c.ti.Width = maxWidth 49 + } 50 + } 51 + 52 + func (c *CommandPallete) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 53 + 54 + switch msg := msg.(type) { 55 + case tea.KeyMsg: 56 + switch msg.String() { 57 + case "enter": 58 + val := c.ti.Value() 59 + if val == "" { 60 + c.err = "Input cannot be empty" 61 + return c, nil 62 + } 63 + id, err := syntax.ParseAtIdentifier(val) 64 + if err != nil { 65 + c.err = fmt.Sprintf("Must use handle, DID or AT URI: %s", err.Error()) 66 + return c, nil 67 + } 68 + c.err = "" 69 + c.loading = true 70 + return c, func() tea.Msg { 71 + log.Printf("Looking up identifier: %s", id.String()) 72 + return searchSubmitMsg{identifier: id} 73 + } 74 + } 75 + 76 + } 77 + 78 + var cmds []tea.Cmd 79 + ti, tcmd := c.ti.Update(msg) 80 + c.ti = ti 81 + cmds = append(cmds, tcmd) 82 + 83 + sp, scmd := c.spinner.Update(msg) 84 + c.spinner = sp 85 + cmds = append(cmds, scmd) 86 + 87 + return c, tea.Batch(cmds...) 88 + } 89 + 90 + var searchStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("62")) 91 + 92 + func (c *CommandPallete) View() string { 93 + // make centered search box 94 + s := c.ti.View() 95 + if c.err != "" { 96 + s += fmt.Sprintf("\nError: %s", c.err) 97 + } else if c.loading { 98 + s += "\nLoading... " + c.spinner.View() 99 + } 100 + 101 + // Center the box in the full terminal window 102 + box := searchStyle.Render(s) 103 + return lipgloss.Place(c.width, c.height, lipgloss.Center, lipgloss.Center, box) 104 + }