mirror of
https://github.com/go-gitea/gitea.git
synced 2025-05-05 23:42:54 +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:
parent
86c1a33369
commit
56e42be36d
@ -5,6 +5,7 @@ package badge
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
@ -49,23 +50,40 @@ func (b Badge) Width() int {
|
|||||||
return b.Label.width + b.Message.width
|
return b.Label.width + b.Message.width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Style follows https://shields.io/badges
|
||||||
|
const (
|
||||||
|
StyleFlat = "flat"
|
||||||
|
StyleFlatSquare = "flat-square"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultOffset = 10
|
defaultOffset = 10
|
||||||
defaultFontSize = 11
|
defaultFontSize = 11
|
||||||
DefaultColor = "#9f9f9f" // Grey
|
DefaultColor = "#9f9f9f" // Grey
|
||||||
DefaultFontFamily = "DejaVu Sans,Verdana,Geneva,sans-serif"
|
DefaultFontFamily = "DejaVu Sans,Verdana,Geneva,sans-serif"
|
||||||
|
DefaultStyle = StyleFlat
|
||||||
)
|
)
|
||||||
|
|
||||||
var StatusColorMap = map[actions_model.Status]string{
|
var GlobalVars = sync.OnceValue(func() (ret struct {
|
||||||
actions_model.StatusSuccess: "#4c1", // Green
|
StatusColorMap map[actions_model.Status]string
|
||||||
actions_model.StatusSkipped: "#dfb317", // Yellow
|
DejaVuGlyphWidthData map[rune]uint8
|
||||||
actions_model.StatusUnknown: "#97ca00", // Light Green
|
AllStyles []string
|
||||||
actions_model.StatusFailure: "#e05d44", // Red
|
},
|
||||||
actions_model.StatusCancelled: "#fe7d37", // Orange
|
) {
|
||||||
actions_model.StatusWaiting: "#dfb317", // Yellow
|
ret.StatusColorMap = map[actions_model.Status]string{
|
||||||
actions_model.StatusRunning: "#dfb317", // Yellow
|
actions_model.StatusSuccess: "#4c1", // Green
|
||||||
actions_model.StatusBlocked: "#dfb317", // Yellow
|
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
|
// GenerateBadge generates badge with given template
|
||||||
func GenerateBadge(label, message, color string) Badge {
|
func GenerateBadge(label, message, color string) Badge {
|
||||||
@ -93,7 +111,7 @@ func GenerateBadge(label, message, color string) Badge {
|
|||||||
|
|
||||||
func calculateTextWidth(text string) int {
|
func calculateTextWidth(text string) int {
|
||||||
width := 0
|
width := 0
|
||||||
widthData := DejaVuGlyphWidthData()
|
widthData := GlobalVars().DejaVuGlyphWidthData
|
||||||
for _, char := range strings.TrimSpace(text) {
|
for _, char := range strings.TrimSpace(text) {
|
||||||
charWidth, ok := widthData[char]
|
charWidth, ok := widthData[char]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
package badge
|
package badge
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
// DejaVuGlyphWidthData is generated by `sfnt.Face.GlyphAdvance(nil, <rune>, 11, font.HintingNone)` with DejaVu Sans
|
// 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).
|
// 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.
|
// 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{
|
return map[rune]uint8{
|
||||||
32: 3,
|
32: 3,
|
||||||
33: 4,
|
33: 4,
|
||||||
@ -205,4 +203,4 @@ var DejaVuGlyphWidthData = sync.OnceValue(func() map[rune]uint8 {
|
|||||||
254: 7,
|
254: 7,
|
||||||
255: 7,
|
255: 7,
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package devtest
|
package devtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
@ -128,6 +129,7 @@ func prepareMockDataBadgeCommitSign(ctx *context.Context) {
|
|||||||
func prepareMockDataBadgeActionsSvg(ctx *context.Context) {
|
func prepareMockDataBadgeActionsSvg(ctx *context.Context) {
|
||||||
fontFamilyNames := strings.Split(badge.DefaultFontFamily, ",")
|
fontFamilyNames := strings.Split(badge.DefaultFontFamily, ",")
|
||||||
selectedFontFamilyName := ctx.FormString("font", fontFamilyNames[0])
|
selectedFontFamilyName := ctx.FormString("font", fontFamilyNames[0])
|
||||||
|
selectedStyle := ctx.FormString("style", badge.DefaultStyle)
|
||||||
var badges []badge.Badge
|
var badges []badge.Badge
|
||||||
badges = append(badges, badge.GenerateBadge("啊啊啊啊啊啊啊啊啊啊啊啊", "🌞🌞🌞🌞🌞", "green"))
|
badges = append(badges, badge.GenerateBadge("啊啊啊啊啊啊啊啊啊啊啊啊", "🌞🌞🌞🌞🌞", "green"))
|
||||||
for r := rune(0); r < 256; r++ {
|
for r := rune(0); r < 256; r++ {
|
||||||
@ -141,7 +143,16 @@ func prepareMockDataBadgeActionsSvg(ctx *context.Context) {
|
|||||||
for i, b := range badges {
|
for i, b := range badges {
|
||||||
b.IDPrefix = "devtest-" + strconv.FormatInt(int64(i), 10) + "-"
|
b.IDPrefix = "devtest-" + strconv.FormatInt(int64(i), 10) + "-"
|
||||||
b.FontFamily = selectedFontFamilyName
|
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 {
|
if err != nil {
|
||||||
ctx.ServerError("RenderToHTML", err)
|
ctx.ServerError("RenderToHTML", err)
|
||||||
return
|
return
|
||||||
@ -151,6 +162,8 @@ func prepareMockDataBadgeActionsSvg(ctx *context.Context) {
|
|||||||
ctx.Data["BadgeSVGs"] = badgeSVGs
|
ctx.Data["BadgeSVGs"] = badgeSVGs
|
||||||
ctx.Data["BadgeFontFamilyNames"] = fontFamilyNames
|
ctx.Data["BadgeFontFamilyNames"] = fontFamilyNames
|
||||||
ctx.Data["SelectedFontFamilyName"] = selectedFontFamilyName
|
ctx.Data["SelectedFontFamilyName"] = selectedFontFamilyName
|
||||||
|
ctx.Data["BadgeStyles"] = badge.GlobalVars().AllStyles
|
||||||
|
ctx.Data["SelectedStyle"] = selectedStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareMockData(ctx *context.Context) {
|
func prepareMockData(ctx *context.Context) {
|
||||||
|
@ -5,35 +5,38 @@ package actions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
"code.gitea.io/gitea/modules/badge"
|
"code.gitea.io/gitea/modules/badge"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetWorkflowBadge(ctx *context.Context) {
|
func GetWorkflowBadge(ctx *context.Context) {
|
||||||
workflowFile := ctx.PathParam("workflow_name")
|
workflowFile := ctx.PathParam("workflow_name")
|
||||||
branch := ctx.Req.URL.Query().Get("branch")
|
branch := ctx.FormString("branch", ctx.Repo.Repository.DefaultBranch)
|
||||||
if branch == "" {
|
event := ctx.FormString("event")
|
||||||
branch = ctx.Repo.Repository.DefaultBranch
|
style := ctx.FormString("style")
|
||||||
}
|
|
||||||
branchRef := fmt.Sprintf("refs/heads/%s", branch)
|
|
||||||
event := ctx.Req.URL.Query().Get("event")
|
|
||||||
|
|
||||||
badge, err := getWorkflowBadge(ctx, workflowFile, branchRef, event)
|
branchRef := git.RefNameFromBranch(branch)
|
||||||
|
b, err := getWorkflowBadge(ctx, workflowFile, branchRef.String(), event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetWorkflowBadge", err)
|
ctx.ServerError("GetWorkflowBadge", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["Badge"] = badge
|
ctx.Data["Badge"] = b
|
||||||
ctx.RespHeader().Set("Content-Type", "image/svg+xml")
|
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) {
|
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
|
return badge.Badge{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
color, ok := badge.StatusColorMap[run.Status]
|
color, ok := badge.GlobalVars().StatusColorMap[run.Status]
|
||||||
if !ok {
|
if !ok {
|
||||||
return badge.GenerateBadge(workflowName, "unknown status", badge.DefaultColor), nil
|
return badge.GenerateBadge(workflowName, "unknown status", badge.DefaultColor), nil
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,16 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1>Actions SVG</h1>
|
<h1>Actions SVG</h1>
|
||||||
<form class="tw-my-3">
|
<form class="tw-my-3">
|
||||||
{{range $fontName := .BadgeFontFamilyNames}}
|
<div class="tw-mb-2">
|
||||||
<label><input name="font" type="radio" value="{{$fontName}}" {{Iif (eq $.SelectedFontFamilyName $fontName) "checked"}}>{{$fontName}}</label>
|
{{range $fontName := .BadgeFontFamilyNames}}
|
||||||
{{end}}
|
<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>
|
<button>submit</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="flex-text-block tw-flex-wrap">
|
<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