discordo/internal/ui/root/model.go

243 lines
5.7 KiB
Go

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.Init()
}
func (m *Model) showChat(token string) tview.Cmd {
m.inner = chat.NewModel(m.cfg, token)
m.buildLayout()
return m.inner.Init()
}
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) Init() tview.Cmd {
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,
)
}
func (m *Model) Update(msg tview.Msg) tview.Cmd {
switch msg := msg.(type) {
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
}