121 lines
2.8 KiB
Go
121 lines
2.8 KiB
Go
|
|
package adminui
|
||
|
|
|
||
|
|
import (
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
tea "github.com/charmbracelet/bubbletea"
|
||
|
|
"github.com/charmbracelet/lipgloss"
|
||
|
|
"skyrama-tui/internal/db"
|
||
|
|
)
|
||
|
|
|
||
|
|
var tabNames = []string{"Products", "Countries", "Buildings", "Planes", "Users", "Airports", "Factions"}
|
||
|
|
|
||
|
|
// App is the root admin model. It owns the tab bar and routes messages.
|
||
|
|
type App struct {
|
||
|
|
tabs []Tab
|
||
|
|
activeTab int
|
||
|
|
width int
|
||
|
|
height int
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewApp constructs the admin app and wires up all tab sub-models.
|
||
|
|
func NewApp(client *db.AdminClient) App {
|
||
|
|
return App{
|
||
|
|
tabs: []Tab{
|
||
|
|
newProductsTab(client),
|
||
|
|
newCountriesTab(client),
|
||
|
|
newBuildingsTab(client),
|
||
|
|
newPlanesTab(client),
|
||
|
|
newUsersTab(client),
|
||
|
|
newAirportsTab(client),
|
||
|
|
newFactionsTab(client),
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (a App) Init() tea.Cmd {
|
||
|
|
var cmds []tea.Cmd
|
||
|
|
for _, t := range a.tabs {
|
||
|
|
if cmd := t.Init(); cmd != nil {
|
||
|
|
cmds = append(cmds, cmd)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return tea.Batch(cmds...)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||
|
|
switch m := msg.(type) {
|
||
|
|
case tea.WindowSizeMsg:
|
||
|
|
a.width, a.height = m.Width, m.Height
|
||
|
|
// Fan out window size to all tabs
|
||
|
|
for i, t := range a.tabs {
|
||
|
|
updated, _ := t.Update(msg)
|
||
|
|
a.tabs[i] = updated.(Tab)
|
||
|
|
}
|
||
|
|
return a, nil
|
||
|
|
|
||
|
|
case tea.KeyMsg:
|
||
|
|
activeInForm := a.tabs[a.activeTab].InForm()
|
||
|
|
|
||
|
|
// Global quit always works
|
||
|
|
if m.String() == "ctrl+c" {
|
||
|
|
return a, tea.Quit
|
||
|
|
}
|
||
|
|
|
||
|
|
// Tab switching only when not in a form
|
||
|
|
if !activeInForm {
|
||
|
|
switch m.String() {
|
||
|
|
case "1", "2", "3", "4", "5", "6", "7":
|
||
|
|
idx := int(m.String()[0] - '1')
|
||
|
|
if idx < len(a.tabs) {
|
||
|
|
a.activeTab = idx
|
||
|
|
}
|
||
|
|
return a, nil
|
||
|
|
case "tab":
|
||
|
|
a.activeTab = (a.activeTab + 1) % len(a.tabs)
|
||
|
|
return a, nil
|
||
|
|
case "shift+tab":
|
||
|
|
a.activeTab = (a.activeTab - 1 + len(a.tabs)) % len(a.tabs)
|
||
|
|
return a, nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fan out non-key messages to all tabs; key messages only go to active tab
|
||
|
|
if _, isKey := msg.(tea.KeyMsg); !isKey {
|
||
|
|
var cmds []tea.Cmd
|
||
|
|
for i, t := range a.tabs {
|
||
|
|
updated, cmd := t.Update(msg)
|
||
|
|
a.tabs[i] = updated.(Tab)
|
||
|
|
if cmd != nil {
|
||
|
|
cmds = append(cmds, cmd)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return a, tea.Batch(cmds...)
|
||
|
|
}
|
||
|
|
|
||
|
|
updated, cmd := a.tabs[a.activeTab].Update(msg)
|
||
|
|
a.tabs[a.activeTab] = updated.(Tab)
|
||
|
|
return a, cmd
|
||
|
|
}
|
||
|
|
|
||
|
|
func (a App) View() string {
|
||
|
|
// Tab bar
|
||
|
|
var tabs []string
|
||
|
|
for i, name := range tabNames {
|
||
|
|
key := dimSt.Render(string(rune('1'+i))) + " "
|
||
|
|
if i == a.activeTab {
|
||
|
|
tabs = append(tabs, lipgloss.NewStyle().Bold(true).
|
||
|
|
Foreground(lipgloss.Color("#E94560")).
|
||
|
|
Render("["+key+name+"]"))
|
||
|
|
} else {
|
||
|
|
tabs = append(tabs, dimSt.Render(" "+key+name+" "))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
bar := strings.Join(tabs, dimSt.Render("│"))
|
||
|
|
header := titleSt.Render("✈ SKYRAMA ADMIN") + " " + bar
|
||
|
|
|
||
|
|
divider := dimSt.Render(strings.Repeat("─", a.width))
|
||
|
|
|
||
|
|
return header + "\n" + divider + "\n" + a.tabs[a.activeTab].View()
|
||
|
|
}
|