package root import ( "os" "github.com/ayn2op/discordo/internal/config" "github.com/ayn2op/discordo/internal/consts" "github.com/ayn2op/discordo/internal/ui" "github.com/ayn2op/discordo/internal/ui/chat" "github.com/ayn2op/discordo/internal/ui/login" "github.com/ayn2op/discordo/internal/ui/login/password" "github.com/ayn2op/discordo/internal/ui/login/qr" "github.com/ayn2op/discordo/internal/ui/login/token" "github.com/ayn2op/tview" "github.com/ayn2op/tview/flex" "github.com/ayn2op/tview/help" "github.com/ayn2op/tview/keybind" "github.com/ayn2op/tview/layers" "github.com/ayn2op/tview/modal" "github.com/gdamore/tcell/v3" ) const ( tokenEnvVarKey = "DISCORDO_TOKEN" contentLayerName = "content" modalLayerName = "modal" ) type Model struct { *layers.Layers rootFlex *flex.Model // inner + help inner tview.Model help *help.Model modal *ui.ModalMsg cfg *config.Config } func NewModel(cfg *config.Config) *Model { m := &Model{ Layers: layers.New(), rootFlex: flex.NewModel(), cfg: cfg, } m.SetBackgroundLayerStyle(cfg.Theme.Dialog.BackgroundStyle.Style) m.rootFlex.SetDirection(flex.DirectionRow) if cfg.Help.Enabled { m.help = newHelpModel(cfg, m) } m.buildLayout() return m } func newHelpModel(cfg *config.Config, keyMap help.KeyMap) *help.Model { styles := help.DefaultStyles() styles.ShortKey = cfg.Theme.Help.ShortKeyStyle.Style styles.ShortDesc = cfg.Theme.Help.ShortDescStyle.Style styles.FullKey = cfg.Theme.Help.FullKeyStyle.Style styles.FullDesc = cfg.Theme.Help.FullDescStyle.Style h := help.NewModel() h.SetStyles(styles). SetKeyMap(keyMap). SetCompactModifiers(cfg.Help.CompactModifiers). SetShortSeparator(cfg.Help.Separator) h.SetBorderPadding(0, 0, cfg.Help.Padding[0], cfg.Help.Padding[1]) return h } func (m *Model) showLogin() tview.Cmd { m.inner = login.NewModel(m.cfg) m.buildLayout() return m.inner.Update(tview.InitMsg{}) } func (m *Model) showChat(token string) tview.Cmd { m.inner = chat.NewModel(m.cfg, token) m.buildLayout() return m.inner.Update(tview.InitMsg{}) } func (m *Model) buildLayout() { m.Clear() m.modal = nil m.rootFlex.Clear() if m.inner != nil { m.rootFlex.AddItem(m.inner, 0, 1, true) } if m.help != nil { m.rootFlex.AddItem(m.help, m.helpHeight(), 0, false) } m.AddLayer(m.rootFlex, layers.WithName(contentLayerName), layers.WithResize(true), layers.WithVisible(true)) } var _ tview.Model = (*Model)(nil) func (m *Model) Update(msg tview.Msg) tview.Cmd { switch msg := msg.(type) { case tview.InitMsg: var cmd tview.Cmd if token := os.Getenv(tokenEnvVarKey); token != "" { cmd = tokenCmd(token) } else { cmd = getToken() } return tview.Batch( tview.SetTitle(consts.Name), initClipboard(), cmd, ) case loginMsg: return m.showLogin() case tokenMsg: return m.showChat(string(msg)) case password.TokenMsg: return tview.Batch(m.showChat(string(msg)), setToken(string(msg))) case qr.TokenMsg: return tview.Batch(m.showChat(string(msg)), setToken(string(msg))) case token.TokenMsg: return tview.Batch(m.showChat(string(msg)), setToken(string(msg))) case chat.LogoutMsg: return tview.Batch( m.showLogin(), deleteToken(), ) case ui.ModalMsg: return m.showModal(msg) case modal.DoneMsg: return m.finishModal(msg) case tview.KeyMsg: if m.modal != nil { return m.Layers.Update(msg) } switch { case keybind.Matches(msg, m.cfg.Keybinds.ToggleHelp.Keybind): m.toggleHelp() return nil case keybind.Matches(msg, m.cfg.Keybinds.ToggleFullHelp.Keybind): if m.help == nil { return nil } m.help.SetShowAll(!m.help.ShowAll()) m.rootFlex.ResizeItem(m.help, m.helpHeight(), 0) return nil case keybind.Matches(msg, m.cfg.Keybinds.Suspend.Keybind): return suspend() case keybind.Matches(msg, m.cfg.Keybinds.Quit.Keybind): var innerCmd tview.Cmd if m.inner != nil { innerCmd = m.inner.Update(chat.QuitMsg{}) } return tview.Batch(innerCmd, tview.Quit()) } case tview.MouseMsg, tview.PasteMsg, tview.FormSubmitMsg, tview.FormCancelMsg: if m.modal != nil { return m.Layers.Update(msg) } } if m.inner != nil { return m.inner.Update(msg) } return nil } func (m *Model) toggleHelp() { if m.help == nil { return } count := m.rootFlex.GetItemCount() if count > 0 && m.rootFlex.GetItem(count-1) == m.help { m.rootFlex.RemoveItem(m.help) } else { m.rootFlex.AddItem(m.help, m.helpHeight(), 0, false) } } func (m *Model) showModal(request ui.ModalMsg) tview.Cmd { if m.modal != nil { return nil } labels := make([]string, len(request.Buttons)) for i, button := range request.Buttons { labels[i] = button.Label } dialog := modal.NewModel().SetText(request.Text).AddButtons(labels) style := m.cfg.Theme.Dialog.Style.Style if bg := style.GetBackground(); bg != tcell.ColorDefault { dialog.SetBackgroundColor(bg) } if fg := style.GetForeground(); fg != tcell.ColorDefault { dialog.SetTextColor(fg) } dialog.SetButtonStyle(style).SetButtonActivatedStyle(style.Reverse(true)) m.modal = &request m.AddLayer(dialog, layers.WithName(modalLayerName), layers.WithResize(true), layers.WithVisible(true), layers.WithOverlay(), ) return nil } func (m *Model) finishModal(done modal.DoneMsg) tview.Cmd { state := m.modal if state == nil { return nil } var result tview.Msg if done.ButtonIndex >= 0 && done.ButtonIndex < len(state.Buttons) { button := state.Buttons[done.ButtonIndex] if button.KeepOpen { return func() tview.Msg { return button.Result } } result = button.Result } m.RemoveLayer(modalLayerName) m.modal = nil if result == nil { return nil } return func() tview.Msg { return result } } func (m *Model) helpHeight() int { height := 1 if m.help.ShowAll() { height = max(len(m.help.FullHelpLines(m.FullHelp(), 0)), 1) } return height }