diff --git a/docs/keys.md b/docs/keys.md index 893d179..7496083 100644 --- a/docs/keys.md +++ b/docs/keys.md @@ -113,7 +113,8 @@ back. A value you reveal stays in view whichever pane you're in, until it's hidden. On top, the status pane (`0`) shows the version, a newer release and how -your accounts are doing. Focused, the right pane lists more: the version +your accounts are doing. Focused, the right pane lists more under the +logo (when there's room): the version and updates (`U`), each account (`L` on one that needs it signs in), the config, state and cache files (`e` opens one in your editor), and links to the docs, source, issues, releases and changelog (`o` opens one; on diff --git a/internal/ui/app/app_test.go b/internal/ui/app/app_test.go index e526a91..0a97da7 100644 --- a/internal/ui/app/app_test.go +++ b/internal/ui/app/app_test.go @@ -1205,7 +1205,7 @@ func TestStatusPane(t *testing.T) { t.Fatalf("focus %v", d.m.focus) } scr := d.screen() - for _, want := range []string{"version v0.1.0", "GPL-3.0-or-later", "Azure · 2 subscriptions", "Azure · admin · sign-in required", + for _, want := range []string{logo[2], "version v0.1.0", "GPL-3.0-or-later", "Azure · 2 subscriptions", "Azure · admin · sign-in required", "/cfg/config.yaml", "/state/state.json", "lazysecrets/…/docs"} { if !strings.Contains(scr, want) { t.Errorf("missing %q:\n%s", want, scr) @@ -1231,6 +1231,11 @@ func TestStatusPane(t *testing.T) { if len(opened) != 1 || !strings.HasSuffix(opened[0], "/src/branch/main/docs") { t.Errorf("o opened %q", opened) } + d.m.fields.SelectWhere(func(f panes.Field) bool { return f.Label == "version" }) + d.keys("o") + if len(opened) != 2 || !strings.HasSuffix(opened[1], "/releases/tag/v0.1.0") { + t.Errorf("o on the version opened %q", opened) + } d.keys("shift+tab") if d.m.focus != paneStatus { t.Errorf("back to %v", d.m.focus) diff --git a/internal/ui/app/fields.go b/internal/ui/app/fields.go index 6a0f253..802fffe 100644 --- a/internal/ui/app/fields.go +++ b/internal/ui/app/fields.go @@ -49,6 +49,7 @@ func (m *Model) syncFields() tea.Cmd { m.fields.Hide() return nil } + m.fields.Header = nil switch m.detailOf() { case paneSources: r, ok := m.sources.Selected() @@ -65,6 +66,7 @@ func (m *Model) syncFields() tea.Cmd { m.fields.Show("", nil) } case paneStatus: + m.fields.Header = logo m.fields.Show("lazysecrets", m.statusFields()) case paneContainers: if c, ok := m.containers.Selected(); ok { diff --git a/internal/ui/app/status.go b/internal/ui/app/status.go index 02628fa..4612ea7 100644 --- a/internal/ui/app/status.go +++ b/internal/ui/app/status.go @@ -16,6 +16,16 @@ import ( // (0), the right pane has the rest: updates, each account, the files // lazysecrets uses, links, and the changelog. +// logo heads the status pane's rows. +var logo = []string{ + ` _ _`, + `| | __ _ _____ _ ___ ___ ___ _ __ ___| |_ ___`, + `| |/ _` + "`" + ` |_ / | | / __|/ _ \/ __| '__/ _ \ __/ __|`, + `| | (_| |/ /| |_| \__ \ __/ (__| | | __/ |_\__ \`, + `|_|\__,_/___|\__, |___/\___|\___|_| \___|\__|___/`, + ` |___/`, +} + // statusH is the status pane's height: one line in a box. const statusH = 3 diff --git a/internal/ui/panes/list.go b/internal/ui/panes/list.go index 21252b2..535cedc 100644 --- a/internal/ui/panes/list.go +++ b/internal/ui/panes/list.go @@ -34,6 +34,9 @@ type List[T any] struct { Empty string // shown when loaded with no items Loading bool Err error + // Header lines (a logo) go above the items, when the pane has room + // for them and there's no filter. + Header []string item Item[T] items []T @@ -159,9 +162,29 @@ func (l List[T]) rows() int { if l.filtering { r-- } + if l.headerShown() { + r -= len(l.Header) + 1 + } return max(0, r) } +// headerMinRows is how many item rows the header leaves at least. +const headerMinRows = 5 + +// headerShown reports whether Header fits: as wide as the pane allows, +// leaving headerMinRows, a blank line under it, and no filter. +func (l List[T]) headerShown() bool { + if len(l.Header) == 0 || l.filtering || l.input.Value() != "" { + return false + } + for _, line := range l.Header { + if ansi.StringWidth(line) > l.w-3 { + return false + } + } + return l.h-2-len(l.Header)-1 >= headerMinRows +} + // Filtering reports whether the filter input has focus. func (l List[T]) Filtering() bool { return l.filtering } @@ -325,6 +348,12 @@ func (l List[T]) View(st styles.Styles, focused bool, spin string) string { if l.filtering { body = append(body, l.input.View()) } + if l.headerShown() { + for _, line := range l.Header { + body = append(body, st.Notice.Render(" "+line)) + } + body = append(body, "") + } switch { case l.Loading && len(l.items) == 0: