channels_picker.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package chat
  2. import (
  3. "log/slog"
  4. "strings"
  5. "github.com/ayn2op/discordo/internal/config"
  6. "github.com/ayn2op/discordo/internal/ui"
  7. "github.com/ayn2op/tview"
  8. "github.com/ayn2op/tview/help"
  9. "github.com/ayn2op/tview/keybind"
  10. "github.com/ayn2op/tview/list"
  11. "github.com/ayn2op/tview/picker"
  12. "github.com/diamondburned/arikawa/v3/discord"
  13. )
  14. type channelsPicker struct {
  15. *picker.Model
  16. chatView *Model
  17. }
  18. var _ help.KeyMap = (*channelsPicker)(nil)
  19. func newChannelsPicker(cfg *config.Config, chatView *Model) *channelsPicker {
  20. cp := &channelsPicker{picker.NewModel(), chatView}
  21. cp.Box = ui.ConfigureBox(tview.NewBox(), &cfg.Theme)
  22. // When a child of the parent flex is focused, the parent layout itself is not reported as focused.
  23. // Instead, the focused child (picker) is considered focused.
  24. // Therefore, we manually set the active border style on the picker to ensure it displays the correct focused appearance.
  25. cp.
  26. SetBlurFunc(nil).
  27. SetFocusFunc(nil).
  28. SetBorderSet(cfg.Theme.Border.ActiveSet.BorderSet).
  29. SetBorderStyle(cfg.Theme.Border.ActiveStyle.Style).
  30. SetTitleStyle(cfg.Theme.Title.ActiveStyle.Style).
  31. SetFooterStyle(cfg.Theme.Footer.ActiveStyle.Style)
  32. cp.SetTitle("Channels")
  33. cp.SetScrollBarVisibility(cfg.Theme.ScrollBar.Visibility.ScrollBarVisibility)
  34. cp.SetScrollBar(tview.NewScrollBar().
  35. SetTrackStyle(cfg.Theme.ScrollBar.TrackStyle.Style).
  36. SetThumbStyle(cfg.Theme.ScrollBar.ThumbStyle.Style).
  37. SetGlyphSet(cfg.Theme.ScrollBar.GlyphSet.GlyphSet))
  38. cp.SetKeybinds(picker.Keybinds{
  39. Cancel: cfg.Keybinds.Picker.Cancel.Keybind,
  40. Keybinds: list.Keybinds{
  41. SelectUp: cfg.Keybinds.Picker.Up.Keybind,
  42. SelectDown: cfg.Keybinds.Picker.Down.Keybind,
  43. SelectTop: cfg.Keybinds.Picker.Top.Keybind,
  44. SelectBottom: cfg.Keybinds.Picker.Bottom.Keybind,
  45. },
  46. Select: cfg.Keybinds.Picker.Select.Keybind,
  47. })
  48. return cp
  49. }
  50. func (cp *channelsPicker) HandleEvent(event tview.Event) tview.Command {
  51. switch event := event.(type) {
  52. case *picker.SelectedEvent:
  53. channelID, ok := event.Reference.(discord.ChannelID)
  54. if !ok || !channelID.IsValid() {
  55. return nil
  56. }
  57. channel, err := cp.chatView.state.Cabinet.Channel(channelID)
  58. if err != nil {
  59. slog.Error("failed to get channel from state", "err", err, "channel_id", channelID)
  60. return nil
  61. }
  62. node := cp.chatView.guildsTree.findNodeByChannelID(channel.ID)
  63. if node == nil {
  64. slog.Error("failed to locate channel in tree", "channel_id", channel.ID)
  65. return nil
  66. }
  67. cp.chatView.guildsTree.expandPathToNode(node)
  68. cp.chatView.guildsTree.SetCurrentNode(node)
  69. if channel.Type != discord.GuildCategory {
  70. cp.chatView.guildsTree.onSelected(node)
  71. }
  72. cp.chatView.closePicker()
  73. cp.chatView.focusMessageInput()
  74. return nil
  75. case *picker.CancelEvent:
  76. cp.chatView.closePicker()
  77. return nil
  78. }
  79. return cp.Model.HandleEvent(event)
  80. }
  81. func (cp *channelsPicker) update() {
  82. cp.ClearItems()
  83. state := cp.chatView.state
  84. privateChannels, err := state.Cabinet.PrivateChannels()
  85. if err != nil {
  86. slog.Error("failed to get private channels from state", "err", err)
  87. return
  88. }
  89. ui.SortPrivateChannels(privateChannels)
  90. for _, channel := range privateChannels {
  91. cp.addChannel(nil, channel)
  92. }
  93. guilds, err := state.Cabinet.Guilds()
  94. if err != nil {
  95. slog.Error("failed to get guilds from state", "err", err)
  96. return
  97. }
  98. for _, guild := range guilds {
  99. channels, err := state.Cabinet.Channels(guild.ID)
  100. if err != nil {
  101. slog.Error("failed to get channels from state", "err", err, "guild_id", guild.ID)
  102. continue
  103. }
  104. for _, channel := range channels {
  105. cp.addChannel(&guild, channel)
  106. }
  107. }
  108. cp.Update()
  109. }
  110. func (cp *channelsPicker) addChannel(guild *discord.Guild, channel discord.Channel) {
  111. var b strings.Builder
  112. b.WriteString(ui.ChannelToString(channel, cp.chatView.cfg.Icons, cp.chatView.state))
  113. if guild != nil {
  114. b.WriteString(" - ")
  115. b.WriteString(guild.Name)
  116. }
  117. name := b.String()
  118. cp.AddItem(picker.Item{Text: name, FilterText: name, Reference: channel.ID})
  119. }
  120. func (cp *channelsPicker) ShortHelp() []keybind.Keybind {
  121. cfg := cp.chatView.cfg.Keybinds.Picker
  122. return []keybind.Keybind{cfg.Up.Keybind, cfg.Down.Keybind, cfg.Select.Keybind, cfg.Cancel.Keybind}
  123. }
  124. func (cp *channelsPicker) FullHelp() [][]keybind.Keybind {
  125. cfg := cp.chatView.cfg.Keybinds.Picker
  126. return [][]keybind.Keybind{
  127. {cfg.Up.Keybind, cfg.Down.Keybind, cfg.Top.Keybind, cfg.Bottom.Keybind},
  128. {cfg.Select.Keybind, cfg.Cancel.Keybind},
  129. }
  130. }