Skip to content

Commit

Permalink
Label display improvements for scopes and consistency
Browse files Browse the repository at this point in the history
* Show scoped label as "Scope > Name", with scope prefix slightly dimmed
* Show label with background color also in assignment and filter menus for
  consistency, in particular for scoped label rendering.
* Tweak light/dark color text detection slight, to make it white on some
  background colors that I found otherwise hard to read.
* Don't use exactly black/white text colors to look a bit nicer
* Show label in labels editing list the same size as elsewhere, and without
  tag icon. To give a better preview of how it will actually look.
* Increase height of menus to show more labels (and projects, milestones, ..).
  Showing only 3-4 labels as before leads to a lot of scrolling.
* Refactor code so label rendering is done in a single helper function.
  • Loading branch information
brechtvl committed Jan 24, 2023
1 parent dd45362 commit 9172675
Show file tree
Hide file tree
Showing 14 changed files with 64 additions and 77 deletions.
51 changes: 12 additions & 39 deletions models/issues/label.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ package issues
import (
"context"
"fmt"
"html/template"
"math"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -163,49 +161,24 @@ func (label *Label) BelongsToRepo() bool {
return label.RepoID > 0
}

// SrgbToLinear converts a component of an sRGB color to its linear intensity
// See: https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation_(sRGB_to_CIE_XYZ)
func SrgbToLinear(color uint8) float64 {
flt := float64(color) / 255
if flt <= 0.04045 {
return flt / 12.92
}
return math.Pow((flt+0.055)/1.055, 2.4)
}

// Luminance returns the luminance of an sRGB color
func Luminance(color uint32) float64 {
r := SrgbToLinear(uint8(0xFF & (color >> 16)))
g := SrgbToLinear(uint8(0xFF & (color >> 8)))
b := SrgbToLinear(uint8(0xFF & color))

// luminance ratios for sRGB
return 0.2126*r + 0.7152*g + 0.0722*b
}

// LuminanceThreshold is the luminance at which white and black appear to have the same contrast
// i.e. x such that 1.05 / (x + 0.05) = (x + 0.05) / 0.05
// i.e. math.Sqrt(1.05*0.05) - 0.05
const LuminanceThreshold float64 = 0.179

// ForegroundColor calculates the text color for labels based
// on their background color.
func (label *Label) ForegroundColor() template.CSS {
// Determine if label text should be light or dark to be readable on
// background color.
func (label *Label) UseLightTextColor() bool {
if strings.HasPrefix(label.Color, "#") {
if color, err := strconv.ParseUint(label.Color[1:], 16, 64); err == nil {
// NOTE: see web_src/js/components/ContextPopup.vue for similar implementation
luminance := Luminance(uint32(color))
// sRGB color space luminance
r := float64(uint8(0xFF & (uint32(color) >> 16)))
g := float64(uint8(0xFF & (uint32(color) >> 8)))
b := float64(uint8(0xFF & uint32(color)))

// prefer white or black based upon contrast
if luminance < LuminanceThreshold {
return template.CSS("#fff")
}
return template.CSS("#000")
luminance := (0.299*r + 0.587*g + 0.114*b) / 255

// NOTE: see web_src/js/components/ContextPopup.vue for similar implementation
return luminance < 0.5
}
}

// default to black
return template.CSS("#000")
return false
}

// Return scope substring of label name, or empty string if none exists.
Expand Down
7 changes: 3 additions & 4 deletions models/issues/label_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package issues_test

import (
"html/template"
"testing"

"code.gitea.io/gitea/models/db"
Expand All @@ -25,13 +24,13 @@ func TestLabel_CalOpenIssues(t *testing.T) {
assert.EqualValues(t, 2, label.NumOpenIssues)
}

func TestLabel_ForegroundColor(t *testing.T) {
func TestLabel_TextColor(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
assert.Equal(t, template.CSS("#000"), label.ForegroundColor())
assert.False(t, label.UseLightTextColor())

label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2})
assert.Equal(t, template.CSS("#fff"), label.ForegroundColor())
assert.True(t, label.UseLightTextColor())
}

func TestLabel_Scope(t *testing.T) {
Expand Down
31 changes: 29 additions & 2 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,15 +379,18 @@ func NewFuncMap() []template.FuncMap {
// the table is NOT sorted with this header
return ""
},
"RenderLabel": func(label *issues_model.Label) template.HTML {
return template.HTML(RenderLabel(label))
},
"RenderLabels": func(labels []*issues_model.Label, repoLink string) template.HTML {
htmlCode := `<span class="labels-list">`
for _, label := range labels {
// Protect against nil value in labels - shouldn't happen but would cause a panic if so
if label == nil {
continue
}
htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d' class='ui label' style='color: %s !important; background-color: %s !important' title='%s'>%s</a> ",
repoLink, label.ID, label.ForegroundColor(), label.Color, html.EscapeString(label.Description), RenderEmoji(label.Name))
htmlCode += fmt.Sprintf("<a href='%s/issues?labels=%d'>%s</a> ",
repoLink, label.ID, RenderLabel(label))
}
htmlCode += "</span>"
return template.HTML(htmlCode)
Expand Down Expand Up @@ -795,6 +798,30 @@ func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[str
return template.HTML(renderedText)
}

// RenderLabel renders a label
func RenderLabel(label *issues_model.Label) string {
labelScope := label.Scope()

textColor := "#000"
scopeTextColor := "#444"
if label.UseLightTextColor() {
textColor = "#ddd"
scopeTextColor = "#bbb"
}

var text string
if labelScope == "" {
text = string(RenderEmoji(label.Name))
} else {
scopeText := strings.ReplaceAll(labelScope, "::", " \u203A ")
itemText := label.Name[len(labelScope):]
text = fmt.Sprintf("<span style='color: %s !important;'>%s</span> %s", scopeTextColor, RenderEmoji(scopeText), RenderEmoji(itemText))
}

return fmt.Sprintf("<div class='ui label' style='color: %s!important; background-color: %s !important' title='%s'>%s</div>",
textColor, label.Color, emoji.ReplaceAliases(label.Description), text)
}

// RenderEmoji renders html text with emoji post processors
func RenderEmoji(text string) template.HTML {
renderedText, err := markup.RenderEmoji(template.HTMLEscapeString(text))
Expand Down
2 changes: 1 addition & 1 deletion templates/projects/view.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@
{{if or .Labels .Assignees}}
<div class="extra content labels-list p-0 pt-2">
{{range .Labels}}
<a class="ui label" target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}};" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
<a target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{RenderLabel .}}</a>
{{end}}
<div class="right floated">
{{range .Assignees}}
Expand Down
6 changes: 2 additions & 4 deletions templates/repo/issue/labels/label.tmpl
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<a
class="ui label item {{if not .label.IsChecked}}hide{{end}}"
class="item {{if not .label.IsChecked}}hide{{end}}"
id="label_{{.label.ID}}"
href="{{.root.RepoLink}}/{{if or .root.IsPull .root.Issue.IsPull}}pulls{{else}}issues{{end}}?labels={{.label.ID}}"{{/* FIXME: use .root.Issue.Link or create .root.Link */}}
style="color: {{.label.ForegroundColor}}; background-color: {{.label.Color}}"
title="{{.label.Description | RenderEmojiPlain}}"
>
{{.label.Name | RenderEmoji}}
{{RenderLabel .label}}
</a>
4 changes: 2 additions & 2 deletions templates/repo/issue/labels/label_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<li class="item">
<div class="ui grid middle aligned">
<div class="four wide column">
<div class="ui label" style="color: {{.ForegroundColor}}; background-color: {{.Color}}">{{svg "octicon-tag"}} {{.Name | RenderEmoji}}</div>
{{RenderLabel .}}
</div>
<div class="six wide column">
<div class="ui">
Expand Down Expand Up @@ -74,7 +74,7 @@
<li class="item">
<div class="ui grid middle aligned">
<div class="three wide column">
<div class="ui label" style="color: {{.ForegroundColor}}; background-color: {{.Color}}">{{svg "octicon-tag"}} {{.Name | RenderEmoji}}</div>
{{RenderLabel .}}
</div>
<div class="seven wide column">
<div class="ui">
Expand Down
4 changes: 2 additions & 2 deletions templates/repo/issue/list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<div class="ui divider"></div>
{{end}}
{{$previousScope = $scope}}
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}</a>
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel .}}</a>
{{end}}
</div>
</div>
Expand Down Expand Up @@ -192,7 +192,7 @@
{{end}}
{{$previousScope = $scope}}
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
{{if contain $.SelLabelIDs .ID}}{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
{{if contain $.SelLabelIDs .ID}}{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel .}}
</div>
{{end}}
</div>
Expand Down
4 changes: 2 additions & 2 deletions templates/repo/issue/milestone_issues.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<span class="info">{{.locale.Tr "repo.issues.filter_label_exclude" | Safe}}</span>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_no_select"}}</a>
{{range .Labels}}
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}</a>
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}} {{RenderLabel .}}</a>
{{end}}
</div>
</div>
Expand Down Expand Up @@ -161,7 +161,7 @@
<div class="menu">
{{range .Labels}}
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
{{if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
{{if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}} {{RenderLabel .}}
</div>
{{end}}
</div>
Expand Down
4 changes: 2 additions & 2 deletions templates/repo/issue/new_form.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
<div class="ui divider"></div>
{{end}}
{{$previousScope = $scope}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{.Scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{.Scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
{{end}}

Expand All @@ -72,7 +72,7 @@
<div class="ui divider"></div>
{{end}}
{{$previousScope = $scope}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{.Scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{.Scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
{{end}}
{{else}}
Expand Down
4 changes: 2 additions & 2 deletions templates/repo/issue/view_content/sidebar.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
<div class="ui divider"></div>
{{end}}
{{$previousScope = $scope}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
{{end}}
<div class="ui divider"></div>
Expand All @@ -141,7 +141,7 @@
<div class="ui divider"></div>
{{end}}
{{$previousScope = $scope}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
<a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}" data-scope="{{$scope}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{if $scope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}</span>&nbsp;&nbsp;{{RenderLabel .}}
{{if .Description}}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a>
{{end}}
{{else}}
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/projects/view.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@
{{if or .Labels .Assignees}}
<div class="extra content labels-list p-0 pt-2">
{{range .Labels}}
<a class="ui label" target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}};" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
<a target="_blank" href="{{$.RepoLink}}/issues?labels={{.ID}}">{{RenderLabel .}}</a>
{{end}}
<div class="right floated">
{{range .Assignees}}
Expand Down
2 changes: 1 addition & 1 deletion templates/shared/issuelist.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
{{end}}
<span class="labels-list ml-2">
{{range .Labels}}
<a class="ui label" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
<a href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{RenderLabel .}}</a>
{{end}}
</span>
</div>
Expand Down
6 changes: 1 addition & 5 deletions web_src/less/_base.less
Original file line number Diff line number Diff line change
Expand Up @@ -2529,7 +2529,7 @@ table th[data-sortt-desc] {
border-top: none;

a {
font-size: 15px;
font-size: 12px;
padding-top: 5px;
padding-right: 10px;
color: var(--color-text-light);
Expand All @@ -2542,10 +2542,6 @@ table th[data-sortt-desc] {
margin-right: 30px;
}
}

.ui.label {
font-size: 1em;
}
}

.item:last-child {
Expand Down
14 changes: 4 additions & 10 deletions web_src/less/_repository.less
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
.metas {
.menu {
overflow-x: auto;
max-height: 300px;
max-height: 500px;
}

.ui.list {
Expand Down Expand Up @@ -155,12 +155,6 @@
}

.filter.menu {
.label.color {
border-radius: 3px;
margin-left: 15px;
padding: 0 8px;
}

&.labels {
.label-filter .menu .info {
display: inline-block;
Expand All @@ -181,7 +175,7 @@
}

.menu {
max-height: 300px;
max-height: 500px;
overflow-x: auto;
right: 0 !important;
left: auto !important;
Expand All @@ -190,7 +184,7 @@

.select-label {
.desc {
padding-left: 16px;
padding-left: 23px;
}
}

Expand Down Expand Up @@ -607,7 +601,7 @@
min-width: 220px;

.filter.menu {
max-height: 300px;
max-height: 500px;
overflow-x: auto;
}
}
Expand Down

0 comments on commit 9172675

Please sign in to comment.