Initial implementation of Gitea Wrapped TUI
- Gitea API client with repository and commit fetching - Interactive credential input screen with masked token input - Statistics analyzer for commits, languages, and activity patterns - Multi-page report screen with ASCII charts and visualizations - Integration of all components in main app coordinator - Comprehensive README with usage instructions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type inputScreenState int
|
||||
|
||||
const (
|
||||
urlInput inputScreenState = iota
|
||||
usernameInput
|
||||
tokenInput
|
||||
)
|
||||
|
||||
type InputScreen struct {
|
||||
urlField textinput.Model
|
||||
usernameField textinput.Model
|
||||
tokenField textinput.Model
|
||||
focusedField inputScreenState
|
||||
err error
|
||||
url string
|
||||
username string
|
||||
token string
|
||||
done bool
|
||||
}
|
||||
|
||||
func NewInputScreen() *InputScreen {
|
||||
urlField := textinput.New()
|
||||
urlField.Placeholder = "https://gitea.example.com"
|
||||
urlField.Focus()
|
||||
|
||||
usernameField := textinput.New()
|
||||
usernameField.Placeholder = "your_username"
|
||||
|
||||
tokenField := textinput.New()
|
||||
tokenField.Placeholder = "your_access_token"
|
||||
tokenField.EchoMode = textinput.EchoPassword
|
||||
|
||||
return &InputScreen{
|
||||
urlField: urlField,
|
||||
usernameField: usernameField,
|
||||
tokenField: tokenField,
|
||||
focusedField: urlInput,
|
||||
}
|
||||
}
|
||||
|
||||
func (is *InputScreen) Init() tea.Cmd {
|
||||
return textinput.Blink
|
||||
}
|
||||
|
||||
func (is *InputScreen) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.Type {
|
||||
case tea.KeyCtrlC:
|
||||
return is, tea.Quit
|
||||
case tea.KeyEnter:
|
||||
switch is.focusedField {
|
||||
case urlInput:
|
||||
is.url = is.urlField.Value()
|
||||
is.focusedField = usernameInput
|
||||
is.usernameField.Focus()
|
||||
return is, nil
|
||||
case usernameInput:
|
||||
is.username = is.usernameField.Value()
|
||||
is.focusedField = tokenInput
|
||||
is.tokenField.Focus()
|
||||
return is, nil
|
||||
case tokenInput:
|
||||
is.token = is.tokenField.Value()
|
||||
is.done = true
|
||||
return is, nil
|
||||
}
|
||||
case tea.KeyTab, tea.KeyShiftTab:
|
||||
switch is.focusedField {
|
||||
case urlInput:
|
||||
is.url = is.urlField.Value()
|
||||
case usernameInput:
|
||||
is.username = is.usernameField.Value()
|
||||
case tokenInput:
|
||||
is.token = is.tokenField.Value()
|
||||
}
|
||||
|
||||
if msg.Type == tea.KeyTab {
|
||||
is.focusedField = (is.focusedField + 1) % 3
|
||||
} else {
|
||||
is.focusedField = (is.focusedField - 1 + 3) % 3
|
||||
}
|
||||
|
||||
is.urlField.Blur()
|
||||
is.usernameField.Blur()
|
||||
is.tokenField.Blur()
|
||||
|
||||
switch is.focusedField {
|
||||
case urlInput:
|
||||
is.urlField.Focus()
|
||||
case usernameInput:
|
||||
is.usernameField.Focus()
|
||||
case tokenInput:
|
||||
is.tokenField.Focus()
|
||||
}
|
||||
return is, nil
|
||||
}
|
||||
|
||||
case errMsg:
|
||||
is.err = msg
|
||||
return is, nil
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
switch is.focusedField {
|
||||
case urlInput:
|
||||
is.urlField, cmd = is.urlField.Update(msg)
|
||||
case usernameInput:
|
||||
is.usernameField, cmd = is.usernameField.Update(msg)
|
||||
case tokenInput:
|
||||
is.tokenField, cmd = is.tokenField.Update(msg)
|
||||
}
|
||||
|
||||
return is, cmd
|
||||
}
|
||||
|
||||
func (is *InputScreen) View() string {
|
||||
var s string
|
||||
|
||||
s += lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("212")).
|
||||
Bold(true).
|
||||
Render("Gitea Wrapped") + "\n\n"
|
||||
|
||||
s += "Enter your Gitea credentials to get started:\n\n"
|
||||
|
||||
s += "Gitea URL\n"
|
||||
s += is.urlField.View() + "\n\n"
|
||||
|
||||
s += "Username\n"
|
||||
s += is.usernameField.View() + "\n\n"
|
||||
|
||||
s += "Access Token\n"
|
||||
s += is.tokenField.View() + "\n\n"
|
||||
|
||||
if is.err != nil {
|
||||
s += lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("1")).
|
||||
Render(fmt.Sprintf("Error: %v", is.err)) + "\n"
|
||||
}
|
||||
|
||||
s += lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("8")).
|
||||
Render("(press Tab to navigate, Enter to continue, Ctrl+C to quit)") + "\n"
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (is *InputScreen) IsDone() bool {
|
||||
return is.done
|
||||
}
|
||||
|
||||
func (is *InputScreen) GetCredentials() (string, string, string) {
|
||||
return is.url, is.username, is.token
|
||||
}
|
||||
|
||||
type errMsg error
|
||||
Reference in New Issue
Block a user