182 lines
6.1 KiB
Go
182 lines
6.1 KiB
Go
package git
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/core"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/display"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/icons"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/internal"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/MujicaUI/theme"
|
|
)
|
|
|
|
// PullRequestState is where a pull request stands.
|
|
type PullRequestState int
|
|
|
|
const (
|
|
PullRequestOpen PullRequestState = iota
|
|
PullRequestDraft
|
|
PullRequestMerged
|
|
PullRequestClosed
|
|
)
|
|
|
|
// ReviewState is a reviewer's verdict.
|
|
type ReviewState int
|
|
|
|
const (
|
|
ReviewPending ReviewState = iota
|
|
ReviewApproved
|
|
ReviewChangesRequested
|
|
)
|
|
|
|
// Reviewer is a person asked to review.
|
|
type Reviewer struct {
|
|
Name string
|
|
State ReviewState
|
|
}
|
|
|
|
// PullRequest is what a PullRequestCard shows; the caller fetches it.
|
|
type PullRequest struct {
|
|
Number int
|
|
Title string
|
|
Author string
|
|
State PullRequestState
|
|
Head, Base string
|
|
// Checks counts CI results.
|
|
ChecksPassed, ChecksFailed, ChecksPending int
|
|
Reviewers []Reviewer
|
|
Added, Deleted, Files int
|
|
}
|
|
|
|
// PullRequestCardOptions configure a PullRequestCard.
|
|
type PullRequestCardOptions struct {
|
|
Disabled bool
|
|
}
|
|
|
|
// PullRequestCard summarizes a pull request; Changed reports opening.
|
|
func PullRequestCard(c *ui.Context, pr PullRequest, opts PullRequestCardOptions) core.ChoiceResult {
|
|
if pr.Number <= 0 || pr.Title == "" {
|
|
panic("mujica: PullRequest needs a positive Number and a Title")
|
|
}
|
|
if pr.ChecksPassed < 0 || pr.ChecksFailed < 0 || pr.ChecksPending < 0 || pr.Files < 0 {
|
|
panic("mujica: PullRequest counts must not be negative")
|
|
}
|
|
k := core.Tokens(c)
|
|
u := core.Density(c).Unit()
|
|
small := core.FontSize(c, theme.CaptionSize)
|
|
card := ui.Column(c).AlignItems(ui.Stretch).Gap(u*2).Padding(u*4).Background(k.Surface).
|
|
Border(theme.BorderWidth, k.Border).Radius(theme.CardRadius).Disabled(opts.Disabled)
|
|
res := core.ChoiceResult{Element: card}
|
|
card.Children(func() {
|
|
ui.Row(c).AlignItems(ui.Center).Gap(u * 2).Children(func() {
|
|
pullRequestStateBadge(c, pr.State)
|
|
title := ui.ButtonBase(c).Justify(ui.Start).Gap(u*2).Padding(u, u).Radius(theme.ControlRadius).
|
|
Role(ui.RoleLink).Label(fmt.Sprintf("#%d %s", pr.Number, pr.Title)).Shrink(1).MinWidth(0).Cursor(ui.CursorPointer)
|
|
internal.Ring(title, k.Focus, theme.ControlRadius)
|
|
if title.Hovered() {
|
|
title.Background(k.SurfaceHover)
|
|
}
|
|
title.Transition(core.ColorFade)
|
|
title.Children(func() {
|
|
ui.Text(c, pr.Title).FontWeight(600).SingleLine().MinWidth(0)
|
|
ui.Text(c, fmt.Sprintf("#%d", pr.Number)).TextColor(k.TextMuted).FontFeatures("tnum").Shrink(0)
|
|
})
|
|
if title.Clicked() && !title.IsDisabled() {
|
|
res = core.NewChoiceResult(res.Element, true)
|
|
slog.Debug("mujica: pull request card open", "number", pr.Number)
|
|
}
|
|
})
|
|
ui.Row(c).AlignItems(ui.Center).Gap(u * 2).FontSize(small).TextColor(k.TextMuted).Wrap().Children(func() {
|
|
ui.Text(c, pr.Author).FontWeight(600).TextColor(k.Text)
|
|
if pr.Head != "" && pr.Base != "" {
|
|
ui.Icon(c, icons.Must("git-pull-request")).Size(12, 12)
|
|
ui.Text(c, pr.Head+" → "+pr.Base).Font(theme.MonoFont).SingleLine().MaxWidth(320)
|
|
}
|
|
})
|
|
ui.Row(c).AlignItems(ui.Center).Gap(u * 4).Wrap().FontSize(small).Children(func() {
|
|
pullRequestChecks(c, pr)
|
|
if len(pr.Reviewers) > 0 {
|
|
ui.Row(c).AlignItems(ui.Center).Gap(u * 2).Children(func() {
|
|
for _, r := range pr.Reviewers {
|
|
pullRequestReviewer(c, r)
|
|
}
|
|
})
|
|
}
|
|
ui.Spacer(c)
|
|
ui.Text(c, fmt.Sprintf(core.Msg(c, "pull_request_card.files"), pr.Files)).TextColor(k.TextMuted).FontFeatures("tnum")
|
|
DiffStat(c, pr.Added, pr.Deleted, DiffStatOptions{})
|
|
})
|
|
})
|
|
return res
|
|
}
|
|
|
|
func pullRequestStateBadge(c *ui.Context, st PullRequestState) {
|
|
k := core.Tokens(c)
|
|
u := core.Density(c).Unit()
|
|
icon, col, key := "git-pull-request", k.Success, "open"
|
|
switch st {
|
|
case PullRequestOpen:
|
|
case PullRequestDraft:
|
|
icon, col, key = "git-pull-request", k.TextMuted, "draft"
|
|
case PullRequestMerged:
|
|
icon, col, key = "git-merge", k.AccentText, "merged"
|
|
case PullRequestClosed:
|
|
icon, col, key = "circle-x", k.Danger, "closed"
|
|
default:
|
|
panic("mujica: unknown PullRequestState")
|
|
}
|
|
ui.Row(c).AlignItems(ui.Center).Gap(u).Padding(2, u*2).Radius(theme.ControlRadius).Border(theme.BorderWidth, col).
|
|
TextColor(col).Shrink(0).Children(func() {
|
|
ui.Icon(c, icons.Must(icon)).Size(14, 14)
|
|
ui.Text(c, core.Msg(c, "pull_request_card."+key)).FontSize(core.FontSize(c, theme.CaptionSize)).FontWeight(600).SingleLine()
|
|
})
|
|
}
|
|
|
|
func pullRequestChecks(c *ui.Context, pr PullRequest) {
|
|
k := core.Tokens(c)
|
|
u := core.Density(c).Unit()
|
|
ui.Row(c).AlignItems(ui.Center).Gap(u * 3).Children(func() {
|
|
for _, ch := range []struct {
|
|
n int
|
|
icon, key string
|
|
col ui.Color
|
|
}{
|
|
{pr.ChecksPassed, "circle-check", "pull_request_card.passed", k.Success},
|
|
{pr.ChecksFailed, "circle-x", "pull_request_card.failed", k.Danger},
|
|
{pr.ChecksPending, "clock", "pull_request_card.pending", k.Warning},
|
|
} {
|
|
if ch.n == 0 {
|
|
continue
|
|
}
|
|
name := fmt.Sprintf(core.Msg(c, ch.key), ch.n)
|
|
ui.Row(c).AlignItems(ui.Center).Gap(u).TextColor(ch.col).Label(name).Tooltip(name).Children(func() {
|
|
ui.Icon(c, icons.Must(ch.icon)).Size(14, 14)
|
|
ui.Text(c, fmt.Sprint(ch.n)).FontFeatures("tnum")
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func pullRequestReviewer(c *ui.Context, r Reviewer) {
|
|
k := core.Tokens(c)
|
|
key, icon, col := "pull_request_card.review_pending", "clock", k.TextMuted
|
|
switch r.State {
|
|
case ReviewPending:
|
|
case ReviewApproved:
|
|
key, icon, col = "pull_request_card.approved", "check", k.Success
|
|
case ReviewChangesRequested:
|
|
key, icon, col = "pull_request_card.changes", "circle-alert", k.Danger
|
|
default:
|
|
panic("mujica: unknown ReviewState")
|
|
}
|
|
name := r.Name + ": " + core.Msg(c, key)
|
|
ui.Box(c).Size(28, 28).Shrink(0).Label(name).Tooltip(name).Children(func() {
|
|
display.Avatar(c, r.Name, display.AvatarOptions{Size: 24}).Absolute().Left(0).Top(0)
|
|
ui.Box(c).Size(14, 14).Radius(7).Background(k.Surface).Center().Absolute().Right(0).Bottom(0).TextColor(col).
|
|
Children(func() { ui.Icon(c, icons.Must(icon)).Size(10, 10) })
|
|
})
|
|
}
|