convert project panel to use new struct

This commit is contained in:
Jesse Duffield 2022-10-22 11:14:28 -07:00
parent 5aea90c08b
commit 158ef68372
11 changed files with 221 additions and 217 deletions

9
pkg/commands/project.go Normal file
View file

@ -0,0 +1,9 @@
package commands
type Project struct {
Name string
}
func (self *Project) GetDisplayStrings(isFocused bool) []string {
return []string{self.Name}
}

View file

@ -42,7 +42,8 @@ func (gui *Gui) getContainersPanel() *SideListPanel[*commands.Container] {
contextIdx: 0, contextIdx: 0,
noItemsMessge: gui.Tr.NoContainers, noItemsMessge: gui.Tr.NoContainers,
gui: gui.intoInterface(), gui: gui.intoInterface(),
contexts: []ContextConfig[*commands.Container]{ getContexts: func() []ContextConfig[*commands.Container] {
return []ContextConfig[*commands.Container]{
{ {
key: "logs", key: "logs",
title: gui.Tr.LogsTitle, title: gui.Tr.LogsTitle,
@ -68,6 +69,7 @@ func (gui *Gui) getContainersPanel() *SideListPanel[*commands.Container] {
title: gui.Tr.TopTitle, title: gui.Tr.TopTitle,
render: gui.renderContainerTop, render: gui.renderContainerTop,
}, },
}
}, },
getSearchStrings: func(container *commands.Container) []string { getSearchStrings: func(container *commands.Container) []string {
// TODO: think about more things to search on // TODO: think about more things to search on

View file

@ -48,6 +48,7 @@ type Gui struct {
} }
type Panels struct { type Panels struct {
Projects *SideListPanel[*commands.Project]
Services *SideListPanel[*commands.Service] Services *SideListPanel[*commands.Service]
Containers *SideListPanel[*commands.Container] Containers *SideListPanel[*commands.Container]
Images *SideListPanel[*commands.Image] Images *SideListPanel[*commands.Image]
@ -239,6 +240,7 @@ func (gui *Gui) Run() error {
// TODO: see if we can avoid the circular dependency // TODO: see if we can avoid the circular dependency
gui.Panels = Panels{ gui.Panels = Panels{
Projects: gui.getProjectPanel(),
Services: gui.getServicesPanel(), Services: gui.getServicesPanel(),
Containers: gui.getContainersPanel(), Containers: gui.getContainersPanel(),
Images: gui.getImagesPanel(), Images: gui.getImagesPanel(),
@ -294,7 +296,11 @@ func (gui *Gui) rerenderContainersAndServices() error {
} }
func (gui *Gui) refresh() { func (gui *Gui) refresh() {
go gui.refreshProject() go func() {
if err := gui.refreshProject(); err != nil {
gui.Log.Error(err)
}
}()
go func() { go func() {
if err := gui.refreshContainersAndServices(); err != nil { if err := gui.refreshContainersAndServices(); err != nil {
gui.Log.Error(err) gui.Log.Error(err)

View file

@ -25,7 +25,8 @@ func (gui *Gui) getImagesPanel() *SideListPanel[*commands.Image] {
contextIdx: 0, contextIdx: 0,
noItemsMessge: gui.Tr.NoImages, noItemsMessge: gui.Tr.NoImages,
gui: gui.intoInterface(), gui: gui.intoInterface(),
contexts: []ContextConfig[*commands.Image]{ getContexts: func() []ContextConfig[*commands.Image] {
return []ContextConfig[*commands.Image]{
{ {
key: "config", key: "config",
title: gui.Tr.ConfigTitle, title: gui.Tr.ConfigTitle,
@ -33,6 +34,7 @@ func (gui *Gui) getImagesPanel() *SideListPanel[*commands.Image] {
return gui.renderImageConfig(image) return gui.renderImageConfig(image)
}, },
}, },
}
}, },
getSearchStrings: func(image *commands.Image) []string { getSearchStrings: func(image *commands.Image) []string {
return []string{image.Name, image.Tag} return []string{image.Name, image.Tag}

View file

@ -151,22 +151,16 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "project", ViewName: "project",
Key: '[', Key: '[',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleProjectPrevContext, Handler: wrappedHandler(gui.Panels.Projects.OnPrevContext),
Description: gui.Tr.PreviousContext, Description: gui.Tr.PreviousContext,
}, },
{ {
ViewName: "project", ViewName: "project",
Key: ']', Key: ']',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleProjectNextContext, Handler: wrappedHandler(gui.Panels.Projects.OnNextContext),
Description: gui.Tr.NextContext, Description: gui.Tr.NextContext,
}, },
{
ViewName: "project",
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
Handler: gui.handleProjectClick,
},
{ {
ViewName: "project", ViewName: "project",
Key: 'm', Key: 'm',
@ -178,7 +172,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "project", ViewName: "project",
Key: gocui.MouseLeft, Key: gocui.MouseLeft,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleProjectSelect, Handler: wrappedHandler(gui.Panels.Projects.OnClick),
}, },
{ {
ViewName: "menu", ViewName: "menu",
@ -603,9 +597,9 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
}...) }...)
} }
for _, viewName := range []string{"project", "services", "containers", "images", "volumes"} { for _, sidePanel := range gui.allSidepanels() {
bindings = append(bindings, &Binding{ bindings = append(bindings, &Binding{
ViewName: viewName, ViewName: sidePanel.View().Name(),
Key: gocui.KeyEnter, Key: gocui.KeyEnter,
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handleEnterMain, Handler: gui.handleEnterMain,

View file

@ -49,28 +49,34 @@ func (self *ListPanel[T]) SelectPrevLine() {
// list panel at the side of the screen that renders content to the main panel // list panel at the side of the screen that renders content to the main panel
type SideListPanel[T comparable] struct { type SideListPanel[T comparable] struct {
contextKeyPrefix string contextKeyPrefix string
contextIdx int
// contexts []ContextConfig[T]
getContexts func() []ContextConfig[T]
// this tells us whether we need to re-render to the main panel
getContextCacheKey func(item T) string
ListPanel[T] ListPanel[T]
contextIdx int
noItemsMessge string noItemsMessge string
gui IGui gui IGui
contexts []ContextConfig[T]
// returns strings that can be filtered on // returns strings that can be filtered on
getSearchStrings func(item T) []string getSearchStrings func(item T) []string
// this tells us whether we need to re-render to the main panel
getContextCacheKey func(item T) string
sort func(a, b T) bool
// this filter is applied on top of additional default filters // this filter is applied on top of additional default filters
filter func(T) bool filter func(T) bool
sort func(a, b T) bool
} }
type ISideListPanel interface {
SetContextIndex(int)
HandleSelect() error
View() *gocui.View
}
var _ ISideListPanel = &SideListPanel[int]{}
type ContextConfig[T any] struct { type ContextConfig[T any] struct {
key string key string
title string title string
@ -103,6 +109,10 @@ func (self *SideListPanel[T]) OnClick() error {
return self.gui.HandleClick(self.view, itemCount, selectedLine, handleSelect) return self.gui.HandleClick(self.view, itemCount, selectedLine, handleSelect)
} }
func (self *SideListPanel[T]) View() *gocui.View {
return self.view
}
func (self *SideListPanel[T]) HandleSelect() error { func (self *SideListPanel[T]) HandleSelect() error {
item, err := self.GetSelectedItem() item, err := self.GetSelectedItem()
if err != nil { if err != nil {
@ -115,7 +125,9 @@ func (self *SideListPanel[T]) HandleSelect() error {
self.Refocus() self.Refocus()
key := self.contextKeyPrefix + "-" + self.getContextCacheKey(item) + "-" + self.contexts[self.contextIdx].key contexts := self.getContexts()
key := self.contextKeyPrefix + "-" + self.getContextCacheKey(item) + "-" + contexts[self.contextIdx].key
if !self.gui.ShouldRefresh(key) { if !self.gui.ShouldRefresh(key) {
return nil return nil
} }
@ -125,11 +137,11 @@ func (self *SideListPanel[T]) HandleSelect() error {
mainView.TabIndex = self.contextIdx mainView.TabIndex = self.contextIdx
// now I have an item. What do I do with it? // now I have an item. What do I do with it?
return self.contexts[self.contextIdx].render(item) return contexts[self.contextIdx].render(item)
} }
func (self *SideListPanel[T]) GetContextTitles() []string { func (self *SideListPanel[T]) GetContextTitles() []string {
return lo.Map(self.contexts, func(context ContextConfig[T], _ int) string { return lo.Map(self.getContexts(), func(context ContextConfig[T], _ int) string {
return context.title return context.title
}) })
} }
@ -171,13 +183,25 @@ func (self *SideListPanel[T]) ignoreKeypress() bool {
} }
func (self *SideListPanel[T]) OnNextContext() error { func (self *SideListPanel[T]) OnNextContext() error {
self.contextIdx = (self.contextIdx + 1) % len(self.contexts) contexts := self.getContexts()
if len(contexts) == 0 {
return nil
}
self.contextIdx = (self.contextIdx + 1) % len(contexts)
return self.HandleSelect() return self.HandleSelect()
} }
func (self *SideListPanel[T]) OnPrevContext() error { func (self *SideListPanel[T]) OnPrevContext() error {
self.contextIdx = (self.contextIdx - 1 + len(self.contexts)) % len(self.contexts) contexts := self.getContexts()
if len(contexts) == 0 {
return nil
}
self.contextIdx = (self.contextIdx - 1 + len(contexts)) % len(contexts)
return self.HandleSelect() return self.HandleSelect()
} }

View file

@ -83,25 +83,14 @@ func (gui *Gui) onMainTabClick(tabIndex int) error {
viewName = mainView.ParentView.Name() viewName = mainView.ParentView.Name()
} }
switch viewName { currentSidePanel, ok := gui.currentSidePanel()
case "project":
gui.State.Panels.Project.ContextIndex = tabIndex if !ok {
return gui.handleProjectSelect(gui.g, gui.getProjectView()) return nil
case "services":
gui.Panels.Services.SetContextIndex(tabIndex)
return gui.Panels.Services.HandleSelect()
case "containers":
gui.Panels.Containers.SetContextIndex(tabIndex)
return gui.Panels.Containers.HandleSelect()
case "images":
gui.Panels.Images.SetContextIndex(tabIndex)
return gui.Panels.Images.HandleSelect()
case "volumes":
gui.Panels.Volumes.SetContextIndex(tabIndex)
return gui.Panels.Volumes.HandleSelect()
} }
return nil currentSidePanel.SetContextIndex(tabIndex)
return currentSidePanel.HandleSelect()
} }
func (gui *Gui) handleEnterMain(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleEnterMain(g *gocui.Gui, v *gocui.View) error {

View file

@ -2,42 +2,73 @@ package gui
import ( import (
"bytes" "bytes"
"fmt"
"path" "path"
"strings" "strings"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/go-errors/errors"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/jesseduffield/yaml" "github.com/jesseduffield/yaml"
) )
func (gui *Gui) getProjectContexts() []string { // Although at the moment we'll only have one project, in future we could have
// a list of projects in the project panel.
func (gui *Gui) getProjectPanel() *SideListPanel[*commands.Project] {
return &SideListPanel[*commands.Project]{
contextKeyPrefix: "project",
ListPanel: ListPanel[*commands.Project]{
list: NewFilteredList[*commands.Project](),
view: gui.Views.Project,
},
contextIdx: 0,
noItemsMessge: "no projects", // we don't expect to ever actually see this
gui: gui.intoInterface(),
getContexts: func() []ContextConfig[*commands.Project] {
if gui.DockerCommand.InDockerComposeProject { if gui.DockerCommand.InDockerComposeProject {
return []string{"logs", "config", "credits"} return []ContextConfig[*commands.Project]{
{
key: "logs",
title: gui.Tr.LogsTitle,
render: gui.renderAllLogs,
},
{
key: "config",
title: gui.Tr.DockerComposeConfigTitle,
render: gui.renderDockerComposeConfig,
},
{
key: "credits",
title: gui.Tr.CreditsTitle,
render: gui.renderCredits,
},
} }
return []string{"credits"}
} }
func (gui *Gui) getProjectContextTitles() []string { return []ContextConfig[*commands.Project]{
if gui.DockerCommand.InDockerComposeProject { {
return []string{gui.Tr.LogsTitle, gui.Tr.DockerComposeConfigTitle, gui.Tr.CreditsTitle} key: "credits",
title: gui.Tr.CreditsTitle,
render: gui.renderCredits,
},
}
},
getSearchStrings: func(project *commands.Project) []string {
return []string{project.Name}
},
getContextCacheKey: func(project *commands.Project) string {
return project.Name
},
sort: func(a *commands.Project, b *commands.Project) bool {
return false
},
} }
return []string{gui.Tr.CreditsTitle}
} }
func (gui *Gui) refreshProject() { func (gui *Gui) refreshProject() error {
v := gui.getProjectView() gui.Panels.Projects.SetItems([]*commands.Project{{Name: gui.getProjectName()}})
return gui.Panels.Projects.RerenderList()
projectName := gui.getProjectName()
gui.g.Update(func(*gocui.Gui) error {
v.Clear()
fmt.Fprint(v, projectName)
return nil
})
} }
func (gui *Gui) getProjectName() string { func (gui *Gui) getProjectName() string {
@ -54,55 +85,7 @@ func (gui *Gui) getProjectName() string {
return projectName return projectName
} }
func (gui *Gui) handleProjectClick(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) renderCredits(_project *commands.Project) error {
if gui.popupPanelFocused() {
return nil
}
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
return gui.handleProjectSelect(g, v)
}
func (gui *Gui) handleProjectSelect(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
return nil
}
key := gui.getProjectContexts()[gui.State.Panels.Project.ContextIndex]
if !gui.shouldRefresh(key) {
return nil
}
gui.clearMainView()
mainView := gui.getMainView()
mainView.Tabs = gui.getProjectContextTitles()
mainView.TabIndex = gui.State.Panels.Project.ContextIndex
switch gui.getProjectContexts()[gui.State.Panels.Project.ContextIndex] {
case "credits":
if err := gui.renderCredits(); err != nil {
return err
}
case "logs":
if err := gui.renderAllLogs(); err != nil {
return err
}
case "config":
if err := gui.renderDockerComposeConfig(); err != nil {
return err
}
default:
return errors.New("Unknown context for status panel")
}
return nil
}
func (gui *Gui) renderCredits() error {
return gui.T.NewTask(func(stop chan struct{}) { return gui.T.NewTask(func(stop chan struct{}) {
mainView := gui.getMainView() mainView := gui.getMainView()
mainView.Autoscroll = false mainView.Autoscroll = false
@ -127,7 +110,7 @@ func (gui *Gui) renderCredits() error {
}) })
} }
func (gui *Gui) renderAllLogs() error { func (gui *Gui) renderAllLogs(_project *commands.Project) error {
return gui.T.NewTask(func(stop chan struct{}) { return gui.T.NewTask(func(stop chan struct{}) {
mainView := gui.getMainView() mainView := gui.getMainView()
mainView.Autoscroll = true mainView.Autoscroll = true
@ -159,7 +142,7 @@ func (gui *Gui) renderAllLogs() error {
}) })
} }
func (gui *Gui) renderDockerComposeConfig() error { func (gui *Gui) renderDockerComposeConfig(_project *commands.Project) error {
return gui.T.NewTask(func(stop chan struct{}) { return gui.T.NewTask(func(stop chan struct{}) {
mainView := gui.getMainView() mainView := gui.getMainView()
mainView.Autoscroll = false mainView.Autoscroll = false
@ -191,32 +174,6 @@ func lazydockerTitle() string {
` `
} }
func (gui *Gui) handleProjectNextContext(g *gocui.Gui, v *gocui.View) error {
contexts := gui.getProjectContexts()
if gui.State.Panels.Project.ContextIndex >= len(contexts)-1 {
gui.State.Panels.Project.ContextIndex = 0
} else {
gui.State.Panels.Project.ContextIndex++
}
_ = gui.handleProjectSelect(gui.g, v)
return nil
}
func (gui *Gui) handleProjectPrevContext(g *gocui.Gui, v *gocui.View) error {
contexts := gui.getProjectContexts()
if gui.State.Panels.Project.ContextIndex <= 0 {
gui.State.Panels.Project.ContextIndex = len(contexts) - 1
} else {
gui.State.Panels.Project.ContextIndex--
}
_ = gui.handleProjectSelect(gui.g, v)
return nil
}
// handleViewAllLogs switches to a subprocess viewing all the logs from docker-compose // handleViewAllLogs switches to a subprocess viewing all the logs from docker-compose
func (gui *Gui) handleViewAllLogs(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleViewAllLogs(g *gocui.Gui, v *gocui.View) error {
c, err := gui.DockerCommand.ViewAllLogs() c, err := gui.DockerCommand.ViewAllLogs()

View file

@ -22,7 +22,8 @@ func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] {
// TODO: i18n // TODO: i18n
noItemsMessge: "no service selected", noItemsMessge: "no service selected",
gui: gui.intoInterface(), gui: gui.intoInterface(),
contexts: []ContextConfig[*commands.Service]{ getContexts: func() []ContextConfig[*commands.Service] {
return []ContextConfig[*commands.Service]{
{ {
key: "logs", key: "logs",
title: gui.Tr.LogsTitle, title: gui.Tr.LogsTitle,
@ -48,6 +49,7 @@ func (gui *Gui) getServicesPanel() *SideListPanel[*commands.Service] {
title: gui.Tr.TopTitle, title: gui.Tr.TopTitle,
render: gui.renderServiceTop, render: gui.renderServiceTop,
}, },
}
}, },
getSearchStrings: func(service *commands.Service) []string { getSearchStrings: func(service *commands.Service) []string {
// TODO: think about more things to search on // TODO: think about more things to search on

View file

@ -71,19 +71,14 @@ func (gui *Gui) newLineFocused(v *gocui.View) error {
return nil return nil
} }
currentSidePanel, ok := gui.currentSidePanel()
if ok {
return currentSidePanel.HandleSelect()
}
switch v.Name() { switch v.Name() {
case "menu": case "menu":
return gui.handleMenuSelect(gui.g, v) return gui.handleMenuSelect(gui.g, v)
case "project":
return gui.handleProjectSelect(gui.g, v)
case "services":
return gui.Panels.Services.HandleSelect()
case "containers":
return gui.Panels.Containers.HandleSelect()
case "images":
return gui.Panels.Images.HandleSelect()
case "volumes":
return gui.Panels.Volumes.HandleSelect()
case "confirmation": case "confirmation":
return nil return nil
case "main": case "main":
@ -501,3 +496,25 @@ func prevIntInCycle(sl []WindowMaximisation, current WindowMaximisation) WindowM
func (gui *Gui) CurrentView() *gocui.View { func (gui *Gui) CurrentView() *gocui.View {
return gui.g.CurrentView() return gui.g.CurrentView()
} }
func (gui *Gui) currentSidePanel() (ISideListPanel, bool) {
viewName := gui.currentViewName()
for _, sidePanel := range gui.allSidepanels() {
if sidePanel.View().Name() == viewName {
return sidePanel, true
}
}
return nil, false
}
func (gui *Gui) allSidepanels() []ISideListPanel {
return []ISideListPanel{
gui.Panels.Projects,
gui.Panels.Services,
gui.Panels.Containers,
gui.Panels.Images,
gui.Panels.Volumes,
}
}

View file

@ -20,12 +20,14 @@ func (gui *Gui) getVolumesPanel() *SideListPanel[*commands.Volume] {
contextIdx: 0, contextIdx: 0,
noItemsMessge: gui.Tr.NoVolumes, noItemsMessge: gui.Tr.NoVolumes,
gui: gui.intoInterface(), gui: gui.intoInterface(),
contexts: []ContextConfig[*commands.Volume]{ getContexts: func() []ContextConfig[*commands.Volume] {
return []ContextConfig[*commands.Volume]{
{ {
key: "config", key: "config",
title: gui.Tr.ConfigTitle, title: gui.Tr.ConfigTitle,
render: gui.renderVolumeConfig, render: gui.renderVolumeConfig,
}, },
}
}, },
getSearchStrings: func(volume *commands.Volume) []string { getSearchStrings: func(volume *commands.Volume) []string {
// TODO: think about more things to search on // TODO: think about more things to search on