From 8c43384fcae0416922e36342a1aacc17220ddf9c Mon Sep 17 00:00:00 2001 From: Dolev Hadar <6196971+dlvhdr@users.noreply.github.com> Date: Sat, 3 Aug 2024 22:14:51 +0300 Subject: [PATCH] Support sparse layout in tables (#408) --- config/parser.go | 8 +- docs/data/schemas/theme.yaml | 10 +++ ui/components/issue/issue.go | 3 +- ui/components/issuessection/issuessection.go | 10 +-- ui/components/pr/pr.go | 94 ++++++++++++-------- ui/components/prssection/prssection.go | 57 ++++++++++-- ui/components/table/table.go | 18 ++-- ui/components/utils.go | 32 +++---- ui/constants/constants.go | 5 ++ ui/context/styles.go | 2 +- utils/utils.go | 8 +- 11 files changed, 167 insertions(+), 80 deletions(-) diff --git a/config/parser.go b/config/parser.go index 5ea7d313..2828c401 100644 --- a/config/parser.go +++ b/config/parser.go @@ -163,6 +163,7 @@ type ColorThemeConfig struct { type TableUIThemeConfig struct { ShowSeparator bool `yaml:"showSeparator" default:"true"` + Compact bool `yaml:"compact" default:"false"` } type UIThemeConfig struct { @@ -208,10 +209,10 @@ func (parser ConfigParser) getDefaultConfig() Config { Layout: LayoutConfig{ Prs: PrsLayoutConfig{ UpdatedAt: ColumnConfig{ - Width: utils.IntPtr(lipgloss.Width("2mo ago")), + Width: utils.IntPtr(lipgloss.Width("2mo ")), }, Repo: ColumnConfig{ - Width: utils.IntPtr(15), + Width: utils.IntPtr(20), }, Author: ColumnConfig{ Width: utils.IntPtr(15), @@ -230,7 +231,7 @@ func (parser ConfigParser) getDefaultConfig() Config { }, Issues: IssuesLayoutConfig{ UpdatedAt: ColumnConfig{ - Width: utils.IntPtr(lipgloss.Width("2mo ago")), + Width: utils.IntPtr(lipgloss.Width("2mo ")), }, Repo: ColumnConfig{ Width: utils.IntPtr(15), @@ -284,6 +285,7 @@ func (parser ConfigParser) getDefaultConfig() Config { SectionsShowCount: true, Table: TableUIThemeConfig{ ShowSeparator: true, + Compact: false, }, }, }, diff --git a/docs/data/schemas/theme.yaml b/docs/data/schemas/theme.yaml index b2b6e884..379e1a45 100644 --- a/docs/data/schemas/theme.yaml +++ b/docs/data/schemas/theme.yaml @@ -66,6 +66,15 @@ properties: schematize: skip_schema_render: true format: yaml + compact: + title: Compact + description: >- + Whether to show table rows in a compact way or not + type: boolean + default: false + schematize: + skip_schema_render: true + format: yaml colors: title: Theme Colors description: Defines text, background, and border colors for the dashboard. @@ -378,6 +387,7 @@ default: sectionsShowCount: true table: showSeparators: true + compact: false colors: text: primary: "#ffffff" diff --git a/ui/components/issue/issue.go b/ui/components/issue/issue.go index 6128b748..248bd8f3 100644 --- a/ui/components/issue/issue.go +++ b/ui/components/issue/issue.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/charmbracelet/lipgloss" + "github.com/dlvhdr/gh-dash/v4/data" "github.com/dlvhdr/gh-dash/v4/ui/components" "github.com/dlvhdr/gh-dash/v4/ui/components/table" @@ -19,7 +20,6 @@ type Issue struct { func (issue *Issue) ToTableRow() table.Row { return table.Row{ - issue.renderUpdateAt(), issue.renderStatus(), issue.renderRepoName(), issue.renderTitle(), @@ -27,6 +27,7 @@ func (issue *Issue) ToTableRow() table.Row { issue.renderAssignees(), issue.renderNumComments(), issue.renderNumReactions(), + issue.renderUpdateAt(), } } diff --git a/ui/components/issuessection/issuessection.go b/ui/components/issuessection/issuessection.go index c5599015..d46bb6cb 100644 --- a/ui/components/issuessection/issuessection.go +++ b/ui/components/issuessection/issuessection.go @@ -182,11 +182,6 @@ func GetSectionColumns( ) return []table.Column{ - { - Title: "", - Width: updatedAtLayout.Width, - Hidden: updatedAtLayout.Hidden, - }, { Title: "", Width: stateLayout.Width, @@ -222,6 +217,11 @@ func GetSectionColumns( Width: &issueNumCommentsCellWidth, Hidden: reactionsLayout.Hidden, }, + { + Title: "", + Width: updatedAtLayout.Width, + Hidden: updatedAtLayout.Hidden, + }, } } diff --git a/ui/components/pr/pr.go b/ui/components/pr/pr.go index 4952bea1..f2a3a0e9 100644 --- a/ui/components/pr/pr.go +++ b/ui/components/pr/pr.go @@ -26,20 +26,16 @@ func (pr *PullRequest) getTextStyle() lipgloss.Style { func (pr *PullRequest) renderReviewStatus() string { reviewCellStyle := pr.getTextStyle() if pr.Data.ReviewDecision == "APPROVED" { - if pr.Data.State == "OPEN" { - reviewCellStyle = reviewCellStyle.Foreground( - pr.Ctx.Theme.SuccessText, - ) - } + reviewCellStyle = reviewCellStyle.Foreground( + pr.Ctx.Theme.SuccessText, + ) return reviewCellStyle.Render("󰄬") } if pr.Data.ReviewDecision == "CHANGES_REQUESTED" { - if pr.Data.State == "OPEN" { - reviewCellStyle = reviewCellStyle.Foreground( - pr.Ctx.Theme.WarningText, - ) - } + reviewCellStyle = reviewCellStyle.Foreground( + pr.Ctx.Theme.WarningText, + ) return reviewCellStyle.Render("󰌑") } @@ -51,16 +47,16 @@ func (pr *PullRequest) renderState() string { switch pr.Data.State { case "OPEN": if pr.Data.IsDraft { - return mergeCellStyle.Foreground(pr.Ctx.Theme.FaintText).Render("") + return mergeCellStyle.Foreground(pr.Ctx.Theme.FaintText).Render(constants.DraftIcon) } else { - return mergeCellStyle.Foreground(pr.Ctx.Styles.Colors.OpenPR).Render("") + return mergeCellStyle.Foreground(pr.Ctx.Styles.Colors.OpenPR).Render(constants.OpenIcon) } case "CLOSED": return mergeCellStyle.Foreground(pr.Ctx.Styles.Colors.ClosedPR). - Render("") + Render(constants.ClosedIcon) case "MERGED": return mergeCellStyle.Foreground(pr.Ctx.Styles.Colors.MergedPR). - Render("") + Render(constants.MergedIcon) default: return mergeCellStyle.Foreground(pr.Ctx.Theme.FaintText).Render("-") } @@ -107,9 +103,7 @@ func (pr *PullRequest) renderCiStatus() string { accStatus := pr.GetStatusChecksRollup() ciCellStyle := pr.getTextStyle() if accStatus == "SUCCESS" { - if pr.Data.State == "OPEN" { - ciCellStyle = ciCellStyle.Foreground(pr.Ctx.Theme.SuccessText) - } + ciCellStyle = ciCellStyle.Foreground(pr.Ctx.Theme.SuccessText) return ciCellStyle.Render(constants.SuccessIcon) } @@ -117,9 +111,7 @@ func (pr *PullRequest) renderCiStatus() string { return ciCellStyle.Render(pr.Ctx.Styles.Common.WaitingGlyph) } - if pr.Data.State == "OPEN" { - ciCellStyle = ciCellStyle.Foreground(pr.Ctx.Theme.WarningText) - } + ciCellStyle = ciCellStyle.Foreground(pr.Ctx.Theme.WarningText) return ciCellStyle.Render(constants.FailureIcon) } @@ -130,14 +122,8 @@ func (pr *PullRequest) renderLines(isSelected bool) string { } var additionsFg, deletionsFg lipgloss.AdaptiveColor - state := pr.Data.State - if state != "OPEN" { - additionsFg = pr.Ctx.Theme.FaintText - deletionsFg = pr.Ctx.Theme.FaintText - } else { - additionsFg = pr.Ctx.Theme.SuccessText - deletionsFg = pr.Ctx.Theme.WarningText - } + additionsFg = pr.Ctx.Theme.SuccessText + deletionsFg = pr.Ctx.Theme.WarningText baseStyle := lipgloss.NewStyle() if isSelected { @@ -182,6 +168,22 @@ func (pr *PullRequest) renderTitle() string { ) } +func (pr *PullRequest) renderExtendedTitle(isSelected bool) string { + baseStyle := lipgloss.NewStyle() + if isSelected { + baseStyle = baseStyle.Foreground(pr.Ctx.Theme.SecondaryText).Background(pr.Ctx.Theme.SelectedBackground) + } + repoName := baseStyle.Render(pr.Data.Repository.NameWithOwner) + prNumber := baseStyle.Render(fmt.Sprintf("#%d ", pr.Data.Number)) + top := lipgloss.JoinHorizontal(lipgloss.Top, repoName, baseStyle.Render(" "), prNumber) + title := pr.Data.Title + width := max(lipgloss.Width(top), lipgloss.Width(title)) + top = baseStyle.Foreground(pr.Ctx.Theme.SecondaryText).Width(width).Render(top) + title = baseStyle.Foreground(pr.Ctx.Theme.PrimaryText).Width(width).Render(title) + + return baseStyle.Render(lipgloss.JoinVertical(lipgloss.Left, top, title)) +} + func (pr *PullRequest) renderAuthor() string { return pr.getTextStyle().Render(pr.Data.Author.Login) } @@ -195,8 +197,13 @@ func (pr *PullRequest) renderAssignees() string { } func (pr *PullRequest) renderRepoName() string { - repoName := pr.Data.HeadRepository.Name - return pr.getTextStyle().Render(repoName) + repoName := "" + if !pr.Ctx.Config.Theme.Ui.Table.Compact { + repoName = pr.Data.Repository.NameWithOwner + } else { + repoName = pr.Data.HeadRepository.Name + } + return pr.getTextStyle().Copy().Foreground(pr.Ctx.Theme.FaintText).Render(repoName) } func (pr *PullRequest) renderUpdateAt() string { @@ -209,7 +216,7 @@ func (pr *PullRequest) renderUpdateAt() string { updatedAtOutput = pr.Data.UpdatedAt.Format(timeFormat) } - return pr.getTextStyle().Render(updatedAtOutput) + return pr.getTextStyle().Copy().Foreground(pr.Ctx.Theme.FaintText).Render(updatedAtOutput) } func (pr *PullRequest) renderBaseName() string { @@ -220,31 +227,44 @@ func (pr *PullRequest) RenderState() string { switch pr.Data.State { case "OPEN": if pr.Data.IsDraft { - return " Draft" + return constants.DraftIcon + " Draft" } else { - return " Open" + return constants.OpenIcon + " Open" } case "CLOSED": - return "󰗨 Closed" + return constants.ClosedIcon + " Closed" case "MERGED": - return " Merged" + return constants.MergedIcon + " Merged" default: return "" } } func (pr *PullRequest) ToTableRow(isSelected bool) table.Row { + if !pr.Ctx.Config.Theme.Ui.Table.Compact { + return table.Row{ + pr.renderState(), + pr.renderExtendedTitle(isSelected), + pr.renderBaseName(), + pr.renderAssignees(), + pr.renderReviewStatus(), + pr.renderCiStatus(), + pr.renderLines(isSelected), + pr.renderUpdateAt(), + } + } + return table.Row{ - pr.renderUpdateAt(), pr.renderState(), pr.renderRepoName(), pr.renderTitle(), pr.renderAuthor(), - pr.renderAssignees(), pr.renderBaseName(), + pr.renderAssignees(), pr.renderReviewStatus(), pr.renderCiStatus(), pr.renderLines(isSelected), + pr.renderUpdateAt(), } } diff --git a/ui/components/prssection/prssection.go b/ui/components/prssection/prssection.go index f0b27080..ebf6c9ac 100644 --- a/ui/components/prssection/prssection.go +++ b/ui/components/prssection/prssection.go @@ -211,14 +211,56 @@ func GetSectionColumns( ciLayout := config.MergeColumnConfigs(dLayout.Ci, sLayout.Ci) linesLayout := config.MergeColumnConfigs(dLayout.Lines, sLayout.Lines) + if !ctx.Config.Theme.Ui.Table.Compact { + return []table.Column{ + { + Title: "", + Width: utils.IntPtr(3), + Hidden: stateLayout.Hidden, + }, + { + Title: "Title", + Grow: utils.BoolPtr(true), + Hidden: titleLayout.Hidden, + }, + { + Title: "Assignees", + Width: assigneesLayout.Width, + Hidden: assigneesLayout.Hidden, + }, + { + Title: "Base", + Width: baseLayout.Width, + Hidden: baseLayout.Hidden, + }, + { + Title: "󰯢", + Width: utils.IntPtr(4), + Hidden: reviewStatusLayout.Hidden, + }, + { + Title: "", + Width: &ctx.Styles.PrSection.CiCellWidth, + Grow: new(bool), + Hidden: ciLayout.Hidden, + }, + { + Title: "", + Width: linesLayout.Width, + Hidden: linesLayout.Hidden, + }, + { + Title: "", + Width: updatedAtLayout.Width, + Hidden: updatedAtLayout.Hidden, + }, + } + } + return []table.Column{ - { - Title: "", - Width: updatedAtLayout.Width, - Hidden: updatedAtLayout.Hidden, - }, { Title: "", + Width: utils.IntPtr(3), Hidden: stateLayout.Hidden, }, { @@ -262,6 +304,11 @@ func GetSectionColumns( Width: linesLayout.Width, Hidden: linesLayout.Hidden, }, + { + Title: "", + Width: updatedAtLayout.Width, + Hidden: updatedAtLayout.Hidden, + }, } } diff --git a/ui/components/table/table.go b/ui/components/table/table.go index fdefd748..079296ee 100644 --- a/ui/components/table/table.go +++ b/ui/components/table/table.go @@ -47,8 +47,11 @@ func NewModel( isLoading bool, ) Model { itemHeight := 1 + if !ctx.Config.Theme.Ui.Table.Compact { + itemHeight += 1 + } if ctx.Config.Theme.Ui.Table.ShowSeparator { - itemHeight = 2 + itemHeight += 1 } loadingSpinner := spinner.New() @@ -266,7 +269,6 @@ func (m *Model) renderRow(rowId int, headerColumns []string) string { } renderedColumns := make([]string, 0, len(m.Columns)) - headerColId := 0 for i, column := range m.Columns { @@ -275,12 +277,18 @@ func (m *Model) renderRow(rowId int, headerColumns []string) string { } colWidth := lipgloss.Width(headerColumns[headerColId]) + colHeight := 1 + if !m.ctx.Config.Theme.Ui.Table.Compact { + colHeight = 2 + } + col := m.Rows[rowId][i] renderedCol := style.Copy(). Width(colWidth). MaxWidth(colWidth). - Height(1). - MaxHeight(1). - Render(m.Rows[rowId][i]) + Height(colHeight). + MaxHeight(colHeight). + Render(col) + renderedColumns = append(renderedColumns, renderedCol) headerColId++ } diff --git a/ui/components/utils.go b/ui/components/utils.go index bdf9f9a2..16b0bd50 100644 --- a/ui/components/utils.go +++ b/ui/components/utils.go @@ -26,10 +26,7 @@ func GetIssueTextStyle( ctx *context.ProgramContext, state string, ) lipgloss.Style { - if state == "OPEN" { - return lipgloss.NewStyle().Foreground(ctx.Theme.PrimaryText) - } - return lipgloss.NewStyle().Foreground(ctx.Theme.FaintText) + return lipgloss.NewStyle().Foreground(ctx.Theme.PrimaryText) } func RenderIssueTitle( @@ -38,21 +35,24 @@ func RenderIssueTitle( title string, number int, ) string { - prNumber := fmt.Sprintf("#%d", number) - var prNumberFg lipgloss.AdaptiveColor - if state != "OPEN" { - prNumberFg = ctx.Theme.FaintText - } else { - prNumberFg = ctx.Theme.SecondaryText + prNumber := "" + if ctx.Config.Theme.Ui.Table.Compact { + prNumber = fmt.Sprintf("#%d ", number) + var prNumberFg lipgloss.AdaptiveColor + if state != "OPEN" { + prNumberFg = ctx.Theme.FaintText + } else { + prNumberFg = ctx.Theme.SecondaryText + } + prNumber = lipgloss.NewStyle().Foreground(prNumberFg).Render(prNumber) + // TODO: hack - see issue https://github.com/charmbracelet/lipgloss/issues/144 + // Provide ability to prevent insertion of Reset sequence #144 + prNumber = strings.Replace(prNumber, "\x1b[0m", "", -1) + } - prNumber = lipgloss.NewStyle().Foreground(prNumberFg).Render(prNumber) rTitle := GetIssueTextStyle(ctx, state).Render(title) - // TODO: hack - see issue https://github.com/charmbracelet/lipgloss/issues/144 - // Provide ability to prevent insertion of Reset sequence #144 - prNumber = strings.Replace(prNumber, "\x1b[0m", "", -1) - - res := fmt.Sprintf("%s %s", prNumber, rTitle) + res := fmt.Sprintf("%s%s", prNumber, rTitle) return res } diff --git a/ui/constants/constants.go b/ui/constants/constants.go index 28c594f7..addc0a4f 100644 --- a/ui/constants/constants.go +++ b/ui/constants/constants.go @@ -29,4 +29,9 @@ const ( WaitingIcon = "" FailureIcon = "󰅙" SuccessIcon = "" + + DraftIcon = "" + MergedIcon = "" + OpenIcon = "" + ClosedIcon = "" ) diff --git a/ui/context/styles.go b/ui/context/styles.go index 67c02447..fa8a6dac 100644 --- a/ui/context/styles.go +++ b/ui/context/styles.go @@ -145,7 +145,7 @@ func InitStyles(theme theme.Theme) Styles { s.PrSection.CiCellWidth = lipgloss.Width(" CI ") s.PrSection.LinesCellWidth = lipgloss.Width(" 123450 / -123450 ") - s.PrSection.UpdatedAtCellWidth = lipgloss.Width("2mo ago") + s.PrSection.UpdatedAtCellWidth = lipgloss.Width("2mo ") s.PrSection.PrRepoCellWidth = 15 s.PrSection.PrAuthorCellWidth = 15 diff --git a/utils/utils.go b/utils/utils.go index 068b84ab..24724df9 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -68,14 +68,8 @@ func TimeElapsed(then time.Time) string { parts = append(parts, strconv.Itoa(int(second))+"s") } - if now.After(then) { - text = " ago" - } else { - text = " after" - } - if len(parts) == 0 { - return "just now" + return "now" } return parts[0] + text