Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flat-square action badge style #34062

Merged
merged 8 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions modules/badge/badge.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package badge

import (
"strings"
"sync"
"unicode"

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

// styles 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 {
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 2 additions & 4 deletions modules/badge/badge_glyph_width.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
//
Expand All @@ -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,
Expand Down Expand Up @@ -205,4 +203,4 @@ var DejaVuGlyphWidthData = sync.OnceValue(func() map[rune]uint8 {
254: 7,
255: 7,
}
})
}
15 changes: 14 additions & 1 deletion routers/web/devtest/devtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package devtest

import (
"fmt"
"html/template"
"net/http"
"path"
Expand Down Expand Up @@ -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++ {
Expand All @@ -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
Expand All @@ -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) {
Expand Down
25 changes: 14 additions & 11 deletions routers/web/repo/actions/badge.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
}
Expand Down
13 changes: 10 additions & 3 deletions templates/devtest/badge-actions-svg.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
15 changes: 15 additions & 0 deletions templates/shared/actions/runner_badge_flat-square.tmpl
Original file line number Diff line number Diff line change
@@ -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>
Loading