From 6223f6907550dd6572d6ed7e7e11e96e83bcc6bf Mon Sep 17 00:00:00 2001 From: Cam Sweeney Date: Fri, 14 Jun 2024 22:29:01 -0700 Subject: [PATCH 1/3] support casting to channel --- ui/app.go | 41 ++++++++++---------- ui/keybindings.go | 4 +- ui/publish.go | 95 +++++++++++++++++++++++++++++++++-------------- ui/quickselect.go | 43 +++++++++++++++------ 4 files changed, 121 insertions(+), 62 deletions(-) diff --git a/ui/app.go b/ui/app.go index 682d9c2..e1a39f7 100644 --- a/ui/app.go +++ b/ui/app.go @@ -49,20 +49,19 @@ type AppContext struct { } type App struct { - ctx *AppContext - client *api.Client - cfg *config.Config - focusedModel tea.Model - focused string - navname string - sidebar *Sidebar - showSidebar bool - prev string - prevName string - quickSelect *QuickSelect - showQuickSelect bool - publish *PublishInput - statusLine *StatusLine + ctx *AppContext + client *api.Client + cfg *config.Config + focusedModel tea.Model + focused string + navname string + sidebar *Sidebar + showSidebar bool + prev string + prevName string + quickSelect *QuickSelect + publish *PublishInput + statusLine *StatusLine // signinPrompt *SigninPrompt splash *SplashView help *HelpView @@ -150,8 +149,8 @@ func (a *App) SetNavName(name string) { } func (a *App) focusMain() { - if a.showQuickSelect { - a.showQuickSelect = false + if a.quickSelect.Active() { + a.quickSelect.SetActive(false) } if a.publish.Active() { a.publish.SetActive(false) @@ -245,10 +244,10 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.activeOnly { _, cmd := a.sidebar.Update(msg) return a, cmd - } else { - _, cmd := a.quickSelect.Update(msg.channels) - return a, cmd } + _, qcmd := a.quickSelect.Update(msg.channels) + _, pcmd := a.publish.Update(msg.channels) + return a, tea.Batch(qcmd, pcmd) case *feedLoadedMsg: a.splash.SetActive(false) case *channelInfoMsg: @@ -338,7 +337,7 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) { _, cmd := a.publish.Update(msg) return a, cmd } - if a.showQuickSelect { + if a.quickSelect.Active() { q, cmd := a.quickSelect.Update(msg) a.quickSelect = q.(*QuickSelect) return a, cmd @@ -382,7 +381,7 @@ func (a *App) View() string { if a.publish.Active() { main = a.publish.View() } - if a.showQuickSelect { + if a.quickSelect.Active() { main = a.quickSelect.View() } if !a.showSidebar { diff --git a/ui/keybindings.go b/ui/keybindings.go index 3cbc62e..ee0c435 100644 --- a/ui/keybindings.go +++ b/ui/keybindings.go @@ -152,7 +152,7 @@ func (k navKeymap) HandleMsg(a *App, msg tea.KeyMsg) tea.Cmd { return noOp() case key.Matches(msg, k.QuickSelect): - a.showQuickSelect = true + a.quickSelect.SetActive(true) return nil case key.Matches(msg, k.Help): @@ -172,7 +172,7 @@ func (k navKeymap) HandleMsg(a *App, msg tea.KeyMsg) tea.Cmd { return noOp() case key.Matches(msg, k.ToggleSidebarFocus): - if a.showQuickSelect { + if a.quickSelect.Active() { _, cmd := a.quickSelect.Update(msg) return cmd } diff --git a/ui/publish.go b/ui/publish.go index ea61ee4..8e9bf52 100644 --- a/ui/publish.go +++ b/ui/publish.go @@ -43,7 +43,7 @@ type keyMap struct { } func (k keyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Cast, k.Back} + return []key.Binding{k.Cast, k.Back, k.ChooseChannel} } func (k keyMap) FullHelp() [][]key.Binding { @@ -62,9 +62,13 @@ var keys = keyMap{ key.WithKeys("esc"), key.WithHelp("esc", "back to feed"), ), + ChooseChannel: key.NewBinding( + key.WithKeys("ctrl+w"), + key.WithHelp("ctrl+w", "choose channel"), + ), } -type ctx struct { +type castContext struct { channel string parent string parentAuthor uint64 @@ -80,7 +84,8 @@ type PublishInput struct { showConfirm bool active bool w, h int - ctx ctx + castCtx castContext + qs *QuickSelect } func NewPublishInput(app *App) *PublishInput { @@ -97,11 +102,17 @@ func NewPublishInput(app *App) *PublishInput { vp := viewport.New(0, 0) vp.SetContent(ta.View()) - return &PublishInput{ta: &ta, vp: &vp, keys: keys, help: help.New(), app: app} + qs := NewQuickSelect(app) + + return &PublishInput{ta: &ta, vp: &vp, keys: keys, help: help.New(), app: app, qs: qs} } func (m *PublishInput) Init() tea.Cmd { - return nil + m.qs.SetOnSelect(func(i *selectItem) tea.Cmd { + m.qs.SetActive(false) + return m.SetContext("", i.name, 0) + }) + return m.qs.Init() } func (m *PublishInput) Active() bool { @@ -118,27 +129,39 @@ func (m *PublishInput) SetSize(w, h int) { m.ta.SetHeight(h) m.vp.Width = w m.vp.Height = h + m.qs.SetSize(w, h) } -func (m *PublishInput) SetContext(parent, channel string, parentAuthor uint64) tea.Cmd { +func (m *PublishInput) SetContext(parent, channelParentUrl string, parentAuthor uint64) tea.Cmd { return func() tea.Msg { - m.ctx.channel = channel - m.ctx.parent = parent - m.ctx.parentAuthor = parentAuthor - m.ctx.parentUser = nil + m.castCtx.channel = channelParentUrl + m.castCtx.parent = parent + m.castCtx.parentAuthor = parentAuthor + m.castCtx.parentUser = nil var viewer uint64 if m.app.ctx.signer != nil { viewer = m.app.ctx.signer.FID } - parentUser, err := m.app.client.GetUserByFID(parentAuthor, viewer) - if err != nil { - log.Println("error getting parent author: ", err) - return nil + var parentUser *api.User + var channel *api.Channel + var err error + if parentAuthor > 0 { + parentUser, err = m.app.client.GetUserByFID(parentAuthor, viewer) + if err != nil { + log.Println("error getting parent author: ", err) + return nil + } } - channel, err := m.app.client.GetChannelByParentUrl(channel) - if err != nil { - log.Println("error getting channel: ", err) - return nil + if channelParentUrl != "" { + channel, err = m.app.client.GetChannelByParentUrl(channelParentUrl) + if err != nil { + log.Println("error getting channel by parent url, trying channel id: ", err) + channel, err = m.app.client.GetChannelById(channelParentUrl) + if err != nil { + log.Println("error getting channel by id: ", err) + return nil + } + } } return &ctxInfoMsg{user: parentUser, channel: channel} } @@ -156,13 +179,16 @@ func (m *PublishInput) Clear() { m.vp.SetContent(m.ta.View()) m.showConfirm = false m.SetFocus(false) + m.SetContext("", "", 0) } func (m *PublishInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case *ctxInfoMsg: - m.ctx.parentUser = msg.user - m.ctx.channel = msg.channel.Name + m.castCtx.parentUser = msg.user + if msg.channel != nil { + m.castCtx.channel = msg.channel.Name + } return m, nil case *postResponseMsg: if msg.err != nil { @@ -182,6 +208,10 @@ func (m *PublishInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.String() == "ctrl+c" { return nil, tea.Quit } + if m.qs.Active() { + _, cmd := m.qs.Update(msg) + return m, cmd + } switch { case key.Matches(msg, m.keys.Cast): @@ -190,13 +220,16 @@ func (m *PublishInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, m.keys.Back): m.active = false return nil, nil + case key.Matches(msg, m.keys.ChooseChannel): + m.qs.SetActive(true) } if m.showConfirm { if msg.String() == "y" || msg.String() == "Y" { return m, postCastCmd( m.app.client, m.app.ctx.signer, - m.ta.Value(), m.ctx.parent, m.ctx.channel, m.ctx.parentAuthor, + m.ta.Value(), m.castCtx.parent, + m.castCtx.channel, m.castCtx.parentAuthor, ) } else if msg.String() == "n" || msg.String() == "N" || msg.String() == "esc" { m.showConfirm = false @@ -210,9 +243,14 @@ func (m *PublishInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } - ta, cmd := m.ta.Update(msg) + var cmds []tea.Cmd + _, cmd := m.qs.Update(msg) + cmds = append(cmds, cmd) + + ta, tcmd := m.ta.Update(msg) m.ta = &ta - return m, cmd + cmds = append(cmds, tcmd) + return m, tea.Batch(cmds...) } func (m *PublishInput) viewConfirm() string { @@ -225,6 +263,9 @@ func (m *PublishInput) View() string { content := m.ta.View() if m.showConfirm { content = m.viewConfirm() + } else if m.qs.Active() { + content = m.qs.View() + } else { content = lipgloss.JoinVertical(lipgloss.Top, content, @@ -233,10 +274,10 @@ func (m *PublishInput) View() string { } titleText := "publish cast" - if m.ctx.parentUser != nil { - titleText = fmt.Sprintf("reply to @%s", m.ctx.parentUser.Username) - } else if m.ctx.channel != "" { - titleText = fmt.Sprintf("publish cast to channel: /%s", m.ctx.channel) + if m.castCtx.parentUser != nil { + titleText = fmt.Sprintf("reply to @%s", m.castCtx.parentUser.Username) + } else if m.castCtx.channel != "" { + titleText = fmt.Sprintf("publish cast to channel: /%s", m.castCtx.channel) } titleStyle := NewStyle().Foreground(lipgloss.Color("#874BFD")).BorderBottom(true).BorderStyle(lipgloss.NormalBorder()) diff --git a/ui/quickselect.go b/ui/quickselect.go index 1e01857..9e5b5e3 100644 --- a/ui/quickselect.go +++ b/ui/quickselect.go @@ -11,10 +11,11 @@ import ( ) type QuickSelect struct { - app *App - active bool - channels *list.Model - w, h int + app *App + active bool + channelList *list.Model + w, h int + onSelect func(i *selectItem) tea.Cmd } type selectItem struct { @@ -47,7 +48,7 @@ func NewQuickSelect(app *App) *QuickSelect { l.KeyMap.CursorUp.SetKeys("k", "up") l.KeyMap.CursorDown.SetKeys("j", "down") l.KeyMap.Quit.SetKeys("ctrl+c") - l.Title = "channel switcher" + l.Title = "select channel" l.SetShowTitle(true) l.SetFilteringEnabled(true) l.SetShowFilter(true) @@ -55,7 +56,7 @@ func NewQuickSelect(app *App) *QuickSelect { l.SetShowStatusBar(true) l.SetShowPagination(true) - return &QuickSelect{app: app, channels: &l} + return &QuickSelect{app: app, channelList: &l} } type channelListMsg struct { @@ -93,11 +94,20 @@ func getChannelsCmd(client *api.Client, activeOnly bool, fid uint64) tea.Cmd { return msg } } +func (m *QuickSelect) SetOnSelect(f func(i *selectItem) tea.Cmd) { + m.onSelect = f +} func (m *QuickSelect) SetSize(w, h int) { m.w = w m.h = h - m.channels.SetSize(w, h) + m.channelList.SetSize(w, h) +} +func (m *QuickSelect) Active() bool { + return m.active +} +func (m *QuickSelect) SetActive(active bool) { + m.active = active } func (m *QuickSelect) Init() tea.Cmd { @@ -116,11 +126,20 @@ func (m *QuickSelect) Update(msg tea.Msg) (tea.Model, tea.Cmd) { for _, c := range msg { items = append(items, &selectItem{name: c.Name, value: c.ParentURL, itype: "channel"}) } - return m, m.channels.SetItems(items) + return m, m.channelList.SetItems(items) case tea.KeyMsg: + if msg.String() == "ctrl+c" { + return m, tea.Quit + } + if !m.active { + return m, nil + } if msg.String() == "enter" { - currentItem, ok := m.channels.SelectedItem().(*selectItem) + if m.onSelect != nil { + return m, m.onSelect(m.channelList.SelectedItem().(*selectItem)) + } + currentItem, ok := m.channelList.SelectedItem().(*selectItem) if !ok { log.Println("no item selected") return m, nil @@ -151,8 +170,8 @@ func (m *QuickSelect) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } } - l, cmd := m.channels.Update(msg) - m.channels = &l + l, cmd := m.channelList.Update(msg) + m.channelList = &l return m, cmd } @@ -168,7 +187,7 @@ var dialogBoxStyle = NewStyle(). func (m *QuickSelect) View() string { dialog := lipgloss.Place(10, 10, lipgloss.Center, lipgloss.Center, - dialogBoxStyle.Width(m.w).Height(m.h).Render(m.channels.View()), + dialogBoxStyle.Width(m.w).Height(m.h).Render(m.channelList.View()), lipgloss.WithWhitespaceChars("~~"), lipgloss.WithWhitespaceForeground(subtle), ) From 7de44d3518b6541f1ccd52c93245fa4f4c51c52f Mon Sep 17 00:00:00 2001 From: Cam Sweeney Date: Fri, 14 Jun 2024 22:31:34 -0700 Subject: [PATCH 2/3] bump charlimit --- ui/publish.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/publish.go b/ui/publish.go index 8e9bf52..2979d25 100644 --- a/ui/publish.go +++ b/ui/publish.go @@ -95,7 +95,7 @@ func NewPublishInput(app *App) *PublishInput { } else { ta.Placeholder = "publish cast..." } - ta.CharLimit = 320 + ta.CharLimit = 1024 ta.ShowLineNumbers = false ta.Prompt = "" From c6938c769affd7b6afba700e4a19e4c3835221f0 Mon Sep 17 00:00:00 2001 From: Cam Sweeney Date: Sat, 15 Jun 2024 00:11:23 -0700 Subject: [PATCH 3/3] more sizing, active border --- ui/app.go | 46 ++++++++++++++++++++++++++++------------------ ui/cast_details.go | 14 +++++++------- ui/feed.go | 7 +++++-- ui/quickselect.go | 13 +++---------- ui/sidebar.go | 7 ++++++- 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/ui/app.go b/ui/app.go index e1a39f7..1538bd4 100644 --- a/ui/app.go +++ b/ui/app.go @@ -16,8 +16,10 @@ import ( // TODO provide to models var renderer *lipgloss.Renderer = lipgloss.DefaultRenderer() +var activeColor = lipgloss.AdaptiveColor{Dark: "#874BFD", Light: "#874BFD"} + var ( - mainStyle = lipgloss.NewStyle().Margin(0).Padding(0).Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("#874BFD")) + mainStyle = lipgloss.NewStyle().Margin(0).Padding(0).Border(lipgloss.RoundedBorder()) ) func NewStyle() lipgloss.Style { @@ -276,25 +278,28 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) { _, statusHeight := lipgloss.Size(a.statusLine.View()) wx, wy := msg.Width, msg.Height-statusHeight + fx, fy := mainStyle.GetFrameSize() + wx = wx - fx + wy = wy - fy - sideMax := 30 - sidePct := int(float64(wx) * 0.2) - sx := sidePct - if sideMax < sidePct { - sx = sideMax - } - a.sidebar.SetSize(sx, wy-4) + spx := min(80, wx-10) + spy := min(80, wy-10) + + a.splash.SetSize(spx, spy) + + sx := min(30, int(float64(wx)*0.2)) + a.sidebar.SetSize(sx, wy-statusHeight) sideWidth, _ := lipgloss.Size(a.sidebar.View()) - pw := wx - sideWidth - py := wy - 10 - a.publish.SetSize(pw, py) - a.splash.SetSize(pw, py) - a.quickSelect.SetSize(pw, py) - a.help.SetSize(pw, py) + mx := wx - sideWidth + mx = min(mx, int(float64(wx)*0.8)) - fx, fy := mainStyle.GetFrameSize() - mx, my := wx-sideWidth-fx-4, wy-fy + my := min(wy, int(float64(wy)*0.9)) + + dialogX, dialogY := int(float64(mx)*0.8), int(float64(my)*0.8) + a.publish.SetSize(dialogX, dialogY) + a.quickSelect.SetSize(dialogX, dialogY) + a.help.SetSize(dialogX, dialogY) childMsg := tea.WindowSizeMsg{ Width: mx, @@ -375,7 +380,8 @@ func (a *App) View() string { main := focus.View() side := a.sidebar.View() if a.splash.Active() { - main = a.splash.View() + main = lipgloss.Place(GetWidth(), GetHeight(), lipgloss.Center, lipgloss.Center, a.splash.View()) + return main } if a.publish.Active() { @@ -392,7 +398,11 @@ func (a *App) View() string { main = a.help.View() } - main = mainStyle.Render(main) + ss := mainStyle + if !a.sidebar.Active() { + ss = ss.BorderForeground(activeColor) + } + main = ss.Render(main) return lipgloss.JoinVertical(lipgloss.Top, lipgloss.JoinHorizontal(lipgloss.Center, side, main), diff --git a/ui/cast_details.go b/ui/cast_details.go index 9106352..7a37eca 100644 --- a/ui/cast_details.go +++ b/ui/cast_details.go @@ -89,19 +89,19 @@ func (m *CastView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds := []tea.Cmd{} fx, fy := style.GetFrameSize() + w := min(msg.Width-fx, int(float64(GetWidth())*0.75)) + h := min(msg.Height-fy, GetHeight()-4) - w, h := msg.Width-fx, msg.Height-fy m.w, m.h = w, h - m.header.Width = w m.header.Height = min(10, int(float64(h)*0.2)) hHeight := lipgloss.Height(m.header.View()) - cx, cy := w, h-hHeight + cy := h - hHeight - m.vp.Width = cx - m.vp.Height = int(float64(cy) * 0.5) + m.vp.Width = w - fx + m.vp.Height = int(float64(cy) * 0.5) //- fy m.img.SetSize(0, 0) @@ -109,9 +109,9 @@ func (m *CastView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.img.SetSize(4, 4) m.vp.Height = int(float64(cy) * 0.25) } - m.replies.SetSize(msg.Width, int(float64(cy)*0.5)) + m.replies.SetSize(w, int(float64(cy)*0.5)) - m.pubReply.SetSize(msg.Width, msg.Height-10) + m.pubReply.SetSize(m.w, m.h) return m, tea.Batch(cmds...) case *ctxInfoMsg: diff --git a/ui/feed.go b/ui/feed.go index 7411b09..dd80f56 100644 --- a/ui/feed.go +++ b/ui/feed.go @@ -329,11 +329,14 @@ func (m *FeedView) SetSize(w, h int) { _, dy := lipgloss.Size(channelHeaderStyle.Render(m.descVp.View())) fx, fy := feedStyle.GetFrameSize() - m.table.SetWidth(w - fx) + x := min(w-fx, int(float64(GetWidth())*0.75)) + m.table.SetWidth(x) m.table.SetHeight(h - fy - dy) + + // m.table.SetWidth(w -fx) m.setTableConfig() - lw := int(float64(w) * 0.2) + lw := int(float64(w) * 0.75) m.loading.SetSize(lw, h) } diff --git a/ui/quickselect.go b/ui/quickselect.go index 9e5b5e3..bc9f1ad 100644 --- a/ui/quickselect.go +++ b/ui/quickselect.go @@ -175,19 +175,12 @@ func (m *QuickSelect) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } -var dialogBoxStyle = NewStyle(). - Border(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("#874BFD")). - Padding(1, 0). - BorderTop(true). - BorderLeft(true). - BorderRight(true). - BorderBottom(true) +var dialogBoxStyle = NewStyle() func (m *QuickSelect) View() string { - dialog := lipgloss.Place(10, 10, + dialog := lipgloss.Place(m.h, m.h, lipgloss.Center, lipgloss.Center, - dialogBoxStyle.Width(m.w).Height(m.h).Render(m.channelList.View()), + dialogBoxStyle.Render(m.channelList.View()), lipgloss.WithWhitespaceChars("~~"), lipgloss.WithWhitespaceForeground(subtle), ) diff --git a/ui/sidebar.go b/ui/sidebar.go index c3efff6..e63014b 100644 --- a/ui/sidebar.go +++ b/ui/sidebar.go @@ -188,9 +188,14 @@ func (m *Sidebar) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(cmds...) } func (m *Sidebar) View() string { + ss := navStyle if m.account == nil { return navStyle.Render(m.nav.View()) } + if m.active { + ss = navStyle.BorderForeground(activeColor) + + } accountStyle := NewStyle(). Border(lipgloss.RoundedBorder(), true, false, true). @@ -208,7 +213,7 @@ func (m *Sidebar) View() string { ), ) - return navStyle.Render( + return ss.Render( lipgloss.JoinVertical(lipgloss.Top, m.nav.View(), account,