Skip to content

Commit 56e42be

Browse files
Add flat-square action badge style (#34062)
Adds the `flat-square` style to action badges. Styles can be selected by adding `?style=<style>` to the badge endpoint. If no style query is given, or if the query is invalid, the style defaults to `flat`. --------- Co-authored-by: wxiaoguang <[email protected]>
1 parent 86c1a33 commit 56e42be

File tree

7 files changed

+84
-30
lines changed

7 files changed

+84
-30
lines changed

modules/badge/badge.go

+29-11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package badge
55

66
import (
77
"strings"
8+
"sync"
89
"unicode"
910

1011
actions_model "code.gitea.io/gitea/models/actions"
@@ -49,23 +50,40 @@ func (b Badge) Width() int {
4950
return b.Label.width + b.Message.width
5051
}
5152

53+
// Style follows https://shields.io/badges
54+
const (
55+
StyleFlat = "flat"
56+
StyleFlatSquare = "flat-square"
57+
)
58+
5259
const (
5360
defaultOffset = 10
5461
defaultFontSize = 11
5562
DefaultColor = "#9f9f9f" // Grey
5663
DefaultFontFamily = "DejaVu Sans,Verdana,Geneva,sans-serif"
64+
DefaultStyle = StyleFlat
5765
)
5866

59-
var StatusColorMap = map[actions_model.Status]string{
60-
actions_model.StatusSuccess: "#4c1", // Green
61-
actions_model.StatusSkipped: "#dfb317", // Yellow
62-
actions_model.StatusUnknown: "#97ca00", // Light Green
63-
actions_model.StatusFailure: "#e05d44", // Red
64-
actions_model.StatusCancelled: "#fe7d37", // Orange
65-
actions_model.StatusWaiting: "#dfb317", // Yellow
66-
actions_model.StatusRunning: "#dfb317", // Yellow
67-
actions_model.StatusBlocked: "#dfb317", // Yellow
68-
}
67+
var GlobalVars = sync.OnceValue(func() (ret struct {
68+
StatusColorMap map[actions_model.Status]string
69+
DejaVuGlyphWidthData map[rune]uint8
70+
AllStyles []string
71+
},
72+
) {
73+
ret.StatusColorMap = map[actions_model.Status]string{
74+
actions_model.StatusSuccess: "#4c1", // Green
75+
actions_model.StatusSkipped: "#dfb317", // Yellow
76+
actions_model.StatusUnknown: "#97ca00", // Light Green
77+
actions_model.StatusFailure: "#e05d44", // Red
78+
actions_model.StatusCancelled: "#fe7d37", // Orange
79+
actions_model.StatusWaiting: "#dfb317", // Yellow
80+
actions_model.StatusRunning: "#dfb317", // Yellow
81+
actions_model.StatusBlocked: "#dfb317", // Yellow
82+
}
83+
ret.DejaVuGlyphWidthData = dejaVuGlyphWidthDataFunc()
84+
ret.AllStyles = []string{StyleFlat, StyleFlatSquare}
85+
return ret
86+
})
6987

7088
// GenerateBadge generates badge with given template
7189
func GenerateBadge(label, message, color string) Badge {
@@ -93,7 +111,7 @@ func GenerateBadge(label, message, color string) Badge {
93111

94112
func calculateTextWidth(text string) int {
95113
width := 0
96-
widthData := DejaVuGlyphWidthData()
114+
widthData := GlobalVars().DejaVuGlyphWidthData
97115
for _, char := range strings.TrimSpace(text) {
98116
charWidth, ok := widthData[char]
99117
if !ok {

modules/badge/badge_glyph_width.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
package badge
55

6-
import "sync"
7-
86
// DejaVuGlyphWidthData is generated by `sfnt.Face.GlyphAdvance(nil, <rune>, 11, font.HintingNone)` with DejaVu Sans
97
// v2.37 (https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-sans-ttf-2.37.zip).
108
//
@@ -13,7 +11,7 @@ import "sync"
1311
//
1412
// A devtest page "/devtest/badge-actions-svg" could be used to check the rendered images.
1513

16-
var DejaVuGlyphWidthData = sync.OnceValue(func() map[rune]uint8 {
14+
func dejaVuGlyphWidthDataFunc() map[rune]uint8 {
1715
return map[rune]uint8{
1816
32: 3,
1917
33: 4,
@@ -205,4 +203,4 @@ var DejaVuGlyphWidthData = sync.OnceValue(func() map[rune]uint8 {
205203
254: 7,
206204
255: 7,
207205
}
208-
})
206+
}

routers/web/devtest/devtest.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package devtest
55

66
import (
7+
"fmt"
78
"html/template"
89
"net/http"
910
"path"
@@ -128,6 +129,7 @@ func prepareMockDataBadgeCommitSign(ctx *context.Context) {
128129
func prepareMockDataBadgeActionsSvg(ctx *context.Context) {
129130
fontFamilyNames := strings.Split(badge.DefaultFontFamily, ",")
130131
selectedFontFamilyName := ctx.FormString("font", fontFamilyNames[0])
132+
selectedStyle := ctx.FormString("style", badge.DefaultStyle)
131133
var badges []badge.Badge
132134
badges = append(badges, badge.GenerateBadge("啊啊啊啊啊啊啊啊啊啊啊啊", "🌞🌞🌞🌞🌞", "green"))
133135
for r := rune(0); r < 256; r++ {
@@ -141,7 +143,16 @@ func prepareMockDataBadgeActionsSvg(ctx *context.Context) {
141143
for i, b := range badges {
142144
b.IDPrefix = "devtest-" + strconv.FormatInt(int64(i), 10) + "-"
143145
b.FontFamily = selectedFontFamilyName
144-
h, err := ctx.RenderToHTML("shared/actions/runner_badge", map[string]any{"Badge": b})
146+
var h template.HTML
147+
var err error
148+
switch selectedStyle {
149+
case badge.StyleFlat:
150+
h, err = ctx.RenderToHTML("shared/actions/runner_badge_flat", map[string]any{"Badge": b})
151+
case badge.StyleFlatSquare:
152+
h, err = ctx.RenderToHTML("shared/actions/runner_badge_flat-square", map[string]any{"Badge": b})
153+
default:
154+
err = fmt.Errorf("unknown badge style: %s", selectedStyle)
155+
}
145156
if err != nil {
146157
ctx.ServerError("RenderToHTML", err)
147158
return
@@ -151,6 +162,8 @@ func prepareMockDataBadgeActionsSvg(ctx *context.Context) {
151162
ctx.Data["BadgeSVGs"] = badgeSVGs
152163
ctx.Data["BadgeFontFamilyNames"] = fontFamilyNames
153164
ctx.Data["SelectedFontFamilyName"] = selectedFontFamilyName
165+
ctx.Data["BadgeStyles"] = badge.GlobalVars().AllStyles
166+
ctx.Data["SelectedStyle"] = selectedStyle
154167
}
155168

156169
func prepareMockData(ctx *context.Context) {

routers/web/repo/actions/badge.go

+14-11
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,38 @@ package actions
55

66
import (
77
"errors"
8-
"fmt"
98
"net/http"
109
"path/filepath"
1110
"strings"
1211

1312
actions_model "code.gitea.io/gitea/models/actions"
1413
"code.gitea.io/gitea/modules/badge"
14+
"code.gitea.io/gitea/modules/git"
1515
"code.gitea.io/gitea/modules/util"
1616
"code.gitea.io/gitea/services/context"
1717
)
1818

1919
func GetWorkflowBadge(ctx *context.Context) {
2020
workflowFile := ctx.PathParam("workflow_name")
21-
branch := ctx.Req.URL.Query().Get("branch")
22-
if branch == "" {
23-
branch = ctx.Repo.Repository.DefaultBranch
24-
}
25-
branchRef := fmt.Sprintf("refs/heads/%s", branch)
26-
event := ctx.Req.URL.Query().Get("event")
21+
branch := ctx.FormString("branch", ctx.Repo.Repository.DefaultBranch)
22+
event := ctx.FormString("event")
23+
style := ctx.FormString("style")
2724

28-
badge, err := getWorkflowBadge(ctx, workflowFile, branchRef, event)
25+
branchRef := git.RefNameFromBranch(branch)
26+
b, err := getWorkflowBadge(ctx, workflowFile, branchRef.String(), event)
2927
if err != nil {
3028
ctx.ServerError("GetWorkflowBadge", err)
3129
return
3230
}
3331

34-
ctx.Data["Badge"] = badge
32+
ctx.Data["Badge"] = b
3533
ctx.RespHeader().Set("Content-Type", "image/svg+xml")
36-
ctx.HTML(http.StatusOK, "shared/actions/runner_badge")
34+
switch style {
35+
case badge.StyleFlatSquare:
36+
ctx.HTML(http.StatusOK, "shared/actions/runner_badge_flat-square")
37+
default: // defaults to badge.StyleFlat
38+
ctx.HTML(http.StatusOK, "shared/actions/runner_badge_flat")
39+
}
3740
}
3841

3942
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
4851
return badge.Badge{}, err
4952
}
5053

51-
color, ok := badge.StatusColorMap[run.Status]
54+
color, ok := badge.GlobalVars().StatusColorMap[run.Status]
5255
if !ok {
5356
return badge.GenerateBadge(workflowName, "unknown status", badge.DefaultColor), nil
5457
}

templates/devtest/badge-actions-svg.tmpl

+10-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@
33
<div>
44
<h1>Actions SVG</h1>
55
<form class="tw-my-3">
6-
{{range $fontName := .BadgeFontFamilyNames}}
7-
<label><input name="font" type="radio" value="{{$fontName}}" {{Iif (eq $.SelectedFontFamilyName $fontName) "checked"}}>{{$fontName}}</label>
8-
{{end}}
6+
<div class="tw-mb-2">
7+
{{range $fontName := .BadgeFontFamilyNames}}
8+
<label><input name="font" type="radio" value="{{$fontName}}" {{Iif (eq $.SelectedFontFamilyName $fontName) "checked"}}>{{$fontName}}</label>
9+
{{end}}
10+
</div>
11+
<div class="tw-mb-2">
12+
{{range $style := .BadgeStyles}}
13+
<label><input name="style" type="radio" value="{{$style}}" {{Iif (eq $.SelectedStyle $style) "checked"}}>{{$style}}</label>
14+
{{end}}
15+
</div>
916
<button>submit</button>
1017
</form>
1118
<div class="flex-text-block tw-flex-wrap">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{.Badge.Width}}" height="20"
2+
role="img" aria-label="{{.Badge.Label.Text}}: {{.Badge.Message.Text}}">
3+
<title>{{.Badge.Label.Text}}: {{.Badge.Message.Text}}</title>
4+
<g shape-rendering="crispEdges">
5+
<rect width="{{.Badge.Label.Width}}" height="20" fill="#555" />
6+
<rect x="{{.Badge.Label.Width}}" width="{{.Badge.Message.Width}}" height="20" fill="{{.Badge.Color}}" />
7+
</g>
8+
<g fill="#fff" text-anchor="middle" font-family="{{.Badge.FontFamily}}"
9+
text-rendering="geometricPrecision" font-size="{{.Badge.FontSize}}">
10+
<text x="{{.Badge.Label.X}}" y="140"
11+
transform="scale(.1)" fill="#fff" textLength="{{.Badge.Label.TextLength}}">{{.Badge.Label.Text}}</text>
12+
<text x="{{.Badge.Message.X}}" y="140" transform="scale(.1)" fill="#fff"
13+
textLength="{{.Badge.Message.TextLength}}">{{.Badge.Message.Text}}</text>
14+
</g>
15+
</svg>

0 commit comments

Comments
 (0)