mirror of
https://github.com/lukaszraczylo/lolcathost.git
synced 2026-10-15 05:37:29 +00:00
refactor(tui): move to Bubble Tea, Lip Gloss and Bubbles v2
Switch imports to the charm.land v2 modules. Return tea.View from Model.View, set alt screen and window title there, use KeyPressMsg and the "space" key name, and call SetWidth on text inputs.
This commit is contained in:
+35
-29
@@ -7,9 +7,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"charm.land/bubbles/v2/textinput"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
|
||||
"github.com/lukaszraczylo/lolcathost/internal/client"
|
||||
"github.com/lukaszraczylo/lolcathost/internal/config"
|
||||
@@ -143,7 +143,7 @@ func NewModel(socketPath string) *Model {
|
||||
searchInput := textinput.New()
|
||||
searchInput.Placeholder = "Search..."
|
||||
searchInput.CharLimit = 100
|
||||
searchInput.Width = 50
|
||||
searchInput.SetWidth(50)
|
||||
|
||||
return &Model{
|
||||
client: client.New(socketPath),
|
||||
@@ -161,7 +161,6 @@ func NewModel(socketPath string) *Model {
|
||||
func (m *Model) Init() tea.Cmd {
|
||||
return tea.Batch(
|
||||
m.connect(),
|
||||
tea.SetWindowTitle("lolcathost"),
|
||||
m.tick(),
|
||||
m.checkForUpdate(),
|
||||
)
|
||||
@@ -330,9 +329,9 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if searchWidth > 60 {
|
||||
searchWidth = 60
|
||||
}
|
||||
m.searchInput.Width = searchWidth
|
||||
m.searchInput.SetWidth(searchWidth)
|
||||
|
||||
case tea.KeyMsg:
|
||||
case tea.KeyPressMsg:
|
||||
cmd := m.handleKey(msg)
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
@@ -505,7 +504,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
// Global keys
|
||||
switch msg.String() {
|
||||
case "ctrl+c":
|
||||
@@ -535,7 +534,7 @@ func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handleListKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleListKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "q":
|
||||
return tea.Quit
|
||||
@@ -549,7 +548,7 @@ func (m *Model) handleListKey(msg tea.KeyMsg) tea.Cmd {
|
||||
m.list.MoveUp()
|
||||
case "down", "j":
|
||||
m.list.MoveDown()
|
||||
case " ", "enter":
|
||||
case "space", "enter":
|
||||
return m.toggleSelected()
|
||||
case "n":
|
||||
m.mode = ViewForm
|
||||
@@ -587,7 +586,7 @@ func (m *Model) handleListKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handleFormKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleFormKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
m.mode = ViewList
|
||||
@@ -615,7 +614,7 @@ func (m *Model) handleFormKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return m.form.Update(msg)
|
||||
}
|
||||
|
||||
func (m *Model) handlePresetKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handlePresetKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
// Handle based on preset picker mode
|
||||
switch m.presetPicker.Mode() {
|
||||
case PresetModeSelect:
|
||||
@@ -630,7 +629,7 @@ func (m *Model) handlePresetKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handlePresetSelectKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handlePresetSelectKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "esc", "q":
|
||||
m.mode = ViewList
|
||||
@@ -652,7 +651,7 @@ func (m *Model) handlePresetSelectKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handlePresetFormKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handlePresetFormKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
m.presetPicker.CancelForm()
|
||||
@@ -690,7 +689,7 @@ func (m *Model) handlePresetFormKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return m.presetPicker.Update(msg)
|
||||
}
|
||||
|
||||
func (m *Model) handlePresetPickerKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handlePresetPickerKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
m.presetPicker.ClosePicker()
|
||||
@@ -700,13 +699,13 @@ func (m *Model) handlePresetPickerKey(msg tea.KeyMsg) tea.Cmd {
|
||||
m.presetPicker.PickerMoveUp()
|
||||
case "down", "j":
|
||||
m.presetPicker.PickerMoveDown()
|
||||
case " ":
|
||||
case "space":
|
||||
m.presetPicker.TogglePickerSelection()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handlePresetDeleteKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handlePresetDeleteKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "y", "Y":
|
||||
if preset := m.presetPicker.Selected(); preset != "" {
|
||||
@@ -719,7 +718,7 @@ func (m *Model) handlePresetDeleteKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handleGroupKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleGroupKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
// Handle based on group picker mode
|
||||
switch m.groupPicker.Mode() {
|
||||
case GroupModeSelect:
|
||||
@@ -732,7 +731,7 @@ func (m *Model) handleGroupKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handleGroupSelectKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleGroupSelectKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "esc", "q":
|
||||
m.mode = ViewList
|
||||
@@ -750,7 +749,7 @@ func (m *Model) handleGroupSelectKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handleGroupFormKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleGroupFormKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
m.groupPicker.CancelForm()
|
||||
@@ -770,7 +769,7 @@ func (m *Model) handleGroupFormKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return m.groupPicker.Update(msg)
|
||||
}
|
||||
|
||||
func (m *Model) handleGroupDeleteKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleGroupDeleteKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "y", "Y":
|
||||
if group := m.groupPicker.Selected(); group != "" {
|
||||
@@ -783,7 +782,7 @@ func (m *Model) handleGroupDeleteKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handleBackupKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleBackupKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch m.backupPicker.Mode() {
|
||||
case BackupModeSelect:
|
||||
return m.handleBackupSelectKey(msg)
|
||||
@@ -793,7 +792,7 @@ func (m *Model) handleBackupKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handleBackupSelectKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleBackupSelectKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "esc", "q":
|
||||
m.mode = ViewList
|
||||
@@ -821,7 +820,7 @@ func (m *Model) handleBackupSelectKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handleBackupRestoreKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleBackupRestoreKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "y", "Y":
|
||||
if backup := m.backupPicker.Selected(); backup != "" {
|
||||
@@ -834,7 +833,7 @@ func (m *Model) handleBackupRestoreKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handleHelpKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleHelpKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "esc", "q", "?":
|
||||
m.mode = ViewList
|
||||
@@ -842,7 +841,7 @@ func (m *Model) handleHelpKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) handleSearchKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleSearchKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
m.mode = ViewList
|
||||
@@ -860,7 +859,7 @@ func (m *Model) handleSearchKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (m *Model) handleConfirmDeleteKey(msg tea.KeyMsg) tea.Cmd {
|
||||
func (m *Model) handleConfirmDeleteKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "y", "Y":
|
||||
alias := m.pendingDeleteAlias
|
||||
@@ -900,7 +899,14 @@ func (m *Model) setSuccess(msg string) {
|
||||
}
|
||||
|
||||
// View renders the UI.
|
||||
func (m *Model) View() string {
|
||||
func (m *Model) View() tea.View {
|
||||
v := tea.NewView(m.render())
|
||||
v.AltScreen = true
|
||||
v.WindowTitle = "lolcathost"
|
||||
return v
|
||||
}
|
||||
|
||||
func (m *Model) render() string {
|
||||
var sb strings.Builder
|
||||
|
||||
// Title with version
|
||||
@@ -1142,7 +1148,7 @@ func RunWithVersion(socketPath, version, githubOwner, githubRepo string) error {
|
||||
m.version = version
|
||||
m.githubOwner = githubOwner
|
||||
m.githubRepo = githubRepo
|
||||
p := tea.NewProgram(m, tea.WithAltScreen())
|
||||
p := tea.NewProgram(m)
|
||||
|
||||
_, err := p.Run()
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestModel_View_UsesAltScreenAndTitle(t *testing.T) {
|
||||
m := NewModel("/nonexistent.sock")
|
||||
|
||||
v := m.View()
|
||||
|
||||
assert.True(t, v.AltScreen)
|
||||
assert.Equal(t, "lolcathost", v.WindowTitle)
|
||||
assert.Contains(t, v.Content, "lolcathost - Host Management")
|
||||
}
|
||||
|
||||
func TestModel_SpaceKeyIsNamedSpace(t *testing.T) {
|
||||
msg := tea.KeyPressMsg{Code: tea.KeySpace, Text: " "}
|
||||
|
||||
require.Equal(t, "space", msg.String())
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"charm.land/lipgloss/v2"
|
||||
"github.com/lukaszraczylo/lolcathost/internal/protocol"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"charm.land/bubbles/v2/textinput"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/lukaszraczylo/lolcathost/internal/config"
|
||||
)
|
||||
|
||||
@@ -127,14 +127,14 @@ func (f *Form) SetSize(width, height int) {
|
||||
|
||||
inputWidth := min(50, width-10)
|
||||
for i := range f.fields {
|
||||
f.fields[i].Width = inputWidth
|
||||
f.fields[i].SetWidth(inputWidth)
|
||||
}
|
||||
}
|
||||
|
||||
// Update handles input events.
|
||||
func (f *Form) Update(msg tea.Msg) tea.Cmd {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
case tea.KeyPressMsg:
|
||||
// Handle group dropdown navigation
|
||||
if f.focus == FieldGroup {
|
||||
switch msg.String() {
|
||||
|
||||
@@ -4,8 +4,8 @@ package tui
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"charm.land/bubbles/v2/textinput"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
)
|
||||
|
||||
// GroupMode represents the group view mode.
|
||||
@@ -53,7 +53,7 @@ func (g *GroupPicker) SetGroups(groups []string) {
|
||||
func (g *GroupPicker) SetSize(width, height int) {
|
||||
g.width = width
|
||||
g.height = height
|
||||
g.input.Width = min(50, width-10)
|
||||
g.input.SetWidth(min(50, width-10))
|
||||
}
|
||||
|
||||
// MoveUp moves the cursor up.
|
||||
@@ -126,7 +126,7 @@ func (g *GroupPicker) CancelForm() {
|
||||
}
|
||||
|
||||
// Update handles input events for form mode.
|
||||
func (g *GroupPicker) Update(msg tea.KeyMsg) tea.Cmd {
|
||||
func (g *GroupPicker) Update(msg tea.KeyPressMsg) tea.Cmd {
|
||||
var cmd tea.Cmd
|
||||
g.input, cmd = g.input.Update(msg)
|
||||
return cmd
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/lipgloss/table"
|
||||
"charm.land/lipgloss/v2"
|
||||
"charm.land/lipgloss/v2/table"
|
||||
"github.com/lukaszraczylo/lolcathost/internal/protocol"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ package tui
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"charm.land/bubbles/v2/textinput"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"github.com/lukaszraczylo/lolcathost/internal/protocol"
|
||||
)
|
||||
|
||||
@@ -108,7 +108,7 @@ func (p *PresetPicker) SetSize(width, height int) {
|
||||
|
||||
inputWidth := min(60, width-10)
|
||||
for i := range p.fields {
|
||||
p.fields[i].Width = inputWidth
|
||||
p.fields[i].SetWidth(inputWidth)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ func (p *PresetPicker) CancelForm() {
|
||||
}
|
||||
|
||||
// Update handles input events for form mode.
|
||||
func (p *PresetPicker) Update(msg tea.KeyMsg) tea.Cmd {
|
||||
func (p *PresetPicker) Update(msg tea.KeyPressMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "tab", "down":
|
||||
p.nextField()
|
||||
|
||||
@@ -4,7 +4,7 @@ package tui
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"charm.land/lipgloss/v2"
|
||||
)
|
||||
|
||||
// Colors - matching kportal style, optimized for dark terminals
|
||||
|
||||
Reference in New Issue
Block a user