convert images panel to use new struct

pull/392/head
Jesse Duffield 2 years ago
parent e46b908006
commit b80c8edcd0

@ -59,14 +59,10 @@ type Gui struct {
Tr *i18n.TranslationSet
Errors SentinelErrors
statusManager *statusManager
waitForIntro sync.WaitGroup
T *tasks.TaskManager
ErrorChan chan error
CyclableViews []string
Views Views
// returns true if our views have been created and assigned to gui.Views.
// Views are setup only once, upon application start.
ViewsSetup bool
// if we've suspended the gui (e.g. because we've switched to a subprocess)
// we typically want to pause some things that are running like background
@ -111,11 +107,6 @@ type mainPanelState struct {
ObjectKey string
}
type imagePanelState struct {
SelectedLine int
ContextIndex int // for specifying if you are looking at logs/stats/config/etc
}
type volumePanelState struct {
SelectedLine int
ContextIndex int
@ -126,7 +117,6 @@ type panelStates struct {
Containers *containerPanelState
Menu *menuPanelState
Main *mainPanelState
Images *imagePanelState
Volumes *volumePanelState
Project *projectState
}
@ -180,7 +170,6 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
Panels: &panelStates{
Services: &servicePanelState{SelectedLine: -1, ContextIndex: 0},
Containers: &containerPanelState{SelectedLine: -1, ContextIndex: 0},
Images: &imagePanelState{SelectedLine: -1, ContextIndex: 0},
Volumes: &volumePanelState{SelectedLine: -1, ContextIndex: 0},
Menu: &menuPanelState{SelectedLine: 0},
Main: &mainPanelState{
@ -216,11 +205,6 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
CyclableViews: cyclableViews,
}
// TODO: see if we can avoid the circular dependency
gui.Panels = Panels{
Images: gui.getImagePanel(),
}
gui.GenerateSentinelErrors()
return gui, nil
@ -273,8 +257,6 @@ func (gui *Gui) Run() error {
return err
}
gui.waitForIntro.Add(1)
throttledRefresh := throttle.ThrottleFunc(time.Millisecond*50, true, gui.refresh)
defer throttledRefresh.Stop()
@ -285,7 +267,6 @@ func (gui *Gui) Run() error {
go gui.DockerCommand.MonitorContainerStats(ctx)
go func() {
gui.waitForIntro.Wait()
throttledRefresh.Trigger()
gui.goEvery(time.Millisecond*30, gui.reRenderMain)
@ -310,10 +291,31 @@ func (gui *Gui) Run() error {
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
if err := gui.createAllViews(); err != nil {
return err
}
// TODO: see if we can avoid the circular dependency
gui.Panels = Panels{
Images: gui.getImagePanel(),
}
if err = gui.keybindings(g); err != nil {
return err
}
if gui.g.CurrentView() == nil {
viewName := gui.initiallyFocusedViewName()
view, err := gui.g.View(viewName)
if err != nil {
return err
}
if err := gui.switchFocus(view); err != nil {
return err
}
}
err = g.MainLoop()
if err == gocui.ErrQuit {
return nil

@ -7,73 +7,12 @@ import (
"github.com/docker/docker/api/types"
"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"
"github.com/samber/lo"
)
// list panel functions
func (gui *Gui) getImageContexts() []string {
return []string{"config"}
}
func (gui *Gui) getImageContextTitles() []string {
return []string{gui.Tr.ConfigTitle}
}
func (gui *Gui) getSelectedImage() (*commands.Image, error) {
selectedLine := gui.State.Panels.Images.SelectedLine
if selectedLine == -1 {
return &commands.Image{}, gui.Errors.ErrNoImages
}
return gui.State.Lists.Images.Get(selectedLine), nil
}
func (gui *Gui) handleImagesClick(g *gocui.Gui, v *gocui.View) error {
itemCount := gui.State.Lists.Images.Len()
handleSelect := gui.handleImageSelect
selectedLine := &gui.State.Panels.Images.SelectedLine
return gui.handleClick(v, itemCount, selectedLine, handleSelect)
}
func (gui *Gui) handleImageSelect(g *gocui.Gui, v *gocui.View) error {
image, err := gui.getSelectedImage()
if err != nil {
if err != gui.Errors.ErrNoImages {
return err
}
return gui.renderStringMain(gui.Tr.NoImages)
}
gui.focusY(gui.State.Panels.Images.SelectedLine, gui.State.Lists.Images.Len(), v)
key := "images-" + image.ID + "-" + gui.getImageContexts()[gui.State.Panels.Images.ContextIndex]
if !gui.shouldRefresh(key) {
return nil
}
mainView := gui.getMainView()
mainView.Tabs = gui.getImageContextTitles()
mainView.TabIndex = gui.State.Panels.Images.ContextIndex
switch gui.getImageContexts()[gui.State.Panels.Images.ContextIndex] {
case "config":
if err := gui.renderImageConfig(image); err != nil {
return err
}
default:
return errors.New("Unknown context for Images panel")
}
return nil
}
func (gui *Gui) renderImageConfig(image *commands.Image) error {
return gui.T.NewTask(func(stop chan struct{}) {
padding := 10
@ -104,63 +43,7 @@ func (gui *Gui) reloadImages() error {
return err
}
return gui.rerenderImages()
}
func (gui *Gui) rerenderImages() error {
filterString := gui.filterString(gui.Views.Images)
gui.State.Lists.Images.Filter(func(image *commands.Image, index int) bool {
if lo.SomeBy(gui.Config.UserConfig.Ignore, func(ignore string) bool {
return strings.Contains(image.Name, ignore)
}) {
return false
}
if filterString != "" {
return strings.Contains(image.Name, filterString)
}
return true
})
noneLabel := "<none>"
gui.State.Lists.Images.Sort(func(a *commands.Image, b *commands.Image) bool {
if a.Name == noneLabel && b.Name != noneLabel {
return false
}
if a.Name != noneLabel && b.Name == noneLabel {
return true
}
return a.Name < b.Name
})
if gui.State.Lists.Images.Len() > 0 && gui.State.Panels.Images.SelectedLine == -1 {
gui.State.Panels.Images.SelectedLine = 0
}
if gui.State.Lists.Images.Len()-1 < gui.State.Panels.Images.SelectedLine {
gui.State.Panels.Images.SelectedLine = gui.State.Lists.Images.Len() - 1
}
gui.g.Update(func(g *gocui.Gui) error {
gui.Views.Images.Clear()
isFocused := gui.g.CurrentView() == gui.Views.Images
list, err := utils.RenderList(gui.State.Lists.Images.GetItems(), utils.IsFocused(isFocused))
if err != nil {
return err
}
fmt.Fprint(gui.Views.Images, list)
if gui.Views.Images == g.CurrentView() {
return gui.handleImageSelect(g, gui.Views.Images)
}
return nil
})
return nil
return gui.Panels.Images.RerenderList()
}
func (gui *Gui) refreshStateImages() error {
@ -170,7 +53,7 @@ func (gui *Gui) refreshStateImages() error {
}
// TODO: think about also re-filtering/sorting
gui.State.Lists.Images.SetItems(images)
gui.Panels.Images.list.SetItems(images)
return nil
}
@ -188,54 +71,6 @@ func (gui *Gui) FilterString(view *gocui.View) string {
return gui.filterString(view)
}
func (gui *Gui) handleImagesNextLine(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
return nil
}
panelState := gui.State.Panels.Images
gui.changeSelectedLine(&panelState.SelectedLine, gui.State.Lists.Images.Len(), false)
return gui.handleImageSelect(gui.g, v)
}
func (gui *Gui) handleImagesPrevLine(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
return nil
}
panelState := gui.State.Panels.Images
gui.changeSelectedLine(&panelState.SelectedLine, gui.State.Lists.Images.Len(), true)
return gui.handleImageSelect(gui.g, v)
}
func (gui *Gui) handleImagesNextContext(g *gocui.Gui, v *gocui.View) error {
contexts := gui.getImageContexts()
if gui.State.Panels.Images.ContextIndex >= len(contexts)-1 {
gui.State.Panels.Images.ContextIndex = 0
} else {
gui.State.Panels.Images.ContextIndex++
}
_ = gui.handleImageSelect(gui.g, v)
return nil
}
func (gui *Gui) handleImagesPrevContext(g *gocui.Gui, v *gocui.View) error {
contexts := gui.getImageContexts()
if gui.State.Panels.Images.ContextIndex <= 0 {
gui.State.Panels.Images.ContextIndex = len(contexts) - 1
} else {
gui.State.Panels.Images.ContextIndex--
}
_ = gui.handleImageSelect(gui.g, v)
return nil
}
type removeImageOption struct {
description string
command string
@ -249,12 +84,12 @@ func (r *removeImageOption) GetDisplayStrings(isFocused bool) []string {
}
func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
Image, err := gui.getSelectedImage()
image, err := gui.Panels.Images.GetSelectedItem()
if err != nil {
return nil
}
shortSha := Image.ID[7:17]
shortSha := image.ID[7:17]
// TODO: have a way of toggling in a menu instead of showing each permutation as a separate menu item
options := []*removeImageOption{
@ -293,7 +128,7 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
return nil
}
configOptions := options[index].configOptions
if cerr := Image.Remove(configOptions); cerr != nil {
if cerr := image.Remove(configOptions); cerr != nil {
return gui.createErrorPanel(cerr.Error())
}
@ -316,7 +151,7 @@ func (gui *Gui) handlePruneImages() error {
}
func (gui *Gui) handleImagesCustomCommand(g *gocui.Gui, v *gocui.View) error {
image, err := gui.getSelectedImage()
image, err := gui.Panels.Images.GetSelectedItem()
if err != nil {
return nil
}

@ -412,14 +412,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "images",
Key: '[',
Modifier: gocui.ModNone,
Handler: gui.handleImagesPrevContext,
Handler: wrappedHandler(gui.Panels.Images.OnPrevContext),
Description: gui.Tr.PreviousContext,
},
{
ViewName: "images",
Key: ']',
Modifier: gocui.ModNone,
Handler: gui.handleImagesNextContext,
Handler: wrappedHandler(gui.Panels.Images.OnNextContext),
Description: gui.Tr.NextContext,
},
{

@ -59,14 +59,6 @@ func (gui *Gui) onFocus(v *gocui.View) {
// layout is called for every screen re-render e.g. when the screen is resized
func (gui *Gui) layout(g *gocui.Gui) error {
if !gui.ViewsSetup {
if err := gui.createAllViews(); err != nil {
return err
}
gui.ViewsSetup = true
}
g.Highlight = true
width, height := g.Size()
@ -116,18 +108,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
}
if gui.g.CurrentView() == nil {
viewName := gui.initiallyFocusedViewName()
view, err := gui.g.View(viewName)
if err != nil {
return err
}
if err := gui.switchFocus(view); err != nil {
return err
}
}
// here is a good place log some stuff
// if you download humanlog and do tail -f development.log | humanlog
// this will let you see these branches as prettified json
@ -147,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)},
"images": {selectedLine: gui.State.Panels.Images.SelectedLine, lineCount: gui.State.Lists.Images.Len()},
"volumes": {selectedLine: gui.State.Panels.Volumes.SelectedLine, lineCount: len(gui.DockerCommand.Volumes)},
"services": {selectedLine: gui.State.Panels.Services.SelectedLine, lineCount: len(gui.DockerCommand.Services)},
"menu": {selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount},
@ -156,6 +135,11 @@ func (gui *Gui) focusPointInView(view *gocui.View) {
if state, ok := listViews[view.Name()]; ok {
gui.focusY(state.selectedLine, state.lineCount, view)
}
switch view.Name() {
case "images":
gui.Panels.Images.Refocus()
}
}
func (gui *Gui) prepareView(viewName string) (*gocui.View, error) {

@ -20,7 +20,7 @@ type ListPanel[T comparable] struct {
}
func (self *ListPanel[T]) setSelectedLineIdx(value int) {
clampedValue := -1
clampedValue := 0
if self.list.Len() > 0 {
clampedValue = utils.Clamp(value, 0, self.list.Len()-1)
}
@ -57,6 +57,7 @@ type SideListPanel[T comparable] struct {
// returns strings that can be filtered on
getSearchStrings func(item T) []string
getId func(item T) string
sort func(a, b T) bool
}
@ -101,7 +102,7 @@ func (gui *Gui) getImagePanel() *SideListPanel[*commands.Image] {
list: NewFilteredList[*commands.Image](),
view: gui.Views.Images,
},
contextIdx: -1, // TODO: see if this should be 0
contextIdx: 0,
noItemsMessge: gui.Tr.NoImages,
gui: gui.intoInterface(),
contexts: []ContextConfig[*commands.Image]{
@ -116,6 +117,9 @@ func (gui *Gui) getImagePanel() *SideListPanel[*commands.Image] {
getSearchStrings: func(image *commands.Image) []string {
return []string{image.Name, image.Tag}
},
getId: func(image *commands.Image) string {
return image.ID
},
sort: func(a *commands.Image, b *commands.Image) bool {
if a.Name == noneLabel && b.Name != noneLabel {
return false
@ -148,9 +152,9 @@ func (self *SideListPanel[T]) HandleSelect() error {
return self.gui.RenderStringMain(self.noItemsMessge)
}
self.gui.FocusY(self.selectedIdx, self.list.Len(), self.view)
self.Refocus()
key := self.contextKeyPrefix + "-" + self.contexts[self.contextIdx].key
key := self.contextKeyPrefix + "-" + self.getId(item) + "-" + self.contexts[self.contextIdx].key
if !self.gui.ShouldRefresh(key) {
return nil
}
@ -172,10 +176,6 @@ func (self *SideListPanel[T]) GetContextTitles() []string {
func (self *SideListPanel[T]) GetSelectedItem() (T, error) {
var zero T
if self.selectedIdx == -1 {
return zero, errors.New(self.noItemsMessge)
}
item, ok := self.list.TryGet(self.selectedIdx)
if !ok {
// could probably have a better error here
@ -221,6 +221,10 @@ func (self *SideListPanel[T]) OnPrevContext() error {
return self.HandleSelect()
}
func (self *SideListPanel[T]) Refocus() {
self.gui.FocusY(self.selectedIdx, self.list.Len(), self.view)
}
func (self *SideListPanel[T]) RerenderList() error {
filterString := self.gui.FilterString(self.view)
@ -245,9 +249,6 @@ func (self *SideListPanel[T]) RerenderList() error {
self.list.Sort(self.sort)
// TODO: use clamp?
if self.list.Len() > 0 && self.selectedIdx == -1 {
self.selectedIdx = 0
}
if self.list.Len()-1 < self.selectedIdx {
self.selectedIdx = self.list.Len() - 1
}
@ -269,3 +270,7 @@ func (self *SideListPanel[T]) RerenderList() error {
return nil
}
func (self *SideListPanel[T]) SetContextIndex(index int) {
self.contextIdx = index
}

@ -94,8 +94,8 @@ func (gui *Gui) onMainTabClick(tabIndex int) error {
gui.State.Panels.Containers.ContextIndex = tabIndex
return gui.handleContainerSelect(gui.g, gui.getContainersView())
case "images":
gui.State.Panels.Images.ContextIndex = tabIndex
return gui.handleImageSelect(gui.g, gui.getImagesView())
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())

@ -18,7 +18,7 @@ func (gui *Gui) handleOpenSearch(view *gocui.View) error {
func (gui *Gui) onNewSearchString(value string) error {
// need to refresh the right list panel.
gui.State.Searching.searchString = value
return gui.rerenderImages()
return gui.Panels.Images.RerenderList()
}
func (gui *Gui) wrapEditor(f func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool) func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) bool {
@ -46,7 +46,7 @@ func (gui *Gui) clearSearch() error {
gui.State.Searching.view = nil
gui.Views.Search.ClearTextArea()
return gui.rerenderImages()
return gui.Panels.Images.RerenderList()
}
// returns to the list view with the filter still applied

@ -81,7 +81,7 @@ func (gui *Gui) newLineFocused(v *gocui.View) error {
case "containers":
return gui.handleContainerSelect(gui.g, v)
case "images":
return gui.handleImageSelect(gui.g, v)
return gui.Panels.Images.HandleSelect()
case "volumes":
return gui.handleVolumeSelect(gui.g, v)
case "confirmation":

@ -139,8 +139,6 @@ func (gui *Gui) createAllViews() error {
gui.Views.Search.Frame = false
gui.Views.Search.Editor = gocui.EditorFunc(gui.wrapEditor(gocui.SimpleEditor))
gui.waitForIntro.Done()
return nil
}

Loading…
Cancel
Save