refactor(ui): use event-driven startup and logout

This commit is contained in:
ayn2op 2026-03-04 00:25:33 -05:00
parent aa6845949f
commit 41a6833b81
7 changed files with 87 additions and 39 deletions

View file

@ -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)

2
go.mod
View file

@ -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

4
go.sum
View file

@ -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=

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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 {