feat(ui): improve modal handling

This commit is contained in:
Rob Watson 2025-03-09 13:22:17 +01:00
parent ab6ed51b77
commit d6a028c37b
2 changed files with 96 additions and 55 deletions

View File

@ -61,6 +61,7 @@ func Run(ctx context.Context, params RunParams) error {
return nil return nil
} }
} }
ui.AllowQuit()
srv := mediaserver.StartActor(ctx, mediaserver.StartActorParams{ srv := mediaserver.StartActor(ctx, mediaserver.StartActorParams{
ContainerClient: containerClient, ContainerClient: containerClient,

View File

@ -53,6 +53,7 @@ type UI struct {
mu sync.Mutex mu sync.Mutex
urlsToStartState map[string]startState urlsToStartState map[string]startState
allowQuit bool
} }
// StartParams contains the parameters for starting a new terminal user // StartParams contains the parameters for starting a new terminal user
@ -240,12 +241,11 @@ func (ui *UI) ShowStartupCheckModal() bool {
done := make(chan bool) done := make(chan bool)
ui.app.QueueUpdateDraw(func() { ui.app.QueueUpdateDraw(func() {
modal := tview.NewModal() ui.showModal(
modal.SetText("Another instance of Octoplex may already be running. Pressing continue will close that instance. Continue?"). modalGroupStartupCheck,
AddButtons([]string{"Continue", "Exit"}). "Another instance of Octoplex may already be running. Pressing continue will close that instance. Continue?",
SetBackgroundColor(tcell.ColorBlack). []string{"Continue", "Exit"},
SetTextColor(tcell.ColorWhite). func(buttonIndex int, _ string) {
SetDoneFunc(func(buttonIndex int, _ string) {
if buttonIndex == 0 { if buttonIndex == 0 {
ui.pages.RemovePage("modal") ui.pages.RemovePage("modal")
ui.app.SetFocus(ui.destView) ui.app.SetFocus(ui.destView)
@ -253,15 +253,26 @@ func (ui *UI) ShowStartupCheckModal() bool {
} else { } else {
done <- false done <- false
} }
}) },
modal.SetBorderStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite)) )
ui.pages.AddPage("modal", modal, true, true)
}) })
return <-done return <-done
} }
// AllowQuit enables the quit action.
func (ui *UI) AllowQuit() {
ui.mu.Lock()
defer ui.mu.Unlock()
// This is required to prevent the user from quitting during the startup
// check modal, when the main event loop is not yet running, and avoid an
// unexpected user experience. It might be nice to find a way to remove this
// but it probably means refactoring the mediaserver actor to separate
// starting the server from starting the event loop.
ui.allowQuit = true
}
// SetState sets the state of the terminal user interface. // SetState sets the state of the terminal user interface.
func (ui *UI) SetState(state domain.AppState) { func (ui *UI) SetState(state domain.AppState) {
if state.Source.ExitReason != "" { if state.Source.ExitReason != "" {
@ -280,6 +291,44 @@ func (ui *UI) SetState(state domain.AppState) {
ui.app.QueueUpdateDraw(func() { ui.redrawFromState(stateClone) }) ui.app.QueueUpdateDraw(func() { ui.redrawFromState(stateClone) })
} }
// modalGroup represents a specific modal of which only one may be shown
// simultaneously.
type modalGroup string
const (
modalGroupAbout modalGroup = "about"
modalGroupQuit modalGroup = "quit"
modalGroupStartupCheck modalGroup = "startup-check"
modalGroupClipboard modalGroup = "clipboard"
)
func (ui *UI) showModal(group modalGroup, text string, buttons []string, doneFunc func(int, string)) {
modalName := "modal-" + string(group)
if ui.pages.HasPage(modalName) {
return
}
modal := tview.NewModal()
modal.SetText(text).
AddButtons(buttons).
SetBackgroundColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite).
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
ui.pages.RemovePage(modalName)
if ui.pages.GetPageCount() == 1 {
ui.app.SetFocus(ui.destView)
}
if doneFunc != nil {
doneFunc(buttonIndex, buttonLabel)
}
}).
SetBorderStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite))
ui.pages.AddPage(modalName, modal, true, true)
}
func (ui *UI) handleMediaServerClosed(exitReason string) { func (ui *UI) handleMediaServerClosed(exitReason string) {
done := make(chan struct{}) done := make(chan struct{})
@ -480,59 +529,50 @@ func (ui *UI) copySourceURLToClipboard(clipboardAvailable bool) {
text = "Copy to clipboard not available" text = "Copy to clipboard not available"
} }
modal := tview.NewModal() ui.showModal(
modal.SetText(text). modalGroupClipboard,
AddButtons([]string{"Ok"}). text,
SetBackgroundColor(tcell.ColorBlack). []string{"Ok"},
SetTextColor(tcell.ColorWhite). nil,
SetBorderStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite)) )
modal.SetDoneFunc(func(buttonIndex int, _ string) {
ui.pages.RemovePage("modal")
ui.app.SetFocus(ui.destView)
})
ui.pages.AddPage("modal", modal, true, true)
} }
func (ui *UI) confirmQuit() { func (ui *UI) confirmQuit() {
modal := tview.NewModal() var allowQuit bool
modal.SetText("Are you sure you want to quit?"). ui.mu.Lock()
AddButtons([]string{"Quit", "Cancel"}). allowQuit = ui.allowQuit
SetBackgroundColor(tcell.ColorBlack). ui.mu.Unlock()
SetTextColor(tcell.ColorWhite).
SetDoneFunc(func(buttonIndex int, _ string) { if !allowQuit {
if buttonIndex == 1 || buttonIndex == -1 {
ui.pages.RemovePage("modal")
ui.app.SetFocus(ui.destView)
return return
} }
ui.showModal(
modalGroupQuit,
"Are you sure you want to quit?",
[]string{"Quit", "Cancel"},
func(buttonIndex int, _ string) {
if buttonIndex == 0 {
ui.logger.Info("quitting")
ui.commandCh <- CommandQuit{} ui.commandCh <- CommandQuit{}
}) return
modal.SetBorderStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite)) }
},
ui.pages.AddPage("modal", modal, true, true) )
} }
func (ui *UI) showAbout() { func (ui *UI) showAbout() {
modal := tview.NewModal() ui.showModal(
modal.SetText(fmt.Sprintf( modalGroupAbout,
fmt.Sprintf(
"%s: live stream multiplexer\n\nv0.0.0 %s (%s)", "%s: live stream multiplexer\n\nv0.0.0 %s (%s)",
domain.AppName, domain.AppName,
ui.buildInfo.Version, ui.buildInfo.Version,
ui.buildInfo.GoVersion, ui.buildInfo.GoVersion,
)). ),
AddButtons([]string{"Ok"}). []string{"Ok"},
SetBackgroundColor(tcell.ColorBlack). nil,
SetTextColor(tcell.ColorWhite). )
SetDoneFunc(func(buttonIndex int, _ string) {
ui.pages.RemovePage("modal")
ui.app.SetFocus(ui.destView)
})
modal.SetBorderStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite))
ui.pages.AddPage("modal", modal, true, true)
} }
// comtainerStateToStartState converts a container state to a start state. // comtainerStateToStartState converts a container state to a start state.