1package tui
  2
  3import (
  4	"fmt"
  5	"smoothie/git"
  6	"smoothie/tui/bubbles/commits"
  7	"smoothie/tui/bubbles/repo"
  8	"smoothie/tui/bubbles/selection"
  9
 10	tea "github.com/charmbracelet/bubbletea"
 11	"github.com/charmbracelet/lipgloss"
 12	"github.com/gliderlabs/ssh"
 13)
 14
 15type sessionState int
 16
 17const (
 18	startState sessionState = iota
 19	errorState
 20	loadedState
 21	quittingState
 22	quitState
 23)
 24
 25type Config struct {
 26	Name         string      `json:"name"`
 27	Host         string      `json:"host"`
 28	Port         int64       `json:"port"`
 29	ShowAllRepos bool        `json:"show_all_repos"`
 30	Menu         []MenuEntry `json:"menu"`
 31	RepoSource   *git.RepoSource
 32}
 33
 34type MenuEntry struct {
 35	Name   string `json:"name"`
 36	Note   string `json:"note"`
 37	Repo   string `json:"repo"`
 38	bubble *repo.Bubble
 39}
 40
 41type SessionConfig struct {
 42	Width         int
 43	Height        int
 44	WindowChanges <-chan ssh.Window
 45}
 46
 47type Bubble struct {
 48	config        *Config
 49	state         sessionState
 50	error         string
 51	width         int
 52	height        int
 53	windowChanges <-chan ssh.Window
 54	repoSource    *git.RepoSource
 55	repoMenu      []MenuEntry
 56	repos         []*git.Repo
 57	boxes         []tea.Model
 58	activeBox     int
 59	repoSelect    *selection.Bubble
 60	commitsLog    *commits.Bubble
 61}
 62
 63func NewBubble(cfg *Config, sCfg *SessionConfig) *Bubble {
 64	b := &Bubble{
 65		config:        cfg,
 66		width:         sCfg.Width,
 67		height:        sCfg.Height,
 68		windowChanges: sCfg.WindowChanges,
 69		repoSource:    cfg.RepoSource,
 70		repoMenu:      make([]MenuEntry, 0),
 71		boxes:         make([]tea.Model, 2),
 72	}
 73	b.state = startState
 74	return b
 75}
 76
 77func (b *Bubble) Init() tea.Cmd {
 78	return tea.Batch(b.windowChangesCmd, b.setupCmd)
 79}
 80
 81func (b *Bubble) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 82	cmds := make([]tea.Cmd, 0)
 83	// Always allow state, error, info, window resize and quit messages
 84	switch msg := msg.(type) {
 85	case tea.KeyMsg:
 86		switch msg.String() {
 87		case "q", "ctrl+c":
 88			return b, tea.Quit
 89		case "tab":
 90			b.activeBox = (b.activeBox + 1) % 2
 91		}
 92	case errMsg:
 93		b.error = msg.Error()
 94		b.state = errorState
 95		return b, nil
 96	case windowMsg:
 97		cmds = append(cmds, b.windowChangesCmd)
 98	case tea.WindowSizeMsg:
 99		b.width = msg.Width
100		b.height = msg.Height
101	case selection.SelectedMsg:
102		b.activeBox = 1
103		rb := b.repoMenu[msg.Index].bubble
104		rb.GotoTop()
105		b.boxes[1] = rb
106	case selection.ActiveMsg:
107		rb := b.repoMenu[msg.Index].bubble
108		rb.GotoTop()
109		b.boxes[1] = b.repoMenu[msg.Index].bubble
110	}
111	if b.state == loadedState {
112		ab, cmd := b.boxes[b.activeBox].Update(msg)
113		b.boxes[b.activeBox] = ab
114		if cmd != nil {
115			cmds = append(cmds, cmd)
116		}
117	}
118	return b, tea.Batch(cmds...)
119}
120
121func (b *Bubble) viewForBox(i int, width int) string {
122	var ls lipgloss.Style
123	if i == b.activeBox {
124		ls = activeBoxStyle.Width(width)
125	} else {
126		ls = inactiveBoxStyle.Width(width)
127	}
128	return ls.Render(b.boxes[i].View())
129}
130
131func (b *Bubble) View() string {
132	h := headerStyle.Width(b.width - horizontalPadding).Render(b.config.Name)
133	f := footerStyle.Render("")
134	s := ""
135	content := ""
136	switch b.state {
137	case loadedState:
138		lb := b.viewForBox(0, boxLeftWidth)
139		rb := b.viewForBox(1, boxRightWidth)
140		s += lipgloss.JoinHorizontal(lipgloss.Top, lb, rb)
141	case errorState:
142		s += errorStyle.Render(fmt.Sprintf("Bummer: %s", b.error))
143	}
144	content = h + "\n\n" + s + "\n" + f
145	return appBoxStyle.Render(content)
146}