refactor(ui): replace tview modal usage

This commit is contained in:
ayn2op 2026-07-09 04:12:33 -04:00
parent c1af115e27
commit 391f3b974c
6 changed files with 93 additions and 54 deletions

10
go.mod
View file

@ -10,7 +10,7 @@ require (
github.com/andybalholm/brotli v1.2.2
github.com/ayn2op/arikawa/v3 v3.0.0-20260708075022-9cc760fe507e
github.com/ayn2op/ningen/v3 v3.0.1-0.20260708075321-0ba1ffc70bd6
github.com/ayn2op/tview v0.0.0-20260708025551-16f85f97975e
github.com/ayn2op/tview v0.0.0-20260709081114-77dc5f1dd56c
github.com/deckarep/gosx-notifier v0.0.0-20180201035817-e127226297fb
github.com/gdamore/tcell/v3 v3.4.1-0.20260703160154-d85b1fbd48b8
github.com/gen2brain/beeep v0.11.2
@ -55,10 +55,10 @@ require (
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
golang.design/x/x11 v0.2.0 // indirect
golang.org/x/exp/shiny v0.0.0-20260611194520-c48552f49976 // indirect
golang.org/x/image v0.43.0 // indirect
golang.org/x/image v0.44.0 // indirect
golang.org/x/mobile v0.0.0-20260611195102-4dd8f1dbf5d2 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/term v0.44.0 // indirect
golang.org/x/text v0.39.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/time v0.15.0 // indirect
)

20
go.sum
View file

@ -16,8 +16,8 @@ github.com/ayn2op/arikawa/v3 v3.0.0-20260708075022-9cc760fe507e h1:rsMifRXwxkbha
github.com/ayn2op/arikawa/v3 v3.0.0-20260708075022-9cc760fe507e/go.mod h1:gqklpHK7WHYTzpd1ZY6Ho/p9GURBghzBCzRiBYpaUP8=
github.com/ayn2op/ningen/v3 v3.0.1-0.20260708075321-0ba1ffc70bd6 h1:/khHm6G+GWwXpmwA8bJoaXoUhubqMEUG6spjZsnklIw=
github.com/ayn2op/ningen/v3 v3.0.1-0.20260708075321-0ba1ffc70bd6/go.mod h1:vqLIvETLkjc2vIgQzVEBqpGNCrgNe7YJqS9TUAJsXRI=
github.com/ayn2op/tview v0.0.0-20260708025551-16f85f97975e h1:WVf0fqBMetR5F5VoA1lMvRpSX25YUyekaNwaWXieW7c=
github.com/ayn2op/tview v0.0.0-20260708025551-16f85f97975e/go.mod h1:kBq8YDymQUJsGxUfhatCnwc1XU0z1tXjSLNpGpzcMp8=
github.com/ayn2op/tview v0.0.0-20260709081114-77dc5f1dd56c h1:hX1fxRak+RQnwWjeTVv7xuuOxqBxrT5ITM+reIq5eKs=
github.com/ayn2op/tview v0.0.0-20260709081114-77dc5f1dd56c/go.mod h1:kBq8YDymQUJsGxUfhatCnwc1XU0z1tXjSLNpGpzcMp8=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
@ -123,8 +123,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp/shiny v0.0.0-20260611194520-c48552f49976 h1:xAKfctaZuXDxw/I98uYRl7TXMBy4a1BgPgl7NavbrdY=
golang.org/x/exp/shiny v0.0.0-20260611194520-c48552f49976/go.mod h1:UXxgIHNj0uSRIk9ua62CRJcUpfHN9V+cRZRXHEo4GhE=
golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY=
golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
golang.org/x/mobile v0.0.0-20260611195102-4dd8f1dbf5d2 h1:zoM1gIKhVkcQNm43kad8OHLgPNoJ12xIqmxHtKr8Mug=
golang.org/x/mobile v0.0.0-20260611195102-4dd8f1dbf5d2/go.mod h1:QGMqsqLn6orFQ/ksqYMf+Fa33Soa1vPoHEd0Pj7N+lQ=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -143,20 +143,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View file

@ -1278,7 +1278,7 @@ func (ml *messagesList) confirmDelete() {
}
}
ml.chat.showConfirmModal(
ml.chat.showConfirmDialog(
"Are you sure you want to delete this message?",
[]string{"Yes", "No"},
onChoice,

View file

@ -21,6 +21,7 @@ import (
"github.com/ayn2op/ningen/v3/states/read"
"github.com/ayn2op/tview"
"github.com/ayn2op/tview/flex"
"github.com/ayn2op/tview/frame"
"github.com/ayn2op/tview/keybind"
"github.com/ayn2op/tview/layers"
"github.com/gdamore/tcell/v3"
@ -29,9 +30,9 @@ import (
const typingDuration = 10 * time.Second
const (
flexLayerName = "flex"
mentionsListLayerName = "mentionsList"
confirmModalLayerName = "confirmModal"
flexLayerName = "flex"
mentionsListLayerName = "mentionsList"
confirmDialogLayerName = "confirmDialog"
channelsPickerLayerName = "channelsPicker"
attachmentsPickerLayerName = "attachmentsPicker"
@ -53,8 +54,8 @@ type Model struct {
selectedChannel *discord.Channel
selectedChannelMu sync.RWMutex
confirmModalDone func(label string)
confirmModalPreviousFocus tview.Model
confirmDialogDone func(label string)
confirmDialogPreviousFocus tview.Model
state *ningen.State
events chan gateway.Event
@ -318,21 +319,36 @@ func (m *Model) Update(msg tview.Msg) tview.Cmd {
return focusCmd
case QuitMsg:
return m.closeState()
case tview.ModalDoneMsg:
if m.HasLayer(confirmModalLayerName) {
m.RemoveLayer(confirmModalLayerName)
case tview.FormSubmitMsg:
if m.HasLayer(confirmDialogLayerName) {
m.RemoveLayer(confirmDialogLayerName)
var focusCmd tview.Cmd
if m.confirmModalPreviousFocus != nil {
focusCmd = tview.SetFocus(m.confirmModalPreviousFocus)
if m.confirmDialogPreviousFocus != nil {
focusCmd = tview.SetFocus(m.confirmDialogPreviousFocus)
}
onDone := m.confirmModalDone
m.confirmModalDone = nil
m.confirmModalPreviousFocus = nil
onDone := m.confirmDialogDone
m.confirmDialogDone = nil
m.confirmDialogPreviousFocus = nil
if onDone != nil {
onDone(msg.ButtonLabel)
}
return focusCmd
}
case tview.FormCancelMsg:
if m.HasLayer(confirmDialogLayerName) {
m.RemoveLayer(confirmDialogLayerName)
var focusCmd tview.Cmd
if m.confirmDialogPreviousFocus != nil {
focusCmd = tview.SetFocus(m.confirmDialogPreviousFocus)
}
onDone := m.confirmDialogDone
m.confirmDialogDone = nil
m.confirmDialogPreviousFocus = nil
if onDone != nil {
onDone("")
}
return focusCmd
}
case tview.KeyMsg:
switch {
case keybind.Matches(msg, m.cfg.Keybinds.FocusGuildsTree.Keybind):
@ -366,22 +382,27 @@ func (m *Model) Update(msg tview.Msg) tview.Cmd {
return m.Layers.Update(msg)
}
func (m *Model) showConfirmModal(prompt string, buttons []string, onDone func(label string)) {
m.confirmModalPreviousFocus = m.app.Focused()
m.confirmModalDone = onDone
func (m *Model) showConfirmDialog(prompt string, buttons []string, onDone func(label string)) {
m.confirmDialogPreviousFocus = m.app.Focused()
m.confirmDialogDone = onDone
modal := tview.NewModal().
SetText(prompt).
AddButtons(buttons)
form := tview.NewForm().SetButtonsAlignment(tview.AlignmentCenter)
for _, button := range buttons {
form.AddButton(button)
}
dialog := frame.NewModel(form).SetBorders(0, 0, 1, 0, 0, 0)
for _, line := range tview.WordWrap(prompt, 80) {
dialog.AddText(line, true, tview.AlignmentCenter, tview.Styles.PrimaryTextColor)
}
m.
AddLayer(
ui.Centered(modal, 0, 0),
layers.WithName(confirmModalLayerName),
ui.Centered(dialog, 0, 0),
layers.WithName(confirmDialogLayerName),
layers.WithResize(true),
layers.WithVisible(true),
layers.WithOverlay(),
).
SendToFront(confirmModalLayerName)
SendToFront(confirmDialogLayerName)
}
func (m *Model) clearTypers() {

View file

@ -3,6 +3,7 @@ package login
import (
"log/slog"
"github.com/ayn2op/tview/frame"
"github.com/ayn2op/tview/layers"
"github.com/ayn2op/tview/tabs"
"github.com/gdamore/tcell/v3"
@ -23,8 +24,8 @@ type Model struct {
*layers.Layers
tabs *tabs.Model
cfg *config.Config
errorModalText string
cfg *config.Config
errorDialogText string
}
func NewModel(cfg *config.Config) *Model {
@ -48,15 +49,22 @@ func (m *Model) Update(msg tview.Msg) tview.Cmd {
return nil
}
return m.showErrorDialog(msg.err)
case tview.ModalDoneMsg:
case tview.FormSubmitMsg:
if !m.HasLayer(errorLayerName) {
return nil
}
if msg.ButtonIndex == 0 {
return setClipboard(m.errorModalText)
return setClipboard(m.errorDialogText)
}
m.RemoveLayer(errorLayerName)
m.errorModalText = ""
m.errorDialogText = ""
return nil
case tview.FormCancelMsg:
if !m.HasLayer(errorLayerName) {
return nil
}
m.RemoveLayer(errorLayerName)
m.errorDialogText = ""
return nil
}
return m.Layers.Update(msg)
@ -66,34 +74,43 @@ func (m *Model) showErrorDialog(err error) tview.Cmd {
slog.Error("failed to login", "err", err)
message := err.Error()
m.errorModalText = message
modal := tview.NewModal().
SetText(message).
AddButtons([]string{"Copy", "Close"})
m.errorDialogText = message
form := tview.NewForm().
SetButtonsAlignment(tview.AlignmentCenter).
AddButton("Copy").
AddButton("Close")
dialog := frame.NewModel(form).SetBorders(0, 0, 1, 0, 0, 0)
for _, line := range tview.WordWrap(message, 80) {
dialog.AddText(line, true, tview.AlignmentCenter, tview.Styles.PrimaryTextColor)
}
{
bg := m.cfg.Theme.Dialog.Style.GetBackground()
buttonStyle := m.cfg.Theme.Dialog.Style.Style
if bg != tcell.ColorDefault {
modal.SetBackgroundColor(bg)
form.SetBackgroundColor(bg)
dialog.SetBackgroundColor(bg)
buttonStyle = buttonStyle.Background(bg)
}
fg := m.cfg.Theme.Dialog.Style.GetForeground()
if fg != tcell.ColorDefault {
modal.SetTextColor(fg)
dialog.Clear()
for _, line := range tview.WordWrap(message, 80) {
dialog.AddText(line, true, tview.AlignmentCenter, fg)
}
buttonStyle = buttonStyle.Foreground(fg)
}
// Keep button styles aligned with dialog content and still show focus.
modal.SetButtonStyle(buttonStyle)
modal.SetButtonActivatedStyle(buttonStyle.Reverse(true))
form.SetButtonStyle(buttonStyle)
form.SetButtonActivatedStyle(buttonStyle.Reverse(true))
}
m.
AddLayer(
ui.Centered(modal, 0, 0),
ui.Centered(dialog, 0, 0),
layers.WithName(errorLayerName),
layers.WithResize(true),
layers.WithVisible(true),
layers.WithOverlay(),
).
SendToFront(errorLayerName)
return tview.SetFocus(modal)
return tview.SetFocus(dialog)
}

View file

@ -9,6 +9,7 @@ import (
"github.com/ayn2op/discordo/internal/config"
"github.com/ayn2op/ningen/v3"
"github.com/ayn2op/tview"
"github.com/ayn2op/tview/grid"
)
// ConfigureBox configures the provided box according to the provided theme.
@ -55,7 +56,7 @@ func ConfigureBox(box *tview.Box, cfg *config.Theme) *tview.Box {
// Centered creates a new grid with provided primitive aligned in the center.
func Centered(m tview.Model, width, height int) tview.Model {
return tview.NewGrid().
return grid.NewModel().
SetColumns(0, width, 0).
SetRows(0, height, 0).
AddItem(m, 1, 1, 1, 1, 0, 0, true)