From 2085cd9de8efefa801a6ab26368387e0ec4707ea Mon Sep 17 00:00:00 2001 From: Jorge Rojas Date: Sat, 7 Sep 2024 15:08:39 -0400 Subject: [PATCH] feat: edit sidebar input fields --- app/Keymap.go | 14 +++ commands/commands.go | 15 +++ components/Home.go | 6 +- components/ResultsTable.go | 92 ++++++++++------ components/Sidebar.go | 214 ++++++++++++++++++++++++++++++++++++- components/constants.go | 5 +- 6 files changed, 309 insertions(+), 37 deletions(-) diff --git a/app/Keymap.go b/app/Keymap.go index f80a57d..1a3803d 100644 --- a/app/Keymap.go +++ b/app/Keymap.go @@ -44,6 +44,7 @@ const ( TableGroup = "table" EditorGroup = "editor" ConnectionGroup = "connection" + SidebarGroup = "sidebar" ) // Define a global KeymapSystem object with default keybinds @@ -110,11 +111,24 @@ var Keymaps = KeymapSystem{ Bind{Key: Key{Char: '3'}, Cmd: cmd.ConstraintsMenu, Description: "Switch to constraints menu"}, Bind{Key: Key{Char: '4'}, Cmd: cmd.ForeignKeysMenu, Description: "Switch to foreign keys menu"}, Bind{Key: Key{Char: '5'}, Cmd: cmd.IndexesMenu, Description: "Switch to indexes menu"}, + // Sidebar + Bind{Key: Key{Char: 'S'}, Cmd: cmd.ToggleSidebar, Description: "Toggle sidebar"}, + Bind{Key: Key{Char: 's'}, Cmd: cmd.FocusSidebar, Description: "Focus sidebar"}, }, EditorGroup: { Bind{Key: Key{Code: tcell.KeyCtrlR}, Cmd: cmd.Execute, Description: "Execute query"}, Bind{Key: Key{Code: tcell.KeyEscape}, Cmd: cmd.UnfocusEditor, Description: "Unfocus editor"}, Bind{Key: Key{Code: tcell.KeyCtrlSpace}, Cmd: cmd.OpenInExternalEditor, Description: "Open in external editor"}, }, + SidebarGroup: { + Bind{Key: Key{Char: 's'}, Cmd: cmd.UnfocusSidebar, Description: "Focus table"}, + Bind{Key: Key{Char: 'j'}, Cmd: cmd.MoveDown, Description: "Focus next field"}, + Bind{Key: Key{Char: 'k'}, Cmd: cmd.MoveUp, Description: "Focus previous field"}, + Bind{Key: Key{Char: 'g'}, Cmd: cmd.GotoStart, Description: "Focus first field"}, + Bind{Key: Key{Char: 'G'}, Cmd: cmd.GotoEnd, Description: "Focus last field"}, + Bind{Key: Key{Char: 'c'}, Cmd: cmd.Edit, Description: "Edit field"}, + Bind{Key: Key{Code: tcell.KeyEnter}, Cmd: cmd.CommitEdit, Description: "Add edit to pending changes"}, + Bind{Key: Key{Code: tcell.KeyEscape}, Cmd: cmd.DiscardEdit, Description: "Discard edit"}, + }, }, } diff --git a/commands/commands.go b/commands/commands.go index 7929380..6773810 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -45,6 +45,8 @@ const ( UnfocusEditor Copy Edit + CommitEdit + DiscardEdit Save Delete Search @@ -60,6 +62,9 @@ const ( PreviousFoundNode TreeCollapseAll ExpandAll + FocusSidebar + UnfocusSidebar + ToggleSidebar // Connection NewConnection @@ -179,6 +184,16 @@ func (c Command) String() string { return "TreeCollapseAll" case ExpandAll: return "ExpandAll" + case FocusSidebar: + return "FocusSidebar" + case ToggleSidebar: + return "ToggleSidebar" + case UnfocusSidebar: + return "UnfocusSidebar" + case CommitEdit: + return "CommitEdit" + case DiscardEdit: + return "DiscardEdit" } return "Unknown" } diff --git a/components/Home.go b/components/Home.go index cc8f07c..cdca7a5 100644 --- a/components/Home.go +++ b/components/Home.go @@ -104,10 +104,14 @@ func (home *Home) subscribeToTreeChanges() { } - table.FetchRecords(func() { + results := table.FetchRecords(func() { home.focusLeftWrapper() }) + if len(results) > 1 && !table.GetShowSidebar() { // 1 because the row 0 is the column names + table.ShowSidebar(true) + } + if table.state.error == "" { home.focusRightWrapper() } diff --git a/components/ResultsTable.go b/components/ResultsTable.go index 096b6e3..490b4ac 100644 --- a/components/ResultsTable.go +++ b/components/ResultsTable.go @@ -31,7 +31,7 @@ type ResultsTableState struct { isEditing bool isFiltering bool isLoading bool - setShowSidebar bool + showSidebar bool } type ResultsTable struct { @@ -69,7 +69,7 @@ func NewResultsTable(listOfDbChanges *[]models.DbDmlChange, tree *Tree, dbdriver isEditing: false, isLoading: false, listOfDbChanges: listOfDbChanges, - setShowSidebar: true, + showSidebar: false, } wrapper := tview.NewFlex() @@ -95,6 +95,8 @@ func NewResultsTable(listOfDbChanges *[]models.DbDmlChange, tree *Tree, dbdriver pagination := NewPagination() + sidebar := NewSidebar() + table := &ResultsTable{ Table: tview.NewTable(), state: state, @@ -106,7 +108,7 @@ func NewResultsTable(listOfDbChanges *[]models.DbDmlChange, tree *Tree, dbdriver Editor: nil, Tree: tree, DBDriver: dbdriver, - Sidebar: NewSidebar(), + Sidebar: sidebar, } table.SetSelectable(true, true) @@ -114,34 +116,16 @@ func NewResultsTable(listOfDbChanges *[]models.DbDmlChange, tree *Tree, dbdriver table.SetFixed(1, 0) table.SetInputCapture(table.tableInputCapture) table.SetSelectedStyle(tcell.StyleDefault.Background(tview.Styles.SecondaryTextColor).Foreground(tview.Styles.ContrastSecondaryTextColor)) - table.Page.AddPage("sidebar", table.Sidebar, false, false) - - table.SetSelectionChangedFunc(func(row, _ int) { - columnCount := table.GetColumnCount() - - tableX, tableY, tableWidth, tableHeight := table.GetRect() - - sidebarWidth := tableWidth / 3 - table.Sidebar.SetRect(tableX+tableWidth-sidebarWidth, tableY, sidebarWidth, tableHeight) - table.Sidebar.Clear() - - for i := 0; i < columnCount; i++ { - label := tview.NewTextView() - field := tview.NewInputField() - field.SetBorder(true) - field.SetFieldStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.SecondaryTextColor)) - - label.SetText(table.GetColumnNameByIndex(i)) - field.SetText(table.GetCell(row, i).Text) + table.Page.AddPage(SidebarPageName, table.Sidebar, false, false) - table.Sidebar.AddItem(label, 1, 0, false) - table.Sidebar.AddItem(field, 3, 0, false) + table.SetSelectionChangedFunc(func(_, _ int) { + if table.GetShowSidebar() { + table.UpdateSidebar() } - - table.ShowSidebar(true) }) go table.subscribeToTreeChanges() + go table.subscribeToSidebarChanges() return table } @@ -215,6 +199,21 @@ func (table *ResultsTable) subscribeToTreeChanges() { } } +func (table *ResultsTable) subscribeToSidebarChanges() { + ch := table.Sidebar.Subscribe() + + for stateChange := range ch { + switch stateChange.Key { + case "Editing": + editing := stateChange.Value.(bool) + table.SetIsEditing(editing) + case "Unfocusing": + App.SetFocus(table) + App.ForceDraw() + } + } +} + func (table *ResultsTable) AddRows(rows [][]string) { for i, row := range rows { for j, cell := range row { @@ -396,6 +395,10 @@ func (table *ResultsTable) tableInputCapture(event *tcell.EventKey) *tcell.Event } } + } else if command == commands.ToggleSidebar { + table.ShowSidebar(!table.GetShowSidebar()) + } else if command == commands.FocusSidebar { + App.SetFocus(table.Sidebar) } if len(table.GetRecords()) > 0 { @@ -655,6 +658,10 @@ func (table *ResultsTable) GetIsFiltering() bool { return table.state.isFiltering } +func (table *ResultsTable) GetShowSidebar() bool { + return table.state.showSidebar +} + // Setters func (table *ResultsTable) SetRecords(rows [][]string) { @@ -1255,13 +1262,34 @@ func (table *ResultsTable) search() { } func (table *ResultsTable) ShowSidebar(show bool) { - if table.state.setShowSidebar != show { - table.state.setShowSidebar = show + table.state.showSidebar = show - if show { - table.Page.ShowPage("sidebar") - } else { - table.Page.HidePage("sidebar") + if show { + table.UpdateSidebar() + table.Page.SendToFront(SidebarPageName) + table.Page.ShowPage(SidebarPageName) + } else { + table.Page.HidePage(SidebarPageName) + } +} + +func (table *ResultsTable) UpdateSidebar() { + columnCount := table.GetColumnCount() + selectedRow, _ := table.GetSelection() + + if selectedRow > 0 { + tableX, tableY, tableWidth, tableHeight := table.GetInnerRect() + + sidebarWidth := (tableWidth / 3) + + table.Sidebar.SetRect(tableX+tableWidth-sidebarWidth, tableY, sidebarWidth, tableHeight) + table.Sidebar.Clear() + + for i := 0; i < columnCount; i++ { + title := table.GetColumnNameByIndex(i) + text := table.GetCell(selectedRow, i).Text + + table.Sidebar.AddField(title, text, sidebarWidth) } } } diff --git a/components/Sidebar.go b/components/Sidebar.go index 43a9301..9432a46 100644 --- a/components/Sidebar.go +++ b/components/Sidebar.go @@ -1,9 +1,23 @@ package components -import "github.com/rivo/tview" +import ( + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + + "github.com/jorgerojas26/lazysql/app" + "github.com/jorgerojas26/lazysql/commands" + "github.com/jorgerojas26/lazysql/models" +) + +type SidebarState struct { + currentFieldIndex int +} type Sidebar struct { *tview.Flex + state *SidebarState + Fields []*tview.TextArea + subscribers []chan models.StateChange } func NewSidebar() *Sidebar { @@ -11,5 +25,201 @@ func NewSidebar() *Sidebar { sidebar.SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor) sidebar.SetBorder(true) - return &Sidebar{sidebar} + sidebarState := &SidebarState{ + currentFieldIndex: 0, + } + + newSidebar := &Sidebar{ + Flex: sidebar, + state: sidebarState, + Fields: []*tview.TextArea{}, + subscribers: []chan models.StateChange{}, + } + + newSidebar.SetInputCapture(newSidebar.inputCapture) + + newSidebar.SetBlurFunc(func() { + newSidebar.SetCurrentFieldIndex(0) + }) + + return newSidebar +} + +func (sidebar *Sidebar) AddField(title, text string, fieldWidth int) { + field := tview.NewTextArea() + field.SetWrap(true) + field.SetDisabled(true) + + field.SetBorder(true) + field.SetTitle(title) + field.SetTitleAlign(tview.AlignLeft) + field.SetTitleColor(tview.Styles.PrimaryTextColor) + field.SetText(text, true) + field.SetTextStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.SecondaryTextColor)) + + textLength := len(field.GetText()) + + itemFixedSize := 3 + + if textLength >= fieldWidth*3 { + itemFixedSize = 5 + } else if textLength >= fieldWidth { + itemFixedSize = 4 + } else { + field.SetWrap(false) + } + + sidebar.Fields = append(sidebar.Fields, field) + sidebar.AddItem(field, itemFixedSize, 0, true) +} + +func (sidebar *Sidebar) FocusNextField() { + newIndex := sidebar.GetCurrentFieldIndex() + 1 + + if newIndex < sidebar.GetItemCount() { + item := sidebar.Fields[newIndex] + + if item != nil { + sidebar.SetCurrentFieldIndex(newIndex) + App.SetFocus(item) + App.ForceDraw() + return + } + + } +} + +func (sidebar *Sidebar) FocusPreviousField() { + newIndex := sidebar.GetCurrentFieldIndex() - 1 + + if newIndex >= 0 { + item := sidebar.Fields[newIndex] + + if item != nil { + sidebar.SetCurrentFieldIndex(newIndex) + App.SetFocus(item) + App.ForceDraw() + return + } + } +} + +func (sidebar *Sidebar) FocusFirstField() { + sidebar.SetCurrentFieldIndex(0) + App.SetFocus(sidebar.Fields[0]) +} + +func (sidebar *Sidebar) FocusLastField() { + newIndex := sidebar.GetItemCount() - 1 + sidebar.SetCurrentFieldIndex(newIndex) + App.SetFocus(sidebar.Fields[newIndex]) +} + +func (sidebar *Sidebar) FocusField(index int) { + sidebar.SetCurrentFieldIndex(index) + App.SetFocus(sidebar.Fields[index]) +} + +func (sidebar *Sidebar) Clear() { + sidebar.Fields = make([]*tview.TextArea, 0) + sidebar.Flex.Clear() +} + +func (sidebar *Sidebar) EditTextCurrentField() { + index := sidebar.GetCurrentFieldIndex() + item := sidebar.Fields[index] + + sidebar.SetEditingStyles(item) +} + +func (sidebar *Sidebar) inputCapture(event *tcell.EventKey) *tcell.EventKey { + command := app.Keymaps.Group(app.SidebarGroup).Resolve(event) + + switch command { + case commands.UnfocusSidebar: + sidebar.Publish(models.StateChange{Key: "Unfocusing", Value: nil}) + case commands.MoveDown: + sidebar.FocusNextField() + case commands.MoveUp: + sidebar.FocusPreviousField() + case commands.GotoStart: + sidebar.FocusFirstField() + case commands.GotoEnd: + sidebar.FocusLastField() + case commands.Edit: + sidebar.Publish(models.StateChange{Key: "Editing", Value: true}) + + currentItemIndex := sidebar.GetCurrentFieldIndex() + item := sidebar.Fields[currentItemIndex] + text := item.GetText() + + sidebar.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + command := app.Keymaps.Group(app.SidebarGroup).Resolve(event) + + switch command { + case commands.CommitEdit: + sidebar.SetInputCapture(sidebar.inputCapture) + sidebar.SetDisabledStyles(item) + sidebar.Publish(models.StateChange{Key: "Editing", Value: false}) + return nil + case commands.DiscardEdit: + sidebar.SetInputCapture(sidebar.inputCapture) + sidebar.SetDisabledStyles(item) + item.SetText(text, true) + sidebar.Publish(models.StateChange{Key: "Editing", Value: false}) + return nil + } + + return event + }) + + sidebar.EditTextCurrentField() + + return nil + } + return event +} + +func (sidebar *Sidebar) SetEditingStyles(item *tview.TextArea) { + item.SetBackgroundColor(tview.Styles.SecondaryTextColor) + item.SetTextStyle(tcell.StyleDefault.Background(tview.Styles.SecondaryTextColor).Foreground(tview.Styles.ContrastSecondaryTextColor)) + item.SetTitleColor(tview.Styles.ContrastSecondaryTextColor) + item.SetBorderColor(tview.Styles.ContrastSecondaryTextColor) + + item.SetWrap(true) + item.SetDisabled(false) +} + +func (sidebar *Sidebar) SetDisabledStyles(item *tview.TextArea) { + item.SetBackgroundColor(tview.Styles.PrimitiveBackgroundColor) + item.SetTextStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.SecondaryTextColor)) + item.SetTitleColor(tview.Styles.PrimaryTextColor) + item.SetBorderColor(tview.Styles.BorderColor) + + item.SetWrap(true) + item.SetDisabled(true) +} + +// Getters +func (sidebar *Sidebar) GetCurrentFieldIndex() int { + return sidebar.state.currentFieldIndex +} + +// Setters +func (sidebar *Sidebar) SetCurrentFieldIndex(index int) { + sidebar.state.currentFieldIndex = index +} + +// Subscribe to changes in the sidebar state +func (sidebar *Sidebar) Subscribe() chan models.StateChange { + subscriber := make(chan models.StateChange) + sidebar.subscribers = append(sidebar.subscribers, subscriber) + return subscriber +} + +// Publish subscribers of changes in the sidebar state +func (sidebar *Sidebar) Publish(change models.StateChange) { + for _, subscriber := range sidebar.subscribers { + subscriber <- change + } } diff --git a/components/constants.go b/components/constants.go index 6422737..101f2f2 100644 --- a/components/constants.go +++ b/components/constants.go @@ -5,6 +5,7 @@ import "github.com/jorgerojas26/lazysql/app" var App = app.App const ( - EditorTabName string = "Editor" - HelpPageName string = "Help" + EditorTabName string = "Editor" + HelpPageName string = "Help" + SidebarPageName string = "Sidebar" )