chatview.go 3.9 KB

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