refactor(ui,picker)!: upgrade tview to latest and use newer api (#771)

This commit is contained in:
ayn2op 2026-03-17 21:09:33 -04:00 • committed by GitHub
parent ee8439bc0a
commit 6167d7d3d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 50 additions and 53 deletions

2
go.mod
View file

@ -11,7 +11,7 @@ require (
github.com/alecthomas/chroma/v2 v2.23.1
github.com/andybalholm/brotli v1.2.0
github.com/ayn2op/clipboard v0.0.0-20260308203959-c5ad7df3fc97
github.com/ayn2op/tview v0.0.0-20260315074416-50a7d9c11304
github.com/ayn2op/tview v0.0.0-20260318010246-0993cedfe0c2
github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb
github.com/diamondburned/arikawa/v3 v3.6.1-0.20260311205148-176ad9b9440f
github.com/diamondburned/ningen/v3 v3.0.1-0.20260306213430-5a08d3a709b4

4
go.sum
View file

@ -16,8 +16,8 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/ayn2op/clipboard v0.0.0-20260308203959-c5ad7df3fc97 h1:WujETUV+v0DEJyZgjeLzQvihWyL80c0Tg4qf0dDo+Io=
github.com/ayn2op/clipboard v0.0.0-20260308203959-c5ad7df3fc97/go.mod h1:3kFnpNCa3dF6WryzOMCDao7PfZ7DTCh+pievlfuwV80=
github.com/ayn2op/tview v0.0.0-20260315074416-50a7d9c11304 h1:IbClNDuGoPrJtP4WkKr/iLXIxsR1WKSSJH46wLM0azM=
github.com/ayn2op/tview v0.0.0-20260315074416-50a7d9c11304/go.mod h1:IXZrCv1VAZyWPdKDUJYs9ZYFiJ1GmCQUo9tLjQWoSnU=
github.com/ayn2op/tview v0.0.0-20260318010246-0993cedfe0c2 h1:AMOGrvQ1+TnDRuKC2Jnr+H1WL4bq8GSNd2GDxvy3dig=
github.com/ayn2op/tview v0.0.0-20260318010246-0993cedfe0c2/go.mod h1:IXZrCv1VAZyWPdKDUJYs9ZYFiJ1GmCQUo9tLjQWoSnU=
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

@ -7,7 +7,6 @@ import (
"github.com/ayn2op/tview"
"github.com/ayn2op/tview/help"
"github.com/ayn2op/tview/keybind"
"github.com/gdamore/tcell/v3"
)
type attachmentItem struct {
@ -74,7 +73,7 @@ func (ap *attachmentsPicker) close() {
ap.chatView.app.SetFocus(ap.chatView.messagesList)
}
func (ap *attachmentsPicker) HandleEvent(event tcell.Event) tview.Command {
func (ap *attachmentsPicker) HandleEvent(event tview.Event) tview.Command {
switch event := event.(type) {
case *picker.SelectedEvent:
index, ok := event.Reference.(int)

View file

@ -11,7 +11,6 @@ import (
"github.com/ayn2op/tview/help"
"github.com/ayn2op/tview/keybind"
"github.com/diamondburned/arikawa/v3/discord"
"github.com/gdamore/tcell/v3"
)
type channelsPicker struct {
@ -52,7 +51,7 @@ func newChannelsPicker(cfg *config.Config, chatView *Model) *channelsPicker {
return cp
}
func (cp *channelsPicker) HandleEvent(event tcell.Event) tview.Command {
func (cp *channelsPicker) HandleEvent(event tview.Event) tview.Command {
switch event := event.(type) {
case *picker.SelectedEvent:
channelID, ok := event.Reference.(discord.ChannelID)

View file

@ -10,7 +10,7 @@ import (
type LogoutEvent struct{ tcell.EventTime }
func (m *Model) logout() tview.Command {
return func() tcell.Event {
return func() tview.Event {
return &LogoutEvent{}
}
}
@ -18,7 +18,7 @@ func (m *Model) logout() tview.Command {
type QuitEvent struct{ tcell.EventTime }
func (m *Model) closeState() tview.Command {
return func() tcell.Event {
return func() tview.Event {
if m.state != nil {
if err := m.state.Close(); err != nil {
slog.Error("failed to close the session", "err", err)
@ -35,7 +35,7 @@ type closeLayerEvent struct {
}
func closeLayer(name string) tview.Command {
return func() tcell.Event {
return func() tview.Event {
return &closeLayerEvent{name: name}
}
}

View file

@ -393,7 +393,7 @@ func (gt *guildsTree) collapseParentNode(node *tview.TreeNode) {
})
}
func (gt *guildsTree) HandleEvent(event tcell.Event) tview.Command {
func (gt *guildsTree) HandleEvent(event tview.Event) tview.Command {
switch event := event.(type) {
case *tview.TreeViewSelectedEvent:
gt.onSelected(event.Node)
@ -437,7 +437,7 @@ func (gt *guildsTree) yankID() tview.Command {
// Reference of a tree node in the guilds tree is its ID.
// discord.Snowflake (discord.GuildID and discord.ChannelID) have the String method.
if id, ok := node.GetReference().(fmt.Stringer); ok {
return func() tcell.Event {
return func() tview.Event {
if err := clipboard.Write(clipboard.FmtText, []byte(id.String())); err != nil {
slog.Error("failed to copy node id", "err", err)
}

View file

@ -103,7 +103,7 @@ func (mi *messageInput) stopTypingTimer() {
}
}
func (mi *messageInput) HandleEvent(event tcell.Event) tview.Command {
func (mi *messageInput) HandleEvent(event tview.Event) tview.Command {
handler := mi.TextArea.HandleEvent
switch event := event.(type) {
case *tview.KeyEvent:

View file

@ -838,7 +838,7 @@ func (ml *messagesList) selectedMessage() (*discord.Message, error) {
return &ml.messages[cursor], nil
}
func (ml *messagesList) HandleEvent(event tcell.Event) tview.Command {
func (ml *messagesList) HandleEvent(event tview.Event) tview.Command {
switch event := event.(type) {
case *tview.KeyEvent:
switch {
@ -1018,7 +1018,7 @@ func (ml *messagesList) yankMessageID() tview.Command {
return nil
}
return func() tcell.Event {
return func() tview.Event {
if err := clipboard.Write(clipboard.FmtText, []byte(msg.ID.String())); err != nil {
slog.Error("failed to copy message id", "err", err)
}
@ -1033,7 +1033,7 @@ func (ml *messagesList) yankContent() tview.Command {
return nil
}
return func() tcell.Event {
return func() tview.Event {
if err := clipboard.Write(clipboard.FmtText, []byte(msg.Content)); err != nil {
slog.Error("failed to copy message content", "err", err)
}
@ -1048,7 +1048,7 @@ func (ml *messagesList) yankURL() tview.Command {
return nil
}
return func() tcell.Event {
return func() tview.Event {
if err := clipboard.Write(clipboard.FmtText, []byte(msg.URL())); err != nil {
slog.Error("failed to copy message url", "err", err)
}
@ -1289,7 +1289,7 @@ func (ml *messagesList) deleteSelectedMessage() tview.Command {
return nil
}
return func() tcell.Event {
return func() tview.Event {
if selectedMessage.GuildID.IsValid() {
me, _ := ml.chatView.state.Cabinet.Me()
if selectedMessage.Author.ID != me.ID && !ml.chatView.state.HasPermissions(selectedMessage.ChannelID, discord.PermissionManageMessages) {

View file

@ -48,7 +48,7 @@ type Model struct {
typers map[discord.UserID]*time.Timer
confirmModalDone func(label string)
confirmModalPreviousFocus tview.Primitive
confirmModalPreviousFocus tview.Model
app *tview.Application
cfg *config.Config
@ -216,10 +216,10 @@ func (m *Model) focusNext() tview.Command {
return nil
}
func (m *Model) HandleEvent(event tcell.Event) tview.Command {
func (m *Model) HandleEvent(event tview.Event) tview.Command {
switch event := event.(type) {
case *tview.InitEvent:
return func() tcell.Event {
return func() tview.Event {
if err := m.OpenState(m.token); err != nil {
slog.Error("failed to open chat state", "err", err)
return tcell.NewEventError(err)

View file

@ -9,7 +9,7 @@ import (
)
func setClipboard(content string) tview.Command {
return func() tcell.Event {
return func() tview.Event {
if err := clipboard.Write(clipboard.FmtText, []byte(content)); err != nil {
slog.Error("failed to copy error message", "err", err)
return tcell.NewEventError(err)

View file

@ -41,7 +41,7 @@ func NewModel(cfg *config.Config) *Model {
}
}
func (m *Model) HandleEvent(event tcell.Event) tview.Command {
func (m *Model) HandleEvent(event tview.Event) tview.Command {
switch event := event.(type) {
case *tcell.EventError:
if m.HasLayer(errorLayerName) {

View file

@ -34,7 +34,7 @@ type connCreateEvent struct {
type connCloseEvent struct{ tcell.EventTime }
func (m *Model) connect() tview.Command {
return func() tcell.Event {
return func() tview.Event {
headers := http.Headers()
headers.Set("User-Agent", http.BrowserUserAgent)
conn, _, err := websocket.DefaultDialer.Dial(remoteAuthGatewayURL, headers)
@ -46,7 +46,7 @@ func (m *Model) connect() tview.Command {
}
func (m *Model) close() tview.Command {
return func() tcell.Event {
return func() tview.Event {
if m.conn != nil {
if err := m.conn.Close(); err != nil {
return tcell.NewEventError(err)
@ -85,7 +85,7 @@ type pendingLoginEvent struct {
type cancelEvent struct{ tcell.EventTime }
func (m *Model) listen() tview.Command {
return func() tcell.Event {
return func() tview.Event {
if m.conn == nil {
return nil
}
@ -155,14 +155,14 @@ func (m *Model) listen() tview.Command {
type heartbeatTickEvent struct{ tcell.EventTime }
func (m *Model) heartbeat() tview.Command {
return func() tcell.Event {
return func() tview.Event {
time.Sleep(m.heartbeatInterval)
return &heartbeatTickEvent{}
}
}
func (m *Model) sendHeartbeat() tview.Command {
return func() tcell.Event {
return func() tview.Event {
if m.conn == nil {
return nil
}
@ -182,7 +182,7 @@ type privateKeyEvent struct {
}
func (m *Model) generatePrivateKey() tview.Command {
return func() tcell.Event {
return func() tview.Event {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return tcell.NewEventError(err)
@ -192,7 +192,7 @@ func (m *Model) generatePrivateKey() tview.Command {
}
func (m *Model) sendInit() tview.Command {
return func() tcell.Event {
return func() tview.Event {
if m.privateKey == nil {
return tcell.NewEventError(errors.New("missing private key"))
}
@ -213,7 +213,7 @@ func (m *Model) sendInit() tview.Command {
}
func (m *Model) sendNonceProof(encryptedNonce string) tview.Command {
return func() tcell.Event {
return func() tview.Event {
decodedNonce, err := base64.StdEncoding.DecodeString(encryptedNonce)
if err != nil {
return tcell.NewEventError(err)
@ -242,7 +242,7 @@ type qrCodeEvent struct {
}
func (m *Model) generateQRCode(fingerprint string) tview.Command {
return func() tcell.Event {
return func() tview.Event {
content := "https://discord.com/ra/" + fingerprint
qrCode, err := qrcode.New(content, qrcode.Low)
if err != nil {
@ -260,7 +260,7 @@ type userEvent struct {
}
func (m *Model) decryptUserPayload(encryptedPayload string) tview.Command {
return func() tcell.Event {
return func() tview.Event {
decodedPayload, err := base64.StdEncoding.DecodeString(encryptedPayload)
if err != nil {
return tcell.NewEventError(err)
@ -281,7 +281,7 @@ func (m *Model) decryptUserPayload(encryptedPayload string) tview.Command {
}
func (m *Model) exchangeTicket(ticket string) tview.Command {
return func() tcell.Event {
return func() tview.Event {
headers := http.Headers()
headers.Set("Referer", "https://discord.com/login")
if m.fingerprint != "" {

View file

@ -44,7 +44,7 @@ func (m *Model) Label() string {
return "QR"
}
func (m *Model) HandleEvent(event tcell.Event) tview.Command {
func (m *Model) HandleEvent(event tview.Event) tview.Command {
switch event := event.(type) {
case *tview.InitEvent:
m.msg = "Connecting to Remote Auth Gateway..."
@ -103,7 +103,7 @@ func (m *Model) HandleEvent(event tcell.Event) tview.Command {
case *tcell.EventError:
m.msg = event.Error()
return tview.Batch(m.close(), tview.Command(func() tcell.Event { return event }))
return tview.Batch(m.close(), tview.Command(func() tview.Event { return event }))
}
return nil

View file

@ -11,7 +11,7 @@ type TokenEvent struct {
}
func tokenCommand(token string) tview.Command {
return func() tcell.Event {
return func() tview.Event {
return &TokenEvent{Token: token}
}
}

View file

@ -3,7 +3,6 @@ package token
import (
"github.com/ayn2op/tview"
"github.com/ayn2op/tview/tabs"
"github.com/gdamore/tcell/v3"
)
type Model struct {
@ -23,7 +22,7 @@ func (m *Model) Label() string {
return "Token"
}
func (m *Model) HandleEvent(event tcell.Event) tview.Command {
func (m *Model) HandleEvent(event tview.Event) tview.Command {
switch event.(type) {
case *tview.FormSubmitEvent:
token := m.GetFormItem(0).(*tview.InputField).GetText()

View file

@ -15,7 +15,7 @@ type tokenEvent struct {
}
func tokenCommand(token string) tview.Command {
return func() tcell.Event {
return func() tview.Event {
return &tokenEvent{token: token}
}
}
@ -23,7 +23,7 @@ func tokenCommand(token string) tview.Command {
type loginEvent struct{ tcell.EventTime }
func getToken() tview.Command {
return func() tcell.Event {
return func() tview.Event {
token, err := keyring.GetToken()
if err != nil {
slog.Info("failed to retrieve token from keyring", "err", err)
@ -34,7 +34,7 @@ func getToken() tview.Command {
}
func setToken(token string) tview.Command {
return func() tcell.Event {
return func() tview.Event {
if err := keyring.SetToken(token); err != nil {
slog.Error("failed to set token to keyring", "err", err)
return tcell.NewEventError(err)
@ -44,7 +44,7 @@ func setToken(token string) tview.Command {
}
func deleteToken() tview.Command {
return func() tcell.Event {
return func() tview.Event {
if err := keyring.DeleteToken(); err != nil {
slog.Error("failed to delete token from keyring", "err", err)
return tcell.NewEventError(err)
@ -54,7 +54,7 @@ func deleteToken() tview.Command {
}
func initClipboard() tview.Command {
return func() tcell.Event {
return func() tview.Event {
if err := clipboard.Init(); err != nil {
slog.Error("failed to init clipboard", "err", err)
return tcell.NewEventError(err)

View file

@ -21,7 +21,7 @@ const tokenEnvVarKey = "DISCORDO_TOKEN"
type Model struct {
app *tview.Application
rootFlex *flex.Model // inner + help
inner tview.Primitive
inner tview.Model
help *help.Help
cfg *config.Config
@ -74,13 +74,13 @@ func (m *Model) buildLayout() {
m.updateHelpHeight()
}
var _ tview.Primitive = (*Model)(nil)
var _ tview.Model = (*Model)(nil)
func (m *Model) Draw(screen tcell.Screen) {
m.rootFlex.Draw(screen)
}
func (m *Model) HandleEvent(event tcell.Event) tview.Command {
func (m *Model) HandleEvent(event tview.Event) tview.Command {
switch event := event.(type) {
case *tview.InitEvent:
var cmd tview.Command
@ -151,7 +151,7 @@ func (m *Model) SetRect(x int, y int, width int, height int) {
m.rootFlex.SetRect(x, y, width, height)
}
func (m *Model) Focus(delegate func(p tview.Primitive)) {
func (m *Model) Focus(delegate func(tview.Model)) {
if m.inner != nil {
delegate(m.inner)
}

View file

@ -55,11 +55,11 @@ func ConfigureBox(box *tview.Box, cfg *config.Theme) *tview.Box {
}
// Centered creates a new grid with provided primitive aligned in the center.
func Centered(p tview.Primitive, width, height int) tview.Primitive {
func Centered(m tview.Model, width, height int) tview.Model {
return tview.NewGrid().
SetColumns(0, width, 0).
SetRows(0, height, 0).
AddItem(p, 1, 1, 1, 1, 0, 0, true)
AddItem(m, 1, 1, 1, 1, 0, 0, true)
}
func ChannelToString(channel discord.Channel, icons config.Icons, state *ningen.State) string {

View file

@ -18,7 +18,7 @@ func (m *Model) _select() tview.Command {
index := m.list.Cursor()
if index >= 0 && index < len(m.filtered) {
item := m.filtered[index]
return func() tcell.Event {
return func() tview.Event {
return newSelectedEvent(item)
}
}
@ -28,7 +28,7 @@ func (m *Model) _select() tview.Command {
type CancelEvent struct{ tcell.EventTime }
func cancel() tview.Command {
return func() tcell.Event {
return func() tview.Event {
return &CancelEvent{}
}
}

View file

@ -128,7 +128,7 @@ func (m *Model) onInputChanged(text string) {
m.setFilteredItems(fuzzied)
}
func (m *Model) HandleEvent(event tcell.Event) tview.Command {
func (m *Model) HandleEvent(event tview.Event) tview.Command {
switch event := event.(type) {
case *tview.KeyEvent:
if m.keyMap != nil {