diff --git a/modules/badge/badge.go b/modules/badge/badge.go index fdf9866f607a5..d2e9bd9d1bcc6 100644 --- a/modules/badge/badge.go +++ b/modules/badge/badge.go @@ -5,6 +5,7 @@ package badge import ( "strings" + "sync" "unicode" actions_model "code.gitea.io/gitea/models/actions" @@ -49,23 +50,40 @@ func (b Badge) Width() int { return b.Label.width + b.Message.width } +// Style follows https://shields.io/badges +const ( + StyleFlat = "flat" + StyleFlatSquare = "flat-square" +) + const ( defaultOffset = 10 defaultFontSize = 11 DefaultColor = "#9f9f9f" // Grey DefaultFontFamily = "DejaVu Sans,Verdana,Geneva,sans-serif" + DefaultStyle = StyleFlat ) -var StatusColorMap = map[actions_model.Status]string{ - actions_model.StatusSuccess: "#4c1", // Green - actions_model.StatusSkipped: "#dfb317", // Yellow - actions_model.StatusUnknown: "#97ca00", // Light Green - actions_model.StatusFailure: "#e05d44", // Red - actions_model.StatusCancelled: "#fe7d37", // Orange - actions_model.StatusWaiting: "#dfb317", // Yellow - actions_model.StatusRunning: "#dfb317", // Yellow - actions_model.StatusBlocked: "#dfb317", // Yellow -} +var GlobalVars = sync.OnceValue(func() (ret struct { + StatusColorMap map[actions_model.Status]string + DejaVuGlyphWidthData map[rune]uint8 + AllStyles []string +}, +) { + ret.StatusColorMap = map[actions_model.Status]string{ + actions_model.StatusSuccess: "#4c1", // Green + actions_model.StatusSkipped: "#dfb317", // Yellow + actions_model.StatusUnknown: "#97ca00", // Light Green + actions_model.StatusFailure: "#e05d44", // Red + actions_model.StatusCancelled: "#fe7d37", // Orange + actions_model.StatusWaiting: "#dfb317", // Yellow + actions_model.StatusRunning: "#dfb317", // Yellow + actions_model.StatusBlocked: "#dfb317", // Yellow + } + ret.DejaVuGlyphWidthData = dejaVuGlyphWidthDataFunc() + ret.AllStyles = []string{StyleFlat, StyleFlatSquare} + return ret +}) // GenerateBadge generates badge with given template func GenerateBadge(label, message, color string) Badge { @@ -93,7 +111,7 @@ func GenerateBadge(label, message, color string) Badge { func calculateTextWidth(text string) int { width := 0 - widthData := DejaVuGlyphWidthData() + widthData := GlobalVars().DejaVuGlyphWidthData for _, char := range strings.TrimSpace(text) { charWidth, ok := widthData[char] if !ok { diff --git a/modules/badge/badge_glyph_width.go b/modules/badge/badge_glyph_width.go index e8e43ec9cb950..0d950c5a70e7f 100644 --- a/modules/badge/badge_glyph_width.go +++ b/modules/badge/badge_glyph_width.go @@ -3,8 +3,6 @@ package badge -import "sync" - // DejaVuGlyphWidthData is generated by `sfnt.Face.GlyphAdvance(nil, <rune>, 11, font.HintingNone)` with DejaVu Sans // v2.37 (https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-sans-ttf-2.37.zip). // @@ -13,7 +11,7 @@ import "sync" // // A devtest page "/devtest/badge-actions-svg" could be used to check the rendered images. -var DejaVuGlyphWidthData = sync.OnceValue(func() map[rune]uint8 { +func dejaVuGlyphWidthDataFunc() map[rune]uint8 { return map[rune]uint8{ 32: 3, 33: 4, @@ -205,4 +203,4 @@ var DejaVuGlyphWidthData = sync.OnceValue(func() map[rune]uint8 { 254: 7, 255: 7, } -}) +} diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go index 063ff42409fa9..995f9694264d1 100644 --- a/routers/web/devtest/devtest.go +++ b/routers/web/devtest/devtest.go @@ -4,6 +4,7 @@ package devtest import ( + "fmt" "html/template" "net/http" "path" @@ -128,6 +129,7 @@ func prepareMockDataBadgeCommitSign(ctx *context.Context) { func prepareMockDataBadgeActionsSvg(ctx *context.Context) { fontFamilyNames := strings.Split(badge.DefaultFontFamily, ",") selectedFontFamilyName := ctx.FormString("font", fontFamilyNames[0]) + selectedStyle := ctx.FormString("style", badge.DefaultStyle) var badges []badge.Badge badges = append(badges, badge.GenerateBadge("啊啊啊啊啊啊啊啊啊啊啊啊", "🌞🌞🌞🌞🌞", "green")) for r := rune(0); r < 256; r++ { @@ -141,7 +143,16 @@ func prepareMockDataBadgeActionsSvg(ctx *context.Context) { for i, b := range badges { b.IDPrefix = "devtest-" + strconv.FormatInt(int64(i), 10) + "-" b.FontFamily = selectedFontFamilyName - h, err := ctx.RenderToHTML("shared/actions/runner_badge", map[string]any{"Badge": b}) + var h template.HTML + var err error + switch selectedStyle { + case badge.StyleFlat: + h, err = ctx.RenderToHTML("shared/actions/runner_badge_flat", map[string]any{"Badge": b}) + case badge.StyleFlatSquare: + h, err = ctx.RenderToHTML("shared/actions/runner_badge_flat-square", map[string]any{"Badge": b}) + default: + err = fmt.Errorf("unknown badge style: %s", selectedStyle) + } if err != nil { ctx.ServerError("RenderToHTML", err) return @@ -151,6 +162,8 @@ func prepareMockDataBadgeActionsSvg(ctx *context.Context) { ctx.Data["BadgeSVGs"] = badgeSVGs ctx.Data["BadgeFontFamilyNames"] = fontFamilyNames ctx.Data["SelectedFontFamilyName"] = selectedFontFamilyName + ctx.Data["BadgeStyles"] = badge.GlobalVars().AllStyles + ctx.Data["SelectedStyle"] = selectedStyle } func prepareMockData(ctx *context.Context) { diff --git a/routers/web/repo/actions/badge.go b/routers/web/repo/actions/badge.go index e920ecaf58063..d268a8df8af37 100644 --- a/routers/web/repo/actions/badge.go +++ b/routers/web/repo/actions/badge.go @@ -5,35 +5,38 @@ package actions import ( "errors" - "fmt" "net/http" "path/filepath" "strings" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/modules/badge" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/context" ) func GetWorkflowBadge(ctx *context.Context) { workflowFile := ctx.PathParam("workflow_name") - branch := ctx.Req.URL.Query().Get("branch") - if branch == "" { - branch = ctx.Repo.Repository.DefaultBranch - } - branchRef := fmt.Sprintf("refs/heads/%s", branch) - event := ctx.Req.URL.Query().Get("event") + branch := ctx.FormString("branch", ctx.Repo.Repository.DefaultBranch) + event := ctx.FormString("event") + style := ctx.FormString("style") - badge, err := getWorkflowBadge(ctx, workflowFile, branchRef, event) + branchRef := git.RefNameFromBranch(branch) + b, err := getWorkflowBadge(ctx, workflowFile, branchRef.String(), event) if err != nil { ctx.ServerError("GetWorkflowBadge", err) return } - ctx.Data["Badge"] = badge + ctx.Data["Badge"] = b ctx.RespHeader().Set("Content-Type", "image/svg+xml") - ctx.HTML(http.StatusOK, "shared/actions/runner_badge") + switch style { + case badge.StyleFlatSquare: + ctx.HTML(http.StatusOK, "shared/actions/runner_badge_flat-square") + default: // defaults to badge.StyleFlat + ctx.HTML(http.StatusOK, "shared/actions/runner_badge_flat") + } } func getWorkflowBadge(ctx *context.Context, workflowFile, branchName, event string) (badge.Badge, error) { @@ -48,7 +51,7 @@ func getWorkflowBadge(ctx *context.Context, workflowFile, branchName, event stri return badge.Badge{}, err } - color, ok := badge.StatusColorMap[run.Status] + color, ok := badge.GlobalVars().StatusColorMap[run.Status] if !ok { return badge.GenerateBadge(workflowName, "unknown status", badge.DefaultColor), nil } diff --git a/templates/devtest/badge-actions-svg.tmpl b/templates/devtest/badge-actions-svg.tmpl index 8125793bb3fa4..5be4fb313128d 100644 --- a/templates/devtest/badge-actions-svg.tmpl +++ b/templates/devtest/badge-actions-svg.tmpl @@ -3,9 +3,16 @@ <div> <h1>Actions SVG</h1> <form class="tw-my-3"> - {{range $fontName := .BadgeFontFamilyNames}} - <label><input name="font" type="radio" value="{{$fontName}}" {{Iif (eq $.SelectedFontFamilyName $fontName) "checked"}}>{{$fontName}}</label> - {{end}} + <div class="tw-mb-2"> + {{range $fontName := .BadgeFontFamilyNames}} + <label><input name="font" type="radio" value="{{$fontName}}" {{Iif (eq $.SelectedFontFamilyName $fontName) "checked"}}>{{$fontName}}</label> + {{end}} + </div> + <div class="tw-mb-2"> + {{range $style := .BadgeStyles}} + <label><input name="style" type="radio" value="{{$style}}" {{Iif (eq $.SelectedStyle $style) "checked"}}>{{$style}}</label> + {{end}} + </div> <button>submit</button> </form> <div class="flex-text-block tw-flex-wrap"> diff --git a/templates/shared/actions/runner_badge_flat-square.tmpl b/templates/shared/actions/runner_badge_flat-square.tmpl new file mode 100644 index 0000000000000..cc1dc1e8f3a90 --- /dev/null +++ b/templates/shared/actions/runner_badge_flat-square.tmpl @@ -0,0 +1,15 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{.Badge.Width}}" height="20" + role="img" aria-label="{{.Badge.Label.Text}}: {{.Badge.Message.Text}}"> + <title>{{.Badge.Label.Text}}: {{.Badge.Message.Text}}</title> + <g shape-rendering="crispEdges"> + <rect width="{{.Badge.Label.Width}}" height="20" fill="#555" /> + <rect x="{{.Badge.Label.Width}}" width="{{.Badge.Message.Width}}" height="20" fill="{{.Badge.Color}}" /> + </g> + <g fill="#fff" text-anchor="middle" font-family="{{.Badge.FontFamily}}" + text-rendering="geometricPrecision" font-size="{{.Badge.FontSize}}"> + <text x="{{.Badge.Label.X}}" y="140" + transform="scale(.1)" fill="#fff" textLength="{{.Badge.Label.TextLength}}">{{.Badge.Label.Text}}</text> + <text x="{{.Badge.Message.X}}" y="140" transform="scale(.1)" fill="#fff" + textLength="{{.Badge.Message.TextLength}}">{{.Badge.Message.Text}}</text> + </g> +</svg> diff --git a/templates/shared/actions/runner_badge.tmpl b/templates/shared/actions/runner_badge_flat.tmpl similarity index 100% rename from templates/shared/actions/runner_badge.tmpl rename to templates/shared/actions/runner_badge_flat.tmpl