feat(ui): 🎨 Implement schema and table management screens
* Add schema management screen with list and editor * Implement table management screen with list and editor * Create data operations for schema and table management * Define UI rules and guidelines for consistency * Ensure circular tab navigation and keyboard shortcuts * Add forms for creating and editing schemas and tables * Implement confirmation dialogs for destructive actions
This commit is contained in:
351
pkg/ui/schema_screens.go
Normal file
351
pkg/ui/schema_screens.go
Normal file
@@ -0,0 +1,351 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
)
|
||||
|
||||
// showSchemaList displays the schema management screen
|
||||
func (se *SchemaEditor) showSchemaList() {
|
||||
flex := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
|
||||
// Title
|
||||
title := tview.NewTextView().
|
||||
SetText("[::b]Manage Schemas").
|
||||
SetDynamicColors(true).
|
||||
SetTextAlign(tview.AlignCenter)
|
||||
|
||||
// Create schemas table
|
||||
schemaTable := tview.NewTable().SetBorders(true).SetSelectable(true, false).SetFixed(1, 0)
|
||||
|
||||
// Add header row with padding for full width
|
||||
headers := []string{"Name", "Sequence", "Total Tables", "Total Sequences", "Total Views", "Description"}
|
||||
headerWidths := []int{20, 15, 20, 20, 15} // 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)
|
||||
schemaTable.SetCell(0, i, cell)
|
||||
}
|
||||
|
||||
// Add existing schemas
|
||||
for row, schema := range se.db.Schemas {
|
||||
schema := schema // capture for closure
|
||||
|
||||
// Name - pad to 20 chars
|
||||
nameStr := fmt.Sprintf("%-20s", schema.Name)
|
||||
nameCell := tview.NewTableCell(nameStr).SetSelectable(true)
|
||||
schemaTable.SetCell(row+1, 0, nameCell)
|
||||
|
||||
// Sequence - pad to 15 chars
|
||||
seqStr := fmt.Sprintf("%-15s", fmt.Sprintf("%d", schema.Sequence))
|
||||
seqCell := tview.NewTableCell(seqStr).SetSelectable(true)
|
||||
schemaTable.SetCell(row+1, 1, seqCell)
|
||||
|
||||
// Total Tables - pad to 20 chars
|
||||
tablesStr := fmt.Sprintf("%-20s", fmt.Sprintf("%d", len(schema.Tables)))
|
||||
tablesCell := tview.NewTableCell(tablesStr).SetSelectable(true)
|
||||
schemaTable.SetCell(row+1, 2, tablesCell)
|
||||
|
||||
// Total Sequences - pad to 20 chars
|
||||
sequencesStr := fmt.Sprintf("%-20s", fmt.Sprintf("%d", len(schema.Sequences)))
|
||||
sequencesCell := tview.NewTableCell(sequencesStr).SetSelectable(true)
|
||||
schemaTable.SetCell(row+1, 3, sequencesCell)
|
||||
|
||||
// Total Views - pad to 15 chars
|
||||
viewsStr := fmt.Sprintf("%-15s", fmt.Sprintf("%d", len(schema.Views)))
|
||||
viewsCell := tview.NewTableCell(viewsStr).SetSelectable(true)
|
||||
schemaTable.SetCell(row+1, 4, viewsCell)
|
||||
|
||||
// Description - no padding, takes remaining space
|
||||
descCell := tview.NewTableCell(schema.Description).SetSelectable(true)
|
||||
schemaTable.SetCell(row+1, 5, descCell)
|
||||
}
|
||||
|
||||
schemaTable.SetTitle(" Schemas ").SetBorder(true).SetTitleAlign(tview.AlignLeft)
|
||||
|
||||
// Action buttons flex (define before input capture)
|
||||
btnFlex := tview.NewFlex()
|
||||
btnNewSchema := tview.NewButton("New Schema [n]").SetSelectedFunc(func() {
|
||||
se.showNewSchemaDialog()
|
||||
})
|
||||
btnBack := tview.NewButton("Back [b]").SetSelectedFunc(func() {
|
||||
se.pages.SwitchToPage("main")
|
||||
se.pages.RemovePage("schemas")
|
||||
})
|
||||
|
||||
// Set up button input captures for Tab/Shift+Tab navigation
|
||||
btnNewSchema.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyBacktab {
|
||||
se.app.SetFocus(schemaTable)
|
||||
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(btnNewSchema)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyTab {
|
||||
se.app.SetFocus(schemaTable)
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
btnFlex.AddItem(btnNewSchema, 0, 1, true).
|
||||
AddItem(btnBack, 0, 1, false)
|
||||
|
||||
schemaTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
se.pages.SwitchToPage("main")
|
||||
se.pages.RemovePage("schemas")
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyTab {
|
||||
se.app.SetFocus(btnNewSchema)
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyEnter {
|
||||
row, _ := schemaTable.GetSelection()
|
||||
if row > 0 && row <= len(se.db.Schemas) { // Skip header row
|
||||
schemaIndex := row - 1
|
||||
se.showSchemaEditor(schemaIndex, se.db.Schemas[schemaIndex])
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if event.Rune() == 'n' {
|
||||
se.showNewSchemaDialog()
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'b' {
|
||||
se.pages.SwitchToPage("main")
|
||||
se.pages.RemovePage("schemas")
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
flex.AddItem(title, 1, 0, false).
|
||||
AddItem(schemaTable, 0, 1, true).
|
||||
AddItem(btnFlex, 1, 0, false)
|
||||
|
||||
se.pages.AddPage("schemas", flex, true, true)
|
||||
}
|
||||
|
||||
// showSchemaEditor shows the editor for a specific schema
|
||||
func (se *SchemaEditor) showSchemaEditor(index int, schema *models.Schema) {
|
||||
flex := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
|
||||
// Title
|
||||
title := tview.NewTextView().
|
||||
SetText(fmt.Sprintf("[::b]Schema: %s", schema.Name)).
|
||||
SetDynamicColors(true).
|
||||
SetTextAlign(tview.AlignCenter)
|
||||
|
||||
// Schema info display
|
||||
info := tview.NewTextView().SetDynamicColors(true)
|
||||
info.SetText(fmt.Sprintf("Tables: %d | Description: %s",
|
||||
len(schema.Tables), schema.Description))
|
||||
|
||||
// Table list
|
||||
tableList := tview.NewList().ShowSecondaryText(true)
|
||||
|
||||
for i, table := range schema.Tables {
|
||||
tableIndex := i
|
||||
table := table
|
||||
colCount := len(table.Columns)
|
||||
tableList.AddItem(table.Name, fmt.Sprintf("%d columns", colCount), rune('0'+i), func() {
|
||||
se.showTableEditor(index, tableIndex, table)
|
||||
})
|
||||
}
|
||||
|
||||
tableList.AddItem("[New Table]", "Add a new table to this schema", 'n', func() {
|
||||
se.showNewTableDialog(index)
|
||||
})
|
||||
|
||||
tableList.AddItem("[Edit Schema Info]", "Edit schema properties", 'e', func() {
|
||||
se.showEditSchemaDialog(index)
|
||||
})
|
||||
|
||||
tableList.AddItem("[Delete Schema]", "Delete this schema", 'd', func() {
|
||||
se.showDeleteSchemaConfirm(index)
|
||||
})
|
||||
|
||||
tableList.SetBorder(true).SetTitle(" Tables ").SetTitleAlign(tview.AlignLeft)
|
||||
|
||||
// Action buttons (define before input capture)
|
||||
btnFlex := tview.NewFlex()
|
||||
btnNewTable := tview.NewButton("New Table [n]").SetSelectedFunc(func() {
|
||||
se.showNewTableDialog(index)
|
||||
})
|
||||
btnBack := tview.NewButton("Back to Schemas [b]").SetSelectedFunc(func() {
|
||||
se.pages.RemovePage("schema-editor")
|
||||
se.pages.SwitchToPage("schemas")
|
||||
})
|
||||
|
||||
// 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(tableList)
|
||||
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(tableList)
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
tableList.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
se.pages.RemovePage("schema-editor")
|
||||
se.pages.SwitchToPage("schemas")
|
||||
return nil
|
||||
}
|
||||
if event.Key() == tcell.KeyTab {
|
||||
se.app.SetFocus(btnNewTable)
|
||||
return nil
|
||||
}
|
||||
if event.Rune() == 'b' {
|
||||
se.pages.RemovePage("schema-editor")
|
||||
se.pages.SwitchToPage("schemas")
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
btnFlex.AddItem(btnNewTable, 0, 1, true).
|
||||
AddItem(btnBack, 0, 1, false)
|
||||
|
||||
flex.AddItem(title, 1, 0, false).
|
||||
AddItem(info, 2, 0, false).
|
||||
AddItem(tableList, 0, 1, true).
|
||||
AddItem(btnFlex, 1, 0, false)
|
||||
|
||||
se.pages.AddPage("schema-editor", flex, true, true)
|
||||
}
|
||||
|
||||
// showNewSchemaDialog shows dialog to create a new schema
|
||||
func (se *SchemaEditor) showNewSchemaDialog() {
|
||||
form := tview.NewForm()
|
||||
|
||||
schemaName := ""
|
||||
description := ""
|
||||
|
||||
form.AddInputField("Schema Name", "", 40, nil, func(value string) {
|
||||
schemaName = value
|
||||
})
|
||||
|
||||
form.AddInputField("Description", "", 40, nil, func(value string) {
|
||||
description = value
|
||||
})
|
||||
|
||||
form.AddButton("Save", func() {
|
||||
if schemaName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
se.CreateSchema(schemaName, description)
|
||||
|
||||
se.pages.RemovePage("new-schema")
|
||||
se.pages.RemovePage("schemas")
|
||||
se.showSchemaList()
|
||||
})
|
||||
|
||||
form.AddButton("Back", func() {
|
||||
se.pages.RemovePage("new-schema")
|
||||
se.pages.RemovePage("schemas")
|
||||
se.showSchemaList()
|
||||
})
|
||||
|
||||
form.SetBorder(true).SetTitle(" New Schema ").SetTitleAlign(tview.AlignLeft)
|
||||
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
se.showExitConfirmation("new-schema", "schemas")
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
se.pages.AddPage("new-schema", form, true, true)
|
||||
}
|
||||
|
||||
// showEditSchemaDialog shows dialog to edit schema properties
|
||||
func (se *SchemaEditor) showEditSchemaDialog(schemaIndex int) {
|
||||
schema := se.db.Schemas[schemaIndex]
|
||||
form := tview.NewForm()
|
||||
|
||||
// Local variables to collect changes
|
||||
newName := schema.Name
|
||||
newOwner := schema.Owner
|
||||
newDescription := schema.Description
|
||||
|
||||
form.AddInputField("Schema Name", schema.Name, 40, nil, func(value string) {
|
||||
newName = value
|
||||
})
|
||||
|
||||
form.AddInputField("Owner", schema.Owner, 40, nil, func(value string) {
|
||||
newOwner = value
|
||||
})
|
||||
|
||||
form.AddTextArea("Description", schema.Description, 40, 5, 0, func(value string) {
|
||||
newDescription = value
|
||||
})
|
||||
|
||||
form.AddButton("Save", func() {
|
||||
// Apply changes using dataops
|
||||
se.UpdateSchema(schemaIndex, newName, newOwner, newDescription)
|
||||
|
||||
schema := se.db.Schemas[schemaIndex]
|
||||
se.pages.RemovePage("edit-schema")
|
||||
se.pages.RemovePage("schema-editor")
|
||||
se.showSchemaEditor(schemaIndex, schema)
|
||||
})
|
||||
|
||||
form.AddButton("Back", func() {
|
||||
// Discard changes - don't apply them
|
||||
schema := se.db.Schemas[schemaIndex]
|
||||
se.pages.RemovePage("edit-schema")
|
||||
se.pages.RemovePage("schema-editor")
|
||||
se.showSchemaEditor(schemaIndex, schema)
|
||||
})
|
||||
|
||||
form.SetBorder(true).SetTitle(" Edit Schema ").SetTitleAlign(tview.AlignLeft)
|
||||
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyEscape {
|
||||
se.showExitConfirmation("edit-schema", "schema-editor")
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
se.pages.AddPage("edit-schema", form, true, true)
|
||||
}
|
||||
Reference in New Issue
Block a user