* 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
259 lines
6.8 KiB
Go
259 lines
6.8 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/gdamore/tcell/v2"
|
|
"github.com/rivo/tview"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
|
)
|
|
|
|
// showDomainList displays the domain management screen
|
|
func (se *SchemaEditor) showDomainList() {
|
|
flex := tview.NewFlex().SetDirection(tview.FlexRow)
|
|
|
|
// Title
|
|
title := tview.NewTextView().
|
|
SetText("[::b]Manage Domains").
|
|
SetDynamicColors(true).
|
|
SetTextAlign(tview.AlignCenter)
|
|
|
|
// Create domains table
|
|
domainTable := tview.NewTable().SetBorders(true).SetSelectable(true, false).SetFixed(1, 0)
|
|
|
|
// Add header row
|
|
headers := []string{"Name", "Sequence", "Total Tables", "Description"}
|
|
headerWidths := []int{20, 15, 20}
|
|
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)
|
|
domainTable.SetCell(0, i, cell)
|
|
}
|
|
|
|
// Add existing domains
|
|
for row, domain := range se.db.Domains {
|
|
domain := domain // capture for closure
|
|
|
|
// Name - pad to 20 chars
|
|
nameStr := fmt.Sprintf("%-20s", domain.Name)
|
|
nameCell := tview.NewTableCell(nameStr).SetSelectable(true)
|
|
domainTable.SetCell(row+1, 0, nameCell)
|
|
|
|
// Sequence - pad to 15 chars
|
|
seqStr := fmt.Sprintf("%-15s", fmt.Sprintf("%d", domain.Sequence))
|
|
seqCell := tview.NewTableCell(seqStr).SetSelectable(true)
|
|
domainTable.SetCell(row+1, 1, seqCell)
|
|
|
|
// Total Tables - pad to 20 chars
|
|
tablesStr := fmt.Sprintf("%-20s", fmt.Sprintf("%d", len(domain.Tables)))
|
|
tablesCell := tview.NewTableCell(tablesStr).SetSelectable(true)
|
|
domainTable.SetCell(row+1, 2, tablesCell)
|
|
|
|
// Description - no padding, takes remaining space
|
|
descCell := tview.NewTableCell(domain.Description).SetSelectable(true)
|
|
domainTable.SetCell(row+1, 3, descCell)
|
|
}
|
|
|
|
domainTable.SetTitle(" Domains ").SetBorder(true).SetTitleAlign(tview.AlignLeft)
|
|
|
|
// Action buttons flex
|
|
btnFlex := tview.NewFlex()
|
|
btnNewDomain := tview.NewButton("New Domain [n]").SetSelectedFunc(func() {
|
|
se.showNewDomainDialog()
|
|
})
|
|
btnBack := tview.NewButton("Back [b]").SetSelectedFunc(func() {
|
|
se.pages.SwitchToPage("main")
|
|
se.pages.RemovePage("domains")
|
|
})
|
|
|
|
// Set up button input captures for Tab/Shift+Tab navigation
|
|
btnNewDomain.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyBacktab {
|
|
se.app.SetFocus(domainTable)
|
|
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(btnNewDomain)
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyTab {
|
|
se.app.SetFocus(domainTable)
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
btnFlex.AddItem(btnNewDomain, 0, 1, true).
|
|
AddItem(btnBack, 0, 1, false)
|
|
|
|
domainTable.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyEscape {
|
|
se.pages.SwitchToPage("main")
|
|
se.pages.RemovePage("domains")
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyTab {
|
|
se.app.SetFocus(btnNewDomain)
|
|
return nil
|
|
}
|
|
if event.Key() == tcell.KeyEnter {
|
|
row, _ := domainTable.GetSelection()
|
|
if row > 0 && row <= len(se.db.Domains) { // Skip header row
|
|
domainIndex := row - 1
|
|
se.showDomainEditor(domainIndex, se.db.Domains[domainIndex])
|
|
return nil
|
|
}
|
|
}
|
|
if event.Rune() == 'n' {
|
|
se.showNewDomainDialog()
|
|
return nil
|
|
}
|
|
if event.Rune() == 'b' {
|
|
se.pages.SwitchToPage("main")
|
|
se.pages.RemovePage("domains")
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
flex.AddItem(title, 1, 0, false).
|
|
AddItem(domainTable, 0, 1, true).
|
|
AddItem(btnFlex, 1, 0, false)
|
|
|
|
se.pages.AddPage("domains", flex, true, true)
|
|
}
|
|
|
|
// showNewDomainDialog displays a dialog to create a new domain
|
|
func (se *SchemaEditor) showNewDomainDialog() {
|
|
form := tview.NewForm()
|
|
|
|
domainName := ""
|
|
domainDesc := ""
|
|
|
|
form.AddInputField("Name", "", 40, nil, func(value string) {
|
|
domainName = value
|
|
})
|
|
|
|
form.AddInputField("Description", "", 50, nil, func(value string) {
|
|
domainDesc = value
|
|
})
|
|
|
|
form.AddButton("Save", func() {
|
|
if domainName == "" {
|
|
return
|
|
}
|
|
se.createDomain(domainName, domainDesc)
|
|
se.pages.RemovePage("new-domain")
|
|
se.pages.RemovePage("domains")
|
|
se.showDomainList()
|
|
})
|
|
|
|
form.AddButton("Back", func() {
|
|
se.pages.RemovePage("new-domain")
|
|
se.pages.RemovePage("domains")
|
|
se.showDomainList()
|
|
})
|
|
|
|
form.SetBorder(true).SetTitle(" New Domain ").SetTitleAlign(tview.AlignLeft)
|
|
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyEscape {
|
|
se.showExitConfirmation("new-domain", "domains")
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
se.pages.AddPage("new-domain", form, true, true)
|
|
}
|
|
|
|
// showDomainEditor displays a dialog to edit an existing domain
|
|
func (se *SchemaEditor) showDomainEditor(index int, domain *models.Domain) {
|
|
form := tview.NewForm()
|
|
|
|
domainName := domain.Name
|
|
domainDesc := domain.Description
|
|
|
|
form.AddInputField("Name", domainName, 40, nil, func(value string) {
|
|
domainName = value
|
|
})
|
|
|
|
form.AddInputField("Description", domainDesc, 50, nil, func(value string) {
|
|
domainDesc = value
|
|
})
|
|
|
|
form.AddButton("Save", func() {
|
|
if domainName == "" {
|
|
return
|
|
}
|
|
se.updateDomain(index, domainName, domainDesc)
|
|
se.pages.RemovePage("edit-domain")
|
|
se.pages.RemovePage("domains")
|
|
se.showDomainList()
|
|
})
|
|
|
|
form.AddButton("Delete", func() {
|
|
se.showDeleteDomainConfirm(index)
|
|
})
|
|
|
|
form.AddButton("Back", func() {
|
|
se.pages.RemovePage("edit-domain")
|
|
se.pages.RemovePage("domains")
|
|
se.showDomainList()
|
|
})
|
|
|
|
form.SetBorder(true).SetTitle(" Edit Domain ").SetTitleAlign(tview.AlignLeft)
|
|
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyEscape {
|
|
se.showExitConfirmation("edit-domain", "domains")
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
se.pages.AddPage("edit-domain", form, true, true)
|
|
}
|
|
|
|
// showDeleteDomainConfirm shows a confirmation dialog before deleting a domain
|
|
func (se *SchemaEditor) showDeleteDomainConfirm(index int) {
|
|
modal := tview.NewModal().
|
|
SetText(fmt.Sprintf("Delete domain '%s'? This action cannot be undone.", se.db.Domains[index].Name)).
|
|
AddButtons([]string{"Cancel", "Delete"}).
|
|
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
|
if buttonLabel == "Delete" {
|
|
se.deleteDomain(index)
|
|
se.pages.RemovePage("delete-domain-confirm")
|
|
se.pages.RemovePage("edit-domain")
|
|
se.pages.RemovePage("domains")
|
|
se.showDomainList()
|
|
} else {
|
|
se.pages.RemovePage("delete-domain-confirm")
|
|
}
|
|
})
|
|
|
|
modal.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
|
if event.Key() == tcell.KeyEscape {
|
|
se.pages.RemovePage("delete-domain-confirm")
|
|
return nil
|
|
}
|
|
return event
|
|
})
|
|
|
|
se.pages.AddAndSwitchToPage("delete-domain-confirm", modal, true)
|
|
}
|