From a4b4fee8680ac4988b0c7c442ab1207ef63f88df Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Thu, 20 Oct 2022 10:27:42 -0700 Subject: [PATCH] convert volumes panel to use new struct --- pkg/commands/docker.go | 1 - pkg/commands/volume.go | 30 +------ pkg/gui/gui.go | 13 +-- pkg/gui/images_panel.go | 1 - pkg/gui/keybindings.go | 6 +- pkg/gui/layout.go | 3 +- pkg/gui/main_panel.go | 4 +- pkg/gui/view_helpers.go | 2 +- pkg/gui/volumes_panel.go | 180 +++++++++++---------------------------- 9 files changed, 62 insertions(+), 178 deletions(-) diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index 2e08c675..347e3edc 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -44,7 +44,6 @@ type DockerCommand struct { Containers []*Container // DisplayContainers is the array of containers we will display in the containers panel. If Gui.ShowAllContainers is false, this will only be those containers which aren't based on a service. This reduces clutter and duplication in the UI DisplayContainers []*Container - Volumes []*Volume Closers []io.Closer } diff --git a/pkg/commands/volume.go b/pkg/commands/volume.go index 489bef0d..ac12bcb2 100644 --- a/pkg/commands/volume.go +++ b/pkg/commands/volume.go @@ -2,13 +2,10 @@ package commands import ( "context" - "sort" - "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" - "github.com/samber/lo" "github.com/sirupsen/logrus" ) @@ -28,29 +25,16 @@ func (v *Volume) GetDisplayStrings(isFocused bool) []string { } // RefreshVolumes gets the volumes and stores them -func (c *DockerCommand) RefreshVolumes() error { +func (c *DockerCommand) RefreshVolumes() ([]*Volume, error) { result, err := c.Client.VolumeList(context.Background(), filters.Args{}) if err != nil { - return err + return nil, err } volumes := result.Volumes ownVolumes := make([]*Volume, len(volumes)) - // we're sorting these volumes based on whether they have labels defined, - // because those are the ones you typically care about. - // Within that, we also sort them alphabetically - sort.Slice(volumes, func(i, j int) bool { - if len(volumes[i].Labels) == 0 && len(volumes[j].Labels) > 0 { - return false - } - if len(volumes[i].Labels) > 0 && len(volumes[j].Labels) == 0 { - return true - } - return volumes[i].Name < volumes[j].Name - }) - for i, volume := range volumes { ownVolumes[i] = &Volume{ Name: volume.Name, @@ -62,15 +46,7 @@ func (c *DockerCommand) RefreshVolumes() error { } } - ownVolumes = lo.Filter(ownVolumes, func(volume *Volume, _ int) bool { - return !lo.SomeBy(c.Config.UserConfig.Ignore, func(ignore string) bool { - return strings.Contains(volume.Name, ignore) - }) - }) - - c.Volumes = ownVolumes - - return nil + return ownVolumes, nil } // PruneVolumes prunes volumes diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 2d4b8409..494e3447 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -26,7 +26,6 @@ var OverlappingEdges = false // by calling functions. The less of these, the better type SentinelErrors struct { ErrNoContainers error - ErrNoImages error ErrNoVolumes error } @@ -43,7 +42,6 @@ type SentinelErrors struct { func (gui *Gui) GenerateSentinelErrors() { gui.Errors = SentinelErrors{ ErrNoContainers: errors.New(gui.Tr.NoContainers), - ErrNoImages: errors.New(gui.Tr.NoImages), ErrNoVolumes: errors.New(gui.Tr.NoVolumes), } } @@ -77,6 +75,7 @@ type Gui struct { type Panels struct { Images *SideListPanel[*commands.Image] Services *SideListPanel[*commands.Service] + Volumes *SideListPanel[*commands.Volume] } type Mutexes struct { @@ -103,16 +102,10 @@ type mainPanelState struct { ObjectKey string } -type volumePanelState struct { - SelectedLine int - ContextIndex int -} - type panelStates struct { Containers *containerPanelState Menu *menuPanelState Main *mainPanelState - Volumes *volumePanelState Project *projectState } @@ -164,7 +157,6 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand Platform: *oSCommand.Platform, Panels: &panelStates{ Containers: &containerPanelState{SelectedLine: -1, ContextIndex: 0}, - Volumes: &volumePanelState{SelectedLine: -1, ContextIndex: 0}, Menu: &menuPanelState{SelectedLine: 0}, Main: &mainPanelState{ ObjectKey: "", @@ -284,6 +276,7 @@ func (gui *Gui) Run() error { gui.Panels = Panels{ Images: gui.getImagesPanel(), Services: gui.getServicesPanel(), + Volumes: gui.getVolumesPanel(), } if err = gui.keybindings(g); err != nil { @@ -332,7 +325,7 @@ func (gui *Gui) refresh() { } }() go func() { - if err := gui.refreshVolumes(); err != nil { + if err := gui.reloadVolumes(); err != nil { gui.Log.Error(err) } }() diff --git a/pkg/gui/images_panel.go b/pkg/gui/images_panel.go index 84e579d4..490847a9 100644 --- a/pkg/gui/images_panel.go +++ b/pkg/gui/images_panel.go @@ -93,7 +93,6 @@ func (gui *Gui) refreshStateImages() error { return err } - // TODO: think about also re-filtering/sorting gui.Panels.Images.SetItems(images) return nil diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index ad22fa17..b1a40fdd 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -454,14 +454,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { ViewName: "volumes", Key: '[', Modifier: gocui.ModNone, - Handler: gui.handleVolumesPrevContext, + Handler: wrappedHandler(gui.Panels.Volumes.OnPrevContext), Description: gui.Tr.PreviousContext, }, { ViewName: "volumes", Key: ']', Modifier: gocui.ModNone, - Handler: gui.handleVolumesNextContext, + Handler: wrappedHandler(gui.Panels.Volumes.OnNextContext), Description: gui.Tr.NextContext, }, { @@ -587,7 +587,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { "services": {onKeyUpPress: wrappedHandler(gui.Panels.Services.OnPrevLine), onKeyDownPress: wrappedHandler(gui.Panels.Services.OnNextLine), onClick: wrappedHandler(gui.Panels.Services.OnClick)}, "containers": {onKeyUpPress: gui.handleContainersPrevLine, onKeyDownPress: gui.handleContainersNextLine, onClick: gui.handleContainersClick}, "images": {onKeyUpPress: wrappedHandler(gui.Panels.Images.OnPrevLine), onKeyDownPress: wrappedHandler(gui.Panels.Images.OnNextLine), onClick: wrappedHandler(gui.Panels.Images.OnClick)}, - "volumes": {onKeyUpPress: gui.handleVolumesPrevLine, onKeyDownPress: gui.handleVolumesNextLine, onClick: gui.handleVolumesClick}, + "volumes": {onKeyUpPress: wrappedHandler(gui.Panels.Volumes.OnPrevLine), onKeyDownPress: wrappedHandler(gui.Panels.Volumes.OnNextLine), onClick: wrappedHandler(gui.Panels.Volumes.OnClick)}, "main": {onKeyUpPress: gui.scrollUpMain, onKeyDownPress: gui.scrollDownMain, onClick: gui.handleMainClick}, } diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index ec7e81fc..a6635706 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -127,7 +127,6 @@ func (gui *Gui) focusPointInView(view *gocui.View) { listViews := map[string]listViewState{ "containers": {selectedLine: gui.State.Panels.Containers.SelectedLine, lineCount: len(gui.DockerCommand.DisplayContainers)}, - "volumes": {selectedLine: gui.State.Panels.Volumes.SelectedLine, lineCount: len(gui.DockerCommand.Volumes)}, "menu": {selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount}, } @@ -140,6 +139,8 @@ func (gui *Gui) focusPointInView(view *gocui.View) { gui.Panels.Images.Refocus() case "services": gui.Panels.Services.Refocus() + case "volumes": + gui.Panels.Volumes.Refocus() } } diff --git a/pkg/gui/main_panel.go b/pkg/gui/main_panel.go index 22834fd7..50eec7f2 100644 --- a/pkg/gui/main_panel.go +++ b/pkg/gui/main_panel.go @@ -97,8 +97,8 @@ func (gui *Gui) onMainTabClick(tabIndex int) error { gui.Panels.Images.SetContextIndex(tabIndex) return gui.Panels.Images.HandleSelect() case "volumes": - gui.State.Panels.Volumes.ContextIndex = tabIndex - return gui.handleVolumeSelect(gui.g, gui.getVolumesView()) + gui.Panels.Volumes.SetContextIndex(tabIndex) + return gui.Panels.Volumes.HandleSelect() } return nil diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 5e5204d7..2154ebd3 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -83,7 +83,7 @@ func (gui *Gui) newLineFocused(v *gocui.View) error { case "images": return gui.Panels.Images.HandleSelect() case "volumes": - return gui.handleVolumeSelect(gui.g, v) + return gui.Panels.Volumes.HandleSelect() case "confirmation": return nil case "main": diff --git a/pkg/gui/volumes_panel.go b/pkg/gui/volumes_panel.go index 405eb57b..6f52b98b 100644 --- a/pkg/gui/volumes_panel.go +++ b/pkg/gui/volumes_panel.go @@ -4,74 +4,54 @@ import ( "fmt" "github.com/fatih/color" - "github.com/go-errors/errors" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/config" "github.com/jesseduffield/lazydocker/pkg/utils" ) -// list panel functions - -func (gui *Gui) getVolumeContexts() []string { - return []string{"config"} -} - -func (gui *Gui) getVolumeContextTitles() []string { - return []string{gui.Tr.ConfigTitle} -} - -func (gui *Gui) getSelectedVolume() (*commands.Volume, error) { - selectedLine := gui.State.Panels.Volumes.SelectedLine - if selectedLine == -1 { - return nil, gui.Errors.ErrNoVolumes +func (gui *Gui) getVolumesPanel() *SideListPanel[*commands.Volume] { + return &SideListPanel[*commands.Volume]{ + contextKeyPrefix: "volumes", + ListPanel: ListPanel[*commands.Volume]{ + list: NewFilteredList[*commands.Volume](), + view: gui.Views.Volumes, + }, + contextIdx: 0, + noItemsMessge: gui.Tr.NoVolumes, + gui: gui.intoInterface(), + contexts: []ContextConfig[*commands.Volume]{ + { + key: "config", + title: gui.Tr.ConfigTitle, + render: gui.renderVolumeConfig, + }, + }, + getSearchStrings: func(volume *commands.Volume) []string { + // TODO: think about more things to search on + return []string{volume.Name} + }, + getContextCacheKey: func(volume *commands.Volume) string { + return volume.Name + }, + // we're sorting these volumes based on whether they have labels defined, + // because those are the ones you typically care about. + // Within that, we also sort them alphabetically + sort: func(a *commands.Volume, b *commands.Volume) bool { + if len(a.Volume.Labels) == 0 && len(b.Volume.Labels) > 0 { + return false + } + if len(a.Volume.Labels) > 0 && len(b.Volume.Labels) == 0 { + return true + } + return a.Name < b.Name + }, } - - return gui.DockerCommand.Volumes[selectedLine], nil } -func (gui *Gui) handleVolumesClick(g *gocui.Gui, v *gocui.View) error { - itemCount := len(gui.DockerCommand.Volumes) - handleSelect := gui.handleVolumeSelect - selectedLine := &gui.State.Panels.Volumes.SelectedLine - - return gui.handleClick(v, itemCount, selectedLine, handleSelect) -} - -func (gui *Gui) handleVolumeSelect(g *gocui.Gui, v *gocui.View) error { - volume, err := gui.getSelectedVolume() - if err != nil { - if err != gui.Errors.ErrNoVolumes { - return err - } - return gui.renderStringMain(gui.Tr.NoVolumes) - } - - gui.focusY(gui.State.Panels.Volumes.SelectedLine, len(gui.DockerCommand.Volumes), v) - - key := "volumes-" + volume.Name + "-" + gui.getVolumeContexts()[gui.State.Panels.Volumes.ContextIndex] - if !gui.shouldRefresh(key) { - return nil - } - - mainView := gui.getMainView() - mainView.Tabs = gui.getVolumeContextTitles() - mainView.TabIndex = gui.State.Panels.Volumes.ContextIndex - - switch gui.getVolumeContexts()[gui.State.Panels.Volumes.ContextIndex] { - case "config": - if err := gui.renderVolumeConfig(mainView, volume); err != nil { - return err - } - default: - return errors.New("Unknown context for Volumes panel") - } - - return nil -} - -func (gui *Gui) renderVolumeConfig(mainView *gocui.View, volume *commands.Volume) error { +func (gui *Gui) renderVolumeConfig(volume *commands.Volume) error { return gui.T.NewTask(func(stop chan struct{}) { + mainView := gui.Views.Main mainView.Autoscroll = false mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel @@ -103,85 +83,21 @@ func (gui *Gui) renderVolumeConfig(mainView *gocui.View, volume *commands.Volume }) } -func (gui *Gui) refreshVolumes() error { - volumesView := gui.getVolumesView() - if volumesView == nil { - // if the volumesView hasn't been instantiated yet we just return - return nil - } - if err := gui.DockerCommand.RefreshVolumes(); err != nil { +func (gui *Gui) reloadVolumes() error { + if err := gui.refreshStateVolumes(); err != nil { return err } - if len(gui.DockerCommand.Volumes) > 0 && gui.State.Panels.Volumes.SelectedLine == -1 { - gui.State.Panels.Volumes.SelectedLine = 0 - } - if len(gui.DockerCommand.Volumes)-1 < gui.State.Panels.Volumes.SelectedLine { - gui.State.Panels.Volumes.SelectedLine = len(gui.DockerCommand.Volumes) - 1 - } - - gui.g.Update(func(g *gocui.Gui) error { - volumesView.Clear() - isFocused := gui.g.CurrentView().Name() == "volumes" - list, err := utils.RenderList(gui.DockerCommand.Volumes, utils.IsFocused(isFocused)) - if err != nil { - return err - } - fmt.Fprint(volumesView, list) - - if volumesView == g.CurrentView() { - return gui.handleVolumeSelect(g, volumesView) - } - return nil - }) - - return nil + return gui.Panels.Volumes.RerenderList() } -func (gui *Gui) handleVolumesNextLine(g *gocui.Gui, v *gocui.View) error { - if gui.popupPanelFocused() || gui.g.CurrentView() != v { - return nil +func (gui *Gui) refreshStateVolumes() error { + volumes, err := gui.DockerCommand.RefreshVolumes() + if err != nil { + return err } - panelState := gui.State.Panels.Volumes - gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Volumes), false) - - return gui.handleVolumeSelect(gui.g, v) -} - -func (gui *Gui) handleVolumesPrevLine(g *gocui.Gui, v *gocui.View) error { - if gui.popupPanelFocused() || gui.g.CurrentView() != v { - return nil - } - - panelState := gui.State.Panels.Volumes - gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Volumes), true) - - return gui.handleVolumeSelect(gui.g, v) -} - -func (gui *Gui) handleVolumesNextContext(g *gocui.Gui, v *gocui.View) error { - contexts := gui.getVolumeContexts() - if gui.State.Panels.Volumes.ContextIndex >= len(contexts)-1 { - gui.State.Panels.Volumes.ContextIndex = 0 - } else { - gui.State.Panels.Volumes.ContextIndex++ - } - - _ = gui.handleVolumeSelect(gui.g, v) - - return nil -} - -func (gui *Gui) handleVolumesPrevContext(g *gocui.Gui, v *gocui.View) error { - contexts := gui.getVolumeContexts() - if gui.State.Panels.Volumes.ContextIndex <= 0 { - gui.State.Panels.Volumes.ContextIndex = len(contexts) - 1 - } else { - gui.State.Panels.Volumes.ContextIndex-- - } - - _ = gui.handleVolumeSelect(gui.g, v) + gui.Panels.Volumes.SetItems(volumes) return nil } @@ -199,7 +115,7 @@ func (r *removeVolumeOption) GetDisplayStrings(isFocused bool) []string { } func (gui *Gui) handleVolumesRemoveMenu(g *gocui.Gui, v *gocui.View) error { - volume, err := gui.getSelectedVolume() + volume, err := gui.Panels.Volumes.GetSelectedItem() if err != nil { return nil } @@ -251,7 +167,7 @@ func (gui *Gui) handlePruneVolumes() error { } func (gui *Gui) handleVolumesCustomCommand(g *gocui.Gui, v *gocui.View) error { - volume, err := gui.getSelectedVolume() + volume, err := gui.Panels.Volumes.GetSelectedItem() if err != nil { return nil }