refactor(app): add AppStateChangedEvent
This commit is contained in:
parent
c02a66202f
commit
b8550f050b
@ -89,7 +89,9 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
// It is only needed for integration tests when rendering modals before the
|
// It is only needed for integration tests when rendering modals before the
|
||||||
// main loop starts. It would be nice to remove this but the risk/impact on
|
// main loop starts. It would be nice to remove this but the risk/impact on
|
||||||
// non-test code is pretty low.
|
// non-test code is pretty low.
|
||||||
emptyUI := func() { ui.SetState(domain.AppState{}) }
|
emptyUI := func() {
|
||||||
|
a.eventBus.Send(event.AppStateChangedEvent{State: domain.AppState{}})
|
||||||
|
}
|
||||||
|
|
||||||
containerClient, err := container.NewClient(ctx, a.dockerClient, a.logger.With("component", "container_client"))
|
containerClient, err := container.NewClient(ctx, a.dockerClient, a.logger.With("component", "container_client"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -109,7 +111,11 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
defer containerClient.Close()
|
defer containerClient.Close()
|
||||||
|
|
||||||
updateUI := func() { ui.SetState(*state) }
|
updateUI := func() {
|
||||||
|
// The state is mutable so can't be passed into another goroutine
|
||||||
|
// without cloning it first.
|
||||||
|
a.eventBus.Send(event.AppStateChangedEvent{State: state.Clone()})
|
||||||
|
}
|
||||||
updateUI()
|
updateUI()
|
||||||
|
|
||||||
var tlsCertPath, tlsKeyPath string
|
var tlsCertPath, tlsKeyPath string
|
||||||
@ -219,7 +225,7 @@ func (a *App) handleCommand(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
a.cfg = newCfg
|
a.cfg = newCfg
|
||||||
handleConfigUpdate(a.cfg, state, ui)
|
a.handleConfigUpdate(state)
|
||||||
a.eventBus.Send(event.DestinationAddedEvent{URL: c.URL})
|
a.eventBus.Send(event.DestinationAddedEvent{URL: c.URL})
|
||||||
case domain.CommandRemoveDestination:
|
case domain.CommandRemoveDestination:
|
||||||
repl.StopDestination(c.URL) // no-op if not live
|
repl.StopDestination(c.URL) // no-op if not live
|
||||||
@ -233,7 +239,7 @@ func (a *App) handleCommand(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
a.cfg = newCfg
|
a.cfg = newCfg
|
||||||
handleConfigUpdate(a.cfg, state, ui)
|
a.handleConfigUpdate(state)
|
||||||
a.eventBus.Send(event.DestinationRemovedEvent{URL: c.URL})
|
a.eventBus.Send(event.DestinationRemovedEvent{URL: c.URL})
|
||||||
case domain.CommandStartDestination:
|
case domain.CommandStartDestination:
|
||||||
if !state.Source.Live {
|
if !state.Source.Live {
|
||||||
@ -251,10 +257,10 @@ func (a *App) handleCommand(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleConfigUpdate applies the config to the app state, and updates the UI.
|
// handleConfigUpdate applies the config to the app state, and sends an AppStateChangedEvent.
|
||||||
func handleConfigUpdate(cfg config.Config, appState *domain.AppState, ui *terminal.UI) {
|
func (a *App) handleConfigUpdate(appState *domain.AppState) {
|
||||||
applyConfig(cfg, appState)
|
applyConfig(a.cfg, appState)
|
||||||
ui.SetState(*appState)
|
a.eventBus.Send(event.AppStateChangedEvent{State: appState.Clone()})
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyServerState applies the current server state to the app state.
|
// applyServerState applies the current server state to the app state.
|
||||||
|
@ -1,18 +1,31 @@
|
|||||||
package event
|
package event
|
||||||
|
|
||||||
|
import "git.netflux.io/rob/octoplex/internal/domain"
|
||||||
|
|
||||||
type Name string
|
type Name string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
EventNameAppStateChanged Name = "app_state_changed"
|
||||||
EventNameDestinationAdded Name = "destination_added"
|
EventNameDestinationAdded Name = "destination_added"
|
||||||
EventNameDestinationRemoved Name = "destination_removed"
|
EventNameDestinationRemoved Name = "destination_removed"
|
||||||
EventNameMediaServerStarted Name = "media_server_started"
|
EventNameMediaServerStarted Name = "media_server_started"
|
||||||
EventNameFatalErrorOccurred Name = "fatal_error_occurred"
|
EventNameFatalErrorOccurred Name = "fatal_error_occurred"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Event represents something which happened in the appllication.
|
||||||
type Event interface {
|
type Event interface {
|
||||||
name() Name
|
name() Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppStateChangedEvent is emitted when the application state changes.
|
||||||
|
type AppStateChangedEvent struct {
|
||||||
|
State domain.AppState
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e AppStateChangedEvent) name() Name {
|
||||||
|
return EventNameAppStateChanged
|
||||||
|
}
|
||||||
|
|
||||||
// DestinationAddedEvent is emitted when a destination is successfully added.
|
// DestinationAddedEvent is emitted when a destination is successfully added.
|
||||||
type DestinationAddedEvent struct {
|
type DestinationAddedEvent struct {
|
||||||
URL string
|
URL string
|
||||||
|
@ -279,6 +279,7 @@ func (ui *UI) C() <-chan domain.Command {
|
|||||||
func (ui *UI) run(ctx context.Context) {
|
func (ui *UI) run(ctx context.Context) {
|
||||||
defer close(ui.commandC)
|
defer close(ui.commandC)
|
||||||
|
|
||||||
|
appStateChangedC := ui.eventBus.Register(event.EventNameAppStateChanged)
|
||||||
destinationAddedC := ui.eventBus.Register(event.EventNameDestinationAdded)
|
destinationAddedC := ui.eventBus.Register(event.EventNameDestinationAdded)
|
||||||
destinationRemovedC := ui.eventBus.Register(event.EventNameDestinationRemoved)
|
destinationRemovedC := ui.eventBus.Register(event.EventNameDestinationRemoved)
|
||||||
mediaServerStartedC := ui.eventBus.Register(event.EventNameMediaServerStarted)
|
mediaServerStartedC := ui.eventBus.Register(event.EventNameMediaServerStarted)
|
||||||
@ -297,14 +298,26 @@ func (ui *UI) run(ctx context.Context) {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
case evt := <-appStateChangedC:
|
||||||
|
ui.app.QueueUpdateDraw(func() {
|
||||||
|
ui.handleAppStateChanged(evt.(event.AppStateChangedEvent))
|
||||||
|
})
|
||||||
case evt := <-destinationAddedC:
|
case evt := <-destinationAddedC:
|
||||||
|
ui.app.QueueUpdateDraw(func() {
|
||||||
ui.handleDestinationAdded(evt.(event.DestinationAddedEvent))
|
ui.handleDestinationAdded(evt.(event.DestinationAddedEvent))
|
||||||
|
})
|
||||||
case evt := <-destinationRemovedC:
|
case evt := <-destinationRemovedC:
|
||||||
|
ui.app.QueueUpdateDraw(func() {
|
||||||
ui.handleDestinationRemoved(evt.(event.DestinationRemovedEvent))
|
ui.handleDestinationRemoved(evt.(event.DestinationRemovedEvent))
|
||||||
|
})
|
||||||
case evt := <-mediaServerStartedC:
|
case evt := <-mediaServerStartedC:
|
||||||
|
ui.app.QueueUpdateDraw(func() {
|
||||||
ui.handleMediaServerStarted(evt.(event.MediaServerStartedEvent))
|
ui.handleMediaServerStarted(evt.(event.MediaServerStartedEvent))
|
||||||
|
})
|
||||||
case evt := <-fatalErrorOccurredC:
|
case evt := <-fatalErrorOccurredC:
|
||||||
|
ui.app.QueueUpdateDraw(func() {
|
||||||
ui.handleFatalErrorOccurred(evt.(event.FatalErrorOccurredEvent))
|
ui.handleFatalErrorOccurred(evt.(event.FatalErrorOccurredEvent))
|
||||||
|
})
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-uiDone:
|
case <-uiDone:
|
||||||
@ -319,7 +332,7 @@ func (ui *UI) handleMediaServerStarted(evt event.MediaServerStartedEvent) {
|
|||||||
ui.rtmpsURL = evt.RTMPSURL
|
ui.rtmpsURL = evt.RTMPSURL
|
||||||
ui.mu.Unlock()
|
ui.mu.Unlock()
|
||||||
|
|
||||||
ui.app.QueueUpdateDraw(ui.renderAboutView)
|
ui.renderAboutView()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UI) inputCaptureHandler(event *tcell.EventKey) *tcell.EventKey {
|
func (ui *UI) inputCaptureHandler(event *tcell.EventKey) *tcell.EventKey {
|
||||||
@ -447,7 +460,6 @@ func (ui *UI) ShowDestinationErrorModal(name string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UI) handleFatalErrorOccurred(evt event.FatalErrorOccurredEvent) {
|
func (ui *UI) handleFatalErrorOccurred(evt event.FatalErrorOccurredEvent) {
|
||||||
ui.app.QueueUpdateDraw(func() {
|
|
||||||
ui.showModal(
|
ui.showModal(
|
||||||
pageNameModalFatalError,
|
pageNameModalFatalError,
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
@ -460,7 +472,6 @@ func (ui *UI) handleFatalErrorOccurred(evt event.FatalErrorOccurredEvent) {
|
|||||||
ui.commandC <- domain.CommandQuit{}
|
ui.commandC <- domain.CommandQuit{}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UI) afterDrawHandler(screen tcell.Screen) {
|
func (ui *UI) afterDrawHandler(screen tcell.Screen) {
|
||||||
@ -491,8 +502,9 @@ func (ui *UI) captureScreen(screen tcell.Screen) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetState sets the state of the terminal user interface.
|
func (ui *UI) handleAppStateChanged(evt event.AppStateChangedEvent) {
|
||||||
func (ui *UI) SetState(state domain.AppState) {
|
state := evt.State
|
||||||
|
|
||||||
if state.Source.ExitReason != "" {
|
if state.Source.ExitReason != "" {
|
||||||
ui.handleMediaServerClosed(state.Source.ExitReason)
|
ui.handleMediaServerClosed(state.Source.ExitReason)
|
||||||
}
|
}
|
||||||
@ -507,10 +519,7 @@ func (ui *UI) SetState(state domain.AppState) {
|
|||||||
ui.hasDestinations = len(state.Destinations) > 0
|
ui.hasDestinations = len(state.Destinations) > 0
|
||||||
ui.mu.Unlock()
|
ui.mu.Unlock()
|
||||||
|
|
||||||
// The state is mutable so can't be passed into QueueUpdateDraw, which
|
ui.redrawFromState(state)
|
||||||
// passes it to another goroutine, without cloning it first.
|
|
||||||
stateClone := state.Clone()
|
|
||||||
ui.app.QueueUpdateDraw(func() { ui.redrawFromState(stateClone) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UI) updatePullProgress(state domain.AppState) {
|
func (ui *UI) updatePullProgress(state domain.AppState) {
|
||||||
@ -531,9 +540,7 @@ func (ui *UI) updatePullProgress(state domain.AppState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(pullingContainers) == 0 {
|
if len(pullingContainers) == 0 {
|
||||||
ui.app.QueueUpdateDraw(func() {
|
|
||||||
ui.hideModal(pageNameModalPullProgress)
|
ui.hideModal(pageNameModalPullProgress)
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -547,7 +554,6 @@ func (ui *UI) updatePullProgress(state domain.AppState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UI) updateProgressModal(container domain.Container) {
|
func (ui *UI) updateProgressModal(container domain.Container) {
|
||||||
ui.app.QueueUpdateDraw(func() {
|
|
||||||
modalName := string(pageNameModalPullProgress)
|
modalName := string(pageNameModalPullProgress)
|
||||||
|
|
||||||
var status string
|
var status string
|
||||||
@ -569,7 +575,6 @@ func (ui *UI) updateProgressModal(container domain.Container) {
|
|||||||
} else {
|
} else {
|
||||||
ui.pages.AddPage(modalName, ui.pullProgressModal, true, true)
|
ui.pages.AddPage(modalName, ui.pullProgressModal, true, true)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// page names represent a specific page in the terminal user interface.
|
// page names represent a specific page in the terminal user interface.
|
||||||
@ -699,7 +704,6 @@ func (ui *UI) hideModal(pageName string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UI) handleMediaServerClosed(exitReason string) {
|
func (ui *UI) handleMediaServerClosed(exitReason string) {
|
||||||
ui.app.QueueUpdateDraw(func() {
|
|
||||||
if ui.pages.HasPage(pageNameModalSourceError) {
|
if ui.pages.HasPage(pageNameModalSourceError) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -715,7 +719,6 @@ func (ui *UI) handleMediaServerClosed(exitReason string) {
|
|||||||
modal.SetBorderStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite))
|
modal.SetBorderStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite))
|
||||||
|
|
||||||
ui.pages.AddPage(pageNameModalSourceError, modal, true, true)
|
ui.pages.AddPage(pageNameModalSourceError, modal, true, true)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dash = "—"
|
const dash = "—"
|
||||||
@ -969,15 +972,13 @@ func (ui *UI) handleDestinationAdded(event.DestinationAddedEvent) {
|
|||||||
ui.hasDestinations = true
|
ui.hasDestinations = true
|
||||||
ui.mu.Unlock()
|
ui.mu.Unlock()
|
||||||
|
|
||||||
ui.app.QueueUpdateDraw(func() {
|
|
||||||
ui.pages.HidePage(pageNameNoDestinations)
|
ui.pages.HidePage(pageNameNoDestinations)
|
||||||
ui.closeAddDestinationForm()
|
ui.closeAddDestinationForm()
|
||||||
ui.selectLastDestination()
|
ui.selectLastDestination()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UI) handleDestinationRemoved(event.DestinationRemovedEvent) {
|
func (ui *UI) handleDestinationRemoved(event.DestinationRemovedEvent) {
|
||||||
ui.app.QueueUpdateDraw(ui.selectPreviousDestination)
|
ui.selectPreviousDestination()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UI) closeAddDestinationForm() {
|
func (ui *UI) closeAddDestinationForm() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user