chatview.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. package cmd
  2. import (
  3. "log/slog"
  4. "github.com/ayn2op/discordo/internal/config"
  5. "github.com/ayn2op/discordo/internal/keyring"
  6. "github.com/ayn2op/discordo/internal/ui"
  7. "github.com/ayn2op/tview"
  8. "github.com/gdamore/tcell/v2"
  9. )
  10. const (
  11. flexPageName = "flex"
  12. mentionsListPageName = "mentionsList"
  13. attachmentsListPageName = "attachmentsList"
  14. confirmModalPageName = "confirmModal"
  15. )
  16. type chatView struct {
  17. *tview.Pages
  18. mainFlex *tview.Flex
  19. rightFlex *tview.Flex
  20. guildsTree *guildsTree
  21. messagesList *messagesList
  22. messageInput *messageInput
  23. app *tview.Application
  24. cfg *config.Config
  25. }
  26. func newChatView(app *tview.Application, cfg *config.Config) *chatView {
  27. cv := &chatView{
  28. Pages: tview.NewPages(),
  29. mainFlex: tview.NewFlex(),
  30. rightFlex: tview.NewFlex(),
  31. guildsTree: newGuildsTree(cfg),
  32. messagesList: newMessagesList(cfg),
  33. messageInput: newMessageInput(cfg),
  34. app: app,
  35. cfg: cfg,
  36. }
  37. cv.SetInputCapture(cv.onInputCapture)
  38. cv.buildLayout()
  39. return cv
  40. }
  41. func (cv *chatView) buildLayout() {
  42. cv.Clear()
  43. cv.mainFlex.Clear()
  44. cv.rightFlex.
  45. SetDirection(tview.FlexRow).
  46. AddItem(cv.messagesList, 0, 1, false).
  47. AddItem(cv.messageInput, 3, 1, false)
  48. // The guilds tree is always focused first at start-up.
  49. cv.mainFlex.
  50. AddItem(cv.guildsTree, 0, 1, true).
  51. AddItem(cv.rightFlex, 0, 4, false)
  52. cv.AddAndSwitchToPage(flexPageName, cv.mainFlex, true)
  53. }
  54. func (cv *chatView) toggleGuildsTree() {
  55. // The guilds tree is visible if the number of items is two.
  56. if cv.mainFlex.GetItemCount() == 2 {
  57. cv.mainFlex.RemoveItem(cv.guildsTree)
  58. if cv.guildsTree.HasFocus() {
  59. cv.app.SetFocus(cv.mainFlex)
  60. }
  61. } else {
  62. cv.buildLayout()
  63. cv.app.SetFocus(cv.guildsTree)
  64. }
  65. }
  66. func (cv *chatView) focusGuildsTree() bool {
  67. // The guilds tree is not hidden if the number of items is two.
  68. if cv.mainFlex.GetItemCount() == 2 {
  69. cv.app.SetFocus(cv.guildsTree)
  70. return true
  71. }
  72. return false
  73. }
  74. func (cv *chatView) focusMessageInput() bool {
  75. if !cv.messageInput.GetDisabled() {
  76. cv.app.SetFocus(cv.messageInput)
  77. return true
  78. }
  79. return false
  80. }
  81. func (cv *chatView) focusPrevious() {
  82. switch cv.app.GetFocus() {
  83. case cv.guildsTree:
  84. cv.app.SetFocus(cv.messageInput)
  85. case cv.messagesList: // Handle both a.messagesList and a.flex as well as other edge cases (if there is).
  86. if ok := cv.focusGuildsTree(); !ok {
  87. cv.app.SetFocus(cv.messageInput)
  88. }
  89. case cv.messageInput:
  90. cv.app.SetFocus(cv.messagesList)
  91. }
  92. }
  93. func (cv *chatView) focusNext() {
  94. switch cv.app.GetFocus() {
  95. case cv.guildsTree:
  96. cv.app.SetFocus(cv.messagesList)
  97. case cv.messagesList:
  98. cv.app.SetFocus(cv.messageInput)
  99. case cv.messageInput: // Handle both a.messageInput and a.flex as well as other edge cases (if there is).
  100. if ok := cv.focusGuildsTree(); !ok {
  101. cv.app.SetFocus(cv.messagesList)
  102. }
  103. }
  104. }
  105. func (cv *chatView) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
  106. switch event.Name() {
  107. case cv.cfg.Keys.FocusGuildsTree:
  108. cv.messageInput.removeMentionsList()
  109. cv.focusGuildsTree()
  110. return nil
  111. case cv.cfg.Keys.FocusMessagesList:
  112. cv.messageInput.removeMentionsList()
  113. cv.app.SetFocus(cv.messagesList)
  114. return nil
  115. case cv.cfg.Keys.FocusMessageInput:
  116. cv.focusMessageInput()
  117. return nil
  118. case cv.cfg.Keys.FocusPrevious:
  119. cv.focusPrevious()
  120. return nil
  121. case cv.cfg.Keys.FocusNext:
  122. cv.focusNext()
  123. return nil
  124. case cv.cfg.Keys.Logout:
  125. app.quit()
  126. if err := keyring.DeleteToken(); err != nil {
  127. slog.Error("failed to delete token from keyring", "err", err)
  128. return nil
  129. }
  130. return nil
  131. case cv.cfg.Keys.ToggleGuildsTree:
  132. cv.toggleGuildsTree()
  133. return nil
  134. }
  135. return event
  136. }
  137. func (cv *chatView) showConfirmModal(prompt string, buttons []string, onDone func(label string)) {
  138. previousFocus := cv.app.GetFocus()
  139. modal := tview.NewModal().
  140. SetText(prompt).
  141. AddButtons(buttons).
  142. SetDoneFunc(func(_ int, buttonLabel string) {
  143. cv.RemovePage(confirmModalPageName).SwitchToPage(flexPageName)
  144. cv.app.SetFocus(previousFocus)
  145. if onDone != nil {
  146. onDone(buttonLabel)
  147. }
  148. })
  149. cv.
  150. AddAndSwitchToPage(confirmModalPageName, ui.Centered(modal, 0, 0), true).
  151. ShowPage(flexPageName)
  152. }