From 41a6833b81fd310a254ae2fa031620346916394e Mon Sep 17 00:00:00 2001 From: ayn2op Date: Wed, 4 Mar 2026 00:25:33 -0500 Subject: [PATCH] refactor(ui): use event-driven startup and logout --- cmd/root.go | 1 + go.mod | 2 +- go.sum | 4 ++-- internal/ui/chat/events.go | 11 +++++++++ internal/ui/chat/view.go | 19 +++++---------- internal/ui/root/events.go | 49 ++++++++++++++++++++++++++++++++++++++ internal/ui/root/view.go | 40 +++++++++++++------------------ 7 files changed, 87 insertions(+), 39 deletions(-) create mode 100644 internal/ui/chat/events.go create mode 100644 internal/ui/root/events.go diff --git a/cmd/root.go b/cmd/root.go index 4a7d786..ed88dce 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -62,6 +62,7 @@ func Run() error { screen.EnablePaste() screen.EnableFocus() + tview.Styles = tview.Theme{} app := tview.NewApplication() app.SetRoot(root.NewView(cfg, app)) app.SetScreen(screen) diff --git a/go.mod b/go.mod index c01693d..20ee81b 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/BurntSushi/toml v1.6.0 github.com/alecthomas/chroma/v2 v2.23.1 github.com/andybalholm/brotli v1.2.0 - github.com/ayn2op/tview v0.0.0-20260304032402-f69fad13517e + github.com/ayn2op/tview v0.0.0-20260304052427-7549865d186e github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb github.com/diamondburned/arikawa/v3 v3.6.1-0.20260226015332-783a3e8e8e86 github.com/diamondburned/ningen/v3 v3.0.1-0.20260226220604-93f1e60c3cdb diff --git a/go.sum b/go.sum index c67958a..40bf80a 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= -github.com/ayn2op/tview v0.0.0-20260304032402-f69fad13517e h1:xbfoet3RFg7bRY3MhRi5WfxhbLNoOlM+E+f83ABCfoM= -github.com/ayn2op/tview v0.0.0-20260304032402-f69fad13517e/go.mod h1:lZ8RdOegQWBQafTOasGE7Ps1/Ymy4jmXoPt5vz2QsS0= +github.com/ayn2op/tview v0.0.0-20260304052427-7549865d186e h1:fc5qHUJV+XYlYabsJtWW/AwbGLFu6XMFTiHXDViUBeM= +github.com/ayn2op/tview v0.0.0-20260304052427-7549865d186e/go.mod h1:lZ8RdOegQWBQafTOasGE7Ps1/Ymy4jmXoPt5vz2QsS0= github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/internal/ui/chat/events.go b/internal/ui/chat/events.go new file mode 100644 index 0000000..35ffbca --- /dev/null +++ b/internal/ui/chat/events.go @@ -0,0 +1,11 @@ +package chat + +import "github.com/gdamore/tcell/v3" + +type LogoutEvent struct{ tcell.EventTime } + +func NewLogoutEvent() *LogoutEvent { + event := &LogoutEvent{} + event.SetEventNow() + return event +} diff --git a/internal/ui/chat/view.go b/internal/ui/chat/view.go index cdc4549..62b9bb0 100644 --- a/internal/ui/chat/view.go +++ b/internal/ui/chat/view.go @@ -7,7 +7,6 @@ import ( "time" "github.com/ayn2op/discordo/internal/config" - "github.com/ayn2op/discordo/internal/keyring" "github.com/ayn2op/discordo/internal/ui" "github.com/ayn2op/tview" "github.com/ayn2op/tview/help" @@ -51,11 +50,9 @@ type View struct { app *tview.Application cfg *config.Config state *ningen.State - - onLogout func() } -func NewView(app *tview.Application, cfg *config.Config, onLogout func()) *View { +func NewView(app *tview.Application, cfg *config.Config) *View { v := &View{ Layers: layers.New(), @@ -65,9 +62,8 @@ func NewView(app *tview.Application, cfg *config.Config, onLogout func()) *View typers: make(map[discord.UserID]*time.Timer), - app: app, - cfg: cfg, - onLogout: onLogout, + app: app, + cfg: cfg, } v.guildsTree = newGuildsTree(cfg, v) @@ -249,13 +245,10 @@ func (v *View) HandleEvent(event tcell.Event) tview.Command { v.focusNext() return redraw case keybind.Matches(event, v.cfg.Keybinds.Logout.Keybind): - if v.onLogout != nil { - v.onLogout() + if err := v.CloseState(); err != nil { + slog.Error("failed to close the session", "err", err) } - if err := keyring.DeleteToken(); err != nil { - slog.Error("failed to delete token from keyring", "err", err) - } - return redraw + return tview.EventCommand(func() tcell.Event { return NewLogoutEvent() }) case keybind.Matches(event, v.cfg.Keybinds.ToggleGuildsTree.Keybind): v.toggleGuildsTree() return redraw diff --git a/internal/ui/root/events.go b/internal/ui/root/events.go new file mode 100644 index 0000000..0b22944 --- /dev/null +++ b/internal/ui/root/events.go @@ -0,0 +1,49 @@ +package root + +import ( + "log/slog" + "os" + + "github.com/ayn2op/discordo/internal/clipboard" + "github.com/ayn2op/discordo/internal/keyring" + "github.com/gdamore/tcell/v3" +) + +type tokenEvent struct { + tcell.EventTime + token string +} + +func newTokenEvent(token string) *tokenEvent { + event := &tokenEvent{token: token} + event.SetEventNow() + return event +} + +func getToken() tcell.Event { + token := os.Getenv(tokenEnvVarKey) + if token == "" { + tok, err := keyring.GetToken() + if err != nil { + slog.Info("failed to retrieve token from keyring", "err", err) + } + token = tok + } + return newTokenEvent(token) +} + +func deleteToken() tcell.Event { + if err := keyring.DeleteToken(); err != nil { + slog.Error("failed to delete token from keyring", "err", err) + return tcell.NewEventError(err) + } + return nil +} + +func initClipboard() tcell.Event { + if err := clipboard.Init(); err != nil { + slog.Error("failed to init clipboard", "err", err) + return tcell.NewEventError(err) + } + return nil +} diff --git a/internal/ui/root/view.go b/internal/ui/root/view.go index 9cbf462..72aa870 100644 --- a/internal/ui/root/view.go +++ b/internal/ui/root/view.go @@ -2,12 +2,9 @@ package root import ( "log/slog" - "os" - "github.com/ayn2op/discordo/internal/clipboard" "github.com/ayn2op/discordo/internal/config" "github.com/ayn2op/discordo/internal/consts" - "github.com/ayn2op/discordo/internal/keyring" "github.com/ayn2op/discordo/internal/ui/chat" "github.com/ayn2op/discordo/internal/ui/login" "github.com/ayn2op/tview" @@ -26,17 +23,10 @@ type View struct { } func NewView(cfg *config.Config, app *tview.Application) *View { - tview.Styles = tview.Theme{} - v := &View{ + return &View{ app: app, cfg: cfg, } - - if err := clipboard.Init(); err != nil { - slog.Error("failed to init clipboard", "err", err) - } - - return v } func (v *View) showLoginView() { @@ -45,11 +35,12 @@ func (v *View) showLoginView() { slog.Error("failed to show chat view", "err", err) } }) + v.chat = nil v.inner = loginForm } func (v *View) showChatView(token string) error { - v.chat = chat.NewView(v.app, v.cfg, v.showLoginView) + v.chat = chat.NewView(v.app, v.cfg) if err := v.chat.OpenState(token); err != nil { return err } @@ -77,24 +68,27 @@ func (v *View) Draw(screen tcell.Screen) { func (v *View) HandleEvent(event tcell.Event) tview.Command { switch event := event.(type) { case *tview.InitEvent: - token := os.Getenv(tokenEnvVarKey) - if token == "" { - tok, err := keyring.GetToken() - if err != nil { - slog.Info("failed to retrieve token from keyring", "err", err) - } - token = tok + return tview.BatchCommand{ + tview.SetTitleCommand(consts.Name), + tview.EventCommand(initClipboard), + tview.EventCommand(getToken), } - - if token == "" { + case *tokenEvent: + if event.token == "" { v.showLoginView() } else { - if err := v.showChatView(token); err != nil { + if err := v.showChatView(event.token); err != nil { slog.Error("failed to show chat view", "err", err) return tview.QuitCommand{} } } - return tview.BatchCommand{tview.SetTitleCommand(consts.Name), tview.SetFocusCommand{Target: v.inner}} + return tview.BatchCommand{tview.SetFocusCommand{Target: v.inner}} + case *chat.LogoutEvent: + v.showLoginView() + return tview.BatchCommand{ + tview.EventCommand(deleteToken), + tview.SetFocusCommand{Target: v.inner}, + } case *tview.KeyEvent: switch {