From 533b6bf5e3be43808ea4d54aad73d8dd607a5c82 Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Wed, 31 Jan 2024 14:50:02 -0500 Subject: [PATCH 01/16] edit text area boilerplate --- tui/attributeEdit.go | 194 ++++++++++++++++++++++++++++++++++++++++ tui/attributeList.go | 4 + tui/constants/consts.go | 7 -- 3 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 tui/attributeEdit.go diff --git a/tui/attributeEdit.go b/tui/attributeEdit.go new file mode 100644 index 00000000..736ef8fb --- /dev/null +++ b/tui/attributeEdit.go @@ -0,0 +1,194 @@ +package tui + +import ( + "fmt" + // "log" + "strconv" + "strings" + + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +// type ( +// errMsg error +// ) + +const ( + ccn = iota + exp + cvv +) + +const ( + hotPink = lipgloss.Color("#FF06B7") + darkGray = lipgloss.Color("#767676") +) + +var ( + inputStyle = lipgloss.NewStyle().Foreground(hotPink) + continueStyle = lipgloss.NewStyle().Foreground(darkGray) +) + +type model struct { + inputs []textinput.Model + focused int + err error +} + +// Validator functions to ensure valid input +func ccnValidator(s string) error { + // Credit Card Number should a string less than 20 digits + // It should include 16 integers and 3 spaces + if len(s) > 16+3 { + return fmt.Errorf("CCN is too long") + } + + if len(s) == 0 || len(s)%5 != 0 && (s[len(s)-1] < '0' || s[len(s)-1] > '9') { + return fmt.Errorf("CCN is invalid") + } + + // The last digit should be a number unless it is a multiple of 4 in which + // case it should be a space + if len(s)%5 == 0 && s[len(s)-1] != ' ' { + return fmt.Errorf("CCN must separate groups with spaces") + } + + // The remaining digits should be integers + c := strings.ReplaceAll(s, " ", "") + _, err := strconv.ParseInt(c, 10, 64) + + return err +} + +func expValidator(s string) error { + // The 3 character should be a slash (/) + // The rest should be numbers + e := strings.ReplaceAll(s, "/", "") + _, err := strconv.ParseInt(e, 10, 64) + if err != nil { + return fmt.Errorf("EXP is invalid") + } + + // There should be only one slash and it should be in the 2nd index (3rd character) + if len(s) >= 3 && (strings.Index(s, "/") != 2 || strings.LastIndex(s, "/") != 2) { + return fmt.Errorf("EXP is invalid") + } + + return nil +} + +func cvvValidator(s string) error { + // The CVV should be a number of 3 digits + // Since the input will already ensure that the CVV is a string of length 3, + // All we need to do is check that it is a number + _, err := strconv.ParseInt(s, 10, 64) + return err +} + +func initialModel() model { + var inputs []textinput.Model = make([]textinput.Model, 3) + inputs[ccn] = textinput.New() + inputs[ccn].Placeholder = "4505 **** **** 1234" + inputs[ccn].Focus() + inputs[ccn].CharLimit = 20 + inputs[ccn].Width = 30 + inputs[ccn].Prompt = "" + inputs[ccn].Validate = ccnValidator + + inputs[exp] = textinput.New() + inputs[exp].Placeholder = "MM/YY " + inputs[exp].CharLimit = 5 + inputs[exp].Width = 5 + inputs[exp].Prompt = "" + inputs[exp].Validate = expValidator + + inputs[cvv] = textinput.New() + inputs[cvv].Placeholder = "XXX" + inputs[cvv].CharLimit = 3 + inputs[cvv].Width = 5 + inputs[cvv].Prompt = "" + inputs[cvv].Validate = cvvValidator + + return model{ + inputs: inputs, + focused: 0, + err: nil, + } +} + +func (m model) Init() tea.Cmd { + return textinput.Blink +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd = make([]tea.Cmd, len(m.inputs)) + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.Type { + case tea.KeyEnter: + if m.focused == len(m.inputs)-1 { + return m, tea.Quit + } + m.nextInput() + case tea.KeyCtrlC, tea.KeyEsc: + return m, tea.Quit + case tea.KeyShiftTab, tea.KeyCtrlP: + m.prevInput() + case tea.KeyTab, tea.KeyCtrlN: + m.nextInput() + } + for i := range m.inputs { + m.inputs[i].Blur() + } + m.inputs[m.focused].Focus() + + // We handle errors just like any other message + case errMsg: + m.err = msg + return m, nil + } + + for i := range m.inputs { + m.inputs[i], cmds[i] = m.inputs[i].Update(msg) + } + return m, tea.Batch(cmds...) +} + +func (m model) View() string { + return fmt.Sprintf( + ` Total: $21.50: + + %s + %s + + %s %s + %s %s + + %s +`, + inputStyle.Width(30).Render("Card Number"), + m.inputs[ccn].View(), + inputStyle.Width(6).Render("EXP"), + inputStyle.Width(6).Render("CVV"), + m.inputs[exp].View(), + m.inputs[cvv].View(), + continueStyle.Render("Continue ->"), + ) + "\n" +} + +// nextInput focuses the next input field +func (m *model) nextInput() { + m.focused = (m.focused + 1) % len(m.inputs) +} + +// prevInput focuses the previous input field +func (m *model) prevInput() { + m.focused-- + // Wrap around + if m.focused < 0 { + m.focused = len(m.inputs) - 1 + } +} diff --git a/tui/attributeList.go b/tui/attributeList.go index 2e415171..d1c65f02 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -94,6 +94,10 @@ func (m AttributeList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // show the add attribute form // InitAttributeCreateView() return m, nil + case "e": + // item := m.list.Items()[0].(AttributeItem) + // attr_keys := []string{"Name", "Namespace", "Rule", "Description", "Values"} + return initialModel(), nil case "enter": item := m.list.Items()[0].(AttributeItem) attr_keys := []string{"Name", "Namespace", "Rule", "Description", "Values"} diff --git a/tui/constants/consts.go b/tui/constants/consts.go index 91ad8101..8184f6ec 100644 --- a/tui/constants/consts.go +++ b/tui/constants/consts.go @@ -5,13 +5,6 @@ import ( "github.com/charmbracelet/lipgloss" ) -var P *tea.Program - -var WindowSize struct { - Width int - Height int -} - var ( P *tea.Program From 4337d72b84d84faab9acd7e42dbe19708a1690af Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Wed, 31 Jan 2024 15:25:13 -0500 Subject: [PATCH 02/16] add back-tracking --- tui/appMenu.go | 7 +++-- tui/attributeEdit.go | 67 ++++++-------------------------------------- tui/attributeList.go | 5 ++-- tui/attributeView.go | 5 ++-- 4 files changed, 17 insertions(+), 67 deletions(-) diff --git a/tui/appMenu.go b/tui/appMenu.go index 0d316ff6..7fa70b30 100644 --- a/tui/appMenu.go +++ b/tui/appMenu.go @@ -75,9 +75,10 @@ func (m AppMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "enter": switch m.list.SelectedItem().(AppMenuItem).id { case attributeMenu: - attributeList := InitAttributeList() - am, cmd := attributeList.Update(WindowMsg()) - return am, cmd + return InitAttributeList() + // attributeList := InitAttributeList() + // am, cmd := attributeList.Update(WindowMsg()) + // return am, cmd } } } diff --git a/tui/attributeEdit.go b/tui/attributeEdit.go index 736ef8fb..9c8ee4bf 100644 --- a/tui/attributeEdit.go +++ b/tui/attributeEdit.go @@ -3,8 +3,6 @@ package tui import ( "fmt" // "log" - "strconv" - "strings" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" @@ -37,79 +35,26 @@ type model struct { err error } -// Validator functions to ensure valid input -func ccnValidator(s string) error { - // Credit Card Number should a string less than 20 digits - // It should include 16 integers and 3 spaces - if len(s) > 16+3 { - return fmt.Errorf("CCN is too long") - } - - if len(s) == 0 || len(s)%5 != 0 && (s[len(s)-1] < '0' || s[len(s)-1] > '9') { - return fmt.Errorf("CCN is invalid") - } - - // The last digit should be a number unless it is a multiple of 4 in which - // case it should be a space - if len(s)%5 == 0 && s[len(s)-1] != ' ' { - return fmt.Errorf("CCN must separate groups with spaces") - } - - // The remaining digits should be integers - c := strings.ReplaceAll(s, " ", "") - _, err := strconv.ParseInt(c, 10, 64) - - return err -} - -func expValidator(s string) error { - // The 3 character should be a slash (/) - // The rest should be numbers - e := strings.ReplaceAll(s, "/", "") - _, err := strconv.ParseInt(e, 10, 64) - if err != nil { - return fmt.Errorf("EXP is invalid") - } - - // There should be only one slash and it should be in the 2nd index (3rd character) - if len(s) >= 3 && (strings.Index(s, "/") != 2 || strings.LastIndex(s, "/") != 2) { - return fmt.Errorf("EXP is invalid") - } - - return nil -} - -func cvvValidator(s string) error { - // The CVV should be a number of 3 digits - // Since the input will already ensure that the CVV is a string of length 3, - // All we need to do is check that it is a number - _, err := strconv.ParseInt(s, 10, 64) - return err -} - func initialModel() model { var inputs []textinput.Model = make([]textinput.Model, 3) inputs[ccn] = textinput.New() inputs[ccn].Placeholder = "4505 **** **** 1234" inputs[ccn].Focus() - inputs[ccn].CharLimit = 20 + // inputs[ccn].CharLimit = 20 inputs[ccn].Width = 30 inputs[ccn].Prompt = "" - inputs[ccn].Validate = ccnValidator inputs[exp] = textinput.New() inputs[exp].Placeholder = "MM/YY " - inputs[exp].CharLimit = 5 + // inputs[exp].CharLimit = 5 inputs[exp].Width = 5 inputs[exp].Prompt = "" - inputs[exp].Validate = expValidator inputs[cvv] = textinput.New() inputs[cvv].Placeholder = "XXX" - inputs[cvv].CharLimit = 3 + // inputs[cvv].CharLimit = 3 inputs[cvv].Width = 5 inputs[cvv].Prompt = "" - inputs[cvv].Validate = cvvValidator return model{ inputs: inputs, @@ -128,6 +73,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { + case tea.KeyBackspace: + // attributeList := InitAttributeList() + // return attributeList.Update(WindowMsg()) + return InitAttributeList() case tea.KeyEnter: if m.focused == len(m.inputs)-1 { return m, tea.Quit @@ -159,7 +108,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m model) View() string { return fmt.Sprintf( - ` Total: $21.50: + ` Edit Attribute %s %s diff --git a/tui/attributeList.go b/tui/attributeList.go index d1c65f02..760abab6 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -36,7 +36,7 @@ func (m AttributeItem) Description() string { return m.description } -func InitAttributeList() AttributeList { +func InitAttributeList() (tea.Model, tea.Cmd) { // TODO: fetch items from API m := AttributeList{} @@ -52,8 +52,7 @@ func InitAttributeList() AttributeList { values: []string{"USA", "GBR"}, }, }) - - return m + return m.Update(WindowMsg()) } func (m AttributeList) Init() tea.Cmd { diff --git a/tui/attributeView.go b/tui/attributeView.go index 2761d219..293df924 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -96,8 +96,9 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "ctrl+c", "q": return m, tea.Quit case "backspace": - attributeList := InitAttributeList() - return attributeList.Update(WindowMsg()) + // attributeList := InitAttributeList() + // return attributeList.Update(WindowMsg()) + return InitAttributeList() } case tea.WindowSizeMsg: From f52d9fd66bc4981fbbd74b0ee2013388da752ce7 Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Wed, 31 Jan 2024 15:32:40 -0500 Subject: [PATCH 03/16] clean up --- tui/appMenu.go | 3 --- tui/attributeEdit.go | 2 -- tui/attributeView.go | 2 -- 3 files changed, 7 deletions(-) diff --git a/tui/appMenu.go b/tui/appMenu.go index 7fa70b30..16544820 100644 --- a/tui/appMenu.go +++ b/tui/appMenu.go @@ -76,9 +76,6 @@ func (m AppMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch m.list.SelectedItem().(AppMenuItem).id { case attributeMenu: return InitAttributeList() - // attributeList := InitAttributeList() - // am, cmd := attributeList.Update(WindowMsg()) - // return am, cmd } } } diff --git a/tui/attributeEdit.go b/tui/attributeEdit.go index 9c8ee4bf..133d04fe 100644 --- a/tui/attributeEdit.go +++ b/tui/attributeEdit.go @@ -74,8 +74,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch msg.Type { case tea.KeyBackspace: - // attributeList := InitAttributeList() - // return attributeList.Update(WindowMsg()) return InitAttributeList() case tea.KeyEnter: if m.focused == len(m.inputs)-1 { diff --git a/tui/attributeView.go b/tui/attributeView.go index 293df924..16159793 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -96,8 +96,6 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "ctrl+c", "q": return m, tea.Quit case "backspace": - // attributeList := InitAttributeList() - // return attributeList.Update(WindowMsg()) return InitAttributeList() } From 2f7aea4c6b716f53c9fc9cfd9ec89dca9d26038f Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Sun, 4 Feb 2024 04:39:09 -0500 Subject: [PATCH 04/16] more progress --- tui/attributeEdit.go | 96 ++++++++++++++++++++++++++------------------ tui/attributeList.go | 27 +++++++------ 2 files changed, 71 insertions(+), 52 deletions(-) diff --git a/tui/attributeEdit.go b/tui/attributeEdit.go index 133d04fe..4f49ecdd 100644 --- a/tui/attributeEdit.go +++ b/tui/attributeEdit.go @@ -7,6 +7,7 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/muesli/reflow/wordwrap" ) // type ( @@ -14,9 +15,12 @@ import ( // ) const ( - ccn = iota - exp - cvv + id = iota + name + namespace + rule + description + values ) const ( @@ -33,34 +37,37 @@ type model struct { inputs []textinput.Model focused int err error + keys []string + // width int } -func initialModel() model { - var inputs []textinput.Model = make([]textinput.Model, 3) - inputs[ccn] = textinput.New() - inputs[ccn].Placeholder = "4505 **** **** 1234" - inputs[ccn].Focus() +func InitAttributeEdit(names []string, item AttributeItem) (tea.Model, tea.Cmd) { + var inputs []textinput.Model = make([]textinput.Model, 6) + inputs[id] = textinput.New() + inputs[id].Placeholder = "4505 **** **** 1234" + inputs[id].Focus() // inputs[ccn].CharLimit = 20 - inputs[ccn].Width = 30 - inputs[ccn].Prompt = "" + // inputs[id].Width = 30 + inputs[id].Prompt = "" - inputs[exp] = textinput.New() - inputs[exp].Placeholder = "MM/YY " + inputs[name] = textinput.New() + inputs[name].Placeholder = "MM/YY " // inputs[exp].CharLimit = 5 - inputs[exp].Width = 5 - inputs[exp].Prompt = "" + // inputs[name].Width = 5 + inputs[name].Prompt = "" - inputs[cvv] = textinput.New() - inputs[cvv].Placeholder = "XXX" + inputs[namespace] = textinput.New() + inputs[namespace].Placeholder = "XXX" // inputs[cvv].CharLimit = 3 - inputs[cvv].Width = 5 - inputs[cvv].Prompt = "" - - return model{ + // inputs[namespace].Width = 5 + inputs[namespace].Prompt = "" + m := model{ + keys: names, inputs: inputs, focused: 0, err: nil, } + return m.Update(WindowMsg()) } func (m model) Init() tea.Cmd { @@ -73,7 +80,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { - case tea.KeyBackspace: + case tea.KeyShiftLeft: //, tea.KeyBackspace: return InitAttributeList() case tea.KeyEnter: if m.focused == len(m.inputs)-1 { @@ -91,7 +98,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.inputs[i].Blur() } m.inputs[m.focused].Focus() - + case tea.WindowSizeMsg: + for i := range m.inputs { + m.inputs[i].Width = msg.Width + } + // m.width = msg.Width + return m, nil // We handle errors just like any other message case errMsg: m.err = msg @@ -104,26 +116,32 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(cmds...) } +func CreateEditFormat(num int) string { + var format string + for i := 0; i < num; i++ { + format += "\n%s\n%s\n" + } + return format +} + func (m model) View() string { - return fmt.Sprintf( - ` Edit Attribute - - %s - %s - - %s %s - %s %s - - %s -`, - inputStyle.Width(30).Render("Card Number"), - m.inputs[ccn].View(), - inputStyle.Width(6).Render("EXP"), - inputStyle.Width(6).Render("CVV"), - m.inputs[exp].View(), - m.inputs[cvv].View(), + content := fmt.Sprintf("Edit Attribute\n"+CreateEditFormat(len(m.inputs))+"\n%s", + inputStyle.Width(len(m.keys[id])).Render(m.keys[id]), + m.inputs[id].View(), + inputStyle.Width(len(m.keys[name])).Render(m.keys[name]), + wordwrap.String(m.inputs[name].View(), 15), + inputStyle.Width(len(m.keys[namespace])).Render(m.keys[namespace]), + m.inputs[namespace].View(), + inputStyle.Width(len(m.keys[rule])).Render(m.keys[rule]), + m.inputs[rule].View(), + inputStyle.Width(len(m.keys[description])).Render(m.keys[description]), + m.inputs[description].View(), + inputStyle.Width(len(m.keys[values])).Render(m.keys[values]), + m.inputs[values].View(), continueStyle.Render("Continue ->"), ) + "\n" + wrapped := wordwrap.String(content, 15) + return wrapped } // nextInput focuses the next input field diff --git a/tui/attributeList.go b/tui/attributeList.go index 760abab6..5c296f1c 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -16,7 +16,7 @@ type AttributeList struct { } type AttributeItem struct { - id int + id string namespace string name string description string @@ -44,7 +44,7 @@ func InitAttributeList() (tea.Model, tea.Cmd) { m.list.Title = "Attributes" m.list.SetItems([]list.Item{ AttributeItem{ - id: 1, + id: "8a6755f2-efa8-4758-b893-af9a488e0bea", namespace: "demo.com", name: "relto", rule: "hierarchical", @@ -65,7 +65,7 @@ func StyleAttr(attr string) string { Render(attr) } -func CreateFormat(num int) string { +func CreateViewFormat(num int) string { var format string for i := 0; i < num; i++ { format += "%s %s\n" @@ -94,19 +94,20 @@ func (m AttributeList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // InitAttributeCreateView() return m, nil case "e": - // item := m.list.Items()[0].(AttributeItem) - // attr_keys := []string{"Name", "Namespace", "Rule", "Description", "Values"} - return initialModel(), nil + item := m.list.Items()[0].(AttributeItem) + attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} + return InitAttributeEdit(attr_keys, item) case "enter": item := m.list.Items()[0].(AttributeItem) - attr_keys := []string{"Name", "Namespace", "Rule", "Description", "Values"} + attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} content := fmt.Sprintf( - CreateFormat(len(attr_keys)), - StyleAttr(attr_keys[0]), item.name, - StyleAttr(attr_keys[1]), item.namespace, - StyleAttr(attr_keys[2]), item.rule, - StyleAttr(attr_keys[3]), item.description, - StyleAttr(attr_keys[4]), item.values, + CreateViewFormat(len(attr_keys)), + StyleAttr(attr_keys[0]), item.id, + StyleAttr(attr_keys[1]), item.name, + StyleAttr(attr_keys[2]), item.namespace, + StyleAttr(attr_keys[3]), item.rule, + StyleAttr(attr_keys[4]), item.description, + StyleAttr(attr_keys[5]), item.values, ) wrapped := wordwrap.String(content, m.width) am := AttributeView{} From c67d3c2392f47c40167e8501c5cc4e511caa49c1 Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Sun, 4 Feb 2024 17:47:41 -0500 Subject: [PATCH 05/16] need viewport --- tui/appMenu.go | 2 +- tui/attributeEdit.go | 155 +++++++++++++++++++++++++++++++------------ tui/attributeList.go | 30 ++++++--- tui/attributeView.go | 2 +- 4 files changed, 135 insertions(+), 54 deletions(-) diff --git a/tui/appMenu.go b/tui/appMenu.go index 16544820..f2b672d2 100644 --- a/tui/appMenu.go +++ b/tui/appMenu.go @@ -75,7 +75,7 @@ func (m AppMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "enter": switch m.list.SelectedItem().(AppMenuItem).id { case attributeMenu: - return InitAttributeList() + return InitAttributeList([]AttributeItem{}) } } } diff --git a/tui/attributeEdit.go b/tui/attributeEdit.go index 4f49ecdd..5640f4ef 100644 --- a/tui/attributeEdit.go +++ b/tui/attributeEdit.go @@ -2,12 +2,15 @@ package tui import ( "fmt" + "strings" + // "log" + "github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/muesli/reflow/wordwrap" + "github.com/opentdf/tructl/tui/constants" ) // type ( @@ -26,41 +29,73 @@ const ( const ( hotPink = lipgloss.Color("#FF06B7") darkGray = lipgloss.Color("#767676") + cyan = lipgloss.Color("#00FFFF") ) var ( - inputStyle = lipgloss.NewStyle().Foreground(hotPink) - continueStyle = lipgloss.NewStyle().Foreground(darkGray) + inputStyle = lipgloss.NewStyle().Foreground(constants.Magenta) + continueStyle = lipgloss.NewStyle().Foreground(cyan) ) type model struct { - inputs []textinput.Model + inputs []interface{} focused int err error keys []string - // width int } func InitAttributeEdit(names []string, item AttributeItem) (tea.Model, tea.Cmd) { - var inputs []textinput.Model = make([]textinput.Model, 6) - inputs[id] = textinput.New() - inputs[id].Placeholder = "4505 **** **** 1234" - inputs[id].Focus() + // inputs := make([]interface{}, len(names)) //= [make([]tea.Model, len(names)) + var inputs []interface{} + // var inputs []textinput.Model = make([]textinput.Model, len(names)) + // inputs = append(inputs, textinput.New()) + ti0 := textinput.New() + ti0.Focus() + ti0.SetValue(item.id) + inputs = append(inputs, ti0) + + // inputs[id] = textinput.New() + // inputs[id].Placeholder = "4505 **** **** 1234" + // inputs[id].Focus() + // inputs[id].SetValue(item.id) // inputs[ccn].CharLimit = 20 // inputs[id].Width = 30 - inputs[id].Prompt = "" - - inputs[name] = textinput.New() - inputs[name].Placeholder = "MM/YY " + // inputs[id].Prompt = "" + ti1 := textinput.New() + ti1.SetValue(item.name) + inputs = append(inputs, ti1) + // inputs[name] = textinput.New() + // inputs[name].Placeholder = "MM/YY " // inputs[exp].CharLimit = 5 // inputs[name].Width = 5 - inputs[name].Prompt = "" - - inputs[namespace] = textinput.New() - inputs[namespace].Placeholder = "XXX" + // inputs[name].Prompt = "" + // inputs[name].SetValue(item.name) + + ti2 := textinput.New() + ti2.SetValue(item.namespace) + inputs = append(inputs, ti2) + // inputs[namespace] = textinput.New() + // inputs[namespace].Placeholder = "XXX" // inputs[cvv].CharLimit = 3 // inputs[namespace].Width = 5 - inputs[namespace].Prompt = "" + // inputs[namespace].Prompt = "" + + // inputs[namespace].SetValue(item.namespace) + ti3 := textinput.New() + ti3.SetValue(item.rule) + inputs = append(inputs, ti3) + // inputs[rule] = textinput.New() + // inputs[rule].SetValue(item.rule) + ti4 := textarea.New() + ti4.SetValue(item.description) + inputs = append(inputs, ti4) + // inputs[description] = textarea.New() + // inputs[description].SetValue(item.description) + ti5 := textinput.New() + ti5.SetValue(strings.Join(item.values, ",")) + inputs = append(inputs, ti5) + // inputs[values] = textinput.New() + // inputs[values].SetValue(strings.Join(item.values, ",")) m := model{ keys: names, inputs: inputs, @@ -76,34 +111,68 @@ func (m model) Init() tea.Cmd { func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd = make([]tea.Cmd, len(m.inputs)) - + 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(), ","), + } + saveModel, saveCmd := InitAttributeList([]AttributeItem{item}) switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { case tea.KeyShiftLeft: //, tea.KeyBackspace: - return InitAttributeList() + return InitAttributeList([]AttributeItem{}) + case tea.KeyShiftRight: + return saveModel, saveCmd case tea.KeyEnter: if m.focused == len(m.inputs)-1 { - return m, tea.Quit + return saveModel, saveCmd } m.nextInput() case tea.KeyCtrlC, tea.KeyEsc: return m, tea.Quit - case tea.KeyShiftTab, tea.KeyCtrlP: + case tea.KeyShiftTab, tea.KeyCtrlP, tea.KeyUp: m.prevInput() - case tea.KeyTab, tea.KeyCtrlN: + case tea.KeyTab, tea.KeyCtrlN, tea.KeyDown: m.nextInput() } for i := range m.inputs { - m.inputs[i].Blur() + // var ( + // tempInput textinput.Model + // tempArea textarea.Model + // ) + 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 + } + // tempInput := m.inputs[i].(textinput.Model) + // tempInput.Blur() + // m.inputs[i] = tempInput + // .Blur() } - m.inputs[m.focused].Focus() - case tea.WindowSizeMsg: - for i := range m.inputs { - m.inputs[i].Width = msg.Width + 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 } - // m.width = msg.Width - return m, nil + // m.inputs[m.focused].Focus() + // case tea.WindowSizeMsg: + // for i := range m.inputs { + // m.inputs[i].Width = msg.Width + // } + // return m, nil // We handle errors just like any other message case errMsg: m.err = msg @@ -111,7 +180,11 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } for i := range m.inputs { - m.inputs[i], cmds[i] = m.inputs[i].Update(msg) + if i == description { + m.inputs[i], cmds[i] = m.inputs[i].(textarea.Model).Update(msg) + } else { + m.inputs[i], cmds[i] = m.inputs[i].(textinput.Model).Update(msg) + } } return m, tea.Batch(cmds...) } @@ -125,23 +198,23 @@ func CreateEditFormat(num int) string { } func (m model) View() string { - content := fmt.Sprintf("Edit Attribute\n"+CreateEditFormat(len(m.inputs))+"\n%s", + return fmt.Sprintf("\n\n%s\n"+CreateEditFormat(len(m.inputs))+"\n%s", + "[Edit Attribute]", inputStyle.Width(len(m.keys[id])).Render(m.keys[id]), - m.inputs[id].View(), + m.inputs[id].(textinput.Model).View(), inputStyle.Width(len(m.keys[name])).Render(m.keys[name]), - wordwrap.String(m.inputs[name].View(), 15), + m.inputs[name].(textinput.Model).View(), inputStyle.Width(len(m.keys[namespace])).Render(m.keys[namespace]), - m.inputs[namespace].View(), + m.inputs[namespace].(textinput.Model).View(), inputStyle.Width(len(m.keys[rule])).Render(m.keys[rule]), - m.inputs[rule].View(), + m.inputs[rule].(textinput.Model).View(), inputStyle.Width(len(m.keys[description])).Render(m.keys[description]), - m.inputs[description].View(), + m.inputs[description].(textarea.Model).View(), inputStyle.Width(len(m.keys[values])).Render(m.keys[values]), - m.inputs[values].View(), - continueStyle.Render("Continue ->"), + // fmt.Sprintf("[%s]", m.inputs[values].View()), + m.inputs[values].(textinput.Model).View(), + continueStyle.Render("<>"), ) + "\n" - wrapped := wordwrap.String(content, 15) - return wrapped } // nextInput focuses the next input field diff --git a/tui/attributeList.go b/tui/attributeList.go index 5c296f1c..e820a347 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -36,22 +36,30 @@ func (m AttributeItem) Description() string { return m.description } -func InitAttributeList() (tea.Model, tea.Cmd) { +func InitAttributeList(items []AttributeItem) (tea.Model, tea.Cmd) { // TODO: fetch items from API m := AttributeList{} m.list = list.New([]list.Item{}, list.NewDefaultDelegate(), constants.WindowSize.Width, constants.WindowSize.Height) m.list.Title = "Attributes" - m.list.SetItems([]list.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"}, - }, - }) + if len(items) > 0 { + var items_ []list.Item + for _, item := range items { + items_ = append(items_, item) + } + m.list.SetItems(items_) + } else { + m.list.SetItems([]list.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"}, + }, + }) + } return m.Update(WindowMsg()) } diff --git a/tui/attributeView.go b/tui/attributeView.go index 16159793..a585c6f6 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -96,7 +96,7 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "ctrl+c", "q": return m, tea.Quit case "backspace": - return InitAttributeList() + return InitAttributeList([]AttributeItem{}) } case tea.WindowSizeMsg: From 6b88fef7a4a1cf348f578989a1146932c18b65b9 Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Tue, 6 Feb 2024 06:50:47 -0500 Subject: [PATCH 06/16] add viewport to edit view --- tui/attributeEdit.go | 133 +++++++++++++++++++++++++++++++++++++------ tui/common.go | 2 +- 2 files changed, 118 insertions(+), 17 deletions(-) diff --git a/tui/attributeEdit.go b/tui/attributeEdit.go index 5640f4ef..3897220b 100644 --- a/tui/attributeEdit.go +++ b/tui/attributeEdit.go @@ -8,8 +8,10 @@ import ( "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/tructl/tui/constants" ) @@ -37,11 +39,74 @@ var ( continueStyle = lipgloss.NewStyle().Foreground(cyan) ) -type model struct { - inputs []interface{} - focused int - err error - keys []string +const useHighPerformanceRenderer2 = false + +var ( + titleStyle2 = func() lipgloss.Style { + b := lipgloss.RoundedBorder() + b.Right = "├" + return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1) + }() + + infoStyle2 = func() lipgloss.Style { + b := lipgloss.RoundedBorder() + b.Left = "┤" + return titleStyle2.Copy().BorderStyle(b) + }() +) + +type AttributeEdit struct { + inputs []interface{} + focused int + err error + keys []string + title string + ready bool + viewport viewport.Model + width, height int +} + +func SetupViewport2(m AttributeEdit, msg tea.WindowSizeMsg) (AttributeEdit, []tea.Cmd) { + var ( + cmds []tea.Cmd + ) + headerHeight := lipgloss.Height(m.CreateHeader2()) + footerHeight := lipgloss.Height(m.CreateFooter2()) + 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 = useHighPerformanceRenderer2 + 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 + } + // var cmd tea.Cmd + // m.viewport, cmd = m.viewport.Update(tea.KeyPgDown) + // cmds = append(cmds, cmd) + // m.viewport.GotoBottom() + // cmds = append(cmds, viewport.Sync(m.viewport)) + if useHighPerformanceRenderer2 { + // 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)) + } + return m, cmds } func InitAttributeEdit(names []string, item AttributeItem) (tea.Model, tea.Cmd) { @@ -96,20 +161,21 @@ func InitAttributeEdit(names []string, item AttributeItem) (tea.Model, tea.Cmd) inputs = append(inputs, ti5) // inputs[values] = textinput.New() // inputs[values].SetValue(strings.Join(item.values, ",")) - m := model{ + m := AttributeEdit{ keys: names, inputs: inputs, focused: 0, err: nil, + title: "[Edit Attribute]", } return m.Update(WindowMsg()) } -func (m model) Init() tea.Cmd { +func (m AttributeEdit) Init() tea.Cmd { return textinput.Blink } -func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m AttributeEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd = make([]tea.Cmd, len(m.inputs)) item := AttributeItem{ id: m.inputs[id].(textinput.Model).Value(), @@ -123,6 +189,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { + // case tea.KeyDown: + // m.viewport.LineDown(1) + // return m, nil case tea.KeyShiftLeft: //, tea.KeyBackspace: return InitAttributeList([]AttributeItem{}) case tea.KeyShiftRight: @@ -136,8 +205,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Quit case tea.KeyShiftTab, tea.KeyCtrlP, tea.KeyUp: m.prevInput() + m.viewport.LineUp(1) case tea.KeyTab, tea.KeyCtrlN, tea.KeyDown: m.nextInput() + m.viewport.LineDown(1) + // case tea.KeyCtrlF: + // m.viewport = m.viewport.ViewUp() } for i := range m.inputs { // var ( @@ -172,6 +245,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // for i := range m.inputs { // m.inputs[i].Width = msg.Width // } + case tea.WindowSizeMsg: + m, cmds = SetupViewport2(m, msg) // return m, nil // We handle errors just like any other message case errMsg: @@ -179,27 +254,34 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } + var cmd tea.Cmd + for i := range m.inputs { if i == description { - m.inputs[i], cmds[i] = m.inputs[i].(textarea.Model).Update(msg) + m.inputs[i], cmd = m.inputs[i].(textarea.Model).Update(msg) } else { - m.inputs[i], cmds[i] = m.inputs[i].(textinput.Model).Update(msg) + m.inputs[i], cmd = m.inputs[i].(textinput.Model).Update(msg) } + cmds = append(cmds, 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 for i := 0; i < num; i++ { - format += "\n%s\n%s\n" + format += "\n%s %s\n" } return format } -func (m model) View() string { - return fmt.Sprintf("\n\n%s\n"+CreateEditFormat(len(m.inputs))+"\n%s", - "[Edit Attribute]", +func (m AttributeEdit) View() string { + content := fmt.Sprintf(CreateEditFormat(len(m.inputs))+"\n%s", + // "[Edit Attribute]", + // inputStyle.Width(len(m.keys[id])).Render(m.keys[id]), + // m.inputs[id].(textinput.Model).View(), 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]), @@ -215,18 +297,37 @@ func (m model) View() string { m.inputs[values].(textinput.Model).View(), continueStyle.Render("<>"), ) + "\n" + + 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.CreateHeader2(), m.viewport.View(), m.CreateFooter2()) } // nextInput focuses the next input field -func (m *model) nextInput() { +func (m *AttributeEdit) nextInput() { m.focused = (m.focused + 1) % len(m.inputs) } // prevInput focuses the previous input field -func (m *model) prevInput() { +func (m *AttributeEdit) prevInput() { m.focused-- // Wrap around if m.focused < 0 { m.focused = len(m.inputs) - 1 } } + +func (m AttributeEdit) CreateHeader2() string { + title := titleStyle2.Render(m.title) + line := CreateLine(m.viewport.Width, title) + return lipgloss.JoinHorizontal(lipgloss.Center, title, line) +} + +func (m AttributeEdit) CreateFooter2() string { + info := infoStyle2.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100)) + line := CreateLine(m.viewport.Width, info) + return lipgloss.JoinHorizontal(lipgloss.Center, line, info) +} diff --git a/tui/common.go b/tui/common.go index 2fc7234e..7d7dcb05 100644 --- a/tui/common.go +++ b/tui/common.go @@ -26,7 +26,7 @@ func StartTea() error { } m, _ := InitAppMenu() - constants.P = tea.NewProgram(m, tea.WithAltScreen()) + constants.P = tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseCellMotion()) if _, err := constants.P.Run(); err != nil { fmt.Println("Error running program:", err) os.Exit(1) From cdc2a9471729480311931ebe25f2f689983c3ec1 Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Tue, 6 Feb 2024 06:59:38 -0500 Subject: [PATCH 07/16] footer --- tui/attributeEdit.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tui/attributeEdit.go b/tui/attributeEdit.go index 3897220b..2b54f125 100644 --- a/tui/attributeEdit.go +++ b/tui/attributeEdit.go @@ -39,8 +39,6 @@ var ( continueStyle = lipgloss.NewStyle().Foreground(cyan) ) -const useHighPerformanceRenderer2 = false - var ( titleStyle2 = func() lipgloss.Style { b := lipgloss.RoundedBorder() @@ -82,7 +80,7 @@ func SetupViewport2(m AttributeEdit, msg tea.WindowSizeMsg) (AttributeEdit, []te // here. m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight) m.viewport.YPosition = headerHeight - m.viewport.HighPerformanceRendering = useHighPerformanceRenderer2 + m.viewport.HighPerformanceRendering = useHighPerformanceRenderer m.ready = true // This is only necessary for high performance rendering, which in @@ -99,7 +97,7 @@ func SetupViewport2(m AttributeEdit, msg tea.WindowSizeMsg) (AttributeEdit, []te // cmds = append(cmds, cmd) // m.viewport.GotoBottom() // cmds = append(cmds, viewport.Sync(m.viewport)) - if useHighPerformanceRenderer2 { + if useHighPerformanceRenderer { // Render (or re-render) the whole viewport. Necessary both to // initialize the viewport and when the window is resized. // @@ -278,10 +276,7 @@ func CreateEditFormat(num int) string { } func (m AttributeEdit) View() string { - content := fmt.Sprintf(CreateEditFormat(len(m.inputs))+"\n%s", - // "[Edit Attribute]", - // inputStyle.Width(len(m.keys[id])).Render(m.keys[id]), - // m.inputs[id].(textinput.Model).View(), + 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]), @@ -295,8 +290,8 @@ func (m AttributeEdit) View() string { inputStyle.Width(len(m.keys[values])).Render(m.keys[values]), // fmt.Sprintf("[%s]", m.inputs[values].View()), m.inputs[values].(textinput.Model).View(), - continueStyle.Render("<>"), - ) + "\n" + // continueStyle.Render("<>"), + ) if !m.ready { return "\n Initializing..." @@ -327,7 +322,7 @@ func (m AttributeEdit) CreateHeader2() string { } func (m AttributeEdit) CreateFooter2() string { - info := infoStyle2.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100)) + info := infoStyle2.Render(fmt.Sprintf("Discard: Shift + < | Save: Shift + > | Scroll: %3.f%%", m.viewport.ScrollPercent()*100)) line := CreateLine(m.viewport.Width, info) return lipgloss.JoinHorizontal(lipgloss.Center, line, info) } From 5a192a19be451a03155c9052c9ae9b544255d609 Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Tue, 6 Feb 2024 07:15:26 -0500 Subject: [PATCH 08/16] move edit view --- tui/attributeEdit.go | 328 ------------------------------------------- tui/attributeList.go | 26 +--- tui/attributeView.go | 200 ++++++++++++++++++++++---- 3 files changed, 181 insertions(+), 373 deletions(-) delete mode 100644 tui/attributeEdit.go diff --git a/tui/attributeEdit.go b/tui/attributeEdit.go deleted file mode 100644 index 2b54f125..00000000 --- a/tui/attributeEdit.go +++ /dev/null @@ -1,328 +0,0 @@ -package tui - -import ( - "fmt" - "strings" - - // "log" - - "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/tructl/tui/constants" -) - -// type ( -// errMsg error -// ) - -const ( - id = iota - name - namespace - rule - description - values -) - -const ( - hotPink = lipgloss.Color("#FF06B7") - darkGray = lipgloss.Color("#767676") - cyan = lipgloss.Color("#00FFFF") -) - -var ( - inputStyle = lipgloss.NewStyle().Foreground(constants.Magenta) - continueStyle = lipgloss.NewStyle().Foreground(cyan) -) - -var ( - titleStyle2 = func() lipgloss.Style { - b := lipgloss.RoundedBorder() - b.Right = "├" - return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1) - }() - - infoStyle2 = func() lipgloss.Style { - b := lipgloss.RoundedBorder() - b.Left = "┤" - return titleStyle2.Copy().BorderStyle(b) - }() -) - -type AttributeEdit struct { - inputs []interface{} - focused int - err error - keys []string - title string - ready bool - viewport viewport.Model - width, height int -} - -func SetupViewport2(m AttributeEdit, msg tea.WindowSizeMsg) (AttributeEdit, []tea.Cmd) { - var ( - cmds []tea.Cmd - ) - headerHeight := lipgloss.Height(m.CreateHeader2()) - footerHeight := lipgloss.Height(m.CreateFooter2()) - 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 - } - // var cmd tea.Cmd - // m.viewport, cmd = m.viewport.Update(tea.KeyPgDown) - // cmds = append(cmds, cmd) - // m.viewport.GotoBottom() - // cmds = append(cmds, viewport.Sync(m.viewport)) - 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)) - } - return m, cmds -} - -func InitAttributeEdit(names []string, item AttributeItem) (tea.Model, tea.Cmd) { - // inputs := make([]interface{}, len(names)) //= [make([]tea.Model, len(names)) - var inputs []interface{} - // var inputs []textinput.Model = make([]textinput.Model, len(names)) - // inputs = append(inputs, textinput.New()) - ti0 := textinput.New() - ti0.Focus() - ti0.SetValue(item.id) - inputs = append(inputs, ti0) - - // inputs[id] = textinput.New() - // inputs[id].Placeholder = "4505 **** **** 1234" - // inputs[id].Focus() - // inputs[id].SetValue(item.id) - // inputs[ccn].CharLimit = 20 - // inputs[id].Width = 30 - // inputs[id].Prompt = "" - ti1 := textinput.New() - ti1.SetValue(item.name) - inputs = append(inputs, ti1) - // inputs[name] = textinput.New() - // inputs[name].Placeholder = "MM/YY " - // inputs[exp].CharLimit = 5 - // inputs[name].Width = 5 - // inputs[name].Prompt = "" - // inputs[name].SetValue(item.name) - - ti2 := textinput.New() - ti2.SetValue(item.namespace) - inputs = append(inputs, ti2) - // inputs[namespace] = textinput.New() - // inputs[namespace].Placeholder = "XXX" - // inputs[cvv].CharLimit = 3 - // inputs[namespace].Width = 5 - // inputs[namespace].Prompt = "" - - // inputs[namespace].SetValue(item.namespace) - ti3 := textinput.New() - ti3.SetValue(item.rule) - inputs = append(inputs, ti3) - // inputs[rule] = textinput.New() - // inputs[rule].SetValue(item.rule) - ti4 := textarea.New() - ti4.SetValue(item.description) - inputs = append(inputs, ti4) - // inputs[description] = textarea.New() - // inputs[description].SetValue(item.description) - ti5 := textinput.New() - ti5.SetValue(strings.Join(item.values, ",")) - inputs = append(inputs, ti5) - // inputs[values] = textinput.New() - // inputs[values].SetValue(strings.Join(item.values, ",")) - m := AttributeEdit{ - keys: names, - inputs: inputs, - focused: 0, - err: nil, - title: "[Edit Attribute]", - } - return m.Update(WindowMsg()) -} - -func (m AttributeEdit) Init() tea.Cmd { - return textinput.Blink -} - -func (m AttributeEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmds []tea.Cmd = make([]tea.Cmd, len(m.inputs)) - 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(), ","), - } - saveModel, saveCmd := InitAttributeList([]AttributeItem{item}) - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.Type { - // case tea.KeyDown: - // m.viewport.LineDown(1) - // return m, nil - case tea.KeyShiftLeft: //, tea.KeyBackspace: - return InitAttributeList([]AttributeItem{}) - case tea.KeyShiftRight: - return saveModel, saveCmd - case tea.KeyEnter: - if m.focused == len(m.inputs)-1 { - return saveModel, saveCmd - } - m.nextInput() - case tea.KeyCtrlC, tea.KeyEsc: - return m, tea.Quit - case tea.KeyShiftTab, tea.KeyCtrlP, tea.KeyUp: - m.prevInput() - m.viewport.LineUp(1) - case tea.KeyTab, tea.KeyCtrlN, tea.KeyDown: - m.nextInput() - m.viewport.LineDown(1) - // case tea.KeyCtrlF: - // m.viewport = m.viewport.ViewUp() - } - for i := range m.inputs { - // var ( - // tempInput textinput.Model - // tempArea textarea.Model - // ) - 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 - } - // tempInput := m.inputs[i].(textinput.Model) - // tempInput.Blur() - // m.inputs[i] = tempInput - // .Blur() - } - 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 - } - // m.inputs[m.focused].Focus() - // case tea.WindowSizeMsg: - // for i := range m.inputs { - // m.inputs[i].Width = msg.Width - // } - case tea.WindowSizeMsg: - m, cmds = SetupViewport2(m, msg) - // return m, nil - // We handle errors just like any other message - case errMsg: - m.err = msg - return m, nil - } - - var cmd tea.Cmd - - 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) - } - m.viewport, cmd = m.viewport.Update(msg) - cmds = append(cmds, cmd) - return m, tea.Batch(cmds...) -} - -func CreateEditFormat(num int) string { - var format string - for i := 0; i < num; i++ { - format += "\n%s %s\n" - } - return format -} - -func (m AttributeEdit) 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]), - // fmt.Sprintf("[%s]", m.inputs[values].View()), - m.inputs[values].(textinput.Model).View(), - // continueStyle.Render("<>"), - ) - - 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.CreateHeader2(), m.viewport.View(), m.CreateFooter2()) -} - -// nextInput focuses the next input field -func (m *AttributeEdit) nextInput() { - m.focused = (m.focused + 1) % len(m.inputs) -} - -// prevInput focuses the previous input field -func (m *AttributeEdit) prevInput() { - m.focused-- - // Wrap around - if m.focused < 0 { - m.focused = len(m.inputs) - 1 - } -} - -func (m AttributeEdit) CreateHeader2() string { - title := titleStyle2.Render(m.title) - line := CreateLine(m.viewport.Width, title) - return lipgloss.JoinHorizontal(lipgloss.Center, title, line) -} - -func (m AttributeEdit) CreateFooter2() string { - info := infoStyle2.Render(fmt.Sprintf("Discard: Shift + < | Save: Shift + > | Scroll: %3.f%%", m.viewport.ScrollPercent()*100)) - line := CreateLine(m.viewport.Width, info) - return lipgloss.JoinHorizontal(lipgloss.Center, line, info) -} diff --git a/tui/attributeList.go b/tui/attributeList.go index e820a347..d1a333af 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -1,12 +1,9 @@ package tui import ( - "fmt" - "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/muesli/reflow/wordwrap" "github.com/opentdf/tructl/tui/constants" ) @@ -101,27 +98,14 @@ func (m AttributeList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // show the add attribute form // InitAttributeCreateView() return m, nil - case "e": - item := m.list.Items()[0].(AttributeItem) - attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} - return InitAttributeEdit(attr_keys, item) + // case "e": + // item := m.list.Items()[0].(AttributeItem) + // attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} + // return InitAttributeView(attr_keys, item) case "enter": item := m.list.Items()[0].(AttributeItem) attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} - content := fmt.Sprintf( - CreateViewFormat(len(attr_keys)), - StyleAttr(attr_keys[0]), item.id, - StyleAttr(attr_keys[1]), item.name, - StyleAttr(attr_keys[2]), item.namespace, - StyleAttr(attr_keys[3]), item.rule, - StyleAttr(attr_keys[4]), item.description, - StyleAttr(attr_keys[5]), item.values, - ) - wrapped := wordwrap.String(content, m.width) - am := AttributeView{} - am.title = "Attribute" - am.content = wrapped - return am.Update(WindowMsg()) + return InitAttributeView(attr_keys, item) } } return m, nil diff --git a/tui/attributeView.go b/tui/attributeView.go index a585c6f6..ccd2f152 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -4,21 +4,43 @@ import ( "fmt" "strings" + // "log" + + "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/tructl/tui/constants" +) + +// type ( +// errMsg error +// ) + +const ( + id = iota + name + namespace + rule + description + values +) + +const ( + hotPink = lipgloss.Color("#FF06B7") + darkGray = lipgloss.Color("#767676") + cyan = lipgloss.Color("#00FFFF") ) -// You generally won't need this unless you're processing stuff with -// complicated ANSI escape sequences. Turn it on if you notice flickering. -// -// Also keep in mind that high performance rendering only works for programs -// that use the full size of the terminal. We're enabling that below with -// tea.EnterAltScreen(). -// Setting this to true is causing issues and preventing items from rendering const useHighPerformanceRenderer = false +var ( + inputStyle = lipgloss.NewStyle().Foreground(constants.Magenta) + continueStyle = lipgloss.NewStyle().Foreground(cyan) +) + var ( titleStyle = func() lipgloss.Style { b := lipgloss.RoundedBorder() @@ -34,11 +56,14 @@ var ( ) type AttributeView struct { - width, height int - content string + inputs []interface{} + focused int + err error + keys []string title string ready bool viewport viewport.Model + width, height int } func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea.Cmd) { @@ -69,7 +94,11 @@ func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea m.viewport.Width = msg.Width m.viewport.Height = msg.Height - verticalMarginHeight } - + // var cmd tea.Cmd + // m.viewport, cmd = m.viewport.Update(tea.KeyPgDown) + // cmds = append(cmds, cmd) + // m.viewport.GotoBottom() + // cmds = append(cmds, viewport.Sync(m.viewport)) if useHighPerformanceRenderer { // Render (or re-render) the whole viewport. Necessary both to // initialize the viewport and when the window is resized. @@ -80,45 +109,168 @@ func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea return m, cmds } +func InitAttributeView(names []string, item AttributeItem) (tea.Model, tea.Cmd) { + var inputs []interface{} + + 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.SetValue(item.description) + inputs = append(inputs, ti4) + + ti5 := textinput.New() + ti5.SetValue(strings.Join(item.values, ",")) + inputs = append(inputs, ti5) + + m := AttributeView{ + keys: names, + inputs: inputs, + focused: 0, + err: nil, + title: "[Edit Attribute]", + } + return m.Update(WindowMsg()) +} + func (m AttributeView) Init() tea.Cmd { - return nil + return textinput.Blink } func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var ( - cmd tea.Cmd - cmds []tea.Cmd - ) - + var cmds []tea.Cmd = make([]tea.Cmd, len(m.inputs)) + 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(), ","), + } + saveModel, saveCmd := InitAttributeList([]AttributeItem{item}) switch msg := msg.(type) { case tea.KeyMsg: - switch msg.String() { - case "ctrl+c", "q": - return m, tea.Quit - case "backspace": + switch msg.Type { + case tea.KeyShiftLeft: //, tea.KeyBackspace: return InitAttributeList([]AttributeItem{}) + case tea.KeyShiftRight: + return saveModel, saveCmd + case tea.KeyEnter: + // if m.focused == len(m.inputs)-1 { + // return saveModel, saveCmd + // } + m.nextInput() + case tea.KeyCtrlC, tea.KeyEsc: + return m, tea.Quit + case tea.KeyShiftTab, tea.KeyCtrlP, tea.KeyUp: + m.prevInput() + case tea.KeyTab, tea.KeyCtrlN, tea.KeyDown: + m.nextInput() + } + 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 + return m, nil } - // Handle keyboard and mouse events in the viewport + var cmd tea.Cmd + + 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) + } m.viewport, cmd = m.viewport.Update(msg) cmds = append(cmds, cmd) - return m, tea.Batch(cmds...) } +func CreateEditFormat(num int) string { + var format string + for i := 0; i < num; i++ { + format += "\n%s %s\n" + } + return format +} + 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(m.content, m.width) + 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))) } @@ -130,7 +282,7 @@ func (m AttributeView) CreateHeader() string { } func (m AttributeView) CreateFooter() string { - info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100)) + info := infoStyle.Render(fmt.Sprintf("discard: shift + left arrow | save: shift + right arrow | scroll: %3.f%%", m.viewport.ScrollPercent()*100)) line := CreateLine(m.viewport.Width, info) return lipgloss.JoinHorizontal(lipgloss.Center, line, info) } From aad811b61247348d3586e24c03f35ab6e50f4487 Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Tue, 6 Feb 2024 16:41:39 -0500 Subject: [PATCH 09/16] more progress --- tui/appMenu.go | 29 ++++++++++++++++++++++++++++- tui/attributeList.go | 38 ++++++++++++++++++++------------------ tui/attributeView.go | 15 +++++++++------ 3 files changed, 57 insertions(+), 25 deletions(-) diff --git a/tui/appMenu.go b/tui/appMenu.go index f2b672d2..ae53c7a9 100644 --- a/tui/appMenu.go +++ b/tui/appMenu.go @@ -75,7 +75,34 @@ func (m AppMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "enter": switch m.list.SelectedItem().(AppMenuItem).id { case attributeMenu: - return InitAttributeList([]AttributeItem{}) + // l := list.New([]list.Item{}, list.NewDefaultDelegate(), constants.WindowSize.Width, constants.WindowSize.Height) + // var al AttributeList + al, cmd := InitAttributeList([]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 = al.(AttributeList) + // l := al.(AttributeList).list + // l.SetItems([]list.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 = al.(AttributeList) + // al.list = l + // m := al.(tea.Model) + return al, cmd } } } diff --git a/tui/attributeList.go b/tui/attributeList.go index d1a333af..777d591c 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -40,22 +40,22 @@ func InitAttributeList(items []AttributeItem) (tea.Model, tea.Cmd) { m.list = list.New([]list.Item{}, list.NewDefaultDelegate(), constants.WindowSize.Width, constants.WindowSize.Height) m.list.Title = "Attributes" if len(items) > 0 { - var items_ []list.Item + var newItems []list.Item for _, item := range items { - items_ = append(items_, item) + newItems = append(newItems, item) } - m.list.SetItems(items_) + m.list.SetItems(newItems) } else { - m.list.SetItems([]list.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"}, - }, - }) + // m.list.SetItems([]list.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"}, + // }, + // }) } return m.Update(WindowMsg()) } @@ -79,7 +79,7 @@ func CreateViewFormat(num int) string { } func (m AttributeList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - + attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} switch msg := msg.(type) { case tea.WindowSizeMsg: constants.WindowSize = msg @@ -97,15 +97,17 @@ func (m AttributeList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "c": // show the add attribute form // InitAttributeCreateView() - return m, nil + // return m, nil + // attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} + return InitAttributeView(attr_keys, AttributeItem{}, "[Create Attribute]") // case "e": // item := m.list.Items()[0].(AttributeItem) // attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} // return InitAttributeView(attr_keys, item) - case "enter": + case "enter", "e": item := m.list.Items()[0].(AttributeItem) - attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} - return InitAttributeView(attr_keys, item) + // attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} + return InitAttributeView(attr_keys, item, "[Edit Attribute]") } } return m, nil diff --git a/tui/attributeView.go b/tui/attributeView.go index ccd2f152..7d6294e2 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -64,6 +64,7 @@ type AttributeView struct { ready bool viewport viewport.Model width, height int + original AttributeItem } func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea.Cmd) { @@ -109,7 +110,7 @@ func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea return m, cmds } -func InitAttributeView(names []string, item AttributeItem) (tea.Model, tea.Cmd) { +func InitAttributeView(names []string, item AttributeItem, title string) (tea.Model, tea.Cmd) { var inputs []interface{} ti0 := textinput.New() @@ -138,11 +139,12 @@ func InitAttributeView(names []string, item AttributeItem) (tea.Model, tea.Cmd) inputs = append(inputs, ti5) m := AttributeView{ - keys: names, - inputs: inputs, - focused: 0, - err: nil, - title: "[Edit Attribute]", + keys: names, + inputs: inputs, + focused: 0, + err: nil, + title: title, + original: item, } return m.Update(WindowMsg()) } @@ -165,6 +167,7 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { + case tea.KeyShiftLeft: //, tea.KeyBackspace: return InitAttributeList([]AttributeItem{}) case tea.KeyShiftRight: From e44ec933ecf80638b1346e95b7d1ede7d5c4ba1c Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Tue, 6 Feb 2024 17:02:58 -0500 Subject: [PATCH 10/16] change init func sig --- tui/attributeList.go | 16 +++------------- tui/attributeView.go | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tui/attributeList.go b/tui/attributeList.go index 777d591c..bc045016 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -79,7 +79,7 @@ func CreateViewFormat(num int) string { } func (m AttributeList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} + switch msg := msg.(type) { case tea.WindowSizeMsg: constants.WindowSize = msg @@ -95,19 +95,9 @@ func (m AttributeList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { am.list.Select(1) return am.Update(WindowMsg()) case "c": - // show the add attribute form - // InitAttributeCreateView() - // return m, nil - // attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} - return InitAttributeView(attr_keys, AttributeItem{}, "[Create Attribute]") - // case "e": - // item := m.list.Items()[0].(AttributeItem) - // attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} - // return InitAttributeView(attr_keys, item) + return InitAttributeView(m.list.Items(), len(m.list.Items())) case "enter", "e": - item := m.list.Items()[0].(AttributeItem) - // attr_keys := []string{"Id", "Name", "Namespace", "Rule", "Description", "Values"} - return InitAttributeView(attr_keys, item, "[Edit Attribute]") + return InitAttributeView(m.list.Items(), m.list.Index()) } } return m, nil diff --git a/tui/attributeView.go b/tui/attributeView.go index 7d6294e2..2279c5ed 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -6,6 +6,7 @@ import ( // "log" + "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/bubbles/viewport" @@ -110,8 +111,17 @@ func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea return m, cmds } -func InitAttributeView(names []string, item AttributeItem, title string) (tea.Model, tea.Cmd) { +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) + } ti0 := textinput.New() ti0.Focus() @@ -139,7 +149,7 @@ func InitAttributeView(names []string, item AttributeItem, title string) (tea.Mo inputs = append(inputs, ti5) m := AttributeView{ - keys: names, + keys: attr_keys, inputs: inputs, focused: 0, err: nil, From 6c7db8b177001776901c3f57b67d72b6488aace7 Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Tue, 6 Feb 2024 21:00:46 -0500 Subject: [PATCH 11/16] more progress with going back --- tui/attributeView.go | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tui/attributeView.go b/tui/attributeView.go index 2279c5ed..1230bab4 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -65,7 +65,7 @@ type AttributeView struct { ready bool viewport viewport.Model width, height int - original AttributeItem + list []AttributeItem } func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea.Cmd) { @@ -147,14 +147,18 @@ func InitAttributeView(items []list.Item, idx int) (tea.Model, tea.Cmd) { ti5 := textinput.New() ti5.SetValue(strings.Join(item.values, ",")) inputs = append(inputs, ti5) + var attrItems []AttributeItem + for _, item := range items { + attrItems = append(attrItems, item.(AttributeItem)) + } m := AttributeView{ - keys: attr_keys, - inputs: inputs, - focused: 0, - err: nil, - title: title, - original: item, + keys: attr_keys, + inputs: inputs, + focused: 0, + err: nil, + title: title, + list: attrItems, } return m.Update(WindowMsg()) } @@ -173,15 +177,16 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { description: m.inputs[description].(textarea.Model).Value(), values: strings.Split(m.inputs[values].(textinput.Model).Value(), ","), } - saveModel, saveCmd := InitAttributeList([]AttributeItem{item}) + // saveModel, saveCmd := InitAttributeList([]AttributeItem{item}) switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { - case tea.KeyShiftLeft: //, tea.KeyBackspace: - return InitAttributeList([]AttributeItem{}) + return InitAttributeList(m.list) case tea.KeyShiftRight: - return saveModel, saveCmd + // return saveModel, saveCmd + // + return InitAttributeList() case tea.KeyEnter: // if m.focused == len(m.inputs)-1 { // return saveModel, saveCmd From 5e873ec6f67ae98b6eae6b1c8a4bc3f60f5a9f6c Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Tue, 6 Feb 2024 21:27:38 -0500 Subject: [PATCH 12/16] create and edit views done --- tui/appMenu.go | 19 +++++++++--------- tui/attributeList.go | 47 ++++++++++++++++++++++++++------------------ tui/attributeView.go | 26 +++++++++++++++--------- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/tui/appMenu.go b/tui/appMenu.go index ae53c7a9..51d69228 100644 --- a/tui/appMenu.go +++ b/tui/appMenu.go @@ -77,16 +77,15 @@ func (m AppMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case attributeMenu: // l := list.New([]list.Item{}, list.NewDefaultDelegate(), constants.WindowSize.Width, constants.WindowSize.Height) // var al AttributeList - al, cmd := InitAttributeList([]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"}, - }, - }) + 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}) // // al = al.(AttributeList) // l := al.(AttributeList).list // l.SetItems([]list.Item{ diff --git a/tui/attributeList.go b/tui/attributeList.go index bc045016..72e2593d 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -33,30 +33,31 @@ func (m AttributeItem) Description() string { return m.description } -func InitAttributeList(items []AttributeItem) (tea.Model, tea.Cmd) { +func InitAttributeList(items []list.Item) (tea.Model, tea.Cmd) { // TODO: fetch items from API m := AttributeList{} m.list = list.New([]list.Item{}, list.NewDefaultDelegate(), constants.WindowSize.Width, constants.WindowSize.Height) m.list.Title = "Attributes" - if len(items) > 0 { - var newItems []list.Item - for _, item := range items { - newItems = append(newItems, item) - } - m.list.SetItems(newItems) - } else { - // m.list.SetItems([]list.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"}, - // }, - // }) - } + m.list.SetItems(items) + // if len(items) > 0 { + // var newItems []list.Item + // for _, item := range items { + // newItems = append(newItems, item) + // } + // m.list.SetItems(newItems) + // } else { + // // m.list.SetItems([]list.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"}, + // // }, + // // }) + // } return m.Update(WindowMsg()) } @@ -94,6 +95,14 @@ func (m AttributeList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { am, _ := InitAppMenu() am.list.Select(1) return am.Update(WindowMsg()) + case "down", "j": + if m.list.Index() < len(m.list.Items())-1 { + m.list.Select(m.list.Index() + 1) + } + case "up", "k": + if m.list.Index() > 0 { + m.list.Select(m.list.Index() - 1) + } case "c": return InitAttributeView(m.list.Items(), len(m.list.Items())) case "enter", "e": diff --git a/tui/attributeView.go b/tui/attributeView.go index 1230bab4..18ea7855 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -65,7 +65,8 @@ type AttributeView struct { ready bool viewport viewport.Model width, height int - list []AttributeItem + list []list.Item + idx int } func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea.Cmd) { @@ -147,10 +148,10 @@ func InitAttributeView(items []list.Item, idx int) (tea.Model, tea.Cmd) { ti5 := textinput.New() ti5.SetValue(strings.Join(item.values, ",")) inputs = append(inputs, ti5) - var attrItems []AttributeItem - for _, item := range items { - attrItems = append(attrItems, item.(AttributeItem)) - } + // var attrItems []AttributeItem + // for _, item := range items { + // attrItems = append(attrItems, item.(AttributeItem)) + // } m := AttributeView{ keys: attr_keys, @@ -158,7 +159,8 @@ func InitAttributeView(items []list.Item, idx int) (tea.Model, tea.Cmd) { focused: 0, err: nil, title: title, - list: attrItems, + list: items, + idx: idx, } return m.Update(WindowMsg()) } @@ -168,7 +170,7 @@ func (m AttributeView) Init() tea.Cmd { } func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmds []tea.Cmd = make([]tea.Cmd, len(m.inputs)) + var cmds []tea.Cmd // = make([]tea.Cmd, len(m.inputs)) item := AttributeItem{ id: m.inputs[id].(textinput.Model).Value(), name: m.inputs[name].(textinput.Model).Value(), @@ -185,8 +187,14 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return InitAttributeList(m.list) case tea.KeyShiftRight: // return saveModel, saveCmd - // - return InitAttributeList() + if m.idx < len(m.list) { + m.list[m.idx] = list.Item(item) + } else { + m.list = append(m.list, list.Item(item)) + } + + return InitAttributeList(m.list) + // return InitAttributeList() case tea.KeyEnter: // if m.focused == len(m.inputs)-1 { // return saveModel, saveCmd From c026e54194f9a786267babf7d8be09ca3355acbc Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Wed, 7 Feb 2024 14:39:46 -0500 Subject: [PATCH 13/16] add footer --- tui/attributeView.go | 87 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 17 deletions(-) diff --git a/tui/attributeView.go b/tui/attributeView.go index 18ea7855..99ec96c4 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -56,6 +56,11 @@ var ( }() ) +// type TextWrapper struct{} + +// func View(m TextWrapper) {} +// func Value(m TextWrapper) {} + type AttributeView struct { inputs []interface{} focused int @@ -67,6 +72,7 @@ type AttributeView struct { width, height int list []list.Item idx int + editMode bool } func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea.Cmd) { @@ -77,6 +83,9 @@ func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea footerHeight := lipgloss.Height(m.CreateFooter()) verticalMarginHeight := headerHeight + footerHeight m.width = msg.Width + // area := m.inputs[description].(textarea.Model) + // area.SetWidth(msg.Width) + // m.inputs[description] = area 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 @@ -142,6 +151,7 @@ func InitAttributeView(items []list.Item, idx int) (tea.Model, tea.Cmd) { inputs = append(inputs, ti3) ti4 := textarea.New() + ti4.ShowLineNumbers = false ti4.SetValue(item.description) inputs = append(inputs, ti4) @@ -154,13 +164,14 @@ func InitAttributeView(items []list.Item, idx int) (tea.Model, tea.Cmd) { // } m := AttributeView{ - keys: attr_keys, - inputs: inputs, - focused: 0, - err: nil, - title: title, - list: items, - idx: idx, + keys: attr_keys, + inputs: inputs, + focused: 0, + err: nil, + title: title, + list: items, + idx: idx, + editMode: idx >= len(items), } return m.Update(WindowMsg()) } @@ -169,8 +180,18 @@ func (m AttributeView) Init() tea.Cmd { return textinput.Blink } +func (m AttributeView) ChangeMode() AttributeView { + if m.idx < len(m.list) { + m.editMode = !m.editMode + } else { + m.editMode = true + } + return m +} + 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(), @@ -201,12 +222,31 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // } m.nextInput() case tea.KeyCtrlC, tea.KeyEsc: - return m, tea.Quit + if m.editMode { + m = m.ChangeMode() + } 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) @@ -237,14 +277,15 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } var cmd tea.Cmd - - 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) + if m.editMode || m.idx >= len(m.list) && !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) } - cmds = append(cmds, cmd) } m.viewport, cmd = m.viewport.Update(msg) cmds = append(cmds, cmd) @@ -253,8 +294,14 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func CreateEditFormat(num int) string { var format string + prefix := "\n%s" + postfix := "%s\n" + var middle string for i := 0; i < num; i++ { - format += "\n%s %s\n" + if i == description { + middle = "\n" + } + format += prefix + middle + postfix } return format } @@ -308,7 +355,13 @@ func (m AttributeView) CreateHeader() string { } func (m AttributeView) CreateFooter() string { - info := infoStyle.Render(fmt.Sprintf("discard: shift + left arrow | save: shift + right arrow | scroll: %3.f%%", m.viewport.ScrollPercent()*100)) + var prefix string + if m.editMode { + prefix = "discard: shift + left arrow | save: shift + right arrow" + } else { + prefix = "enter edit mode: i" + } + 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) } From 9a943880466ce170b724e8bf634f155758945fca Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Wed, 7 Feb 2024 15:02:41 -0500 Subject: [PATCH 14/16] delete view --- tui/attributeList.go | 7 +++++++ tui/attributeView.go | 25 +++++++++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/tui/attributeList.go b/tui/attributeList.go index 72e2593d..c6a0e088 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -107,6 +107,13 @@ func (m AttributeList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return InitAttributeView(m.list.Items(), len(m.list.Items())) case "enter", "e": return InitAttributeView(m.list.Items(), m.list.Index()) + case "ctrl+d": + m.list.RemoveItem(m.list.Index()) + newIndex := m.list.Index() - 1 + if newIndex < 0 { + newIndex = 0 + } + m.list.Select(newIndex) } } return m, nil diff --git a/tui/attributeView.go b/tui/attributeView.go index 99ec96c4..50f731c8 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -180,12 +180,17 @@ 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 { - if m.idx < len(m.list) { - m.editMode = !m.editMode - } else { - m.editMode = true - } + // if m.idx < len(m.list) { + // m.editMode = !m.editMode + // } else { + // m.editMode = true + // } + m.editMode = m.IsNew() || !m.editMode return m } @@ -208,7 +213,7 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return InitAttributeList(m.list) case tea.KeyShiftRight: // return saveModel, saveCmd - if m.idx < len(m.list) { + if !m.IsNew() { m.list[m.idx] = list.Item(item) } else { m.list = append(m.list, list.Item(item)) @@ -223,7 +228,7 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.nextInput() case tea.KeyCtrlC, tea.KeyEsc: if m.editMode { - m = m.ChangeMode() + m.editMode = false } else { return m, tea.Quit } @@ -277,7 +282,7 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } var cmd tea.Cmd - if m.editMode || m.idx >= len(m.list) && !editing { + 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) @@ -356,10 +361,10 @@ func (m AttributeView) CreateHeader() string { func (m AttributeView) CreateFooter() string { var prefix string - if m.editMode { + if m.editMode || m.IsNew() { prefix = "discard: shift + left arrow | save: shift + right arrow" } else { - prefix = "enter edit mode: i" + 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) From 94246465c461115518c241879d3d89d337c6baba Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Wed, 7 Feb 2024 15:20:25 -0500 Subject: [PATCH 15/16] selecting --- tui/appMenu.go | 2 +- tui/attributeList.go | 5 ++++- tui/attributeView.go | 8 ++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/tui/appMenu.go b/tui/appMenu.go index 51d69228..0fcb1118 100644 --- a/tui/appMenu.go +++ b/tui/appMenu.go @@ -85,7 +85,7 @@ func (m AppMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 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}) + al, cmd := InitAttributeList([]list.Item{item}, 0) // // al = al.(AttributeList) // l := al.(AttributeList).list // l.SetItems([]list.Item{ diff --git a/tui/attributeList.go b/tui/attributeList.go index c6a0e088..5e350f1f 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -33,11 +33,14 @@ func (m AttributeItem) Description() string { return m.description } -func InitAttributeList(items []list.Item) (tea.Model, tea.Cmd) { +func InitAttributeList(items []list.Item, selectIdx int) (tea.Model, tea.Cmd) { // TODO: fetch items from API m := AttributeList{} m.list = list.New([]list.Item{}, list.NewDefaultDelegate(), constants.WindowSize.Width, constants.WindowSize.Height) + if selectIdx > 0 { + m.list.Select(selectIdx) + } m.list.Title = "Attributes" m.list.SetItems(items) // if len(items) > 0 { diff --git a/tui/attributeView.go b/tui/attributeView.go index 50f731c8..3159fe00 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -210,7 +210,11 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch msg.Type { case tea.KeyShiftLeft: //, tea.KeyBackspace: - return InitAttributeList(m.list) + listIdx := m.idx + if m.IsNew() { + listIdx -= 1 + } + return InitAttributeList(m.list, listIdx) case tea.KeyShiftRight: // return saveModel, saveCmd if !m.IsNew() { @@ -219,7 +223,7 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.list = append(m.list, list.Item(item)) } - return InitAttributeList(m.list) + return InitAttributeList(m.list, m.idx) // return InitAttributeList() case tea.KeyEnter: // if m.focused == len(m.inputs)-1 { From 78c6ec2876737ac558f1dc9232abd88a248f50a2 Mon Sep 17 00:00:00 2001 From: Krish Suchak Date: Wed, 7 Feb 2024 15:32:58 -0500 Subject: [PATCH 16/16] clean up --- tui/appMenu.go | 17 ----------------- tui/attributeList.go | 18 ------------------ tui/attributeView.go | 33 +++------------------------------ 3 files changed, 3 insertions(+), 65 deletions(-) diff --git a/tui/appMenu.go b/tui/appMenu.go index 0fcb1118..a5441222 100644 --- a/tui/appMenu.go +++ b/tui/appMenu.go @@ -75,8 +75,6 @@ func (m AppMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "enter": switch m.list.SelectedItem().(AppMenuItem).id { case attributeMenu: - // l := list.New([]list.Item{}, list.NewDefaultDelegate(), constants.WindowSize.Width, constants.WindowSize.Height) - // var al AttributeList item := AttributeItem{ id: "8a6755f2-efa8-4758-b893-af9a488e0bea", namespace: "demo.com", @@ -86,21 +84,6 @@ func (m AppMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { values: []string{"USA", "GBR"}, } al, cmd := InitAttributeList([]list.Item{item}, 0) - // // al = al.(AttributeList) - // l := al.(AttributeList).list - // l.SetItems([]list.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 = al.(AttributeList) - // al.list = l - // m := al.(tea.Model) return al, cmd } } diff --git a/tui/attributeList.go b/tui/attributeList.go index 5e350f1f..f6e5b875 100644 --- a/tui/attributeList.go +++ b/tui/attributeList.go @@ -43,24 +43,6 @@ func InitAttributeList(items []list.Item, selectIdx int) (tea.Model, tea.Cmd) { } m.list.Title = "Attributes" m.list.SetItems(items) - // if len(items) > 0 { - // var newItems []list.Item - // for _, item := range items { - // newItems = append(newItems, item) - // } - // m.list.SetItems(newItems) - // } else { - // // m.list.SetItems([]list.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"}, - // // }, - // // }) - // } return m.Update(WindowMsg()) } diff --git a/tui/attributeView.go b/tui/attributeView.go index 3159fe00..4e4ac39c 100644 --- a/tui/attributeView.go +++ b/tui/attributeView.go @@ -4,8 +4,6 @@ import ( "fmt" "strings" - // "log" - "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textinput" @@ -16,10 +14,6 @@ import ( "github.com/opentdf/tructl/tui/constants" ) -// type ( -// errMsg error -// ) - const ( id = iota name @@ -83,9 +77,7 @@ func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea footerHeight := lipgloss.Height(m.CreateFooter()) verticalMarginHeight := headerHeight + footerHeight m.width = msg.Width - // area := m.inputs[description].(textarea.Model) - // area.SetWidth(msg.Width) - // m.inputs[description] = area + 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 @@ -106,11 +98,7 @@ func SetupViewport(m AttributeView, msg tea.WindowSizeMsg) (AttributeView, []tea m.viewport.Width = msg.Width m.viewport.Height = msg.Height - verticalMarginHeight } - // var cmd tea.Cmd - // m.viewport, cmd = m.viewport.Update(tea.KeyPgDown) - // cmds = append(cmds, cmd) - // m.viewport.GotoBottom() - // cmds = append(cmds, viewport.Sync(m.viewport)) + if useHighPerformanceRenderer { // Render (or re-render) the whole viewport. Necessary both to // initialize the viewport and when the window is resized. @@ -158,10 +146,6 @@ func InitAttributeView(items []list.Item, idx int) (tea.Model, tea.Cmd) { ti5 := textinput.New() ti5.SetValue(strings.Join(item.values, ",")) inputs = append(inputs, ti5) - // var attrItems []AttributeItem - // for _, item := range items { - // attrItems = append(attrItems, item.(AttributeItem)) - // } m := AttributeView{ keys: attr_keys, @@ -185,11 +169,6 @@ func (m AttributeView) IsNew() bool { } func (m AttributeView) ChangeMode() AttributeView { - // if m.idx < len(m.list) { - // m.editMode = !m.editMode - // } else { - // m.editMode = true - // } m.editMode = m.IsNew() || !m.editMode return m } @@ -205,18 +184,16 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { description: m.inputs[description].(textarea.Model).Value(), values: strings.Split(m.inputs[values].(textinput.Model).Value(), ","), } - // saveModel, saveCmd := InitAttributeList([]AttributeItem{item}) switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { - case tea.KeyShiftLeft: //, tea.KeyBackspace: + case tea.KeyShiftLeft: listIdx := m.idx if m.IsNew() { listIdx -= 1 } return InitAttributeList(m.list, listIdx) case tea.KeyShiftRight: - // return saveModel, saveCmd if !m.IsNew() { m.list[m.idx] = list.Item(item) } else { @@ -224,11 +201,7 @@ func (m AttributeView) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return InitAttributeList(m.list, m.idx) - // return InitAttributeList() case tea.KeyEnter: - // if m.focused == len(m.inputs)-1 { - // return saveModel, saveCmd - // } m.nextInput() case tea.KeyCtrlC, tea.KeyEsc: if m.editMode {