Skip to content

Commit

Permalink
more ui
Browse files Browse the repository at this point in the history
  • Loading branch information
rusq committed Oct 27, 2024
1 parent 8cf2227 commit cc06f3f
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 122 deletions.
48 changes: 46 additions & 2 deletions cmd/slackdump/internal/diag/wizdebug.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/cfgui"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/dumpui"
Expand All @@ -16,16 +17,55 @@ var CmdWizDebug = &base.Command{
PrintFlags: true,
}

type wdWhat int

const (
wdExit wdWhat = iota
wdDumpUI
wdConfigUI
)

func runWizDebug(ctx context.Context, cmd *base.Command, args []string) error {
var action wdWhat
for {
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[wdWhat]().Options(
huh.NewOption("Dump UI", wdDumpUI),
huh.NewOption("Global Config UI", wdConfigUI),
).Value(&action),
).WithHeight(10),
)

if err := form.RunWithContext(ctx); err != nil {
return err
}
switch action {
case wdDumpUI:
if err := debugDumpUI(ctx); err != nil {
return err
}
case wdConfigUI:
if err := debugConfigUI(ctx); err != nil {
return err
}
case wdExit:
return nil
}
}
}

func debugDumpUI(ctx context.Context) error {
menu := []dumpui.MenuItem{
{
ID: "run",
Name: "Run",
Help: "Run the command",
},
{
Name: "Global Configuration...",
Help: "Set global configuration options",
Model: cfgui.NewConfigUI(cfgui.DefaultStyle(), cfgui.GlobalConfig).(dumpui.FocusModel), // TODO: filthy cast
Model: cfgui.NewConfigUI(cfgui.DefaultStyle(), cfgui.GlobalConfig),
},
{
Name: "Local Configuration...",
Expand All @@ -41,9 +81,13 @@ func runWizDebug(ctx context.Context, cmd *base.Command, args []string) error {
}
w := dumpui.NewModel("Wizard Debug", menu)

if _, err := tea.NewProgram(w).Run(); err != nil {
if _, err := tea.NewProgram(w, tea.WithContext(ctx)).Run(); err != nil {
return err
}

return nil
}

func debugConfigUI(ctx context.Context) error {
return cfgui.Global(ctx)
}
4 changes: 3 additions & 1 deletion cmd/slackdump/internal/ui/cfgui/cfgui.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (

// Global initialises and runs the configuration UI.
func Global(ctx context.Context) error {
p := tea.NewProgram(NewConfigUI(DefaultStyle(), globalConfig))
m := NewConfigUI(DefaultStyle(), globalConfig)
m.SetFocus(true)
p := tea.NewProgram(m)
_, err := p.Run()
return err
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/slackdump/internal/ui/cfgui/configuration.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package cfgui

import (
"context"
"errors"
"os"
"runtime/trace"
"time"

tea "github.com/charmbracelet/bubbletea"
Expand Down Expand Up @@ -127,6 +129,8 @@ func globalConfig() Configuration {
}

func validateAPIconfig(s string) error {
_, task := trace.NewTask(context.Background(), "validateAPIconfig")
defer task.End()
if s == "" {
return nil
}
Expand Down
29 changes: 19 additions & 10 deletions cmd/slackdump/internal/ui/cfgui/model.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package cfgui

import (
"context"
"fmt"
"runtime/trace"
"strings"

"github.com/charmbracelet/bubbles/help"
Expand All @@ -21,7 +23,7 @@ const (
notFound = -1
)

type configmodel struct {
type Model struct {
finished bool
focused bool
cursor int
Expand All @@ -36,13 +38,13 @@ type configmodel struct {
cfgFn func() Configuration
}

func NewConfigUI(sty *Style, cfgFn func() Configuration) tea.Model {
func NewConfigUI(sty *Style, cfgFn func() Configuration) *Model {
end := 0
for _, group := range cfgFn() {
end += len(group.Params)
}
end--
return &configmodel{
return &Model{
cfgFn: cfgFn,
last: end,
keymap: DefaultKeymap(),
Expand All @@ -51,7 +53,7 @@ func NewConfigUI(sty *Style, cfgFn func() Configuration) tea.Model {
}
}

func (m *configmodel) Init() tea.Cmd {
func (m *Model) Init() tea.Cmd {
return nil
}

Expand All @@ -63,15 +65,20 @@ const (
inline
)

func (m *configmodel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
ctx, task := trace.NewTask(context.Background(), "cfgui.Update")
defer task.End()

if !m.focused {
return m, nil
}

var cmds []tea.Cmd

if _, ok := msg.(updaters.WMClose); m.child != nil && !ok && m.state != selecting {
rgn := trace.StartRegion(ctx, "child.Update")
child, cmd := m.child.Update(msg)
rgn.End()
m.child = child
return m, cmd
}
Expand Down Expand Up @@ -131,21 +138,23 @@ func (m *configmodel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Batch(cmds...)
}

func (m *configmodel) SetFocus(b bool) {
func (m *Model) SetFocus(b bool) {
m.focused = b
}

func (m *configmodel) IsFocused() bool {
func (m *Model) IsFocused() bool {
return m.focused
}

func (m *configmodel) Reset() {
func (m *Model) Reset() {
m.finished = false
m.state = selecting
m.child = nil
}

func (m *configmodel) View() string {
func (m *Model) View() string {
_, task := trace.NewTask(context.Background(), "cfgui.View")
defer task.End()
if m.finished {
return ""
}
Expand All @@ -159,7 +168,7 @@ func (m *configmodel) View() string {
return sty.Border.Render(m.view(sty))
}

func (m *configmodel) view(sty StyleSet) string {
func (m *Model) view(sty StyleSet) string {
var buf strings.Builder
line := 0
descr := ""
Expand Down
88 changes: 41 additions & 47 deletions cmd/slackdump/internal/ui/dumpui/dumpui.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ package dumpui
import (
"context"

"github.com/charmbracelet/huh"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg"
tea "github.com/charmbracelet/bubbletea"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/cfgui"
)

Expand Down Expand Up @@ -42,49 +40,53 @@ var description = map[string]string{
}

func (w *Wizard) Run(ctx context.Context) error {
var (
action string = actRun
)

menu := func(opts ...huh.Option[string]) *huh.Form {
return huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title(w.Title).
Options(
opts...,
).Value(&action).
DescriptionFunc(func() string { return description[action] }, &action),
),
).WithTheme(ui.HuhTheme).WithAccessible(cfg.AccessibleMode)
}

LOOP:
for {
var opts []huh.Option[string]
if w.ValidateParamsFn != nil && w.LocalConfig != nil {
if err := w.ValidateParamsFn(); err == nil {
opts = append(opts, huh.NewOption("Run "+w.Name, actRun))
}
action = actLocalConfig
} else {
opts = append(opts, huh.NewOption("Run "+w.Name, actRun))
var menu = func() *Model {
items := []MenuItem{
{
ID: actRun,
Name: "Run " + w.Name,
Help: description[actRun],
Validate: func() error {
if w.ValidateParamsFn != nil {
return w.ValidateParamsFn()
}
return nil
},
},
}
// local config
if w.LocalConfig != nil {
opts = append(opts, huh.NewOption(w.Name+" Configuration...", actLocalConfig))
items = append(items, MenuItem{
ID: actLocalConfig,
Name: w.Name + " Configuration...",
Help: description[actLocalConfig],
Model: cfgui.NewConfigUI(cfgui.DefaultStyle(), w.LocalConfig),
})
}
// final options
opts = append(opts,
huh.NewOption("Global Configuration...", actGlobalConfig),
huh.NewOption(ui.MenuSeparator, ""),
huh.NewOption("<< Exit to Main Menu", actExit),
items = append(
items,
MenuItem{
ID: actGlobalConfig,
Name: "Global Configuration...",
Help: description[actGlobalConfig],
Model: cfgui.NewConfigUI(cfgui.DefaultStyle(), cfgui.GlobalConfig), // TODO: filthy cast
},
MenuItem{Separator: true},
MenuItem{ID: actExit, Name: "Exit", Help: description[actExit]},
)

if err := menu(opts...).RunWithContext(ctx); err != nil {
return NewModel(w.Title, items)
}

LOOP:
for {
m := menu()
if _, err := tea.NewProgram(m, tea.WithContext(ctx)).Run(); err != nil {
return err
}
switch action {
if m.Cancelled {
break
}
switch m.Selected.ID {
case actRun:
if w.ValidateParamsFn != nil {
if err := w.ValidateParamsFn(); err != nil {
Expand All @@ -98,14 +100,6 @@ LOOP:
if err := w.Cmd.Run(ctx, w.Cmd, args); err != nil {
return err
}
case actGlobalConfig:
if err := cfgui.Global(ctx); err != nil {
return err
}
case actLocalConfig:
if err := cfgui.Local(ctx, w.LocalConfig); err != nil {
return err
}
case actExit:
break LOOP
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/slackdump/internal/ui/dumpui/keymap.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func DefaultKeymap() *Keymap {
Up: key.NewBinding(key.WithKeys("up", "k"), key.WithHelp("↑", "up")),
Down: key.NewBinding(key.WithKeys("down", "j"), key.WithHelp("↓", "down")),
Select: key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "submit")),
Quit: key.NewBinding(key.WithKeys("q", "ctrl+c"), key.WithHelp("q", "quit")),
Quit: key.NewBinding(key.WithKeys("q", "ctrl+c", "esc"), key.WithHelp("q", "quit")),
}
}

Expand Down
23 changes: 19 additions & 4 deletions cmd/slackdump/internal/ui/dumpui/menuitem.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ package dumpui
type MenuItem struct {
// ID is an arbitrary ID, up to caller.
ID string
// Separator is a flag that determines whether the item is a separator or not.
// Separator is a flag that determines whether the item is a separator or
// not.
Separator bool
// Name is the name of the Item, will be displayed in the menu.
Name string
Expand All @@ -14,7 +15,21 @@ type MenuItem struct {
// Model is any model that should be displayed when the item is selected,
// or executed when the user presses enter.
Model FocusModel
// IsDisabled determines whether the item is disabled or not. It should
// complete in reasonable time, as it is called on every render.
IsDisabled func() bool // when to enable the item
// Validate determines whether the item is disabled or not. It should
// complete in reasonable time, as it is called on every render. The
// return error is used in the description for the item.
Validate func() error // when to enable the item
}

func (m MenuItem) IsDisabled() bool {
return m.Validate != nil && m.Validate() != nil
}

func (m MenuItem) DisabledReason() string {
if m.Validate != nil {
if err := m.Validate(); err != nil {
return err.Error()
}
}
return ""
}
Loading

0 comments on commit cc06f3f

Please sign in to comment.