caret

[ACTIVE] a command line tool for browsing Lobsters in your terminal
git clone git://git.figbert.com/caret.git
Log | Files | Refs | README | LICENSE

commit 1499ba5280aaa3fe86be3a98fba6b7ebafe536ef
parent ecefaa6c180930ffe10c71bcf633bcf19df53875
Author: FIGBERT <figbert@figbert.com>
Date:   Tue,  5 Sep 2023 08:50:10 -0700

Add open-story-in-browser and error handling

Diffstat:
Mapi/api.go | 4++--
Acmds.go | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmain.go | 43+++++++++++++++++++++++++++++++++++++------
3 files changed, 91 insertions(+), 8 deletions(-)

diff --git a/api/api.go b/api/api.go @@ -9,13 +9,13 @@ import ( ) type PageMsg []Post -type ErrorMsg error +type ErrorMsg struct{} func FetchPage(index int) tea.Cmd { return func() tea.Msg { posts, err := page(index) if err != nil { - return ErrorMsg(err) + return ErrorMsg{} } return PageMsg(*posts) diff --git a/cmds.go b/cmds.go @@ -0,0 +1,52 @@ +package main + +import ( + "os/exec" + "runtime" + "time" + + "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbletea" +) + +type genericError struct{} +type postHasNoLinkToOpen struct{} +type platformUnsupported struct{} +type msgExpired struct{} +type loading struct{} + +func attemptToOpenPostURL(itm list.Item) tea.Cmd { + return func() tea.Msg { + i, ok := itm.(item) + if !ok { + return genericError{} + } + + if i.post.URL == "" { + return postHasNoLinkToOpen{} + } + + switch runtime.GOOS { + case "darwin": + cmd := exec.Command("open", i.post.URL) + _ = cmd.Start() + case "linux": + cmd := exec.Command("xdg-open", i.post.URL) + _ = cmd.Start() + case "windows": + cmd := exec.Command("rundll32", "url.dll,FileProtocolHandler", i.post.URL) + _ = cmd.Start() + } + + return platformUnsupported{} + } +} + +func clearMsg() tea.Msg { + time.Sleep(time.Second * 5) + return msgExpired{} +} + +func startLoading() tea.Msg { + return loading{} +} diff --git a/main.go b/main.go @@ -14,11 +14,12 @@ type model struct { width, height int page int + msg string posts list.Model } func (m model) Init() tea.Cmd { - return api.FetchPage(m.page) + return tea.Batch(api.FetchPage(m.page), startLoading) } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -33,10 +34,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch key.String() { case "ctrl+c": return m, tea.Quit + case "o": + cmds = append(cmds, attemptToOpenPostURL(m.posts.SelectedItem())) case "j": if m.posts.Index() == len(m.posts.Items())-1 { m.page++ - cmds = append(cmds, api.FetchPage(m.page)) + cmds = append(cmds, api.FetchPage(m.page), startLoading) } fallthrough default: @@ -45,9 +48,26 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) } } else if psts, ok := msg.(api.PageMsg); ok { + m.msg = "" for _, pst := range []api.Post(psts) { m.posts.SetItems(append(m.posts.Items(), item{post: pst})) } + } else if _, ok := msg.(api.ErrorMsg); ok { + m.msg = "Failed to fetch new stories." + cmds = append(cmds, clearMsg) + } else if _, ok := msg.(loading); ok { + m.msg = "Loading…" + } else if _, ok := msg.(genericError); ok { + m.msg = "Error! How perplexing." + cmds = append(cmds, clearMsg) + } else if _, ok := msg.(postHasNoLinkToOpen); ok { + m.msg = "This post does not have an external URL to open." + cmds = append(cmds, clearMsg) + } else if _, ok := msg.(platformUnsupported); ok { + m.msg = "Exotic! Opening URLs on your OS is not yet supported. Reach out!" + cmds = append(cmds, clearMsg) + } else if _, ok := msg.(msgExpired); ok { + m.msg = "" } return m, tea.Batch(cmds...) @@ -56,12 +76,23 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m model) View() string { var view strings.Builder - view.WriteString("\n " + ui.Title().Render("Lobsters") + "\n") - view.WriteString(" " + ui.HR(m.width-2) + "\n") + view.WriteString("\n " + ui.Title().Render("Lobsters")) - if len(m.posts.Items()) == 0 { - view.WriteString(" " + ui.SecondaryText().Render("Loading…")) + padding := m.width - 1 - 8 - len(m.msg) - 1 + if padding < 0 { + errorDot := ui.Title().Render("● ") + padding = m.width - 1 - 8 - 1 - 1 + if padding < 0 { + view.WriteString(" " + errorDot) + } else { + view.WriteString(strings.Repeat(" ", padding) + errorDot) + } } else { + view.WriteString(strings.Repeat(" ", padding) + ui.SecondaryText().Render(m.msg) + " ") + } + + view.WriteString("\n " + ui.HR(m.width-2) + "\n") + if len(m.posts.Items()) > 0 { view.WriteString(m.posts.View()) }