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:
parent
1969504611
commit
7526c095ee
24 changed files with 602 additions and 97 deletions
30
internal/common/ui/dispatcher/context.go
Normal file
30
internal/common/ui/dispatcher/context.go
Normal 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,
|
||||
})
|
||||
}
|
||||
49
internal/common/ui/dispatcher/dispatcher.go
Normal file
49
internal/common/ui/dispatcher/dispatcher.go
Normal 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
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
|
||||
7
internal/common/ui/dispatcher/iface.go
Normal file
7
internal/common/ui/dispatcher/iface.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package dispatcher
|
||||
|
||||
import tea "github.com/charmbracelet/bubbletea"
|
||||
|
||||
type MessagePublisher interface {
|
||||
Send(msg tea.Msg)
|
||||
}
|
||||
16
internal/common/ui/events/errors.go
Normal file
16
internal/common/ui/events/errors.go
Normal 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
|
||||
}
|
||||
15
internal/common/ui/uimodels/context.go
Normal file
15
internal/common/ui/uimodels/context.go
Normal 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)
|
||||
}
|
||||
10
internal/common/ui/uimodels/iface.go
Normal file
10
internal/common/ui/uimodels/iface.go
Normal 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)
|
||||
}
|
||||
14
internal/common/ui/uimodels/operations.go
Normal file
14
internal/common/ui/uimodels/operations.go
Normal 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)
|
||||
}
|
||||
|
||||
15
internal/common/ui/uimodels/promptvalue.go
Normal file
15
internal/common/ui/uimodels/promptvalue.go
Normal 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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue