emoji_picker.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package chat
  2. import (
  3. "fmt"
  4. "log/slog"
  5. "github.com/ayn2op/discordo/internal/config"
  6. "github.com/ayn2op/tview"
  7. "github.com/ayn2op/tview/help"
  8. "github.com/ayn2op/tview/keybind"
  9. "github.com/ayn2op/tview/picker"
  10. "github.com/diamondburned/arikawa/v3/discord"
  11. )
  12. const emojiPickerLayerName = "emojiPicker"
  13. type emojiPicker struct {
  14. *picker.Model
  15. chatView *Model
  16. browseMode bool
  17. targetMessageID discord.MessageID
  18. targetChannelID discord.ChannelID
  19. }
  20. var _ help.KeyMap = (*emojiPicker)(nil)
  21. func newEmojiPicker(cfg *config.Config, chatView *Model) *emojiPicker {
  22. ep := &emojiPicker{Model: picker.NewModel(), chatView: chatView}
  23. ConfigurePicker(ep.Model, cfg, "Emoji")
  24. return ep
  25. }
  26. func (ep *emojiPicker) resetBrowse() { ep.browseMode = false }
  27. // commonEmoji returns a list of frequently used unicode emoji for the picker.
  28. var commonEmoji = []struct {
  29. emoji string
  30. names string
  31. }{
  32. {"👍", "thumbsup thumbs_up +1 like"},
  33. {"👎", "thumbsdown thumbs_down -1 dislike"},
  34. {"❤️", "heart love red_heart"},
  35. {"😂", "joy laughing tears"},
  36. {"😭", "sob crying"},
  37. {"😊", "blush smile happy"},
  38. {"😍", "heart_eyes love"},
  39. {"🤣", "rofl rolling"},
  40. {"🙂", "slightly_smiling_face"},
  41. {"😔", "pensive sad"},
  42. {"🔥", "fire lit hot"},
  43. {"✅", "white_check_mark check yes"},
  44. {"❌", "x cross no"},
  45. {"👀", "eyes look"},
  46. {"🎉", "tada party celebration"},
  47. {"💀", "skull dead"},
  48. {"🤔", "thinking hmm"},
  49. {"😐", "neutral_face"},
  50. {"🤷", "shrug"},
  51. {"👋", "wave hello hi"},
  52. {"🙏", "pray please thanks"},
  53. {"💯", "100 hundred perfect"},
  54. {"😎", "sunglasses cool"},
  55. {"🥳", "partying_face party"},
  56. {"😢", "cry sad"},
  57. {"😅", "sweat_smile nervous"},
  58. {"🤗", "hugs hugging"},
  59. {"😤", "triumph angry"},
  60. {"🥺", "pleading_face"},
  61. {"💪", "muscle strong flex"},
  62. {"✨", "sparkles stars"},
  63. {"💜", "purple_heart"},
  64. {"💙", "blue_heart"},
  65. {"💚", "green_heart"},
  66. {"🧡", "orange_heart"},
  67. {"💛", "yellow_heart"},
  68. {"🖤", "black_heart"},
  69. {"🤍", "white_heart"},
  70. {"⭐", "star"},
  71. {"🌟", "star2 glowing_star"},
  72. {"😮", "open_mouth surprised"},
  73. {"😱", "scream shocked"},
  74. {"😈", "smiling_imp devil"},
  75. {"💀", "skull"},
  76. {"🤡", "clown clown_face"},
  77. {"🫡", "salute saluting_face"},
  78. {"🫠", "melting_face"},
  79. {"💤", "zzz sleep"},
  80. {"🤝", "handshake"},
  81. {"🏳️", "white_flag surrender"},
  82. }
  83. func (ep *emojiPicker) update() {
  84. items := make(picker.Items, 0, len(commonEmoji)+50)
  85. for _, e := range commonEmoji {
  86. items = append(items, picker.Item{
  87. Text: e.emoji + " " + e.names[:min(len(e.names), indexOf(e.names, ' '))],
  88. FilterText: e.names,
  89. Reference: discord.APIEmoji(e.emoji),
  90. })
  91. }
  92. // Add guild custom emoji if available
  93. selectedChannel := ep.chatView.SelectedChannel()
  94. if selectedChannel != nil && selectedChannel.GuildID.IsValid() {
  95. emojis, err := ep.chatView.state.Cabinet.Emojis(selectedChannel.GuildID)
  96. if err == nil {
  97. for _, emoji := range emojis {
  98. if !emoji.Available {
  99. continue
  100. }
  101. items = append(items, picker.Item{
  102. Text: ":" + emoji.Name + ":",
  103. FilterText: emoji.Name,
  104. Reference: emoji.APIString(),
  105. })
  106. }
  107. }
  108. }
  109. ep.Model.SetItems(items)
  110. }
  111. func indexOf(s string, c byte) int {
  112. for i := range len(s) {
  113. if s[i] == c {
  114. return i
  115. }
  116. }
  117. return len(s)
  118. }
  119. func (ep *emojiPicker) HandleEvent(event tview.Event) tview.Command {
  120. switch event := event.(type) {
  121. case *tview.KeyEvent:
  122. if cmd, handled := pickerBrowseHandleKey(event, &ep.browseMode, ep.Model, func() { ep.chatView.closeEmojiPicker() }); handled {
  123. return cmd
  124. }
  125. case *picker.SelectedEvent:
  126. apiEmoji, ok := event.Reference.(discord.APIEmoji)
  127. if !ok {
  128. return nil
  129. }
  130. channelID := ep.targetChannelID
  131. messageID := ep.targetMessageID
  132. ep.chatView.closeEmojiPicker()
  133. return func() tview.Event {
  134. if err := ep.chatView.state.React(channelID, messageID, apiEmoji); err != nil {
  135. slog.Error("failed to add reaction", "err", err, "emoji", fmt.Sprint(apiEmoji))
  136. }
  137. return nil
  138. }
  139. case *picker.CancelEvent:
  140. ep.chatView.closeEmojiPicker()
  141. return nil
  142. }
  143. return ep.Model.HandleEvent(event)
  144. }
  145. func (ep *emojiPicker) ShortHelp() []keybind.Keybind {
  146. cfg := ep.chatView.cfg.Keybinds.Picker
  147. return []keybind.Keybind{cfg.Up.Keybind, cfg.Down.Keybind, cfg.Select.Keybind, cfg.Cancel.Keybind}
  148. }
  149. func (ep *emojiPicker) FullHelp() [][]keybind.Keybind {
  150. cfg := ep.chatView.cfg.Keybinds.Picker
  151. return [][]keybind.Keybind{
  152. {cfg.Up.Keybind, cfg.Down.Keybind, cfg.Top.Keybind, cfg.Bottom.Keybind},
  153. {cfg.Select.Keybind, cfg.Cancel.Keybind},
  154. }
  155. }