+++ /dev/null
-package main
-
-// An example demonstrating an application with multiple views.
-//
-// Note that this example was produced before the Bubbles progress component
-// was available (github.com/charmbracelet/bubbles/progress) and thus, we're
-// implementing a progress bar from scratch here.
-
-import (
- "fmt"
- "net/http"
- "io/ioutil"
- // "time"
- "encoding/json"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
-)
-
-const (
- dotChar = " • "
- arrowRightChar = " → "
- apiUrl = "https://repoman.christiangoeschel.com:8443"
-)
-
-// General stuff for styling the view
-var (
- spinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("69"))
- keywordStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
- subtleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
- checkboxStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#03A9F4"))
- titleStyle = lipgloss.NewStyle().Background(lipgloss.Color("#448AFF")).Foreground(lipgloss.Color("#FFFFFF"))
- statusOK = lipgloss.NewStyle().Foreground(lipgloss.Color("200"))
- dotStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("236")).Render(dotChar)
- mainStyle = lipgloss.NewStyle().Margin(10)
-
-
- mainMenuOptions = []string{
- "List all repos",
- "Create a new repository",
- "Delete a repository",
- "Manage API Keys",
- "Check API Status",
- "Settings",
- "Quit",
- }
-
-
-)
-
-func main() {
- initialModel := model{0, false, false, APIStatusMsg{} }
-
- p := tea.NewProgram(initialModel, tea.WithAltScreen())
- if _, err := p.Run(); err != nil {
- fmt.Println("could not start program:", err)
- }
-
-}
-
-type (
- frameMsg struct{}
-)
-
-
-type model struct {
- Choice int
- Chosen bool
- Quitting bool
- APIStatus APIStatusMsg
-}
-
-func (m model) Init() tea.Cmd {
-
- return nil
-}
-
-// Main update function.
-func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
-
- // Make sure these keys always quit
- if msg, ok := msg.(tea.KeyMsg); ok {
- key := msg.String()
-
- if key == "q" || key == "esc" || key == "ctrl+c" {
- m.Quitting = true
- return m, tea.Quit
- } else if key == "backspace" {
- m.Chosen = false
- }
- }
-
- // Hand off the message and model to the appropriate update function for the
- // appropriate view based on the current state.
- if !m.Chosen {
- return updateMainMenu(msg, m)
- }
-
- return updateChosen(msg, m)
-}
-
-// The main view, which just calls the appropriate sub-view
-func (m model) View() string {
- var s string
- if m.Quitting {
- return "\n See you soon my friend! \n\n"
- }
- if !m.Chosen {
- s = mainMenuView(m)
- } else {
- s = chosenView(m)
- }
-
- return mainStyle.Render("\n" + s + "\n\n")
-}
-
-// Sub-update functions
-
-// Update loop for the first view where you're choosing a task.
-func updateMainMenu(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
- switch msg := msg.(type) {
- case tea.KeyMsg:
- var lastChoice int = len(mainMenuOptions) - 1
-
- switch msg.String() {
-
- case "j", "down":
- m.Choice++
- if m.Choice > lastChoice {
- m.Choice = 0
- }
- case "k", "up":
- m.Choice--
- if m.Choice < 0 {
- m.Choice = lastChoice
- }
- case "enter", " ":
- m.Chosen = true
-
- if mainMenuOptions[m.Choice] == "Quit" {
- return m, tea.Quit
- }
-
- return m, nil
- }
-
-}
-
- return m, nil
-}
-
-
-// Update loop for the second view after a choice has been made
-func updateChosen(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
-
- switch msg := msg.(type) {
-
- case tea.KeyMsg:
- switch msg.String() {
- case "enter":
- if m.Choice == 4 {
- m.APIStatus = APIStatusMsg{}
- return m, handleAPIStatusRequest(apiUrl)
- }
- }
-
- case APIStatusMsg:
- m.APIStatus = msg
-
- return m, nil
- }
-
- return m, nil
-}
-
-// Sub-views
-
-// The first view, where you're choosing a task
-func mainMenuView(m model) string {
- c := m.Choice
-
- tpl := fmt.Sprintf("%s\n\n", titleStyle.Render("[ +-+ Repoman CLI +-+ ]"))
- tpl += "Main Menu\n\n"
- tpl += "%s\n\n"
- tpl += fmt.Sprintf("%s\n%s\n%s\n%s\n", subtleStyle.Render("j/k, up/down: select"), subtleStyle.Render("enter, spacebar: choose"),
- subtleStyle.Render("q, esc: quit"), subtleStyle.Render("backspace: main menu"))
-
- var choices string
- for i := 0;i < len(mainMenuOptions);i++{
- choices += fmt.Sprintf("%s\n", checkbox(mainMenuOptions[i], c == i))
- }
-
- return fmt.Sprintf(tpl, choices)
-}
-
-// The second view, after a task has been chosen
-func chosenView(m model) string {
- var msg string
-
- switch m.Choice {
-
- case 0:
- msg = fmt.Sprintf("%s\n\nCool, we'll need %s and %s...",
- titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
- keywordStyle.Render("libgarden"),
- keywordStyle.Render("vegeutils"))
-
- case 1:
- msg = fmt.Sprintf("%s\n\nOkay, then we should install %s and %s...",
- titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
- keywordStyle.Render("marketkit"),
- keywordStyle.Render("libshopping"))
-
- case 2:
- msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.",
- titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
- keywordStyle.Render("actual library"))
-
- case 3:
- msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.",
- titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
- keywordStyle.Render("actual library"))
-
- case 4:
-
- statusResponse := fmt.Sprintf("Status:%s\nDescription:%s\nStart:%s\nExpected End:%s\n",
- keywordStyle.Render(m.APIStatus.Status),
- keywordStyle.Render(m.APIStatus.Description),
- keywordStyle.Render(m.APIStatus.StartDateTime),
- keywordStyle.Render(m.APIStatus.EstimatedEndTime))
-
- msg = fmt.Sprintf("%s\n\nRequesting API Server Status from:\n%s\n\nResponse:\n\n%v\n\n%s",
- titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
- keywordStyle.Render(apiUrl),
- statusResponse,
- subtleStyle.Render("Press Enter to refresh"))
-
- case 5:
- msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.",
- titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
- keywordStyle.Render("actual library"))
-
- default:
- msg = fmt.Sprintf("Seems like you are lost.\n\nI'll help you find your way back ... simply press %s",
- keywordStyle.Render("backspace"))
- }
-
-
- return msg + "\n\n"
-}
-
-func checkbox(label string, checked bool) string {
- if checked {
- return checkboxStyle.Render("+ " + label)
- }
- return fmt.Sprintf("- %s", label)
-}
-
-// Utils
-
-// API Status Handle
-func handleAPIStatusRequest(url string) tea.Cmd {
-
- return func() tea.Msg {
- url := fmt.Sprintf("%s/status", url)
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- fmt.Print(err.Error())
- }
-
- res, err := http.DefaultClient.Do(req)
- if err != nil {
-
- }
- defer res.Body.Close()
-
- body, readErr := ioutil.ReadAll(res.Body)
- if readErr != nil {
- fmt.Print(err.Error())
- }
-
- var apiStatus APIStatusMsg
- if err := json.Unmarshal(body, &apiStatus); err != nil { // Parse []byte to the go struct pointer
- fmt.Println("Can not unmarshal JSON")
- }
-
-
- return apiStatus
- }
-}
-
-type APIStatus struct {
- Status string `json:"Status"`
- Description string `json:"Description"`
- StartDateTime string `json:"StartDateTime"`
- EstimatedEndTime string `json:"EstimatedEndTime"`
-}
-
-type APIStatusMsg APIStatus
-
go 1.23.0
require (
- github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.1.1
github.com/charmbracelet/lipgloss v0.13.0
+ github.com/repoman-cli/screens v0.0.0-00010101000000-000000000000
)
require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
- github.com/mattn/go-runewidth v0.0.16 // indirect
+ github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect
+ github.com/repoman-cli/lib v0.0.0-00010101000000-000000000000 // indirect
+ github.com/repoman-cli/themes v0.0.0-00010101000000-000000000000 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.3.8 // indirect
)
+
+replace github.com/repoman-cli/lib => /home/cgoesche/repoman-cli/lib
+
+replace github.com/repoman-cli/themes => /home/cgoesche/repoman-cli/themes
+
+replace github.com/repoman-cli/screens => /home/cgoesche/repoman-cli/screens
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
-github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
-github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY=
github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
-github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
-github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
--- /dev/null
+module git.christiangoeschel.com/repoman-cli/lib
+
+go 1.23.0
--- /dev/null
+package lib
+
+type ProgramInfo struct {
+ ProgramName string
+ Description string
+ Version string
+ BuildID string
+ CreationDate string
+ LastModification string
+ Maintainers []string
+ License string
+}
+
+func GetProgramInfo() *ProgramInfo{
+
+ maintainers := []string{
+ "Christian Goeschel Ndjomouo (cgoesc2@wgu.edu)",
+ }
+
+ pInfo := ProgramInfo {
+ ProgramName: "Repoman Client",
+ Description: "A simplistic terminal user interface to manage your self-hosted Repoman Git repository",
+ Version: "0.1.0 alpha",
+ BuildID: "a89d2e1",
+ CreationDate: "2024-09-30",
+ Maintainers: maintainers,
+ License: "Copyright 2024 Christian Goeschel - MIT License",
+ }
+
+ return &pInfo
+}
+
+
+
+
+
+
+
--- /dev/null
+package main
+
+import (
+ "fmt"
+ "os"
+ "github.com/repoman-cli/screens"
+
+ tea "github.com/charmbracelet/bubbletea"
+)
+
+// Main
+func main(){
+
+ p := tea.NewProgram(screens.RootScreen(0), tea.WithAltScreen())
+ if _, err := p.Run(); err != nil {
+ fmt.Println("Error starting program:", err)
+ os.Exit(1)
+ }
+}
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+package main
+
+
+import (
+ "fmt"
+ "net/http"
+ "io/ioutil"
+ "encoding/json"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+)
+
+const (
+ dotChar = " • "
+ arrowRightChar = " → "
+ apiUrl = "https://repoman.christiangoeschel.com:8443"
+)
+
+var (
+ spinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("69"))
+ keywordStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
+ subtleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
+ checkboxStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#03A9F4"))
+ titleStyle = lipgloss.NewStyle().Background(lipgloss.Color("#448AFF")).Foreground(lipgloss.Color("#FFFFFF"))
+ statusOK = lipgloss.NewStyle().Foreground(lipgloss.Color("200"))
+ dotStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("236")).Render(dotChar)
+ mainStyle = lipgloss.NewStyle().Margin(15).Padding(10)
+ Altstyle = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("63"))
+
+
+ mainMenuOptions = []string{
+ "List all repos",
+ "Create a new repository",
+ "Delete a repository",
+ "Manage API Keys",
+ "Check API Status",
+ "Settings",
+ "Quit",
+ }
+
+)
+
+func main() {
+
+ initialModel := model{0, false, false, false, APIStatusMsg{} }
+
+ p := tea.NewProgram(initialModel, tea.WithAltScreen())
+ if _, err := p.Run(); err != nil {
+ fmt.Println("could not start program:", err)
+ }
+
+}
+
+type model struct {
+ Choice int
+ Chosen bool
+ Loaded bool
+ Quitting bool
+ APIStatus APIStatusMsg
+}
+
+func (m model) Init() tea.Cmd {
+ return nil
+}
+
+// Main update function.
+func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+
+ // Make sure these keys always quit
+ if msg, ok := msg.(tea.KeyMsg); ok {
+ key := msg.String()
+
+ if key == "q" || key == "esc" || key == "ctrl+c" {
+ m.Quitting = true
+ return m, tea.Quit
+ } else if key == "backspace" {
+ m.Chosen = false
+ }
+ }
+
+ // Hand off the message and model to the appropriate update function for the
+ // appropriate view based on the current state.
+ if !m.Chosen {
+ return updateMainMenu(msg, m)
+ }
+
+ return updateChosen(msg, m)
+}
+
+// The main view, which just calls the appropriate sub-view
+func (m model) View() string {
+ var s string
+ if m.Quitting {
+ return "\n See you soon my friend! \n\n"
+ }
+ if !m.Chosen {
+ s = mainMenuView(m)
+ } else {
+ s = chosenView(m)
+ }
+
+ return mainStyle.Render("\n" + s + "\n\n")
+}
+
+// Sub-update functions
+
+// Update loop for the first view where you're choosing a task.
+func updateMainMenu(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ var lastChoice int = len(mainMenuOptions) - 1
+
+ switch msg.String() {
+
+ case "j", "down":
+ m.Choice++
+ if m.Choice > lastChoice {
+ m.Choice = 0
+ }
+ case "k", "up":
+ m.Choice--
+ if m.Choice < 0 {
+ m.Choice = lastChoice
+ }
+ case "enter", " ":
+ m.Chosen = true
+
+ if mainMenuOptions[m.Choice] == "Quit" {
+ return m, tea.Quit
+ }
+
+ return m, nil
+ }
+
+ }
+
+ return m, nil
+}
+
+
+// Update loop for the second view after a choice has been made
+func updateChosen(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
+
+ switch msg := msg.(type) {
+
+ case tea.KeyMsg:
+ switch msg.String() {
+ case "enter":
+ if m.Choice == 4 {
+ m.APIStatus = APIStatusMsg{}
+ return m, handleAPIStatusRequest(apiUrl)
+ }
+ }
+
+ case APIStatusMsg:
+ m.APIStatus = msg
+ return m, nil
+
+
+ }
+
+ return m, nil
+}
+
+// Sub-views
+
+// The first view, where you're choosing a task
+func mainMenuView(m model) string {
+ c := m.Choice
+
+ tpl := fmt.Sprintf("%s\n\n", titleStyle.Render("[ +-+ Repoman CLI +-+ ]"))
+ tpl += "Main Menu\n\n"
+ tpl += "%s\n\n"
+ tpl += fmt.Sprintf("%s\n%s\n%s\n%s\n", subtleStyle.Render("j/k, up/down: select"), subtleStyle.Render("enter, spacebar: choose"),
+ subtleStyle.Render("q, esc: quit"), subtleStyle.Render("backspace: main menu"))
+
+ var opts string
+ for i := 0;i < len(mainMenuOptions);i++{
+ opts += fmt.Sprintf("%s\n", checkbox(mainMenuOptions[i], c == i))
+ }
+
+ return fmt.Sprintf(tpl, opts)
+}
+
+// The second view, after a task has been chosen
+func chosenView(m model) string {
+ var msg string
+
+ switch m.Choice {
+
+ case 0:
+ msg = fmt.Sprintf("%s\n\nCool, we'll need %s and %s...",
+ titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
+ keywordStyle.Render("libgarden"),
+ keywordStyle.Render("vegeutils"))
+
+ case 1:
+ msg = fmt.Sprintf("%s\n\nOkay, then we should install %s and %s...",
+ titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
+ keywordStyle.Render("marketkit"),
+ keywordStyle.Render("libshopping"))
+
+ case 2:
+ msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.",
+ titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
+ keywordStyle.Render("actual library"))
+
+ case 3:
+ msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.",
+ titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
+ keywordStyle.Render("actual library"))
+
+ case 4:
+
+ statusResponse := fmt.Sprintf("Status:%s\nDescription:%s\nStart:%s\nExpected End:%s\n",
+ keywordStyle.Render(m.APIStatus.Status),
+ keywordStyle.Render(m.APIStatus.Description),
+ keywordStyle.Render(m.APIStatus.StartDateTime),
+ keywordStyle.Render(m.APIStatus.EstimatedEndTime))
+
+ msg = fmt.Sprintf("%s\n\nRequesting API Server Status from:\n%s\n\nResponse:\n\n%v\n\n%s",
+ titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
+ keywordStyle.Render(apiUrl),
+ statusResponse,
+ subtleStyle.Render("Press Enter to refresh"))
+
+ case 5:
+ msg = fmt.Sprintf("%s\n\nOkay, cool, then we’ll need a library. Yes, an %s.",
+ titleStyle.Render("[ +-+ " + mainMenuOptions[m.Choice] + " +-+ ]"),
+ keywordStyle.Render("actual library"))
+
+ default:
+ msg = fmt.Sprintf("Seems like you are lost.\n\nI'll help you find your way back ... simply press %s",
+ keywordStyle.Render("backspace"))
+ }
+
+ return msg + "\n\n"
+}
+
+func checkbox(label string, checked bool) string {
+ if checked {
+ return checkboxStyle.Render("+ " + label)
+ }
+ return fmt.Sprintf("- %s", label)
+}
+
+// Utils
+
+// API Status Handle
+func handleAPIStatusRequest(url string) tea.Cmd {
+
+ return func() tea.Msg {
+ url := fmt.Sprintf("%s/status", url)
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ fmt.Print(err.Error())
+ }
+
+ res, err := http.DefaultClient.Do(req)
+ if err != nil {
+
+ }
+ defer res.Body.Close()
+
+ body, readErr := ioutil.ReadAll(res.Body)
+ if readErr != nil {
+ fmt.Print(err.Error())
+ }
+
+ var apiStatus APIStatusMsg
+ if err := json.Unmarshal(body, &apiStatus); err != nil { // Parse []byte to the go struct pointer
+ fmt.Println("Can not unmarshal JSON")
+ }
+
+
+ return apiStatus
+ }
+}
+
+type APIStatus struct {
+ Status string `json:"Status"`
+ Description string `json:"Description"`
+ StartDateTime string `json:"StartDateTime"`
+ EstimatedEndTime string `json:"EstimatedEndTime"`
+}
+
+type APIStatusMsg APIStatus
+
--- /dev/null
+package screens
+
+
+import (
+ //"fmt"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/repoman-cli/themes"
+)
+
+
+type APIStatusScreenModel struct {
+ APIStatus string
+ URL string
+ Err error
+ StatusCode int
+}
+
+
+// --- AltScreenModel functions
+
+// Creates a new mainMenuScreenModel
+func NewAPIStatusScreen() APIStatusScreenModel {
+ return APIStatusScreenModel {
+ APIStatus: "Normal",
+ URL: "https://repoman.christiangoeschel.com:8443/status",
+ Err: nil,
+ StatusCode: 200,
+ }
+}
+
+// Init
+func (m *APIStatusScreenModel) Init() tea.Cmd {
+ return nil
+}
+
+// Update
+func (m *APIStatusScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ switch msg.String() {
+ case "q":
+ mainMenu := MainMenu()
+ return RootScreen(0).SwitchScreen(&mainMenu)
+
+ }
+
+ default:
+ return m, nil
+ }
+
+ return m, nil
+}
+
+// View
+func (m *APIStatusScreenModel) View() string {
+
+ msg := themes.KeywordStyle.Render("API Status")
+
+ return msg + "\n\n"
+}
+
--- /dev/null
+module git.christiangoeschel.com/repoman-cli/screens
+
+go 1.23.0
+
+require (
+ github.com/charmbracelet/bubbletea v1.1.1
+ github.com/repoman-cli/lib v0.0.0-00010101000000-000000000000
+)
+
+require (
+ github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
+ github.com/charmbracelet/lipgloss v0.13.0 // indirect
+ github.com/charmbracelet/x/ansi v0.2.3 // indirect
+ github.com/charmbracelet/x/term v0.2.0 // indirect
+ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
+ github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-localereader v0.0.1 // indirect
+ github.com/mattn/go-runewidth v0.0.15 // indirect
+ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
+ github.com/muesli/cancelreader v0.2.2 // indirect
+ github.com/muesli/termenv v0.15.2 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
+ golang.org/x/sync v0.8.0 // indirect
+ golang.org/x/sys v0.24.0 // indirect
+ golang.org/x/text v0.3.8 // indirect
+)
+
+replace github.com/repoman-cli/themes => /home/cgoesche/repoman-cli/themes
+replace github.com/repoman-cli/lib => /home/cgoesche/repoman-cli/lib
--- /dev/null
+github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
+github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
+github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY=
+github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
+github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
+github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
+github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
+github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
+github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
+github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
+github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
+github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
+github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
+github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
+github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
+github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
+github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
+github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
--- /dev/null
+package screens
+
+import (
+ "fmt"
+ "github.com/repoman-cli/lib"
+ //"github.com/repoman-cli/themes"
+
+ tea "github.com/charmbracelet/bubbletea"
+)
+
+
+ // Root Screen Model
+ type rootScreenModel struct {
+ model tea.Model // Contains the current screen model which represents the programs current state
+ theme int
+ }
+
+ //Main Menu Screen
+ type mainMenuScreenModel struct {
+ Title string
+ Message string
+ }
+
+
+// Initialize model
+func (m rootScreenModel) Init() tea.Cmd {
+ return m.model.Init() // Calls current model's Init function
+}
+
+// Update programs state based on received messages
+func (m rootScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ return m.model.Update(msg) // Calls current model's Update function
+}
+
+// Change the View of current model based on its state
+func (m rootScreenModel) View() string {
+ return m.model.View()
+}
+
+// --- Utils
+
+// Creates a new model based on what RootScreenModel.model Contains
+func RootScreen(theme int) rootScreenModel {
+ var rootModel tea.Model
+
+
+ // Set the rootModel (current state) to be the mainMenuScreen
+ mainMenu := MainMenu()
+ rootModel = &mainMenu
+
+
+ return rootScreenModel {
+ model: rootModel,
+ theme: theme,
+ }
+}
+
+
+// Screen switcher sets new model and returns
+func (m rootScreenModel) SwitchScreen(model tea.Model) (tea.Model, tea.Cmd) {
+ m.model = model
+ return m.model, m.model.Init()
+}
+
+
+// --- MainMenuScreenModel functions
+
+// Creates a new mainMenuScreenModel
+func MainMenu() mainMenuScreenModel {
+ return mainMenuScreenModel {
+ Title: "Repoman CLI",
+ Message: "Hello my friend",
+ }
+}
+
+// Init
+func (m *mainMenuScreenModel) Init() tea.Cmd {
+ return nil
+}
+
+// Update
+func (m *mainMenuScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ switch msg.String() {
+ case "q":
+ return m, tea.Quit
+
+ case "n":
+ apiStatusScreen := NewAPIStatusScreen()
+ return RootScreen(0).SwitchScreen(&apiStatusScreen)
+ }
+
+
+ default:
+ return m, nil
+ }
+
+ return m, nil
+}
+
+// View
+func (m *mainMenuScreenModel) View() string {
+
+ programInfo := lib.GetProgramInfo()
+
+ message := fmt.Sprintf("%s\n\n%s", programInfo.ProgramName, programInfo.BuildID)
+ return message
+}
+
+
--- /dev/null
+package themes
+
+import (
+
+ "github.com/charmbracelet/lipgloss"
+)
+
+var (
+ spinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("69"))
+ KeywordStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
+ subtleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
+ checkboxStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#03A9F4"))
+ titleStyle = lipgloss.NewStyle().Background(lipgloss.Color("#448AFF")).Foreground(lipgloss.Color("#FFFFFF"))
+ statusOK = lipgloss.NewStyle().Foreground(lipgloss.Color("200"))
+ mainStyle = lipgloss.NewStyle().Margin(15).Padding(10)
+ Altstyle = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("63"))
+)
+
+func ChangeTheme(themeID int) (err error) {
+
+ if themeID == 0 {
+ KeywordStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("200"))
+ return nil
+ }
+
+ return nil
+}
--- /dev/null
+module git.christiangoeschel.com/repoman-cli/themes
+
+go 1.23.0