package app import ( "docker-tui/views" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/lucasdataproyects/devfactory/tui" ) type View int const ( ViewContainers View = iota ViewImages ViewVolumes ViewNetworks ViewCompose ) var tabNames = []string{"Containers", "Images", "Volumes", "Networks", "Compose"} type Model struct { tui.BaseModel activeTab int containers views.ContainersModel images views.ImagesModel volumes views.VolumesModel networks views.NetworksModel compose views.ComposeModel ready bool } func New() Model { styles := tui.DefaultStyles() return Model{ BaseModel: tui.NewBaseModel().WithStyles(styles), containers: views.NewContainersModel(styles), images: views.NewImagesModel(styles), volumes: views.NewVolumesModel(styles), networks: views.NewNetworksModel(styles), compose: views.NewComposeModel(styles), } } func (m Model) Init() tea.Cmd { return m.containers.Init() } func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case views.KeyQuit: return m, tea.Quit case "q", "0", "esc": updated, atBase := m.handleBack() if atBase { return updated, tea.Quit } return updated, nil case views.KeyTab: m.activeTab = (m.activeTab + 1) % len(tabNames) return m, m.initActiveView() case "shift+tab": m.activeTab = (m.activeTab - 1 + len(tabNames)) % len(tabNames) return m, m.initActiveView() } case tea.WindowSizeMsg: m.HandleWindowSize(msg) m.ready = true } var cmd tea.Cmd switch View(m.activeTab) { case ViewContainers: m.containers, cmd = m.containers.Update(msg) case ViewImages: m.images, cmd = m.images.Update(msg) case ViewVolumes: m.volumes, cmd = m.volumes.Update(msg) case ViewNetworks: m.networks, cmd = m.networks.Update(msg) case ViewCompose: m.compose, cmd = m.compose.Update(msg) } return m, cmd } func (m Model) View() string { if !m.ready { return "Loading..." } // Tab bar tabs := m.renderTabs() // Active view content var content string switch View(m.activeTab) { case ViewContainers: content = m.containers.View() case ViewImages: content = m.images.View() case ViewVolumes: content = m.volumes.View() case ViewNetworks: content = m.networks.View() case ViewCompose: content = m.compose.View() } // Status bar status := m.Styles.StatusBar.Render(" Tab: switch view │ Ctrl+C: quit │ Enter: action │ r: refresh") return lipgloss.JoinVertical(lipgloss.Left, tabs, "", content, "", status, ) } func (m Model) renderTabs() string { var tabs []string for i, name := range tabNames { if i == m.activeTab { tabs = append(tabs, m.Styles.Selected.Render(" "+name+" ")) } else { tabs = append(tabs, m.Styles.Muted.Render(" "+name+" ")) } } row := lipgloss.JoinHorizontal(lipgloss.Top, tabs...) return m.Styles.Header.Render("Docker TUI") + " " + row } // handleBack asks the active view to go back one level. // Returns the updated model and true if the view was already at base level (app should quit). func (m Model) handleBack() (Model, bool) { switch View(m.activeTab) { case ViewContainers: atBase := m.containers.HandleBack() return m, atBase case ViewImages: atBase := m.images.HandleBack() return m, atBase case ViewVolumes: atBase := m.volumes.HandleBack() return m, atBase case ViewNetworks: atBase := m.networks.HandleBack() return m, atBase case ViewCompose: atBase := m.compose.HandleBack() return m, atBase } return m, true } func (m Model) initActiveView() tea.Cmd { switch View(m.activeTab) { case ViewContainers: return m.containers.Init() case ViewImages: return m.images.Init() case ViewVolumes: return m.volumes.Init() case ViewNetworks: return m.networks.Init() case ViewCompose: return m.compose.Init() } return nil }