mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	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 <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -5,6 +5,7 @@ package badge | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"unicode" | ||||
|  | ||||
| 	actions_model "code.gitea.io/gitea/models/actions" | ||||
| @@ -49,14 +50,27 @@ 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{ | ||||
| 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 | ||||
| @@ -65,7 +79,11 @@ var StatusColorMap = map[actions_model.Status]string{ | ||||
| 		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 { | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| }) | ||||
| } | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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 | ||||
| 	} | ||||
|   | ||||
| @@ -3,9 +3,16 @@ | ||||
| 	<div> | ||||
| 		<h1>Actions SVG</h1> | ||||
| 		<form class="tw-my-3"> | ||||
| 			<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"> | ||||
|   | ||||
							
								
								
									
										15
									
								
								templates/shared/actions/runner_badge_flat-square.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								templates/shared/actions/runner_badge_flat-square.tmpl
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||
| After Width: | Height: | Size: 924 B | 
| Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB | 
		Reference in New Issue
	
	Block a user