From 6cf2aefc6d5cdafec99c70ac8a9385c47b907cfa Mon Sep 17 00:00:00 2001 From: restitux Date: Fri, 24 Feb 2023 00:53:23 -0700 Subject: [PATCH] Change dashboard tabs to use custom sub widgets --- screens/dashboard.go | 41 ++++---- widget/pipeline.go | 245 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 247 insertions(+), 39 deletions(-) diff --git a/screens/dashboard.go b/screens/dashboard.go index 8a404e8..6e53798 100644 --- a/screens/dashboard.go +++ b/screens/dashboard.go @@ -50,7 +50,7 @@ var ( type Dashboard struct { Tabs []string - TabContent []list.Model + TabContent []tea.Model activeTab int width int height int @@ -67,8 +67,9 @@ func (m Dashboard) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 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) + m.TabContent[i].Update(msg) + // m.TabContent[i].SetWidth(m.width - 10) + // m.TabContent[i].SetHeight(m.height - 10) } case tea.KeyMsg: switch keypress := msg.String(); keypress { @@ -160,34 +161,32 @@ func createDashboard(s CursoriusServer) (Dashboard, error) { client := graphql.NewClient(s.Url, http.DefaultClient) tabs := []string{"Pipelines", "Secrets", "Clone Credentials", "Runners"} - //testTabs := []list.Item{dashboardItem("Pipelines"), dashboardItem("Secrets"), dashboardItem("Clone Credentials"), dashboardItem("Runners")} pipelineWidget, err := widget.CreatePipelineWidget(client) if err != nil { return Dashboard{}, fmt.Errorf("Could not create Pipelines tab: %w", err) } - secretWidget, err := widget.CreateSecretWidget(client) - if err != nil { - return Dashboard{}, fmt.Errorf("Could not create Secrets tab: %w", err) - } + //secretWidget, err := widget.CreateSecretWidget(client) + //if err != nil { + // return Dashboard{}, fmt.Errorf("Could not create Secrets tab: %w", err) + //} - cloneCredentialWidget, err := widget.CreateCloneCredentialWidget(client) - if err != nil { - return Dashboard{}, fmt.Errorf("Could not create Clone Credentials tab: %w", err) - } + //cloneCredentialWidget, err := widget.CreateCloneCredentialWidget(client) + //if err != nil { + // return Dashboard{}, fmt.Errorf("Could not create Clone Credentials tab: %w", err) + //} - runnerWidget, err := widget.CreateRunnerWidget(client) - if err != nil { - return Dashboard{}, fmt.Errorf("Could not create Runner tab: %w", err) - } + //runnerWidget, err := widget.CreateRunnerWidget(client) + //if err != nil { + // return Dashboard{}, fmt.Errorf("Could not create Runner tab: %w", err) + //} - tabContent := []list.Model{ - //list.New(testTabs, dashboardItemDelegate{}, 50, 50), + tabContent := []tea.Model{ pipelineWidget, - secretWidget, - cloneCredentialWidget, - runnerWidget, + //secretWidget, + //cloneCredentialWidget, + //runnerWidget, } return Dashboard{ diff --git a/widget/pipeline.go b/widget/pipeline.go index 2b60ba7..3910cfc 100644 --- a/widget/pipeline.go +++ b/widget/pipeline.go @@ -7,16 +7,175 @@ import ( "github.com/Khan/genqlient/graphql" "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "git.ohea.xyz/cursorius/tui/queries" ) +type pipelineWidgetScreenSwitch tea.Model + +type pipelineWidget struct { + currentView tea.Model + width int + height int +} + +func (m pipelineWidget) Init() tea.Cmd { + return nil +} + +func (m pipelineWidget) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + case pipelineWidgetScreenSwitch: + m.currentView = msg + } + + var cmd tea.Cmd + m.currentView, cmd = m.currentView.Update(msg) + + return m, cmd +} + +func (m pipelineWidget) View() string { + return m.currentView.View() +} + +type pipelineWidgetList struct { + list list.Model + client graphql.Client +} + +func (m pipelineWidgetList) Init() tea.Cmd { + return nil +} + +func (m pipelineWidgetList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.list.SetWidth(msg.Width - 10) + m.list.SetHeight(msg.Height - 10) + case tea.KeyMsg: + switch msg.String() { + case "down": + if m.list.Index() < len(m.list.Items()) { + m.list.Select(m.list.Index() + 1) + } + case "up": + if m.list.Index() > 0 { + m.list.Select(m.list.Index() - 1) + } + case "enter": + if m.list.Index() < len(m.list.Items())-1 { + //item := m.list.SelectedItem() + //if item, ok := item.(pipelineListItem); ok { + // return m, func() tea.Cmd { + // view, err := createPipelineEditForm(client, item.id) + // if err != nil { + // return err + // } + // return pipelineWidgetScreenSwitch(view) + // } + //} + } else { + return m, func() tea.Msg { + view, err := createPipelineCreateForm(m.client, m.list.Width(), m.list.Height()) + if err != nil { + return err + } + return pipelineWidgetScreenSwitch(view) + } + } + } + } + + return m, nil +} + +func (m pipelineWidgetList) View() string { + return m.list.View() +} + type pipelineListItem struct { name string id string } +func createPipelineWidgetList(client graphql.Client) (tea.Model, error) { + getPipelineResp, err := queries.GetPipelines(context.Background(), client) + if err != nil { + return pipelineWidget{}, fmt.Errorf("Could not connect to server: %w", err) + } + + var content []list.Item + + if getPipelineResp.Pipelines != nil { + for _, pipeline := range getPipelineResp.Pipelines { + if pipeline != nil { + content = append(content, pipelineListItem{ + name: pipeline.Name, + id: pipeline.Id, + }) + } + } + } + + content = append(content, pipelineCreateListItem("[Create Pipeline]")) + + pipelineList := list.New(content, pipelineListItemDelegate{}, 0, 0) + pipelineList.SetShowStatusBar(false) + pipelineList.Title = "Pipelines" + + return pipelineWidgetList{ + list: pipelineList, + client: client, + }, nil +} + +type pipelineCreateForm struct { + list list.Model +} + +func (m pipelineCreateForm) Init() tea.Cmd { + return nil +} + +func (m pipelineCreateForm) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "enter": + // TODO: enable submitting + } + } + + var cmd tea.Cmd + m.list, cmd = m.list.Update(msg) + + items := m.list.Items() + + for i := 0; i < len(items); i++ { + if e, ok := items[i].(createPipelineItem); ok { + if i == m.list.Index() { + e.field.Focus() + e.field, _ = e.field.Update(msg) + } else { + e.field.Blur() + } + m.list.SetItem(i, e) + } + } + + return m, cmd +} + +func (m pipelineCreateForm) View() string { + return m.list.View() +} + type pipelineCreateListItem string func (i pipelineListItem) FilterValue() string { return "" } @@ -54,28 +213,78 @@ func (d pipelineListItemDelegate) Render(w io.Writer, m list.Model, index int, l } } -func CreatePipelineWidget(client graphql.Client) (list.Model, error) { - content := []list.Item{} +type createPipelineItem struct { + field textinput.Model +} - getPipelineResp, err := queries.GetPipelines(context.Background(), client) - if err != nil { - return list.Model{}, fmt.Errorf("Could not connect to server: %w", err) +type createPipelineSubmitItem string + +func (i createPipelineItem) FilterValue() string { return "" } +func (i createPipelineSubmitItem) FilterValue() string { return "" } + +type createPipelineDelegate struct{} + +func (d createPipelineDelegate) Height() int { return 1 } +func (d createPipelineDelegate) Spacing() int { return 0 } +func (d createPipelineDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } +func (d createPipelineDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + var str string + switch i := listItem.(type) { + case createPipelineItem: + str = i.field.View() + case createPipelineSubmitItem: + str = string(i) } - if getPipelineResp.Pipelines != nil { - for _, pipeline := range getPipelineResp.Pipelines { - content = append(content, pipelineListItem{ - name: pipeline.Name, - id: pipeline.Id, - }) + fn := itemStyle.Render + if index == m.Index() { + fn = func(s string) string { + return selectedItemStyle.Render("> " + s) } } - content = append(content, pipelineCreateListItem("[Create Pipeline]")) - - model := list.New(content, pipelineListItemDelegate{}, 50, 50) - model.SetShowStatusBar(false) - model.Title = "Pipelines" - - return model, nil + fmt.Fprint(w, fn(str)) +} + +func createPipelineCreateForm(client graphql.Client, width int, height int) (tea.Model, error) { + nameField := textinput.New() + nameField.Focus() + nameField.Width = 20 + nameField.Prompt = "Name: " + nameField.Placeholder = "pipeline0" + urlField := textinput.New() + urlField.Prompt = "Url: " + urlField.Placeholder = "https://git.cursorius.ohea/group/repo" + pollIntervalField := textinput.New() + pollIntervalField.Width = 20 + pollIntervalField.Prompt = "Poll Interval: " + pollIntervalField.Placeholder = "0 (disable polling)" + items := []list.Item{ + createPipelineItem{field: nameField}, + createPipelineItem{field: urlField}, + createPipelineItem{field: pollIntervalField}, + createPipelineSubmitItem("> Submit <"), + } + l := list.New(items, createPipelineDelegate{}, width, height-10) + l.Title = "Create a new pipeline." + l.SetShowStatusBar(false) + l.KeyMap.GoToStart.SetEnabled(false) + l.KeyMap.GoToEnd.SetEnabled(false) + l.KeyMap.Filter.SetEnabled(false) + l.KeyMap.Quit.SetEnabled(false) + return pipelineCreateForm{ + list: l, + }, nil +} + +func CreatePipelineWidget(client graphql.Client) (tea.Model, error) { + + view, err := createPipelineWidgetList(client) + if err != nil { + return pipelineWidget{}, err + } + + return pipelineWidget{ + currentView: view, + }, nil }