commit 3f87018dface9d64d9f865ac948c7e86e4df5838 Author: restitux Date: Sat Feb 18 02:50:14 2023 -0700 Initial commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2ab671f --- /dev/null +++ b/go.mod @@ -0,0 +1,29 @@ +module git.ohea.xyz/cursorius/tui + +go 1.20 + +require ( + github.com/charmbracelet/bubbles v0.15.0 + github.com/charmbracelet/bubbletea v0.23.2 + github.com/charmbracelet/lipgloss v0.6.0 +) + +require ( + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52 v1.2.1 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.14.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/sahilm/fuzzy v0.1.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.3.7 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..048eff9 --- /dev/null +++ b/go.sum @@ -0,0 +1,59 @@ +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 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= +github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI= +github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74= +github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU= +github.com/charmbracelet/bubbletea v0.23.2 h1:vuUJ9HJ7b/COy4I30e8xDVQ+VRDUEFykIjryPfgsdps= +github.com/charmbracelet/bubbletea v0.23.2/go.mod h1:FaP3WUivcTM0xOKNmhciz60M6I+weYLF76mr1JyI7sM= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= +github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +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/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= +github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a3bc750 --- /dev/null +++ b/main.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "os" + + "git.ohea.xyz/cursorius/tui/screens" + tea "github.com/charmbracelet/bubbletea" +) + +type model struct { + screen tea.Model + width int + height int +} + +func (m model) Init() tea.Cmd { + return nil +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c": + return m, tea.Quit + } + case screens.ScreenSwitchMsg: + // when new screen is created, send WindowSizeMsg to init size + m.screen = msg.NewScreen + return m, func() tea.Msg { + return tea.WindowSizeMsg{ + Width: m.width, + Height: m.height, + } + } + } + + var cmd tea.Cmd + m.screen, cmd = m.screen.Update(msg) + + return m, cmd +} + +func (m model) View() string { + return m.screen.View() +} + +func main() { + initialModel := model{ + screen: screens.CreateLogin( + // TODO: load from config file + []screens.CursoriusServer{ + screens.CursoriusServer{ + Name: "ohea", + Url: "https://ci.cursorius.server", + Token: "test", + }, + screens.CursoriusServer{ + Name: "nohea", + Url: "https://ci.cursoriuspreview.server", + Token: "test", + }, + screens.CursoriusServer{ + Name: "work", + Url: "https://ci.acme.corp", + Token: "test", + }, + }, + ), + } + + p := tea.NewProgram(initialModel, tea.WithAltScreen()) + if _, err := p.Run(); err != nil { + fmt.Printf("Alas, there's been an error: %v", err) + os.Exit(1) + } +} diff --git a/screens/dashboard.go b/screens/dashboard.go new file mode 100644 index 0000000..49ba68b --- /dev/null +++ b/screens/dashboard.go @@ -0,0 +1,181 @@ +package screens + +import ( + "fmt" + "io" + "strings" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +type CursoriusServer struct { + Name string + Url string + Token string +} + +func (s CursoriusServer) Login() ScreenSwitchMsg { + dashboard := createDashboard(s) + return ScreenSwitchMsg{ + NewScreen: dashboard, + } +} + +func tabBorderWithBottom(left, middle, right string) lipgloss.Border { + border := lipgloss.RoundedBorder() + border.BottomLeft = left + border.Bottom = middle + border.BottomRight = right + return border +} + +var ( + inactiveTabBorder = tabBorderWithBottom("┴", "─", "┴") + activeTabBorder = tabBorderWithBottom("┘", " ", "└") + docStyle = lipgloss.NewStyle().Padding(1, 2, 1, 2) + highlightColor = lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"} + inactiveTabStyle = lipgloss.NewStyle().Border(inactiveTabBorder, true).BorderForeground(highlightColor).Padding(0, 1) + activeTabStyle = inactiveTabStyle.Copy().Border(activeTabBorder, true) + windowStyle = lipgloss.NewStyle().BorderForeground(highlightColor).Padding(2, 0).Align(lipgloss.Center).Border(lipgloss.NormalBorder()).UnsetBorderTop() +) + +type Dashboard struct { + Tabs []string + TabContent []list.Model + activeTab int + width int + height int +} + +func (m Dashboard) Init() tea.Cmd { + return nil +} + +func (m Dashboard) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + for i := 0; i < len(m.TabContent); i++ { + m.TabContent[i].SetWidth(m.width - 10) + m.TabContent[i].SetHeight(m.height - 10) + } + case tea.KeyMsg: + switch keypress := msg.String(); keypress { + case "ctrl+c", "q": + return m, tea.Quit + case "right", "l", "n", "tab": + m.activeTab = min(m.activeTab+1, len(m.Tabs)-1) + return m, nil + case "left", "h", "p", "shift+tab": + m.activeTab = max(m.activeTab-1, 0) + return m, nil + } + } + + return m, nil +} + +func (m Dashboard) View() string { + doc := strings.Builder{} + + var renderedTabs []string + + for i, t := range m.Tabs { + var style lipgloss.Style + isFirst, isLast, isActive := i == 0, i == len(m.Tabs)-1, i == m.activeTab + if isActive { + style = activeTabStyle.Copy() + } else { + style = inactiveTabStyle.Copy() + } + border, _, _, _, _ := style.GetBorder() + if isFirst && isActive { + border.BottomLeft = "│" + } else if isFirst && !isActive { + border.BottomLeft = "├" + } else if isLast && isActive { + //border.BottomRight = "│" + border.BottomRight = "└" + } else if isLast && !isActive { + //border.BottomRight = "┤" + border.BottomRight = "┴" + } + style = style.Border(border) + renderedTabs = append(renderedTabs, style.Render(t)) + } + + row := lipgloss.JoinHorizontal(lipgloss.Top, renderedTabs...) + doc.WriteString(row) + rows := strings.Split(row, "\n") + // not sure why this is -4 ... :shrug: + x := m.width - lipgloss.Width(rows[2]) - 1 - 4 + extraStyle := lipgloss.NewStyle().Foreground(highlightColor) + for i := 0; i < x; i++ { + doc.WriteString(extraStyle.Render("─")) + } + doc.WriteString(extraStyle.Render("┐")) + doc.WriteString("\n") + doc.WriteString(windowStyle.Width((m.width - 4 - windowStyle.GetHorizontalFrameSize())).Height(m.height - 6).Render(m.TabContent[m.activeTab].View())) + return docStyle.Render(doc.String()) +} + +type dashboardItem string + +func (i dashboardItem) FilterValue() string { return "" } + +type dashboardItemDelegate struct{} + +func (d dashboardItemDelegate) Height() int { return 1 } +func (d dashboardItemDelegate) Spacing() int { return 0 } +func (d dashboardItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } +func (d dashboardItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + var str string + switch i := listItem.(type) { + case dashboardItem: + str = string(i) + } + + fn := itemStyle.Render + if index == m.Index() { + fn = func(s string) string { + return selectedItemStyle.Render("> " + s) + } + } + + fmt.Fprint(w, fn(str)) +} + +func createDashboard(s CursoriusServer) Dashboard { + tabs := []string{"Pipelines", "Secrets", "Clone Credentials", "Runners"} + content := []list.Item{dashboardItem("Pipelines"), dashboardItem("Secrets"), dashboardItem("Clone Credentials"), dashboardItem("Runners")} + + tabContent := []list.Model{ + list.New(content, dashboardItemDelegate{}, 50, 50), + list.New(content, dashboardItemDelegate{}, 50, 50), + list.New(content, dashboardItemDelegate{}, 50, 50), + } + + return Dashboard{ + Tabs: tabs, + TabContent: tabContent, + width: 50, + height: 50, + } +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/screens/editserver.go b/screens/editserver.go new file mode 100644 index 0000000..134427f --- /dev/null +++ b/screens/editserver.go @@ -0,0 +1,151 @@ +package screens + +import ( + "fmt" + "io" + + "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" +) + +type EditServer struct { + entries list.Model + servers []CursoriusServer + pos int +} + +type entryItem struct { + field textinput.Model +} + +type submitItem struct { + text string +} + +func (i entryItem) FilterValue() string { return "" } +func (i submitItem) FilterValue() string { return "" } + +func (m EditServer) Init() tea.Cmd { + return nil +} + +type editServerDelegate struct{} + +func (d editServerDelegate) Height() int { return 1 } +func (d editServerDelegate) Spacing() int { return 0 } +func (d editServerDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } +func (d editServerDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + var str string + switch i := listItem.(type) { + case entryItem: + str = i.field.View() + case submitItem: + str = i.text + } + + fn := itemStyle.Render + if index == m.Index() { + fn = func(s string) string { + return selectedItemStyle.Render("> " + s) + } + } + + fmt.Fprint(w, fn(str)) +} + +func (m EditServer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.WindowSizeMsg: + h, v := appStyle.GetFrameSize() + m.entries.SetSize(msg.Width-h, msg.Height-v) + case tea.KeyMsg: + switch msg.String() { + case "enter": + name := "" + url := "" + token := "" + if e, ok := m.entries.Items()[0].(entryItem); ok { + name = e.field.Value() + } + if e, ok := m.entries.Items()[1].(entryItem); ok { + url = e.field.Value() + } + if e, ok := m.entries.Items()[2].(entryItem); ok { + token = e.field.Value() + } + + newServer := CursoriusServer{ + Name: name, + Url: url, + Token: token, + } + m.servers[m.pos] = newServer + return m, func() tea.Msg { + return ScreenSwitchMsg{ + NewScreen: CreateLogin(m.servers), + } + } + } + } + + m.entries, cmd = m.entries.Update(msg) + + items := m.entries.Items() + for i := 0; i < len(items); i++ { + if e, ok := items[i].(entryItem); ok { + if i == m.entries.Index() { + e.field.Focus() + } else { + e.field.Blur() + } + e.field, _ = e.field.Update(msg) + m.entries.SetItem(i, e) + } + } + + return m, cmd +} + +func (m EditServer) View() string { + return m.entries.View() +} + +func createEditServer(s []CursoriusServer, pos int) EditServer { + nameField := textinput.New() + nameField.Focus() + nameField.Width = 20 + nameField.Prompt = "Name: " + nameField.Placeholder = "Nickname" + nameField.SetValue(s[pos].Name) + urlField := textinput.New() + urlField.Prompt = "Url: " + urlField.Placeholder = "https://ci.cursorius.ohea/" + urlField.SetValue(s[pos].Url) + tokenField := textinput.New() + tokenField.Prompt = "Token: " + tokenField.Placeholder = "1234567890" + tokenField.SetValue(s[pos].Token) + tokenField.EchoMode = textinput.EchoPassword + items := []list.Item{ + entryItem{field: nameField}, + entryItem{field: urlField}, + entryItem{field: tokenField}, + submitItem{text: "> Submit <"}, + } + l := list.New(items, editServerDelegate{}, 50, 50) + l.Title = "Entry your Cursorius Server configuration." + l.SetShowStatusBar(false) + l.KeyMap.GoToStart.SetEnabled(false) + l.KeyMap.GoToEnd.SetEnabled(false) + l.KeyMap.CursorDown.SetKeys(append(l.KeyMap.CursorDown.Keys(), "tab")...) + l.KeyMap.Filter.SetEnabled(false) + e := EditServer{ + entries: l, + servers: s, + pos: pos, + } + return e +} diff --git a/screens/login.go b/screens/login.go new file mode 100644 index 0000000..dfea666 --- /dev/null +++ b/screens/login.go @@ -0,0 +1,143 @@ +package screens + +import ( + "fmt" + "io" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +type Login struct { + selected int + servers []CursoriusServer + serverList list.Model +} + +func (m Login) Init() tea.Cmd { + return nil +} + +var appStyle = lipgloss.NewStyle().Padding(1, 2) + +func (m Login) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.WindowSizeMsg: + h, v := appStyle.GetFrameSize() + m.serverList.SetSize(msg.Width-h, msg.Height-v) + case tea.KeyMsg: + switch msg.String() { + case "e": + if m.serverList.Index() < len(m.servers) { + return m, func() tea.Msg { + return ScreenSwitchMsg{ + NewScreen: createEditServer( + m.servers, + m.serverList.Index(), + ), + } + } + } + case "enter": + if m.serverList.Index() < len(m.servers)-1 { + return m, func() tea.Msg { + return m.servers[m.selected].Login() + } + } else { + return m, func() tea.Msg { + return ScreenSwitchMsg{ + NewScreen: createEditServer( + append(m.servers, CursoriusServer{}), + len(m.servers), + ), + } + } + } + } + } + + m.serverList, cmd = m.serverList.Update(msg) + return m, cmd +} + +func (m Login) View() string { + s := m.serverList.View() + return s +} + +type serverItem struct { + name string + url string +} + +type newServerItem struct { + text string +} + +func (i serverItem) FilterValue() string { return "" } +func (i newServerItem) FilterValue() string { return "" } + +var ( + titleStyle = lipgloss.NewStyle().MarginLeft(2) + itemStyle = lipgloss.NewStyle().PaddingLeft(4) + selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) + paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4) + helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1) + quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4) +) + +type loginItemDelegate struct{} + +func (d loginItemDelegate) Height() int { return 1 } +func (d loginItemDelegate) Spacing() int { return 0 } +func (d loginItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } +func (d loginItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + var str string + switch i := listItem.(type) { + case serverItem: + str = fmt.Sprintf("%d. %s (%s)", index+1, i.name, i.url) + case newServerItem: + str = i.text + } + + fn := itemStyle.Render + if index == m.Index() { + fn = func(s string) string { + return selectedItemStyle.Render("> " + s) + } + } + + fmt.Fprint(w, fn(str)) +} + +func CreateLogin(servers []CursoriusServer) Login { + items := []list.Item{} + for _, server := range servers { + items = append( + items, + serverItem{ + name: server.Name, + url: server.Url, + }, + ) + } + items = append( + items, + newServerItem{text: "> Add new server <"}, + ) + + l := list.New(items, loginItemDelegate{}, 50, 50) + l.Title = "Login to your Cursorius instance." + l.SetShowStatusBar(false) + l.KeyMap.CursorDown.SetKeys(append(l.KeyMap.CursorDown.Keys(), "tab")...) + + return Login{ + selected: 0, + servers: servers, + serverList: l, + } +} diff --git a/screens/msg.go b/screens/msg.go new file mode 100644 index 0000000..1f1623e --- /dev/null +++ b/screens/msg.go @@ -0,0 +1,7 @@ +package screens + +import tea "github.com/charmbracelet/bubbletea" + +type ScreenSwitchMsg struct { + NewScreen tea.Model +}