refactor(chat): extract attachments picker package

This commit is contained in:
ayn2op 2026-08-05 14:54:00 -04:00
parent 3e2222414a
commit 71805de597
9 changed files with 82 additions and 116 deletions

4
go.mod
View file

@ -10,7 +10,7 @@ require (
github.com/andybalholm/brotli v1.2.2
github.com/ayn2op/arikawa/v3 v3.0.0-20260708075022-9cc760fe507e
github.com/ayn2op/ningen/v3 v3.0.1-0.20260708075321-0ba1ffc70bd6
github.com/ayn2op/tview v0.0.0-20260803165347-d8123f4ec53c
github.com/ayn2op/tview v0.0.0-20260805051036-f8d695faec51
github.com/bogdanfinn/fhttp v0.6.8
github.com/bogdanfinn/tls-client v1.15.1
github.com/gdamore/tcell/v3 v3.4.1
@ -56,7 +56,7 @@ require (
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/exp/shiny v0.0.0-20260727155853-b88d891fe743 // indirect
golang.org/x/image v0.44.0 // indirect
golang.org/x/mobile v0.0.0-20260730202154-c700fe717e6e // indirect
golang.org/x/mobile v0.0.0-20260803200217-62cee1672c8e // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect

8
go.sum
View file

@ -14,8 +14,8 @@ github.com/ayn2op/arikawa/v3 v3.0.0-20260708075022-9cc760fe507e h1:rsMifRXwxkbha
github.com/ayn2op/arikawa/v3 v3.0.0-20260708075022-9cc760fe507e/go.mod h1:gqklpHK7WHYTzpd1ZY6Ho/p9GURBghzBCzRiBYpaUP8=
github.com/ayn2op/ningen/v3 v3.0.1-0.20260708075321-0ba1ffc70bd6 h1:/khHm6G+GWwXpmwA8bJoaXoUhubqMEUG6spjZsnklIw=
github.com/ayn2op/ningen/v3 v3.0.1-0.20260708075321-0ba1ffc70bd6/go.mod h1:vqLIvETLkjc2vIgQzVEBqpGNCrgNe7YJqS9TUAJsXRI=
github.com/ayn2op/tview v0.0.0-20260803165347-d8123f4ec53c h1:ApOfdF/YBJ+oy5zG5yJW/IbgCOoWcfJ/qukTnfFlUXc=
github.com/ayn2op/tview v0.0.0-20260803165347-d8123f4ec53c/go.mod h1:bJZdl/FfspPLEW3nuX6ubt7YbmDoSj7f2IoWH37w1NE=
github.com/ayn2op/tview v0.0.0-20260805051036-f8d695faec51 h1:hvyPcZcLG6dEAVXNpsMSWj93oJ1TIcDkJ+2eBcT1Yi0=
github.com/ayn2op/tview v0.0.0-20260805051036-f8d695faec51/go.mod h1:bJZdl/FfspPLEW3nuX6ubt7YbmDoSj7f2IoWH37w1NE=
github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q=
github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM=
github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic=
@ -119,8 +119,8 @@ golang.org/x/exp/shiny v0.0.0-20260727155853-b88d891fe743 h1:rQLtGtwpYzQUyxCqrT3
golang.org/x/exp/shiny v0.0.0-20260727155853-b88d891fe743/go.mod h1:JRiBNgSZB8uVKtHGrvbGDQ8p9Ghn3+lkFSk1KqRIiPc=
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
golang.org/x/mobile v0.0.0-20260730202154-c700fe717e6e h1:v5PGTbI7uer7FhpDeGryGytfbI8t7CDWkKukozvIsNU=
golang.org/x/mobile v0.0.0-20260730202154-c700fe717e6e/go.mod h1:YX+n47s+53POxN3dx9cIGxG3hGUm/lD64hvrRJFbcSA=
golang.org/x/mobile v0.0.0-20260803200217-62cee1672c8e h1:hi1b7Lmv+xvnrjeEv8BoDmU7H/FAOuH7hEHSvjMl2VM=
golang.org/x/mobile v0.0.0-20260803200217-62cee1672c8e/go.mod h1:YX+n47s+53POxN3dx9cIGxG3hGUm/lD64hvrRJFbcSA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=

View file

@ -1,80 +0,0 @@
package chat
import (
"github.com/ayn2op/discordo/internal/config"
"github.com/ayn2op/discordo/internal/ui"
"github.com/ayn2op/tview"
"github.com/ayn2op/tview/help"
"github.com/ayn2op/tview/keybind"
"github.com/ayn2op/tview/picker"
)
type attachmentItem struct {
label string
open tview.Cmd
}
type attachmentsPicker struct {
*picker.Model
cfg *config.Config
chat *Model
items []attachmentItem
}
var _ help.KeyMap = (*attachmentsPicker)(nil)
func newAttachmentsPicker(cfg *config.Config, chat *Model) *attachmentsPicker {
ap := &attachmentsPicker{Model: picker.NewModel(), cfg: cfg, chat: chat}
ui.ConfigurePicker(ap.Model, cfg, "Attachments")
return ap
}
func (ap *attachmentsPicker) SetItems(items []attachmentItem) {
ap.items = items
pickerItems := make(picker.Items, 0, len(items))
for i, item := range items {
pickerItems = append(pickerItems, picker.Item{
Text: item.label,
FilterText: item.label,
Reference: i,
})
}
ap.Model.SetItems(pickerItems)
}
func (ap *attachmentsPicker) close() tview.Cmd {
ap.chat.RemoveLayer(attachmentsPickerLayerName)
return tview.SetFocus(ap.chat.messagesList)
}
func (ap *attachmentsPicker) Update(msg tview.Msg) tview.Cmd {
switch msg := msg.(type) {
case picker.SelectedMsg:
index, ok := msg.Reference.(int)
if !ok {
return nil
}
if index < 0 || index >= len(ap.items) {
return nil
}
return tview.Sequence(ap.items[index].open, ap.close())
case picker.CancelMsg:
return ap.close()
}
return ap.Model.Update(msg)
}
func (ap *attachmentsPicker) ShortHelp() []keybind.Keybind {
cfg := ap.cfg.Keybinds.Picker
return []keybind.Keybind{cfg.SelectUp.Keybind, cfg.SelectDown.Keybind, cfg.Select.Keybind, cfg.Cancel.Keybind}
}
func (ap *attachmentsPicker) FullHelp() [][]keybind.Keybind {
cfg := ap.cfg.Keybinds.Picker
return [][]keybind.Keybind{
{cfg.SelectUp.Keybind, cfg.SelectDown.Keybind, cfg.SelectTop.Keybind, cfg.SelectBottom.Keybind},
{cfg.Select.Keybind, cfg.Cancel.Keybind},
}
}

View file

@ -0,0 +1,44 @@
package attachmentspicker
import (
"github.com/ayn2op/discordo/internal/config"
"github.com/ayn2op/discordo/internal/ui"
"github.com/ayn2op/tview"
"github.com/ayn2op/tview/picker"
)
type Item struct {
Label string
Open tview.Cmd
}
type Model struct{ *picker.Model }
func NewModel(cfg *config.Config) *Model {
m := &Model{Model: picker.NewModel()}
ui.ConfigurePicker(m.Model, cfg, "Attachments")
return m
}
func (m *Model) SetItems(items []Item) {
pickerItems := make(picker.Items, len(items))
for i, item := range items {
pickerItems[i] = picker.Item{Text: item.Label, Reference: item.Open}
}
m.Model.SetItems(pickerItems)
}
func (m *Model) Update(msg tview.Msg) tview.Cmd {
switch msg := msg.(type) {
case picker.SelectedMsg:
open, ok := msg.Reference.(tview.Cmd)
if !ok {
return nil
}
return func() tview.Msg { return SelectedMsg{Open: open} }
case picker.CancelMsg:
return func() tview.Msg { return CancelMsg{} }
}
return m.Model.Update(msg)
}

View file

@ -0,0 +1,9 @@
package attachmentspicker
import "github.com/ayn2op/tview"
type SelectedMsg struct {
Open tview.Cmd
}
type CancelMsg struct{}

View file

@ -1,21 +0,0 @@
package channelspicker
import (
"github.com/ayn2op/tview/help"
"github.com/ayn2op/tview/keybind"
)
var _ help.KeyMap = (*Model)(nil)
func (m *Model) ShortHelp() []keybind.Keybind {
cfg := m.cfg.Keybinds.Picker
return []keybind.Keybind{cfg.SelectUp.Keybind, cfg.SelectDown.Keybind, cfg.Select.Keybind, cfg.Cancel.Keybind}
}
func (m *Model) FullHelp() [][]keybind.Keybind {
cfg := m.cfg.Keybinds.Picker
return [][]keybind.Keybind{
{cfg.SelectUp.Keybind, cfg.SelectDown.Keybind, cfg.SelectTop.Keybind, cfg.SelectBottom.Keybind},
{cfg.Select.Keybind, cfg.Cancel.Keybind},
}
}

View file

@ -29,6 +29,9 @@ func (m *Model) activeKeyMap() help.KeyMap {
if m.GetVisible(channelsPickerLayerName) {
return m.channelsPicker
}
if m.GetVisible(attachmentsPickerLayerName) {
return m.messagesList.attachmentsPicker
}
switch m.app.Focused() {
case m.guildsTree:

View file

@ -25,6 +25,7 @@ import (
"github.com/ayn2op/discordo/internal/consts"
"github.com/ayn2op/discordo/internal/markdown"
"github.com/ayn2op/discordo/internal/ui"
"github.com/ayn2op/discordo/internal/ui/chat/attachmentspicker"
"github.com/ayn2op/ningen/v3/discordmd"
"github.com/ayn2op/tview"
"github.com/ayn2op/tview/help"
@ -54,7 +55,7 @@ type messagesList struct {
// itemByID caches rendered message TextViews.
itemByID map[discord.MessageID]*tview.TextView
attachmentsPicker *attachmentsPicker
attachmentsPicker *attachmentspicker.Model
}
var _ help.KeyMap = (*messagesList)(nil)
@ -80,7 +81,7 @@ func newMessagesList(cfg *config.Config, chat *Model) *messagesList {
renderer: markdown.NewRenderer(cfg),
itemByID: make(map[discord.MessageID]*tview.TextView),
}
ml.attachmentsPicker = newAttachmentsPicker(cfg, chat)
ml.attachmentsPicker = attachmentspicker.NewModel(cfg)
ui.ConfigureBox(ml.Box, &cfg.Theme)
ml.SetTitle("Messages")
@ -1166,23 +1167,23 @@ func messageURLs(msg discord.Message) []string {
}
func (ml *messagesList) showAttachmentsList(urls []string, attachments []discord.Attachment) tview.Cmd {
var items []attachmentItem
var items []attachmentspicker.Item
for _, a := range attachments {
attachment := a
open := ml.openURL(attachment.URL)
if strings.HasPrefix(attachment.ContentType, "image/") {
open = ml.openAttachment(attachment)
}
items = append(items, attachmentItem{
label: attachment.Filename,
open: open,
items = append(items, attachmentspicker.Item{
Label: attachment.Filename,
Open: open,
})
}
for _, u := range urls {
url := u
items = append(items, attachmentItem{
label: url,
open: ml.openURL(url),
items = append(items, attachmentspicker.Item{
Label: url,
Open: ml.openURL(url),
})
}
ml.attachmentsPicker.SetItems(items)

View file

@ -18,6 +18,7 @@ import (
clientgateway "github.com/ayn2op/discordo/internal/gateway"
"github.com/ayn2op/discordo/internal/http"
"github.com/ayn2op/discordo/internal/ui"
"github.com/ayn2op/discordo/internal/ui/chat/attachmentspicker"
"github.com/ayn2op/discordo/internal/ui/chat/channelspicker"
"github.com/ayn2op/ningen/v3"
"github.com/ayn2op/ningen/v3/states/read"
@ -170,6 +171,11 @@ func (m *Model) closePicker() {
m.channelsPicker.Refresh()
}
func (m *Model) closeAttachmentsPicker() tview.Cmd {
m.RemoveLayer(attachmentsPickerLayerName)
return tview.SetFocus(m.messagesList)
}
func (m *Model) navigateToChannel(channelID discord.ChannelID) tview.Cmd {
channel, err := m.state.Cabinet.Channel(channelID)
if err != nil {
@ -354,6 +360,10 @@ func (m *Model) Update(msg tview.Msg) tview.Cmd {
case channelspicker.CancelMsg:
m.closePicker()
return nil
case attachmentspicker.SelectedMsg:
return tview.Sequence(msg.Open, m.closeAttachmentsPicker())
case attachmentspicker.CancelMsg:
return m.closeAttachmentsPicker()
case QuitMsg:
return m.closeState()
case tview.KeyMsg: