)
require (
+ github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/bubbles v0.20.0 // indirect
github.com/charmbracelet/x/ansi v0.2.3 // indirect
github.com/mattn/go-runewidth v0.0.16 // 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/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
github.com/repoman-cli/api v0.0.0-00010101000000-000000000000 // indirect
- github.com/repoman-cli/themes v0.0.0-00010101000000-000000000000 // indirect
+ github.com/repoman-cli/ui v0.0.0-00010101000000-000000000000 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/sahilm/fuzzy v0.1.1 // 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
+ golang.org/x/term v0.22.0 // indirect
+ golang.org/x/text v0.16.0 // 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/ui => /home/cgoesche/repoman-cli/ui
replace github.com/repoman-cli/screens => /home/cgoesche/repoman-cli/screens
+github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
+github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
+github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
+github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
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/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/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
+github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
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=
+github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
+github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
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=
+golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
+golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
import (
"fmt"
+ "os"
tea "github.com/charmbracelet/bubbletea"
- "github.com/repoman-cli/themes"
+ "github.com/charmbracelet/lipgloss"
+ "github.com/repoman-cli/ui"
"github.com/charmbracelet/bubbles/spinner"
"github.com/repoman-cli/api"
)
type APIStatusScreenModel struct {
APIStatus api.APIStatusMsg
- URL string
Err error
- StatusCode int
Spinner spinner.Model
ReqReceived bool
Requested bool
+ Width int
+ Height int
//PrevScreen tea.Model
}
-// --- AltScreenModel functions
-
// Creates a new mainMenuScreenModel
func NewAPIStatusScreen() APIStatusScreenModel {
+ // Get terminal size
+ w, h, err := ui.GetWindowSize()
+ if err != nil {
+ fmt.Printf("Could not get terminal window size.")
+ os.Exit(1)
+ }
+
s := spinner.New()
s.Spinner = spinner.Pulse
- s.Style = themes.SpinnerStyle
+ s.Style = ui.SpinnerStyle
return APIStatusScreenModel {
APIStatus: api.APIStatusMsg{},
- URL: "https://repoman.christiangoeschel.com:8443/status",
Err: nil,
- StatusCode: 200,
Spinner: s,
ReqReceived: false,
Requested: false,
+ Width: w,
+ Height: h,
}
}
func (m *APIStatusScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
+
case tea.KeyMsg:
switch msg.String() {
- case "q", "backspace":
+ case "q", "Q", "backspace":
mainMenu := MainMenu()
return RootScreen().SwitchScreen(&mainMenu)
+ case "ctrl+c", "ctrl+C":
+ return m, tea.Quit
+
case "enter":
- m.APIStatus = api.APIStatusMsg{}
- m.ReqReceived = false
- m.Requested = true
- return m, api.HandleAPIStatusRequest(api.StatusUrl)
+ m.APIStatus = api.APIStatusMsg{}
+ m.ReqReceived = false
+ m.Requested = true
+ return m, api.HandleAPIStatusRequest(api.StatusUrl)
}
return m, nil
case spinner.TickMsg:
-
- //if m.ReqReceived == false && m.Requested == true {
var cmd tea.Cmd
m.Spinner, cmd = m.Spinner.Update(msg)
return m, cmd
- //}
-
+
+ case tea.WindowSizeMsg:
+ w, h, _ := ui.GetWindowSize()
+ m.Width = w
+ m.Height = h
+ return m, nil
+
}
return m, nil
// View
func (m *APIStatusScreenModel) View() string {
-
- msg := themes.KeywordStyle.Render("API Status")
-
- statusResponse := fmt.Sprintf("Status: %s\nDescription: %s\nStart: %s\nExpected End: %s\n",
- themes.KeywordStyle.Render(m.APIStatus.Status),
- themes.KeywordStyle.Render(m.APIStatus.Description),
- themes.KeywordStyle.Render(m.APIStatus.StartDateTime),
- themes.KeywordStyle.Render(m.APIStatus.EstimatedEndTime))
-
- msg = fmt.Sprintf("%s\n\nRequesting API Server Status from:\n\n",
- themes.TitleStyle.Render("[ +-+ API Server Status +-+ ]"))
-
- msg += fmt.Sprintf("%s\n\n%s\n%s\n",
- themes.KeywordStyle.Render(api.StatusUrl),
- statusResponse,
- themes.SubtleStyle.Render("Press Enter to refresh"))
-
- msg += "\n%s"
-
-
- switch m.ReqReceived {
- case false:
- if m.Requested == true {
- return themes.MainStyle.Render(fmt.Sprintf(msg, m.Spinner.View() + " Fetching ..."))
- }
+
+ // Top bar styling
+ roundLabelCorner := ui.RoundLabelCornerStyle.Render("")
+ labelArrow := ui.LabelArrowStyle.Render("")
+ titleBlock := ui.TitleStyle.Render(" Repoman CLI ")
+ rightTopBarBlock := ui.TopBarRightLabel.Render(" API Status Check ")
+
+ if !m.ReqReceived && m.Requested {
+ rightTopBarBlock = ui.TopBarRightLabel.Render( " Fetching ... " + m.Spinner.View())
}
+
+ topBarBlock := ui.TopBarStyle.Render(lipgloss.JoinHorizontal(lipgloss.Center,
+ ui.TopBarLeftStyle.Width((m.Width / 2 ) - 1).Render(roundLabelCorner + titleBlock + labelArrow),
+ ui.TopBarRightStyle.Width((m.Width / 2 ) - 1).Render(rightTopBarBlock)))
+
+ _, topBarBlockHeight := lipgloss.Size(topBarBlock)
+
+ // Footer
+ helpBlock := fmt.Sprintf("%s\n%s\n",
+ ui.SubtleStyle.Render("Q, Backspace - Return to Main menu"),
+ ui.SubtleStyle.Render("Ctrl+C - Quit"))
+
+ footerBlock := ui.FooterStyle.Render(lipgloss.JoinHorizontal(lipgloss.Center,
+ ui.FooterLeftStyle.Width((m.Width / 2 ) - 1).Render("Placeholder"),
+ ui.FooterRightStyle.Width((m.Width / 2 ) - 1 ).Render(helpBlock)))
- return themes.MainStyle.Render(fmt.Sprintf(msg, ""))
+ _, footerBlockHeight := lipgloss.Size(footerBlock)
+
+ // Main Body
+ statusResponse := fmt.Sprintf("Status: %s\nDescription: %s\nStart: %s\nExpected End: %s",
+ ui.KeywordStyle.Render(m.APIStatus.Status),
+ ui.KeywordStyle.Render(m.APIStatus.Description),
+ ui.KeywordStyle.Render(m.APIStatus.StartDateTime),
+ ui.KeywordStyle.Render(m.APIStatus.EstimatedEndTime))
+
+ mainBodyBlock := fmt.Sprintf("Requesting API Server Status from:\n\n%s\n\n%s",
+ ui.URLStyle.Render(api.StatusUrl),
+ statusResponse)
+
+ mainBodyBlockStyle := lipgloss.NewStyle().Height(m.Height - footerBlockHeight - topBarBlockHeight)
+ mainBodyBlock = mainBodyBlockStyle.Render(mainBodyBlock)
+
+
+return lipgloss.PlaceHorizontal(m.Width, lipgloss.Center, lipgloss.JoinVertical(lipgloss.Left, topBarBlock, mainBodyBlock, footerBlock))
}
module git.christiangoeschel.com/repoman-cli/screens
go 1.23.0
-
-require (
- github.com/charmbracelet/bubbles v0.20.0
- github.com/charmbracelet/bubbletea v1.1.1
- github.com/repoman-cli/themes 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.16 // 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/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/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.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
-github.com/mattn/go-runewidth v0.0.16/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 (
+
+ "github.com/charmbracelet/bubbles/help"
+ "github.com/charmbracelet/bubbles/key"
+ "github.com/charmbracelet/bubbles/textarea"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+ "golang.org/x/term"
+ "os"
+)
+
+const (
+ InitialInputs = 2
+ MaxInputs = 16
+ MinInputs = 1
+ HelpHeight = 5
+)
+
+var (
+ cursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("212"))
+
+ cursorLineStyle = lipgloss.NewStyle().
+ Background(lipgloss.Color("57")).
+ Foreground(lipgloss.Color("230"))
+
+ placeholderStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("238"))
+
+ endOfBufferStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("235"))
+
+ focusedPlaceholderStyle = lipgloss.NewStyle().
+ Foreground(lipgloss.Color("99"))
+
+ focusedBorderStyle = lipgloss.NewStyle().
+ Border(lipgloss.RoundedBorder()).
+ BorderForeground(lipgloss.Color("238"))
+
+ blurredBorderStyle = lipgloss.NewStyle().
+ Border(lipgloss.HiddenBorder())
+)
+
+type Keymap = struct {
+ next, prev, add, remove, quit key.Binding
+}
+
+func newTextarea() textarea.Model {
+ t := textarea.New()
+ t.Prompt = ""
+ t.Placeholder = "Type something"
+ t.ShowLineNumbers = true
+ t.Cursor.Style = cursorStyle
+ t.FocusedStyle.Placeholder = focusedPlaceholderStyle
+ t.BlurredStyle.Placeholder = placeholderStyle
+ t.FocusedStyle.CursorLine = cursorLineStyle
+ t.FocusedStyle.Base = focusedBorderStyle
+ t.BlurredStyle.Base = blurredBorderStyle
+ t.FocusedStyle.EndOfBuffer = endOfBufferStyle
+ t.BlurredStyle.EndOfBuffer = endOfBufferStyle
+ t.KeyMap.DeleteWordBackward.SetEnabled(false)
+ t.KeyMap.LineNext = key.NewBinding(key.WithKeys("down"))
+ t.KeyMap.LinePrevious = key.NewBinding(key.WithKeys("up"))
+ t.Blur()
+ return t
+}
+
+type ListReposScreenModel struct {
+ Width int
+ Height int
+ Keymap Keymap
+ Help help.Model
+ Inputs []textarea.Model
+ Focus int
+ Err error
+}
+
+func NewListRepoScreenModel() ListReposScreenModel {
+ // Get terminal size
+ fd := int(os.Stdin.Fd()) // Terminal's file descriptor
+ // Size
+ w, h, _ := term.GetSize(fd)
+
+ m := ListReposScreenModel {
+ Inputs: make([]textarea.Model, InitialInputs),
+ Help: help.New(),
+ Keymap: Keymap{
+ next: key.NewBinding(
+ key.WithKeys("tab"),
+ key.WithHelp("tab", "next"),
+ ),
+ prev: key.NewBinding(
+ key.WithKeys("shift+tab"),
+ key.WithHelp("shift+tab", "prev"),
+ ),
+ add: key.NewBinding(
+ key.WithKeys("ctrl+n"),
+ key.WithHelp("ctrl+n", "add an editor"),
+ ),
+ remove: key.NewBinding(
+ key.WithKeys("ctrl+w"),
+ key.WithHelp("ctrl+w", "remove an editor"),
+ ),
+ quit: key.NewBinding(
+ key.WithKeys("esc", "ctrl+c"),
+ key.WithHelp("esc", "quit"),
+ ),
+ },
+ Width: w,
+ Height: h,
+ }
+ for i := 0; i < InitialInputs; i++ {
+ m.Inputs[i] = newTextarea()
+ }
+ m.Inputs[m.Focus].Focus()
+ m.updateKeybindings()
+ return m
+}
+
+
+func (m *ListReposScreenModel ) Init() tea.Cmd {
+ return textarea.Blink
+}
+
+func (m *ListReposScreenModel ) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ var cmds []tea.Cmd
+
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ switch {
+ case key.Matches(msg, m.Keymap.quit):
+ for i := range m.Inputs {
+ m.Inputs[i].Blur()
+ }
+ return m, tea.Quit
+ case key.Matches(msg, m.Keymap.next):
+ m.Inputs[m.Focus].Blur()
+ m.Focus++
+ if m.Focus > len(m.Inputs)-1 {
+ m.Focus = 0
+ }
+ cmd := m.Inputs[m.Focus].Focus()
+ cmds = append(cmds, cmd)
+ case key.Matches(msg, m.Keymap.prev):
+ m.Inputs[m.Focus].Blur()
+ m.Focus--
+ if m.Focus < 0 {
+ m.Focus = len(m.Inputs) - 1
+ }
+ cmd := m.Inputs[m.Focus].Focus()
+ cmds = append(cmds, cmd)
+ case key.Matches(msg, m.Keymap.add):
+ m.Inputs = append(m.Inputs, newTextarea())
+ case key.Matches(msg, m.Keymap.remove):
+ m.Inputs = m.Inputs[:len(m.Inputs)-1]
+ if m.Focus > len(m.Inputs)-1 {
+ m.Focus = len(m.Inputs) - 1
+ }
+ }
+ case tea.WindowSizeMsg:
+ m.Height = msg.Height
+ m.Width = msg.Width
+ }
+
+ m.updateKeybindings()
+ m.sizeInputs()
+
+ // Update all textareas
+ for i := range m.Inputs {
+ newModel, cmd := m.Inputs[i].Update(msg)
+ m.Inputs[i] = newModel
+ cmds = append(cmds, cmd)
+ }
+
+ return m, tea.Batch(cmds...)
+}
+
+func (m *ListReposScreenModel ) sizeInputs() {
+ for i := range m.Inputs {
+ m.Inputs[i].SetWidth(m.Width / len(m.Inputs))
+ m.Inputs[i].SetHeight(m.Height - HelpHeight)
+ }
+}
+
+func (m *ListReposScreenModel ) updateKeybindings() {
+ m.Keymap.add.SetEnabled(len(m.Inputs) < MaxInputs)
+ m.Keymap.remove.SetEnabled(len(m.Inputs) > MinInputs)
+}
+
+func (m *ListReposScreenModel ) View() string {
+ help := m.Help.ShortHelpView([]key.Binding{
+ m.Keymap.next,
+ m.Keymap.prev,
+ m.Keymap.add,
+ m.Keymap.remove,
+ m.Keymap.quit,
+ })
+
+ var views []string
+ for i := range m.Inputs {
+ views = append(views, m.Inputs[i].View())
+ }
+
+ return lipgloss.JoinHorizontal(lipgloss.Top, views...) + "\n\n" + help
+}
+
+
import (
"fmt"
- //"github.com/repoman-cli/lib"
- "github.com/repoman-cli/themes"
+ "os"
+ "github.com/repoman-cli/ui"
tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
)
model tea.Model // Contains the current screen model which represents the programs current state
}
+type menuOptions struct {
+ title string
+ desc string
+}
//Main Menu Screen
type mainMenuScreenModel struct {
Title string
Message string
- OptionChoice int
- }
-
-var mainMenuOptions = []string{
- "List all repos",
- "Create a new repository",
- "Delete a repository",
- "Manage API Keys",
- "Check API Status",
- "Settings",
- "Quit",
+ OptionChoice int
+ Width int
+ Height int
+ MenuOptions []menuOptions
}
+var menuOpts = []menuOptions{
+ menuOptions{title: "List all repos", desc: "View detailed repository information"},
+ menuOptions{title: "Create new repo", desc: "Nothing better than creating apps"},
+ menuOptions{title: "Delete a repo", desc: "Archive it first ;)"},
+ menuOptions{title: "Manage API keys", desc: "Why don't you open source your work ?"},
+ menuOptions{title: "Check API Status", desc: "Don't worry, everything is fine ( ' o _ o )"},
+ menuOptions{title: "Settings", desc: "You don't like the default me ? :("},
+ menuOptions{title: "Repoman Shell", desc: "Ohhh you will like this one for sure :D"},
+ menuOptions{title: "Quit", desc: "You are seeing someone else, admit it !!"},
+}
// Initialize model
func (m rootScreenModel) Init() tea.Cmd {
func RootScreen() rootScreenModel {
var rootModel tea.Model
-
// Set the rootModel (current state) to be the mainMenuScreen
mainMenu := MainMenu()
rootModel = &mainMenu
- _ = themes.ChangeTheme(themes.DefaultTheme)
+ _ = ui.ChangeTheme(ui.DefaultTheme)
return rootScreenModel {
model: rootModel,
// Creates a new mainMenuScreenModel
func MainMenu() mainMenuScreenModel {
+ // Get terminal size
+ w, h, err := ui.GetWindowSize()
+ if err != nil {
+ fmt.Printf("Could not get terminal window size.")
+ os.Exit(1)
+ }
+
return mainMenuScreenModel {
Title: "Repoman CLI",
Message: "Hello my friend",
+ Width: w,
+ Height: h,
+ MenuOptions: menuOpts,
}
}
switch msg := msg.(type) {
case tea.KeyMsg:
- var lastChoice int = len(mainMenuOptions) - 1
+ var lastChoice int = len(m.MenuOptions) - 1
switch msg.String() {
+
+ case "q", "Q", "esc":
+ return m, tea.Quit
- case "j", "down":
+ case "down":
m.OptionChoice++
if m.OptionChoice > lastChoice {
m.OptionChoice = 0
}
- case "k", "up":
+ case "up":
m.OptionChoice--
if m.OptionChoice < 0 {
m.OptionChoice = lastChoice
case "enter", " ":
- if mainMenuOptions[m.OptionChoice] == "Check API Status" {
- apiStatusScreen := NewAPIStatusScreen()
- return RootScreen().SwitchScreen(&apiStatusScreen)
- } else if mainMenuOptions[m.OptionChoice] == "Quit" {
+ switch m.OptionChoice {
+ case 0:
+ listReposScreen := NewListRepoScreenModel()
+ return RootScreen().SwitchScreen(&listReposScreen)
+
+ case 4:
+ apiStatusScreen := NewAPIStatusScreen()
+ return RootScreen().SwitchScreen(&apiStatusScreen)
+
+ case 6:
return m, tea.Quit
- }
+
+ }
return m, nil
}
+
+ case tea.WindowSizeMsg:
+ w, h, _ := ui.GetWindowSize()
+ m.Width = w
+ m.Height = h
- return m, nil
+ return m, nil
}
return m, nil
// View
func (m *mainMenuScreenModel) View() string {
-
+
c := m.OptionChoice
-
- tpl := fmt.Sprintf("%s\n\n", themes.TitleStyle.Render("[ +-+ Repoman CLI +-+ ]"))
- tpl += "Main Menu\n\n"
- tpl += "%s\n\n"
- tpl += fmt.Sprintf("%s\n%s\n%s\n%s\n",
- themes.SubtleStyle.Render("j/k, up/down: select"),
- themes.SubtleStyle.Render("enter, spacebar: choose"),
- themes.SubtleStyle.Render("q, esc: quit"),
- themes.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))
- }
+ // Top bar styling
+
+ roundLabelCorner := ui.RoundLabelCornerStyle.Render("")
+ labelArrow := ui.LabelArrowStyle.Render("")
+ titleBlock := ui.TitleStyle.Render(" Repoman CLI ")
+ rightTopBarBlock := ui.TopBarRightLabel.Render(" " + m.MenuOptions[m.OptionChoice].title + " ")
+
+ topBarBlock := ui.TopBarStyle.Render(lipgloss.JoinHorizontal(lipgloss.Center,
+ ui.TopBarLeftStyle.Width((m.Width / 2 ) - 1).Render(roundLabelCorner + titleBlock + labelArrow),
+ ui.TopBarRightStyle.Width((m.Width / 2 ) - 1).Render(rightTopBarBlock)))
+
+ _, topBarBlockHeight := lipgloss.Size(topBarBlock)
- return themes.MainStyle.Render(fmt.Sprintf(tpl, opts))
+ // Footer styling
+ copyrightBlock := ui.SubtleStyle.Render("2024 Copyright - Repoman")
+
+ helpBlock := fmt.Sprintf("%s\n%s\n%s",
+ ui.SubtleStyle.Render(" - Select"),
+ ui.SubtleStyle.Render("Enter, Spacebar - Choose"),
+ ui.SubtleStyle.Render("Q, Esc - Quit"))
+
+ footerBlock := ui.FooterStyle.Render(lipgloss.JoinHorizontal(lipgloss.Center,
+ ui.FooterLeftStyle.Width((m.Width / 2 ) - 1).Render(copyrightBlock),
+ ui.FooterRightStyle.Width((m.Width / 2 ) - 1).Render(helpBlock)))
+ _, footerBlockHeight := lipgloss.Size(footerBlock)
+
+ // Menu options
+ var mainMenuBlock string
+
+ for i := 0; i < len(menuOpts); i++ {
+ title, desc := checkbox(m.MenuOptions[i].title, m.MenuOptions[i].desc, c == i)
+ mainMenuBlock += fmt.Sprintf("%s\n%s\n\n", title, desc )
+ }
+
+ mainMenuBlockStyle := lipgloss.NewStyle().Height(m.Height - footerBlockHeight - topBarBlockHeight)
+ mainMenuBlock = mainMenuBlockStyle.Render(mainMenuBlock)
+
+return lipgloss.PlaceHorizontal(m.Width, lipgloss.Center, lipgloss.JoinVertical(lipgloss.Left, topBarBlock, mainMenuBlock, footerBlock))
}
-func checkbox(label string, checked bool) string {
+func checkbox(title string, desc string, checked bool) (string, string) {
if checked {
- return themes.CheckboxStyle.Render("+ " + label)
+ return ui.CheckboxStyle.Render(title) , ui.CheckboxStyle.Render(desc)
}
- return fmt.Sprintf("- %s", label)
+ return ui.MenuOptionTitleStyle.Render(title), ui.MenuOptionDescStyle.Render(desc)
}
+++ /dev/null
-package themes
-
-import (
-
- "github.com/charmbracelet/lipgloss"
-)
-
-var (
- DefaultTheme = 0
- CurrentTheme = 0
-
- 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().MarginLeft(10).MarginTop(20)
- Altstyle = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("63"))
-)
-
-func ChangeTheme(themeID int) (err error) {
-
- switch themeID {
- case 0:
- KeywordStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("200"))
- return nil
-
- case 1:
- KeywordStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("190"))
- return nil
- }
-
- return nil
-}
-
-func SetTheme(themeID int) (err error) {
- DefaultTheme = themeID
- _ = ChangeTheme(DefaultTheme)
-
- return nil
-}
+++ /dev/null
-module git.christiangoeschel.com/repoman-cli/themes
-
-go 1.23.0
--- /dev/null
+module git.christiangoeschel.com/repoman-cli/ui
+
+go 1.23.0
--- /dev/null
+package ui
+
+import (
+ "os"
+ "golang.org/x/term"
+ "github.com/charmbracelet/lipgloss"
+)
+
+var (
+ DefaultTheme = 0
+ CurrentTheme = 0
+ TopBarBackgroundColor = "236"
+
+ RoundLabelCornerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#448AFF"))
+ LabelArrowStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#448AFF")).Background(lipgloss.Color(TopBarBackgroundColor))
+ SpinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("69"))
+ KeywordStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
+ URLStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#03A9F4"))
+ SubtleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
+ TitleStyle = lipgloss.NewStyle().Background(lipgloss.Color("#448AFF")).Foreground(lipgloss.Color("#FFFFFF"))
+ StatusOK = lipgloss.NewStyle().Foreground(lipgloss.Color("200"))
+ CheckboxStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#03A9F4")).BorderLeft(true).BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("#448AFF")).PaddingLeft(2)
+ MenuOptionTitleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF")).BorderLeft(true).BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("241")).PaddingLeft(1)
+ MenuOptionDescStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).BorderLeft(true).BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("241")).PaddingLeft(1)
+
+
+ MainStyle = lipgloss.NewStyle().BorderTop(true).BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("241"))
+ FooterStyle = lipgloss.NewStyle().BorderTop(true).BorderStyle(lipgloss.NormalBorder()).
+ BorderForeground(lipgloss.Color("241"))
+
+ FooterRightStyle = lipgloss.NewStyle().Align(lipgloss.Right)
+ FooterLeftStyle = lipgloss.NewStyle().Align(lipgloss.Left)
+
+
+ TopBarStyle = lipgloss.NewStyle().BorderBottom(true).BorderStyle(lipgloss.NormalBorder()).
+ BorderForeground(lipgloss.Color("241"))
+
+ TopBarRightStyle = lipgloss.NewStyle().Align(lipgloss.Right).Background(lipgloss.Color(TopBarBackgroundColor))
+ TopBarRightLabel = lipgloss.NewStyle().Background(lipgloss.Color("#2ECC71")).Foreground(lipgloss.Color("#FFFFFF"))
+ TopBarLeftStyle = lipgloss.NewStyle().Align(lipgloss.Left).Background(lipgloss.Color(TopBarBackgroundColor))
+
+)
+
+func ChangeTheme(themeID int) (err error) {
+ switch themeID {
+ case 0:
+ KeywordStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("200"))
+ return nil
+
+ case 1:
+ KeywordStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("190"))
+ return nil
+ }
+
+ return nil
+}
+
+func GetWindowSize() (width int, height int, err error) {
+ // Get terminal file descriptor
+ fd := int(os.Stdin.Fd())
+ // Get terminal size
+ width, height, err = term.GetSize(fd)
+ if err != nil {
+ return 0, 0, err
+ }
+
+ return width, height, nil
+}
+
+func SetTheme(themeID int) (err error) {
+ DefaultTheme = themeID
+ _ = ChangeTheme(DefaultTheme)
+
+ return nil
+}
+
+