mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 02:04:11 +01: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:
		
							parent
							
								
									86c1a33369
								
							
						
					
					
						commit
						56e42be36d
					
				@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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">
 | 
			
		||||
			{{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">
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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  | 
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user