]> git.christiangoeschel.com Git - repoman-cli.git/commitdiff
Major changes
authorchristiangoeschel <cndjomouo@icloud.com>
Thu, 3 Oct 2024 01:38:14 +0000 (21:38 -0400)
committerchristiangoeschel <cndjomouo@icloud.com>
Thu, 3 Oct 2024 01:38:14 +0000 (21:38 -0400)
12 files changed:
go.mod
go.sum
main
screens/apistatus.go
screens/go.mod
screens/go.sum [deleted file]
screens/repolist.go [new file with mode: 0644]
screens/root.go
themes/default-theme.go [deleted file]
themes/go.mod [deleted file]
ui/go.mod [new file with mode: 0644]
ui/ui.go [new file with mode: 0644]

diff --git a/go.mod b/go.mod
index 673c02a40d7e43fc69e0d1213ede57c05f16530f..4541cdee6653f17a3ea8d5abb315ac7eb0b9f08e 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,7 @@ require (
 )
 
 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
@@ -20,18 +21,20 @@ require (
        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
 
diff --git a/go.sum b/go.sum
index daac31727c039b56b30ef870018e80b0d7459c73..7e6a9165d3846640128a681f2dcadb4a72c15680 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,7 @@
+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=
@@ -24,16 +28,20 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D
 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=
diff --git a/main b/main
index 3bb7e6863b23fe52f71f32a35b358bd0bf4522a2..4f16249180cc6a44fb69301ec879b697496e08a1 100755 (executable)
Binary files a/main and b/main differ
index daad6a266f839fada79965b67f5704d2a1504739..156bea30dc1ee81622e1cfe906fdb911cf09f498 100644 (file)
@@ -3,8 +3,10 @@ package screens
 
 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"
 )
@@ -12,32 +14,37 @@ import (
 
 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,
   }
 }
 
@@ -50,17 +57,21 @@ func (m *APIStatusScreenModel) Init() tea.Cmd {
 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)
        
     }
     
@@ -71,13 +82,16 @@ func (m *APIStatusScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
     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 
@@ -85,34 +99,50 @@ func (m *APIStatusScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
 // 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))  
 }
 
index b6b930d2c753cf2af7b20681eb531348d8974027..0604bedc2232543277a405471bf7b84de15fa696 100644 (file)
@@ -1,32 +1,3 @@
 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
diff --git a/screens/go.sum b/screens/go.sum
deleted file mode 100644 (file)
index daac317..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-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=
diff --git a/screens/repolist.go b/screens/repolist.go
new file mode 100644 (file)
index 0000000..2ed9669
--- /dev/null
@@ -0,0 +1,208 @@
+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
+}
+
+
index d1085788b233cefb95ef369c3ff320dde2dfe647..4cb8dd8e82b630cd05bbcc133f63fde38f497ef2 100644 (file)
@@ -2,10 +2,11 @@ package screens
 
 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"
 )
 
 
@@ -14,23 +15,30 @@ import (
     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 {
@@ -53,12 +61,11 @@ func (m rootScreenModel) View() string {
 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,
@@ -77,9 +84,19 @@ func (m rootScreenModel) SwitchScreen(model tea.Model) (tea.Model, tea.Cmd) {
 
 // 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,
   }
 }
 
@@ -93,17 +110,20 @@ func (m *mainMenuScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
   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 
@@ -111,17 +131,29 @@ func (m *mainMenuScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
                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 
@@ -129,30 +161,53 @@ func (m *mainMenuScreenModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
 // 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)
 }
diff --git a/themes/default-theme.go b/themes/default-theme.go
deleted file mode 100644 (file)
index 6a31224..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-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
-}
diff --git a/themes/go.mod b/themes/go.mod
deleted file mode 100644 (file)
index 48db9e8..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-module git.christiangoeschel.com/repoman-cli/themes
-
-go 1.23.0
diff --git a/ui/go.mod b/ui/go.mod
new file mode 100644 (file)
index 0000000..3cf6b31
--- /dev/null
+++ b/ui/go.mod
@@ -0,0 +1,3 @@
+module git.christiangoeschel.com/repoman-cli/ui
+
+go 1.23.0
diff --git a/ui/ui.go b/ui/ui.go
new file mode 100644 (file)
index 0000000..ca90f29
--- /dev/null
+++ b/ui/ui.go
@@ -0,0 +1,77 @@
+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
+}
+
+