stage 0 database schema ready
This commit is contained in:
parent
f463e71c28
commit
7720522a32
18 changed files with 5577 additions and 4 deletions
|
|
@ -1 +1,206 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// STYLING DEFINITIONS (Lipgloss)
|
||||
// ============================================================================
|
||||
var (
|
||||
focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true)
|
||||
blurredStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
|
||||
cursorStyle = focusedStyle.Copy()
|
||||
noStyle = lipgloss.NewStyle()
|
||||
|
||||
// A nice box around the login screen
|
||||
dialogBoxStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("87")).
|
||||
Padding(1, 4).
|
||||
MarginTop(1).
|
||||
MarginLeft(2)
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 1. THE MODEL
|
||||
// ============================================================================
|
||||
type loginModel struct {
|
||||
focusIndex int
|
||||
inputs []textinput.Model
|
||||
cursorMode textinput.CursorMode
|
||||
|
||||
// Flags to return back to the main application
|
||||
isSubmitted bool
|
||||
isCanceled bool
|
||||
errorMessage string
|
||||
}
|
||||
|
||||
func initialModell() loginModel {
|
||||
m := loginModel{
|
||||
inputs: make([]textinput.Model, 2),
|
||||
}
|
||||
|
||||
var t textinput.Model
|
||||
for i := range m.inputs {
|
||||
t = textinput.New()
|
||||
t.Cursor.Style = cursorStyle
|
||||
t.CharLimit = 32
|
||||
|
||||
switch i {
|
||||
case 0:
|
||||
t.Placeholder = "Username"
|
||||
t.Focus()
|
||||
t.PromptStyle = focusedStyle
|
||||
t.TextStyle = focusedStyle
|
||||
case 1:
|
||||
t.Placeholder = "Password"
|
||||
t.EchoMode = textinput.EchoPassword // Masks the password with *
|
||||
t.EchoCharacter = '•'
|
||||
}
|
||||
|
||||
m.inputs[i] = t
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 2. THE INIT FUNCTION (Runs once on startup)
|
||||
// ============================================================================
|
||||
func (m loginModel) Init() tea.Cmd {
|
||||
return textinput.Blink
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 3. THE UPDATE FUNCTION (Handles keypresses)
|
||||
// ============================================================================
|
||||
func (m loginModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "esc":
|
||||
m.isCanceled = true
|
||||
return m, tea.Quit
|
||||
|
||||
// Handle switching between username and password fields
|
||||
case "tab", "shift+tab", "enter", "up", "down":
|
||||
s := msg.String()
|
||||
|
||||
// If they press Enter on the password field, submit the form
|
||||
if s == "enter" && m.focusIndex == len(m.inputs)-1 {
|
||||
m.isSubmitted = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
// Cycle through inputs
|
||||
if s == "up" || s == "shift+tab" {
|
||||
m.focusIndex--
|
||||
} else {
|
||||
m.focusIndex++
|
||||
}
|
||||
|
||||
if m.focusIndex > len(m.inputs)-1 {
|
||||
m.focusIndex = 0
|
||||
} else if m.focusIndex < 0 {
|
||||
m.focusIndex = len(m.inputs) - 1
|
||||
}
|
||||
|
||||
// Apply focus states
|
||||
cmds := make([]tea.Cmd, len(m.inputs))
|
||||
for i := 0; i <= len(m.inputs)-1; i++ {
|
||||
if i == m.focusIndex {
|
||||
cmds[i] = m.inputs[i].Focus()
|
||||
m.inputs[i].PromptStyle = focusedStyle
|
||||
m.inputs[i].TextStyle = focusedStyle
|
||||
continue
|
||||
}
|
||||
m.inputs[i].Blur()
|
||||
m.inputs[i].PromptStyle = noStyle
|
||||
m.inputs[i].TextStyle = noStyle
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle character inputs and blinking
|
||||
cmd := m.updateInputs(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m *loginModel) updateInputs(msg tea.Msg) tea.Cmd {
|
||||
cmds := make([]tea.Cmd, len(m.inputs))
|
||||
for i := range m.inputs {
|
||||
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
|
||||
}
|
||||
return tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 4. THE VIEW FUNCTION (Renders the UI)
|
||||
// ============================================================================
|
||||
func (m loginModel) View() string {
|
||||
// Build the UI string
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("78")).Render("✈️ SKYRAMA TERMINAL LOGIN\n\n"))
|
||||
|
||||
for i := range m.inputs {
|
||||
b.WriteString(m.inputs[i].View())
|
||||
if i < len(m.inputs)-1 {
|
||||
b.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
|
||||
button := &lipgloss.Style{}
|
||||
if m.focusIndex == len(m.inputs) {
|
||||
button = &focusedStyle
|
||||
} else {
|
||||
button = &blurredStyle
|
||||
}
|
||||
|
||||
b.WriteString("\n")
|
||||
if m.errorMessage != "" {
|
||||
b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("9")).Render("⚠️ " + m.errorMessage + "\n"))
|
||||
} else {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
b.WriteString(button.Render("[ Submit ]"))
|
||||
b.WriteString(blurredStyle.Render("\n\n(esc to quit)"))
|
||||
|
||||
// Wrap the whole thing in our dialog box
|
||||
return dialogBoxStyle.Render(b.String()) + "\n"
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// THE WRAPPER FUNCTION (Call this from your app)
|
||||
// ============================================================================
|
||||
// RunLoginScreen starts the TUI and returns the username, password, and a boolean
|
||||
// indicating if the user completed the form (true) or pressed escape (false).
|
||||
func RunLoginScreen() (username string, password string, submitted bool) {
|
||||
|
||||
p := tea.NewProgram(initialModell())
|
||||
|
||||
// Run the Bubble Tea program synchronously
|
||||
finalModel, err := p.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("Error running login screen: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Cast the returned tea.Model back into our specific loginModel
|
||||
if m, ok := finalModel.(loginModel); ok {
|
||||
if m.isSubmitted {
|
||||
return m.inputs[0].Value(), m.inputs[1].Value(), true
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue