547 lines
16 KiB
Go
547 lines
16 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/gdamore/tcell/v2"
|
|
"github.com/rivo/tview"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
|
)
|
|
|
|
// showTableList displays all tables across all schemas
|
|
func (se *SchemaEditor) showTableList() {
|
|
flex := tview.NewFlex().SetDirection(tview.FlexRow)
|
|
|
|
// Title
|
|
title := tview.NewTextView().
|
|
SetText("[::b]All Tables").
|
|
SetDynamicColors(true).
|
|
SetTextAlign(tview.AlignCenter)
|
|
|
|
// Create tables table
|
|
tableTable := tview.NewTable().SetBorders(true).SetSelectable(true, false).SetFixed(1, 0)
|
|
|
|
// Add header row with padding for full width
|
|
headers := []string{"Name", "Schema", "Sequence", "Total Columns", "Total Relations", "Total Indexes", "GUID", "Description", "Comment"}
|
|
headerWidths := []int{18, 15, 12, 14, 15, 14, 36, 0, 12} // Description gets remainder
|
|
for i, header := range headers {
|
|
padding := ""
|
|
if i < len(headerWidths) && headerWidths[i] > 0 {
|
|
padding = strings.Repeat(" ", headerWidths[i]-len(header))
|
|
}
|
|
cell := tview.NewTableCell(header + padding).
|
|
SetTextColor(tcell.ColorYellow).
|
|
SetSelectable(false).
|
|
SetAlign(tview.AlignLeft)
|
|
tableTable.SetCell(0, i, cell)
|
|
}
|
|
|
|
var tables []*models.Table
|
|
var tableLocations []struct{ schemaIdx, tableIdx int }
|
|
|
|
for si, schema := range se.db.Schemas {
|
|
for ti, table := range schema.Tables {
|
|
tables = append(tables, table)
|
|
tableLocations = append(tableLocations, struct{ schemaIdx, tableIdx int }{si, ti})
|
|
}
|
|
}
|
|
|
|
for row, table := range tables {
|
|
tableIdx := tableLocations[row]
|
|
schema := se.db.Schemas[tableIdx.schemaIdx]
|
|
|
|
// Name - pad to 18 chars
|
|
nameStr := fmt.Sprintf("%-18s", table.Name)
|
|
nameCell := tview.NewTableCell(nameStr).SetSelectable(true)
|
|
tableTable.SetCell(row+1, 0, nameCell)
|
|
|
|
// Schema - pad to 15 chars
|
|
schemaStr := fmt.Sprintf("%-15s", schema.Name)
|
|
schemaCell := tview.NewTableCell(schemaStr).SetSelectable(true)
|
|
tableTable.SetCell(row+1, 1, schemaCell)
|
|
|
|
// Sequence - pad to 12 chars
|
|
seqStr := fmt.Sprintf("%-12s", fmt.Sprintf("%d", table.Sequence))
|
|
seqCell := tview.NewTableCell(seqStr).SetSelectable(true)
|
|
tableTable.SetCell(row+1, 2, seqCell)
|
|
|
|
// Total Columns - pad to 14 chars
|
|
colsStr := fmt.Sprintf("%-14s", fmt.Sprintf("%d", len(table.Columns)))
|
|
colsCell := tview.NewTableCell(colsStr).SetSelectable(true)
|
|
tableTable.SetCell(row+1, 3, colsCell)
|
|
|
|
// Total Relations - pad to 15 chars
|
|
relsStr := fmt.Sprintf("%-15s", fmt.Sprintf("%d", len(table.Relationships)))
|
|
relsCell := tview.NewTableCell(relsStr).SetSelectable(true)
|
|
tableTable.SetCell(row+1, 4, relsCell)
|
|
|
|
// Total Indexes - pad to 14 chars
|
|
idxStr := fmt.Sprintf("%-14s", fmt.Sprintf("%d", len(table.Indexes)))
|
|
idxCell := tview.NewTableCell(idxStr).SetSelectable(true)
|
|
tableTable.SetCell(row+1, 5, idxCell)
|
|
|
|
// GUID - pad to 36 chars
|
|
guidStr := fmt.Sprintf("%-36s", table.GUID)
|
|
guidCell := tview.NewTableCell(guidStr).SetSelectable(true)
|
|
tableTable.SetCell(row+1, 6, guidCell)
|
|
|
|
// Description - no padding, takes remaining space
|
|
descCell := tview.NewTableCell(table.Description).SetSelectable(true)
|
|
tableTable.SetCell(row+1, 7, descCell)
|
|
|
|
// Comment - pad to 12 chars
|
|
commentStr := fmt.Sprintf("%-12s", table.Comment)
|
|
commentCell := tview.NewTableCell(commentStr).SetSelectable(true)
|
|
tableTable.SetCell(row+1, 8, commentCell)
|
|
}
|
|
|
|
tableTable.SetTitle(" All Tables ").SetBorder(true).SetTitleAlign(tview.AlignLeft)
|
|
|
|
// Action buttons (define before input capture)
|
|
btnFlex := tview.NewFlex()
|
|
btnNewTable := tview.NewButton("New Table [n]").SetSelectedFunc(func() {
|
|
se.showNewTableDialogFromList()
|
|
})
|
|
btnBack := tview.NewButton("Back [b]").SetSelectedFunc(func() {
|
|
se.pages.SwitchToPage("main")
|
|
se.pages.RemovePage("tables")
|
|
})
|
|
|
|
// Set up button input captures for Tab/Shift+Tab navigation
|
|
btnNewTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyBacktab {
|
|
se.app.SetFocus(tableTable)
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyTab {
|
|
se.app.SetFocus(btnBack)
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
btnBack.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyBacktab {
|
|
se.app.SetFocus(btnNewTable)
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyTab {
|
|
se.app.SetFocus(tableTable)
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
btnFlex.AddItem(btnNewTable, 0, 1, true).
|
|
AddItem(btnBack, 0, 1, false)
|
|
|
|
tableTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyEscape {
|
|
se.pages.SwitchToPage("main")
|
|
se.pages.RemovePage("tables")
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyTab {
|
|
se.app.SetFocus(btnNewTable)
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyEnter {
|
|
row, _ := tableTable.GetSelection()
|
|
if row > 0 && row <= len(tables) { // Skip header row
|
|
tableIdx := tableLocations[row-1]
|
|
se.showTableEditor(tableIdx.schemaIdx, tableIdx.tableIdx, tables[row-1])
|
|
return nil
|
|
}
|
|
}
|
|
if event.Rune() == 'n' {
|
|
se.showNewTableDialogFromList()
|
|
return nil
|
|
}
|
|
if event.Rune() == 'b' {
|
|
se.pages.SwitchToPage("main")
|
|
se.pages.RemovePage("tables")
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
flex.AddItem(title, 1, 0, false).
|
|
AddItem(tableTable, 0, 1, true).
|
|
AddItem(btnFlex, 1, 0, false)
|
|
|
|
se.pages.AddPage("tables", flex, true, true)
|
|
}
|
|
|
|
// showTableEditor shows editor for a specific table
|
|
func (se *SchemaEditor) showTableEditor(schemaIndex, tableIndex int, table *models.Table) {
|
|
flex := tview.NewFlex().SetDirection(tview.FlexRow)
|
|
|
|
// Title
|
|
title := tview.NewTextView().
|
|
SetText(fmt.Sprintf("[::b]Table: %s", table.Name)).
|
|
SetDynamicColors(true).
|
|
SetTextAlign(tview.AlignCenter)
|
|
|
|
// Table info
|
|
info := tview.NewTextView().SetDynamicColors(true)
|
|
info.SetText(fmt.Sprintf("Schema: %s | Columns: %d | Description: %s",
|
|
table.Schema, len(table.Columns), table.Description))
|
|
|
|
// Create columns table
|
|
colTable := tview.NewTable().SetBorders(true).SetSelectable(true, false).SetFixed(1, 0)
|
|
|
|
// Add header row with padding for full width
|
|
headers := []string{"Name", "Type", "Default", "KeyType", "GUID", "Description"}
|
|
headerWidths := []int{20, 18, 15, 15, 36} // Last column takes remaining space
|
|
for i, header := range headers {
|
|
padding := ""
|
|
if i < len(headerWidths) {
|
|
padding = strings.Repeat(" ", headerWidths[i]-len(header))
|
|
}
|
|
cell := tview.NewTableCell(header + padding).
|
|
SetTextColor(tcell.ColorYellow).
|
|
SetSelectable(false).
|
|
SetAlign(tview.AlignLeft)
|
|
colTable.SetCell(0, i, cell)
|
|
}
|
|
|
|
// Get sorted column names
|
|
columnNames := getColumnNames(table)
|
|
for row, colName := range columnNames {
|
|
column := table.Columns[colName]
|
|
|
|
// Name - pad to 20 chars
|
|
nameStr := fmt.Sprintf("%-20s", colName)
|
|
nameCell := tview.NewTableCell(nameStr).SetSelectable(true)
|
|
colTable.SetCell(row+1, 0, nameCell)
|
|
|
|
// Type - pad to 18 chars
|
|
typeStr := fmt.Sprintf("%-18s", column.Type)
|
|
typeCell := tview.NewTableCell(typeStr).SetSelectable(true)
|
|
colTable.SetCell(row+1, 1, typeCell)
|
|
|
|
// Default - pad to 15 chars
|
|
defaultStr := ""
|
|
if column.Default != nil {
|
|
defaultStr = fmt.Sprintf("%v", column.Default)
|
|
}
|
|
defaultStr = fmt.Sprintf("%-15s", defaultStr)
|
|
defaultCell := tview.NewTableCell(defaultStr).SetSelectable(true)
|
|
colTable.SetCell(row+1, 2, defaultCell)
|
|
|
|
// KeyType - pad to 15 chars
|
|
keyTypeStr := ""
|
|
if column.IsPrimaryKey {
|
|
keyTypeStr = "PRIMARY"
|
|
} else if column.NotNull {
|
|
keyTypeStr = "NOT NULL"
|
|
}
|
|
keyTypeStr = fmt.Sprintf("%-15s", keyTypeStr)
|
|
keyTypeCell := tview.NewTableCell(keyTypeStr).SetSelectable(true)
|
|
colTable.SetCell(row+1, 3, keyTypeCell)
|
|
|
|
// GUID - pad to 36 chars
|
|
guidStr := fmt.Sprintf("%-36s", column.GUID)
|
|
guidCell := tview.NewTableCell(guidStr).SetSelectable(true)
|
|
colTable.SetCell(row+1, 4, guidCell)
|
|
|
|
// Description
|
|
descCell := tview.NewTableCell(column.Description).SetSelectable(true)
|
|
colTable.SetCell(row+1, 5, descCell)
|
|
}
|
|
|
|
colTable.SetTitle(" Columns ").SetBorder(true).SetTitleAlign(tview.AlignLeft)
|
|
|
|
// Action buttons flex (define before input capture)
|
|
btnFlex := tview.NewFlex()
|
|
btnNewCol := tview.NewButton("Add Column [n]").SetSelectedFunc(func() {
|
|
se.showNewColumnDialog(schemaIndex, tableIndex)
|
|
})
|
|
btnEditTable := tview.NewButton("Edit Table [e]").SetSelectedFunc(func() {
|
|
se.showEditTableDialog(schemaIndex, tableIndex)
|
|
})
|
|
btnEditColumn := tview.NewButton("Edit Column [c]").SetSelectedFunc(func() {
|
|
row, _ := colTable.GetSelection()
|
|
if row > 0 && row <= len(columnNames) { // Skip header row
|
|
colName := columnNames[row-1]
|
|
column := table.Columns[colName]
|
|
se.showColumnEditor(schemaIndex, tableIndex, row-1, column)
|
|
}
|
|
})
|
|
btnDelTable := tview.NewButton("Delete Table [d]").SetSelectedFunc(func() {
|
|
se.showDeleteTableConfirm(schemaIndex, tableIndex)
|
|
})
|
|
btnBack := tview.NewButton("Back to Schema [b]").SetSelectedFunc(func() {
|
|
se.pages.RemovePage("table-editor")
|
|
se.pages.SwitchToPage("schema-editor")
|
|
})
|
|
|
|
// Set up button input captures for Tab/Shift+Tab navigation
|
|
btnNewCol.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyBacktab {
|
|
se.app.SetFocus(colTable)
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyTab {
|
|
se.app.SetFocus(btnEditColumn)
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
btnEditColumn.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyBacktab {
|
|
se.app.SetFocus(btnNewCol)
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyTab {
|
|
se.app.SetFocus(btnEditTable)
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
btnEditTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyBacktab {
|
|
se.app.SetFocus(btnEditColumn)
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyTab {
|
|
se.app.SetFocus(btnDelTable)
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
btnDelTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyBacktab {
|
|
se.app.SetFocus(btnEditTable)
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyTab {
|
|
se.app.SetFocus(btnBack)
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
btnBack.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyBacktab {
|
|
se.app.SetFocus(btnDelTable)
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyTab {
|
|
se.app.SetFocus(colTable)
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
btnFlex.AddItem(btnNewCol, 0, 1, true).
|
|
AddItem(btnEditColumn, 0, 1, false).
|
|
AddItem(btnEditTable, 0, 1, false).
|
|
AddItem(btnDelTable, 0, 1, false).
|
|
AddItem(btnBack, 0, 1, false)
|
|
|
|
colTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyEscape {
|
|
se.pages.SwitchToPage("schema-editor")
|
|
se.pages.RemovePage("table-editor")
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyTab {
|
|
se.app.SetFocus(btnNewCol)
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyEnter {
|
|
row, _ := colTable.GetSelection()
|
|
if row > 0 { // Skip header row
|
|
colName := columnNames[row-1]
|
|
column := table.Columns[colName]
|
|
se.showColumnEditor(schemaIndex, tableIndex, row-1, column)
|
|
return nil
|
|
}
|
|
}
|
|
if event.Rune() == 'c' {
|
|
row, _ := colTable.GetSelection()
|
|
if row > 0 && row <= len(columnNames) { // Skip header row
|
|
colName := columnNames[row-1]
|
|
column := table.Columns[colName]
|
|
se.showColumnEditor(schemaIndex, tableIndex, row-1, column)
|
|
}
|
|
return nil
|
|
}
|
|
if event.Rune() == 'b' {
|
|
se.pages.RemovePage("table-editor")
|
|
se.pages.SwitchToPage("schema-editor")
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
flex.AddItem(title, 1, 0, false).
|
|
AddItem(info, 2, 0, false).
|
|
AddItem(colTable, 0, 1, true).
|
|
AddItem(btnFlex, 1, 0, false)
|
|
|
|
se.pages.AddPage("table-editor", flex, true, true)
|
|
}
|
|
|
|
// showNewTableDialog shows dialog to create a new table
|
|
func (se *SchemaEditor) showNewTableDialog(schemaIndex int) {
|
|
form := tview.NewForm()
|
|
|
|
tableName := ""
|
|
description := ""
|
|
|
|
form.AddInputField("Table Name", "", 40, nil, func(value string) {
|
|
tableName = value
|
|
})
|
|
|
|
form.AddInputField("Description", "", 40, nil, func(value string) {
|
|
description = value
|
|
})
|
|
|
|
form.AddButton("Save", func() {
|
|
if tableName == "" {
|
|
return
|
|
}
|
|
|
|
se.CreateTable(schemaIndex, tableName, description)
|
|
|
|
schema := se.db.Schemas[schemaIndex]
|
|
se.pages.RemovePage("new-table")
|
|
se.pages.RemovePage("schema-editor")
|
|
se.showSchemaEditor(schemaIndex, schema)
|
|
})
|
|
|
|
form.AddButton("Back", func() {
|
|
schema := se.db.Schemas[schemaIndex]
|
|
se.pages.RemovePage("new-table")
|
|
se.pages.RemovePage("schema-editor")
|
|
se.showSchemaEditor(schemaIndex, schema)
|
|
})
|
|
|
|
form.SetBorder(true).SetTitle(" New Table ").SetTitleAlign(tview.AlignLeft)
|
|
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyEscape {
|
|
se.showExitConfirmation("new-table", "schema-editor")
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
se.pages.AddPage("new-table", form, true, true)
|
|
}
|
|
|
|
// showNewTableDialogFromList shows dialog to create a new table with schema selection
|
|
func (se *SchemaEditor) showNewTableDialogFromList() {
|
|
form := tview.NewForm()
|
|
|
|
tableName := ""
|
|
description := ""
|
|
selectedSchemaIdx := 0
|
|
|
|
// Create schema dropdown options
|
|
schemaOptions := make([]string, len(se.db.Schemas))
|
|
for i, schema := range se.db.Schemas {
|
|
schemaOptions[i] = schema.Name
|
|
}
|
|
|
|
form.AddInputField("Table Name", "", 40, nil, func(value string) {
|
|
tableName = value
|
|
})
|
|
|
|
form.AddDropDown("Schema", schemaOptions, 0, func(option string, optionIndex int) {
|
|
selectedSchemaIdx = optionIndex
|
|
})
|
|
|
|
form.AddInputField("Description", "", 40, nil, func(value string) {
|
|
description = value
|
|
})
|
|
|
|
form.AddButton("Save", func() {
|
|
if tableName == "" {
|
|
return
|
|
}
|
|
|
|
se.CreateTable(selectedSchemaIdx, tableName, description)
|
|
|
|
se.pages.RemovePage("new-table-from-list")
|
|
se.pages.RemovePage("tables")
|
|
se.showTableList()
|
|
})
|
|
|
|
form.AddButton("Back", func() {
|
|
se.pages.RemovePage("new-table-from-list")
|
|
se.pages.RemovePage("tables")
|
|
se.showTableList()
|
|
})
|
|
|
|
form.SetBorder(true).SetTitle(" New Table ").SetTitleAlign(tview.AlignLeft)
|
|
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyEscape {
|
|
se.showExitConfirmation("new-table-from-list", "tables")
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
se.pages.AddPage("new-table-from-list", form, true, true)
|
|
}
|
|
|
|
// showEditTableDialog shows dialog to edit table properties
|
|
func (se *SchemaEditor) showEditTableDialog(schemaIndex, tableIndex int) {
|
|
table := se.db.Schemas[schemaIndex].Tables[tableIndex]
|
|
form := tview.NewForm()
|
|
|
|
// Local variables to collect changes
|
|
newName := table.Name
|
|
newDescription := table.Description
|
|
newGUID := table.GUID
|
|
|
|
form.AddInputField("Table Name", table.Name, 40, nil, func(value string) {
|
|
newName = value
|
|
})
|
|
|
|
form.AddTextArea("Description", table.Description, 40, 5, 0, func(value string) {
|
|
newDescription = value
|
|
})
|
|
|
|
form.AddInputField("GUID", table.GUID, 40, nil, func(value string) {
|
|
newGUID = value
|
|
})
|
|
|
|
form.AddButton("Save", func() {
|
|
// Apply changes using dataops
|
|
se.UpdateTable(schemaIndex, tableIndex, newName, newDescription)
|
|
se.db.Schemas[schemaIndex].Tables[tableIndex].GUID = newGUID
|
|
|
|
table := se.db.Schemas[schemaIndex].Tables[tableIndex]
|
|
se.pages.RemovePage("edit-table")
|
|
se.pages.RemovePage("table-editor")
|
|
se.showTableEditor(schemaIndex, tableIndex, table)
|
|
})
|
|
|
|
form.AddButton("Back", func() {
|
|
// Discard changes - don't apply them
|
|
table := se.db.Schemas[schemaIndex].Tables[tableIndex]
|
|
se.pages.RemovePage("edit-table")
|
|
se.pages.RemovePage("table-editor")
|
|
se.showTableEditor(schemaIndex, tableIndex, table)
|
|
})
|
|
|
|
form.SetBorder(true).SetTitle(" Edit Table ").SetTitleAlign(tview.AlignLeft)
|
|
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyEscape {
|
|
se.showExitConfirmation("edit-table", "table-editor")
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
se.pages.AddPage("edit-table", form, true, true)
|
|
}
|