From 72c1b4cc000bd2628b81ca5150d7e56f37afaa0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 08:44:24 +0000 Subject: [PATCH 1/2] chore(dependabot): bump github.com/charmbracelet/bubbletea from 0.26.0 to 0.26.1 (#143) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/charmbracelet/bubbletea](https://github.com/charmbracelet/bubbletea) from 0.26.0 to 0.26.1.
Release notes

Sourced from github.com/charmbracelet/bubbletea's releases.

v0.26.1

This is a quick one to fix a Windows shortcoming in the last release acutely identified by our pal @​jon4hz. Thank you!

What's Changed

Full Changelog: https://github.com/charmbracelet/bubbletea/compare/v0.26.0...v0.26.1


Thoughts? Questions? We love hearing from you. Feel free to reach out on Twitter, The Fediverse, or Discord.

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/charmbracelet/bubbletea&package-manager=go_modules&previous-version=0.26.0&new-version=0.26.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2a046efd..5d0a1a56 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.2 require ( github.com/adrg/frontmatter v0.2.0 github.com/charmbracelet/bubbles v0.18.0 - github.com/charmbracelet/bubbletea v0.26.0 + github.com/charmbracelet/bubbletea v0.26.1 github.com/charmbracelet/glamour v0.7.0 github.com/charmbracelet/huh v0.3.0 github.com/charmbracelet/lipgloss v0.10.0 diff --git a/go.sum b/go.sum index 96ae7ee4..eb60fd58 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqy github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= -github.com/charmbracelet/bubbletea v0.26.0 h1:LRS2uBclVfqh3gWBmU8uso2fXBsroW2Nb6HtAHfzbJI= -github.com/charmbracelet/bubbletea v0.26.0/go.mod h1:FzKr7sKoO8iFVcdIBM9J0sJOcQv5nDQaYwsee3kpbgo= +github.com/charmbracelet/bubbletea v0.26.1 h1:xujcQeF73rh4jwu3+zhfQsvV18x+7zIjlw7/CYbzGJ0= +github.com/charmbracelet/bubbletea v0.26.1/go.mod h1:FzKr7sKoO8iFVcdIBM9J0sJOcQv5nDQaYwsee3kpbgo= github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng= github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps= github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0XsKE= From e6f44e0b06422b0676ce5f7f96630fc80a839f7b Mon Sep 17 00:00:00 2001 From: Krish Suchak <42231639+suchak1@users.noreply.github.com> Date: Fri, 3 May 2024 09:53:10 -0400 Subject: [PATCH 2/2] feat(tui): abstract away read view (#141) --- cmd/interactive.go | 4 +- tui/appMenu.go | 28 ++-- tui/attributeList.go | 49 ++++-- tui/attributeView.go | 373 +++++++------------------------------------ tui/common.go | 5 +- tui/read.go | 43 +++++ 6 files changed, 161 insertions(+), 341 deletions(-) create mode 100644 tui/read.go diff --git a/cmd/interactive.go b/cmd/interactive.go index 81c49a5b..df8689bf 100644 --- a/cmd/interactive.go +++ b/cmd/interactive.go @@ -1,6 +1,7 @@ package cmd import ( + "github.com/opentdf/otdfctl/pkg/cli" "github.com/opentdf/otdfctl/pkg/man" "github.com/opentdf/otdfctl/tui" "github.com/spf13/cobra" @@ -9,7 +10,8 @@ import ( func init() { cmd := man.Docs.GetCommand("interactive", man.WithRun(func(cmd *cobra.Command, args []string) { - tui.StartTea() + h := cli.NewHandler(cmd) + tui.StartTea(h) }), ) RootCmd.AddCommand(&cmd.Command) diff --git a/tui/appMenu.go b/tui/appMenu.go index 92181c4b..95c9873a 100644 --- a/tui/appMenu.go +++ b/tui/appMenu.go @@ -3,6 +3,7 @@ package tui import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + "github.com/opentdf/otdfctl/pkg/handlers" "github.com/opentdf/otdfctl/tui/constants" ) @@ -38,20 +39,22 @@ func (m AppMenuItem) Description() string { type AppMenu struct { list list.Model view tea.Model + sdk handlers.Handler } -func InitAppMenu() (AppMenu, tea.Cmd) { +func InitAppMenu(h handlers.Handler) (AppMenu, tea.Cmd) { m := AppMenu{ view: nil, + sdk: h, } m.list = list.New([]list.Item{}, list.NewDefaultDelegate(), 8, 8) m.list.Title = "OpenTDF" m.list.SetItems([]list.Item{ - AppMenuItem{title: "Namespaces", description: "Manage namespaces", id: namespaceMenu}, + // AppMenuItem{title: "Namespaces", description: "Manage namespaces", id: namespaceMenu}, AppMenuItem{title: "Attributes", description: "Manage attributes", id: attributeMenu}, - AppMenuItem{title: "Entitlements", description: "Manage entitlements", id: entitlementMenu}, - AppMenuItem{title: "Resource Encodings", description: "Manage resource encodings", id: resourceEncodingMenu}, - AppMenuItem{title: "Subject Encodings", description: "Manage subject encodings", id: subjectEncodingMenu}, + // AppMenuItem{title: "Entitlements", description: "Manage entitlements", id: entitlementMenu}, + // AppMenuItem{title: "Resource Encodings", description: "Manage resource encodings", id: resourceEncodingMenu}, + // AppMenuItem{title: "Subject Encodings", description: "Manage subject encodings", id: subjectEncodingMenu}, }) return m, func() tea.Msg { return nil } } @@ -74,16 +77,13 @@ func (m AppMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case "enter": switch m.list.SelectedItem().(AppMenuItem).id { + // case namespaceMenu: + // // get namespaces + // nl, cmd := InitNamespaceList([]list.Item{}, 0) + // return nl, cmd case attributeMenu: - item := AttributeItem{ - id: "8a6755f2-efa8-4758-b893-af9a488e0bea", - namespace: "demo.com", - name: "relto", - rule: "hierarchical", - description: "The relto attribute is used to describe the relationship of the resource to the country of origin.", - values: []string{"USA", "GBR"}, - } - al, cmd := InitAttributeList([]list.Item{item}, 0) + // list attributes + al, cmd := InitAttributeList("", m.sdk) return al, cmd } } diff --git a/tui/attributeList.go b/tui/attributeList.go index 53f49e50..a797cc7f 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -4,12 +4,15 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/opentdf/otdfctl/pkg/handlers" "github.com/opentdf/otdfctl/tui/constants" + "github.com/opentdf/platform/protocol/go/common" ) type AttributeList struct { list list.Model width int + sdk handlers.Handler } type AttributeItem struct { @@ -19,6 +22,7 @@ type AttributeItem struct { description string rule string values []string + title string } func (m AttributeItem) FilterValue() string { @@ -33,16 +37,35 @@ func (m AttributeItem) Description() string { return m.description } -func InitAttributeList(items []list.Item, selectIdx int) (tea.Model, tea.Cmd) { - // TODO: fetch items from API - - m := AttributeList{} +func InitAttributeList(id string, sdk handlers.Handler) (tea.Model, tea.Cmd) { + m := AttributeList{sdk: sdk} m.list = list.New([]list.Item{}, list.NewDefaultDelegate(), constants.WindowSize.Width, constants.WindowSize.Height) - if selectIdx > 0 { - m.list.Select(selectIdx) + res, err := sdk.ListAttributes(common.ActiveStateEnum_ACTIVE_STATE_ENUM_ANY) + if err != nil { + return m, nil + } + var attrs []list.Item + selectIdx := 0 + for i, attr := range res { + var vals []string + for _, val := range attr.Values { + vals = append(vals, val.Value) + } + if attr.Id == id { + selectIdx = i + } + item := AttributeItem{ + id: attr.Id, + namespace: attr.Namespace.Name, + name: attr.Name, + rule: attr.Rule.String(), + values: vals, + } + attrs = append(attrs, item) } m.list.Title = "Attributes" - m.list.SetItems(items) + m.list.SetItems(attrs) + m.list.Select(selectIdx) return m.Update(WindowMsg()) } @@ -76,8 +99,9 @@ func (m AttributeList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "ctrl+c", "q": return m, tea.Quit case "ctrl+[", "backspace": - am, _ := InitAppMenu() - am.list.Select(1) + am, _ := InitAppMenu(m.sdk) + // make enum for Attributes idx in AppMenu + am.list.Select(0) return am.Update(WindowMsg()) case "down", "j": if m.list.Index() < len(m.list.Items())-1 { @@ -87,10 +111,11 @@ func (m AttributeList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.list.Index() > 0 { m.list.Select(m.list.Index() - 1) } - case "c": - return InitAttributeView(m.list.Items(), len(m.list.Items())) + // case "c": + // create new attribute + // return InitAttributeView(m.list.Items(), len(m.list.Items())) case "enter", "e": - return InitAttributeView(m.list.Items(), m.list.Index()) + return InitAttributeView(m.list.Items()[m.list.Index()].(AttributeItem).id, m.sdk) case "ctrl+d": m.list.RemoveItem(m.list.Index()) newIndex := m.list.Index() - 1 diff --git a/tui/attributeView.go b/tui/attributeView.go index 45d43744..1723ae42 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -1,347 +1,96 @@ package tui import ( - "fmt" - "strings" - "github.com/charmbracelet/bubbles/list" - "github.com/charmbracelet/bubbles/textarea" - "github.com/charmbracelet/bubbles/textinput" - "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/muesli/reflow/wordwrap" + "github.com/opentdf/otdfctl/pkg/cli" + "github.com/opentdf/otdfctl/pkg/handlers" "github.com/opentdf/otdfctl/tui/constants" ) -const ( - id = iota - name - namespace - rule - description - values -) - -const ( - hotPink = lipgloss.Color("#FF06B7") - darkGray = lipgloss.Color("#767676") - cyan = lipgloss.Color("#00FFFF") -) - -const useHighPerformanceRenderer = false - -var ( - inputStyle = lipgloss.NewStyle().Foreground(constants.Magenta) - continueStyle = lipgloss.NewStyle().Foreground(cyan) -) - -var ( - titleStyle = func() lipgloss.Style { - b := lipgloss.RoundedBorder() - b.Right = "├" - return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1) - }() +type AttributeSubItem struct { + title string + description string +} - infoStyle = func() lipgloss.Style { - b := lipgloss.RoundedBorder() - b.Left = "┤" - return titleStyle.Copy().BorderStyle(b) - }() -) +func (m AttributeSubItem) FilterValue() string { + return m.title +} -// type TextWrapper struct{} +func (m AttributeSubItem) Title() string { + return m.title +} -// func View(m TextWrapper) {} -// func Value(m TextWrapper) {} +func (m AttributeSubItem) Description() string { + return m.description +} type AttributeView struct { - inputs []interface{} - focused int - err error - keys []string - title string - ready bool - viewport viewport.Model - width, height int - list []list.Item - idx int - editMode bool + read Read } -func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea.Cmd) { - var cmds []tea.Cmd - headerHeight := lipgloss.Height(m.CreateHeader()) - footerHeight := lipgloss.Height(m.CreateFooter()) - verticalMarginHeight := headerHeight + footerHeight - m.width = msg.Width - - if !m.ready { - // Since this program is using the full size of the viewport we - // need to wait until we've received the window dimensions before - // we can initialize the viewport. The initial dimensions come in - // quickly, though asynchronously, which is why we wait for them - // here. - m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight) - m.viewport.YPosition = headerHeight - m.viewport.HighPerformanceRendering = useHighPerformanceRenderer - m.ready = true - - // This is only necessary for high performance rendering, which in - // most cases you won't need. - // - // Render the viewport one line below the header. - m.viewport.YPosition = headerHeight + 1 - } else { - m.viewport.Width = msg.Width - m.viewport.Height = msg.Height - verticalMarginHeight +func InitAttributeView(id string, h handlers.Handler) (AttributeView, tea.Cmd) { + m := AttributeView{} + attr, err := h.GetAttribute(id) + if err != nil { + return m, nil } - - if useHighPerformanceRenderer { - // Render (or re-render) the whole viewport. Necessary both to - // initialize the viewport and when the window is resized. - // - // This is needed for high-performance rendering only. - cmds = append(cmds, viewport.Sync(m.viewport)) + var vals []string + for _, val := range attr.Values { + vals = append(vals, val.Value) } - return m, cmds -} - -func InitAttributeView(items []list.Item, idx int) (tea.Model, tea.Cmd) { - attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} - var inputs []interface{} - title := "Attribute]" - item := AttributeItem{} - if idx >= len(items) { - title = "[Create " + title - } else { - title = "[Edit " + title - item = items[idx].(AttributeItem) + items := []list.Item{ + AttributeSubItem{title: "ID", description: attr.Id}, + AttributeSubItem{title: "Name", description: attr.Name}, + AttributeSubItem{title: "Rule", description: attr.Rule.String()}, + AttributeSubItem{title: "Values", description: cli.CommaSeparated(vals)}, + AttributeSubItem{title: "Namespace", description: attr.Namespace.Name}, + AttributeSubItem{title: "Created At", description: attr.Metadata.CreatedAt.String()}, + AttributeSubItem{title: "Updated At", description: attr.Metadata.UpdatedAt.String()}, } - ti0 := textinput.New() - ti0.Focus() - ti0.SetValue(item.id) - inputs = append(inputs, ti0) - - ti1 := textinput.New() - ti1.SetValue(item.name) - inputs = append(inputs, ti1) - - ti2 := textinput.New() - ti2.SetValue(item.namespace) - inputs = append(inputs, ti2) - - ti3 := textinput.New() - ti3.SetValue(item.rule) - inputs = append(inputs, ti3) - - ti4 := textarea.New() - ti4.ShowLineNumbers = false - ti4.SetValue(item.description) - inputs = append(inputs, ti4) - - ti5 := textinput.New() - ti5.SetValue(strings.Join(item.values, ",")) - inputs = append(inputs, ti5) - - m := AttributeView{ - keys: attr_keys, - inputs: inputs, - focused: 0, - err: nil, - title: title, - list: items, - idx: idx, - editMode: idx >= len(items), - } - return m.Update(WindowMsg()) + model, _ := InitRead("Read Attribute", items) + m.read = model.(Read) + model, msg := m.Update(WindowMsg()) + m = model.(AttributeView) + return m, msg } func (m AttributeView) Init() tea.Cmd { - return textinput.Blink -} - -func (m AttributeView) IsNew() bool { - return m.idx >= len(m.list) -} - -func (m AttributeView) ChangeMode() AttributeView { - m.editMode = m.IsNew() || !m.editMode - return m + return nil } func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmds []tea.Cmd // = make([]tea.Cmd, len(m.inputs)) - var editing bool - item := AttributeItem{ - id: m.inputs[id].(textinput.Model).Value(), - name: m.inputs[name].(textinput.Model).Value(), - namespace: m.inputs[namespace].(textinput.Model).Value(), - rule: m.inputs[rule].(textinput.Model).Value(), - description: m.inputs[description].(textarea.Model).Value(), - values: strings.Split(m.inputs[values].(textinput.Model).Value(), ","), - } switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.Type { - case tea.KeyShiftLeft: - listIdx := m.idx - if m.IsNew() { - listIdx -= 1 - } - return InitAttributeList(m.list, listIdx) - case tea.KeyShiftRight: - if !m.IsNew() { - m.list[m.idx] = list.Item(item) - } else { - m.list = append(m.list, list.Item(item)) - } - - return InitAttributeList(m.list, m.idx) - case tea.KeyEnter: - m.nextInput() - case tea.KeyCtrlC, tea.KeyEsc: - if m.editMode { - m.editMode = false - } else { - return m, tea.Quit - } - case tea.KeyShiftTab, tea.KeyCtrlP, tea.KeyUp: - m.prevInput() - case tea.KeyTab, tea.KeyCtrlN, tea.KeyDown: - m.nextInput() - } - if msg.String() == "i" && !m.editMode { - editing = true - m = m.ChangeMode() - var cmd tea.Cmd - if m.focused == description { - tempArea := m.inputs[m.focused].(textarea.Model) - cmd = tempArea.Cursor.SetMode(0) - m.inputs[m.focused] = tempArea - } else { - tempInput := m.inputs[m.focused].(textinput.Model) - cmd = tempInput.Cursor.SetMode(0) - m.inputs[m.focused] = tempInput - } - return m, cmd - } - for i := range m.inputs { - if i == description { - tempInput := m.inputs[i].(textarea.Model) - tempInput.Blur() - m.inputs[i] = tempInput - } else { - tempArea := m.inputs[i].(textinput.Model) - tempArea.Blur() - m.inputs[i] = tempArea - } - } - if m.focused == description { - tempArea := m.inputs[m.focused].(textarea.Model) - tempArea.Focus() - m.inputs[m.focused] = tempArea - } else { - tempInput := m.inputs[m.focused].(textinput.Model) - tempInput.Focus() - m.inputs[m.focused] = tempInput - } - case tea.WindowSizeMsg: - m, cmds = SetupViewport(m, msg) - // We handle errors just like any other message - case errMsg: - m.err = msg + constants.WindowSize = msg + m.read.list.SetSize(msg.Width, msg.Height) return m, nil - } - - var cmd tea.Cmd - if m.editMode || m.IsNew() && !editing { - for i := range m.inputs { - if i == description { - m.inputs[i], cmd = m.inputs[i].(textarea.Model).Update(msg) - } else { - m.inputs[i], cmd = m.inputs[i].(textinput.Model).Update(msg) - } - cmds = append(cmds, cmd) + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "q": + return m, tea.Quit + case "ctrl+d": + return m, nil + // case "enter": + // switch m.list.SelectedItem().(AttributeItem).id { + // // case namespaceMenu: + // // // get namespaces + // // nl, cmd := InitNamespaceList([]list.Item{}, 0) + // // return nl, cmd + // case attributeMenu: + // // list attributes + // al, cmd := InitAttributeList("", m.sdk) + // return al, cmd + // } } } - m.viewport, cmd = m.viewport.Update(msg) - cmds = append(cmds, cmd) - return m, tea.Batch(cmds...) -} -func CreateEditFormat(num int) string { - var format string - prefix := "\n%s" - postfix := "%s\n" - var middle string - for i := 0; i < num; i++ { - if i == description { - middle = "\n" - } - format += prefix + middle + postfix - } - return format + var cmd tea.Cmd + m.read.list, cmd = m.read.list.Update(msg) + return m, cmd } func (m AttributeView) View() string { - content := fmt.Sprintf(CreateEditFormat(len(m.inputs)), - inputStyle.Width(len(m.keys[id])).Render(m.keys[id]), - m.inputs[id].(textinput.Model).View(), - inputStyle.Width(len(m.keys[name])).Render(m.keys[name]), - m.inputs[name].(textinput.Model).View(), - inputStyle.Width(len(m.keys[namespace])).Render(m.keys[namespace]), - m.inputs[namespace].(textinput.Model).View(), - inputStyle.Width(len(m.keys[rule])).Render(m.keys[rule]), - m.inputs[rule].(textinput.Model).View(), - inputStyle.Width(len(m.keys[description])).Render(m.keys[description]), - m.inputs[description].(textarea.Model).View(), - inputStyle.Width(len(m.keys[values])).Render(m.keys[values]), - m.inputs[values].(textinput.Model).View(), - ) - - if !m.ready { - return "\n Initializing..." - } - wrapped := wordwrap.String(content, m.width) - m.viewport.SetContent(wrapped) - return fmt.Sprintf("%s\n%s\n%s", m.CreateHeader(), m.viewport.View(), m.CreateFooter()) -} - -// nextInput focuses the next input field -func (m *AttributeView) nextInput() { - m.focused = (m.focused + 1) % len(m.inputs) -} - -// prevInput focuses the previous input field -func (m *AttributeView) prevInput() { - m.focused-- - // Wrap around - if m.focused < 0 { - m.focused = len(m.inputs) - 1 - } -} - -func CreateLine(width int, text string) string { - return strings.Repeat("─", max(0, width-lipgloss.Width(text))) -} - -func (m AttributeView) CreateHeader() string { - title := titleStyle.Render(m.title) - line := CreateLine(m.viewport.Width, title) - return lipgloss.JoinHorizontal(lipgloss.Center, title, line) -} - -func (m AttributeView) CreateFooter() string { - var prefix string - if m.editMode || m.IsNew() { - prefix = "discard: shift + left arrow | save: shift + right arrow" - } else { - prefix = "enter edit mode: i | go back: shift + left arrow" - } - info := infoStyle.Render(fmt.Sprintf(prefix+" | scroll: %3.f%%", m.viewport.ScrollPercent()*100)) - line := CreateLine(m.viewport.Width, info) - return lipgloss.JoinHorizontal(lipgloss.Center, line, info) + return m.read.View() } diff --git a/tui/common.go b/tui/common.go index 8b99757a..436731ee 100644 --- a/tui/common.go +++ b/tui/common.go @@ -8,11 +8,12 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/opentdf/otdfctl/pkg/handlers" "github.com/opentdf/otdfctl/tui/constants" ) // StartTea the entry point for the UI. Initializes the model. -func StartTea() error { +func StartTea(h handlers.Handler) error { if f, err := tea.LogToFile("debug.log", "help"); err != nil { fmt.Println("Couldn't open a file for logging:", err) os.Exit(1) @@ -25,7 +26,7 @@ func StartTea() error { }() } - m, _ := InitAppMenu() + m, _ := InitAppMenu(h) constants.P = tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion()) if _, err := constants.P.Run(); err != nil { fmt.Println("Error running program:", err) diff --git a/tui/read.go b/tui/read.go new file mode 100644 index 00000000..ac5f1167 --- /dev/null +++ b/tui/read.go @@ -0,0 +1,43 @@ +package tui + +import ( + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/opentdf/otdfctl/tui/constants" +) + +type Read struct { + list list.Model + width int +} + +func InitRead(title string, items []list.Item) (tea.Model, tea.Cmd) { + m := Read{} + m.list = list.New(items, list.NewDefaultDelegate(), constants.WindowSize.Width, constants.WindowSize.Height) + m.list.Title = title + return m.Update(WindowMsg()) +} + +func (m Read) Init() tea.Cmd { + return nil +} + +func (m Read) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + constants.WindowSize = msg + m.list.SetSize(msg.Width, msg.Height) + m.width = msg.Width + return m, nil + case tea.KeyMsg: + switch msg.Type { + case tea.KeyCtrlC, tea.KeyEsc: + return m, tea.Quit + } + } + return m, nil +} + +func (m Read) View() string { + return ViewList(m.list) +}