Added status and prompt

This commit is contained in:
Leon Mika 2022-03-27 11:40:32 +11:00
parent b0909ffe4e
commit 81cd1d0971
5 changed files with 157 additions and 9 deletions

View file

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/brianvoe/gofakeit/v6"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/awstools/internal/common/ui/commandctrl" "github.com/lmika/awstools/internal/common/ui/commandctrl"
"github.com/lmika/awstools/internal/common/ui/dispatcher" "github.com/lmika/awstools/internal/common/ui/dispatcher"
@ -19,6 +18,7 @@ import (
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/frame" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/frame"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/modal" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/modal"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/statusandprompt"
"github.com/lmika/gopkgs/cli" "github.com/lmika/gopkgs/cli"
"log" "log"
"os" "os"
@ -65,9 +65,12 @@ func main() {
_ = uiModel _ = uiModel
// END TEMP // END TEMP
model := layout.FullScreen(layout.NewVBox( model := layout.FullScreen(statusandprompt.New(
frame.NewFrame("This is the header", layout.Model(newTestModel("this is the top"))), layout.NewVBox(
frame.NewFrame("This is another header", layout.Model(newTestModel("this is the bottom"))), frame.NewFrame("This is the header", true, layout.Model(newTestModel("this is the top"))),
frame.NewFrame("This is another header", false, layout.Model(newTestModel("this is the bottom"))),
),
"Hello world",
)) ))
//frameSet := frameset.New([]frameset.Frame{ //frameSet := frameset.New([]frameset.Frame{
@ -126,7 +129,9 @@ func newTestModel(descr string) tea.Model {
OnKeyPressed: func(k string) tea.Cmd { OnKeyPressed: func(k string) tea.Cmd {
log.Println("got key press: " + k) log.Println("got key press: " + k)
if k == "enter" { if k == "enter" {
return modal.PushMode(newTestModel("this is mode " + gofakeit.CarModel() + " (press k to end)")) return statusandprompt.Prompt("What is your car? ", func(val string) tea.Cmd {
return statusandprompt.SetStatus("Your car is = " + val)
})
} else if k == "k" { } else if k == "k" {
return modal.PopMode return modal.PopMode
} }

View file

@ -13,17 +13,22 @@ var (
Bold(true). Bold(true).
Foreground(lipgloss.Color("#ffffff")). Foreground(lipgloss.Color("#ffffff")).
Background(lipgloss.Color("#4479ff")) Background(lipgloss.Color("#4479ff"))
inactiveHeaderStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#000000")).
Background(lipgloss.Color("#d1d1d1"))
) )
// Frame is a frame that appears in the // Frame is a frame that appears in the
type Frame struct { type Frame struct {
header string header string
active bool
model layout.ResizingModel model layout.ResizingModel
width int width int
} }
func NewFrame(header string, model layout.ResizingModel) Frame { func NewFrame(header string, active bool, model layout.ResizingModel) Frame {
return Frame{header, model, 0} return Frame{header, active, model, 0}
} }
func (f Frame) Init() tea.Cmd { func (f Frame) Init() tea.Cmd {
@ -31,6 +36,14 @@ func (f Frame) Init() tea.Cmd {
} }
func (f Frame) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (f Frame) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.(type) {
case tea.KeyMsg:
// If frame is not active, do not receive key messages
if !f.active {
return f, nil
}
}
newModel, cmd := f.model.Update(msg) newModel, cmd := f.model.Update(msg)
f.model = newModel.(layout.ResizingModel) f.model = newModel.(layout.ResizingModel)
return f, cmd return f, cmd
@ -48,8 +61,13 @@ func (f Frame) Resize(w, h int) layout.ResizingModel {
} }
func (f Frame) headerView() string { func (f Frame) headerView() string {
style := inactiveHeaderStyle
if f.active {
style = activeHeaderStyle
}
titleText := f.header titleText := f.header
title := activeHeaderStyle.Render(titleText) title := style.Render(titleText)
line := activeHeaderStyle.Render(strings.Repeat(" ", utils.Max(0, f.width-lipgloss.Width(title)))) line := style.Render(strings.Repeat(" ", utils.Max(0, f.width-lipgloss.Width(title))))
return lipgloss.JoinHorizontal(lipgloss.Left, title, line) return lipgloss.JoinHorizontal(lipgloss.Left, title, line)
} }

View file

@ -0,0 +1,25 @@
package statusandprompt
import tea "github.com/charmbracelet/bubbletea"
type setStatusMsg string
type startPromptMsg struct {
prompt string
onDone func(val string) tea.Cmd
}
func SetStatus(newStatus string) tea.Cmd {
return func() tea.Msg {
return setStatusMsg(newStatus)
}
}
func Prompt(prompt string, onDone func(val string) tea.Cmd) tea.Cmd {
return func() tea.Msg {
return startPromptMsg{
prompt: prompt,
onDone: onDone,
}
}
}

View file

@ -0,0 +1,94 @@
package statusandprompt
import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/utils"
)
// StatusAndPrompt is a resizing model which displays a submodel and a status bar. When the start prompt
// event is received, focus will be torn away and the user will be given a prompt the enter text.
type StatusAndPrompt struct {
model layout.ResizingModel
statusMessage string
pendingInput *startPromptMsg
textInput textinput.Model
width int
}
func New(model layout.ResizingModel, initialMsg string) StatusAndPrompt {
textInput := textinput.New()
return StatusAndPrompt{model: model, statusMessage: initialMsg, textInput: textInput}
}
func (s StatusAndPrompt) Init() tea.Cmd {
return s.model.Init()
}
func (s StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case setStatusMsg:
s.statusMessage = string(msg)
case startPromptMsg:
if s.pendingInput != nil {
// ignore, already in an input
return s, nil
}
s.textInput.Prompt = msg.prompt
s.textInput.Focus()
s.textInput.SetValue("")
s.pendingInput = &msg
return s, nil
case tea.KeyMsg:
if s.pendingInput != nil {
switch msg.String() {
case "ctrl+c", "esc":
s.pendingInput = nil
case "enter":
pendingInput := s.pendingInput
s.pendingInput = nil
return s, pendingInput.onDone(s.textInput.Value())
}
}
}
if s.pendingInput != nil {
var cc utils.CmdCollector
newTextInput, cmd := s.textInput.Update(msg)
cc.Add(cmd)
s.textInput = newTextInput
if _, isKey := msg.(tea.Key); !isKey {
s.model = cc.Collect(s.model.Update(msg)).(layout.ResizingModel)
}
return s, cc.Cmd()
}
newModel, cmd := s.model.Update(msg)
s.model = newModel.(layout.ResizingModel)
return s, cmd
}
func (s StatusAndPrompt) View() string {
return lipgloss.JoinVertical(lipgloss.Top, s.model.View(), s.viewStatus())
}
func (s StatusAndPrompt) Resize(w, h int) layout.ResizingModel {
s.width = w
submodelHeight := h - lipgloss.Height(s.viewStatus())
s.model = s.model.Resize(w, submodelHeight)
return s
}
func (s StatusAndPrompt) viewStatus() string {
if s.pendingInput != nil {
return s.textInput.View()
}
return s.statusMessage
}

View file

@ -6,6 +6,12 @@ type CmdCollector struct {
cmds []tea.Cmd cmds []tea.Cmd
} }
func (c *CmdCollector) Add(cmd tea.Cmd) {
if cmd != nil {
c.cmds = append(c.cmds, cmd)
}
}
func (c *CmdCollector) Collect(m tea.Model, cmd tea.Cmd) tea.Model { func (c *CmdCollector) Collect(m tea.Model, cmd tea.Cmd) tea.Model {
if cmd != nil { if cmd != nil {
c.cmds = append(c.cmds, cmd) c.cmds = append(c.cmds, cmd)