sqs-browse: a lot of work to try to keep UI complexity down

Added the notion of controllers and a dispatcher which will queue up operations
This commit is contained in:
Leon Mika 2022-03-23 15:40:31 +11:00
parent 1969504611
commit 7526c095ee
24 changed files with 602 additions and 97 deletions

View file

@ -0,0 +1,30 @@
package dispatcher
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/common/ui/events"
"github.com/lmika/awstools/internal/common/ui/uimodels"
)
type dispatcherContext struct {
d *Dispatcher
}
func (dc dispatcherContext) Messagef(format string, args ...interface{}) {
dc.d.publisher.Send(events.Message(fmt.Sprintf(format, args...)))
}
func (dc dispatcherContext) Send(teaMessage tea.Msg) {
dc.d.publisher.Send(teaMessage)
}
func (dc dispatcherContext) Message(msg string) {
dc.d.publisher.Send(events.Message(msg))
}
func (dc dispatcherContext) Input(prompt string, onDone uimodels.Operation) {
dc.d.publisher.Send(events.PromptForInput{
OnDone: onDone,
})
}

View file

@ -0,0 +1,49 @@
package dispatcher
import (
"context"
"github.com/lmika/awstools/internal/common/ui/events"
"github.com/lmika/awstools/internal/common/ui/uimodels"
"github.com/pkg/errors"
"sync"
)
type Dispatcher struct {
mutex *sync.Mutex
runningOp uimodels.Operation
publisher MessagePublisher
}
func NewDispatcher(publisher MessagePublisher) *Dispatcher {
return &Dispatcher{
mutex: new(sync.Mutex),
publisher: publisher,
}
}
func (d *Dispatcher) Start(ctx context.Context, operation uimodels.Operation) {
d.mutex.Lock()
defer d.mutex.Unlock()
if d.runningOp != nil {
d.publisher.Send(events.Error(errors.New("operation already running")))
}
d.runningOp = operation
go func() {
subCtx := uimodels.WithContext(ctx, dispatcherContext{d})
err := operation.Execute(subCtx)
if err != nil {
d.publisher.Send(events.Error(err))
}
d.mutex.Lock()
defer d.mutex.Unlock()
d.runningOp = nil
}()
}

View file

@ -0,0 +1,7 @@
package dispatcher
import tea "github.com/charmbracelet/bubbletea"
type MessagePublisher interface {
Send(msg tea.Msg)
}

View file

@ -0,0 +1,16 @@
package events
import (
"github.com/lmika/awstools/internal/common/ui/uimodels"
)
// Error indicates that an error occurred
type Error error
// Message indicates that a message should be shown to the user
type Message string
// PromptForInput indicates that the context is requesting a line of input
type PromptForInput struct {
OnDone uimodels.Operation
}

View file

@ -0,0 +1,15 @@
package uimodels
import "context"
type uiContextKeyType struct {}
var uiContextKey = uiContextKeyType{}
func Ctx(ctx context.Context) UIContext {
uiCtx, _ := ctx.Value(uiContextKey).(UIContext)
return uiCtx
}
func WithContext(ctx context.Context, uiContext UIContext) context.Context {
return context.WithValue(ctx, uiContextKey, uiContext)
}

View file

@ -0,0 +1,10 @@
package uimodels
import tea "github.com/charmbracelet/bubbletea"
type UIContext interface {
Send(teaMessage tea.Msg)
Message(msg string)
Messagef(format string, args ...interface{})
Input(prompt string, onDone Operation)
}

View file

@ -0,0 +1,14 @@
package uimodels
import "context"
type Operation interface {
Execute(ctx context.Context) error
}
type OperationFn func(ctx context.Context) error
func (f OperationFn) Execute(ctx context.Context) error {
return f(ctx)
}

View file

@ -0,0 +1,15 @@
package uimodels
import "context"
type promptValueKeyType struct {}
var promptValueKey = promptValueKeyType{}
func PromptValue(ctx context.Context) string {
value, _ := ctx.Value(promptValueKey).(string)
return value
}
func WithPromptValue(ctx context.Context, value string) context.Context {
return context.WithValue(ctx, promptValueKey, value)
}