Merge pull request #113 from ludovicm67/patch-colors

bug: add label color directly in the core
This commit is contained in:
Michael Muré 2019-05-22 20:56:37 +02:00 committed by GitHub
commit 6e20bf0e73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 628 additions and 117 deletions

View File

@ -1,8 +1,9 @@
package bug
import (
"crypto/sha1"
"fmt"
"io"
"image/color"
"strings"
"github.com/MichaelMure/git-bug/util/text"
@ -14,21 +15,39 @@ func (l Label) String() string {
return string(l)
}
// UnmarshalGQL implements the graphql.Unmarshaler interface
func (l *Label) UnmarshalGQL(v interface{}) error {
_, ok := v.(string)
if !ok {
return fmt.Errorf("labels must be strings")
// RGBA from a Label computed in a deterministic way
func (l Label) RGBA() color.RGBA {
id := 0
hash := sha1.Sum([]byte(l))
// colors from: https://material-ui.com/style/color/
colors := []color.RGBA{
color.RGBA{R: 244, G: 67, B: 54, A: 255}, // red
color.RGBA{R: 233, G: 30, B: 99, A: 255}, // pink
color.RGBA{R: 156, G: 39, B: 176, A: 255}, // purple
color.RGBA{R: 103, G: 58, B: 183, A: 255}, // deepPurple
color.RGBA{R: 63, G: 81, B: 181, A: 255}, // indigo
color.RGBA{R: 33, G: 150, B: 243, A: 255}, // blue
color.RGBA{R: 3, G: 169, B: 244, A: 255}, // lightBlue
color.RGBA{R: 0, G: 188, B: 212, A: 255}, // cyan
color.RGBA{R: 0, G: 150, B: 136, A: 255}, // teal
color.RGBA{R: 76, G: 175, B: 80, A: 255}, // green
color.RGBA{R: 139, G: 195, B: 74, A: 255}, // lightGreen
color.RGBA{R: 205, G: 220, B: 57, A: 255}, // lime
color.RGBA{R: 255, G: 235, B: 59, A: 255}, // yellow
color.RGBA{R: 255, G: 193, B: 7, A: 255}, // amber
color.RGBA{R: 255, G: 152, B: 0, A: 255}, // orange
color.RGBA{R: 255, G: 87, B: 34, A: 255}, // deepOrange
color.RGBA{R: 121, G: 85, B: 72, A: 255}, // brown
color.RGBA{R: 158, G: 158, B: 158, A: 255}, // grey
color.RGBA{R: 96, G: 125, B: 139, A: 255}, // blueGrey
}
*l = v.(Label)
for _, char := range hash {
id = (id + int(char)) % len(colors)
}
return nil
}
// MarshalGQL implements the graphql.Marshaler interface
func (l Label) MarshalGQL(w io.Writer) {
_, _ = w.Write([]byte(`"` + l.String() + `"`))
return colors[id]
}
func (l Label) Validate() error {

36
bug/label_test.go Normal file
View File

@ -0,0 +1,36 @@
package bug
import (
"image/color"
"testing"
"github.com/stretchr/testify/require"
)
func TestLabelRGBA(t *testing.T) {
rgba := Label("test").RGBA()
expected := color.RGBA{R: 255, G: 87, B: 34, A: 255}
require.Equal(t, expected, rgba)
}
func TestLabelRGBASimilar(t *testing.T) {
rgba := Label("test1").RGBA()
expected := color.RGBA{R: 0, G: 188, B: 212, A: 255}
require.Equal(t, expected, rgba)
}
func TestLabelRGBAReverse(t *testing.T) {
rgba := Label("tset").RGBA()
expected := color.RGBA{R: 233, G: 30, B: 99, A: 255}
require.Equal(t, expected, rgba)
}
func TestLabelRGBAEqual(t *testing.T) {
color1 := Label("test").RGBA()
color2 := Label("test").RGBA()
require.Equal(t, color1, color2)
}

View File

@ -11,6 +11,8 @@ models:
model: github.com/MichaelMure/git-bug/graphql/models.RepositoryMutation
Bug:
model: github.com/MichaelMure/git-bug/bug.Snapshot
Color:
model: image/color.RGBA
Comment:
model: github.com/MichaelMure/git-bug/bug.Comment
Identity:

View File

@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"image/color"
"strconv"
"sync"
"sync/atomic"
@ -43,11 +44,13 @@ type ResolverRoot interface {
AddCommentOperation() AddCommentOperationResolver
AddCommentTimelineItem() AddCommentTimelineItemResolver
Bug() BugResolver
Color() ColorResolver
CommentHistoryStep() CommentHistoryStepResolver
CreateOperation() CreateOperationResolver
CreateTimelineItem() CreateTimelineItemResolver
EditCommentOperation() EditCommentOperationResolver
Identity() IdentityResolver
Label() LabelResolver
LabelChangeOperation() LabelChangeOperationResolver
LabelChangeTimelineItem() LabelChangeTimelineItemResolver
Mutation() MutationResolver
@ -111,6 +114,12 @@ type ComplexityRoot struct {
Node func(childComplexity int) int
}
Color struct {
B func(childComplexity int) int
G func(childComplexity int) int
R func(childComplexity int) int
}
Comment struct {
Author func(childComplexity int) int
Files func(childComplexity int) int
@ -187,6 +196,11 @@ type ComplexityRoot struct {
Node func(childComplexity int) int
}
Label struct {
Color func(childComplexity int) int
Name func(childComplexity int) int
}
LabelChangeOperation struct {
Added func(childComplexity int) int
Author func(childComplexity int) int
@ -306,6 +320,11 @@ type BugResolver interface {
Timeline(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.TimelineItemConnection, error)
Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (*models.OperationConnection, error)
}
type ColorResolver interface {
R(ctx context.Context, obj *color.RGBA) (int, error)
G(ctx context.Context, obj *color.RGBA) (int, error)
B(ctx context.Context, obj *color.RGBA) (int, error)
}
type CommentHistoryStepResolver interface {
Date(ctx context.Context, obj *bug.CommentHistoryStep) (*time.Time, error)
}
@ -329,6 +348,10 @@ type IdentityResolver interface {
AvatarURL(ctx context.Context, obj *identity.Interface) (*string, error)
IsProtected(ctx context.Context, obj *identity.Interface) (bool, error)
}
type LabelResolver interface {
Name(ctx context.Context, obj *bug.Label) (string, error)
Color(ctx context.Context, obj *bug.Label) (*color.RGBA, error)
}
type LabelChangeOperationResolver interface {
Date(ctx context.Context, obj *bug.LabelChangeOperation) (*time.Time, error)
}
@ -642,6 +665,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.BugEdge.Node(childComplexity), true
case "Color.B":
if e.complexity.Color.B == nil {
break
}
return e.complexity.Color.B(childComplexity), true
case "Color.G":
if e.complexity.Color.G == nil {
break
}
return e.complexity.Color.G(childComplexity), true
case "Color.R":
if e.complexity.Color.R == nil {
break
}
return e.complexity.Color.R(childComplexity), true
case "Comment.author":
if e.complexity.Comment.Author == nil {
break
@ -964,6 +1008,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.IdentityEdge.Node(childComplexity), true
case "Label.color":
if e.complexity.Label.Color == nil {
break
}
return e.complexity.Label.Color(childComplexity), true
case "Label.name":
if e.complexity.Label.Name == nil {
break
}
return e.complexity.Label.Name(childComplexity), true
case "LabelChangeOperation.added":
if e.complexity.LabelChangeOperation.Added == nil {
break
@ -1910,9 +1968,26 @@ type SetTitleTimelineItem implements TimelineItem {
}
`},
&ast.Source{Name: "schema/types.graphql", Input: `scalar Time
scalar Label
scalar Hash
"""Defines a color by red, green and blue components."""
type Color {
"""Red component of the color."""
R: Int!
"""Green component of the color."""
G: Int!
"""Blue component of the color."""
B: Int!
}
"""Label for a bug."""
type Label {
"""The name of the label."""
name: String!
"""Color of the label."""
color: Color!
}
"""Information about pagination in a connection."""
type PageInfo {
"""When paginating forwards, are there more items?"""
@ -3434,6 +3509,87 @@ func (ec *executionContext) _BugEdge_node(ctx context.Context, field graphql.Col
return ec.marshalNBug2ᚖgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐSnapshot(ctx, field.Selections, res)
}
func (ec *executionContext) _Color_R(ctx context.Context, field graphql.CollectedField, obj *color.RGBA) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
rctx := &graphql.ResolverContext{
Object: "Color",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithResolverContext(ctx, rctx)
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Color().R(rctx, obj)
})
if resTmp == nil {
if !ec.HasError(rctx) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(int)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _Color_G(ctx context.Context, field graphql.CollectedField, obj *color.RGBA) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
rctx := &graphql.ResolverContext{
Object: "Color",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithResolverContext(ctx, rctx)
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Color().G(rctx, obj)
})
if resTmp == nil {
if !ec.HasError(rctx) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(int)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _Color_B(ctx context.Context, field graphql.CollectedField, obj *color.RGBA) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
rctx := &graphql.ResolverContext{
Object: "Color",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithResolverContext(ctx, rctx)
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Color().B(rctx, obj)
})
if resTmp == nil {
if !ec.HasError(rctx) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(int)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalNInt2int(ctx, field.Selections, res)
}
func (ec *executionContext) _Comment_author(ctx context.Context, field graphql.CollectedField, obj *bug.Comment) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
@ -4664,6 +4820,60 @@ func (ec *executionContext) _IdentityEdge_node(ctx context.Context, field graphq
return ec.marshalNIdentity2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋidentityᚐInterface(ctx, field.Selections, res)
}
func (ec *executionContext) _Label_name(ctx context.Context, field graphql.CollectedField, obj *bug.Label) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
rctx := &graphql.ResolverContext{
Object: "Label",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithResolverContext(ctx, rctx)
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Label().Name(rctx, obj)
})
if resTmp == nil {
if !ec.HasError(rctx) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Label_color(ctx context.Context, field graphql.CollectedField, obj *bug.Label) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
rctx := &graphql.ResolverContext{
Object: "Label",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithResolverContext(ctx, rctx)
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Label().Color(rctx, obj)
})
if resTmp == nil {
if !ec.HasError(rctx) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*color.RGBA)
rctx.Result = res
ctx = ec.Tracer.StartFieldChildExecution(ctx)
return ec.marshalNColor2ᚖimageᚋcolorᚐRGBA(ctx, field.Selections, res)
}
func (ec *executionContext) _LabelChangeOperation_hash(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) graphql.Marshaler {
ctx = ec.Tracer.StartFieldExecution(ctx, field)
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
@ -7656,6 +7866,70 @@ func (ec *executionContext) _BugEdge(ctx context.Context, sel ast.SelectionSet,
return out
}
var colorImplementors = []string{"Color"}
func (ec *executionContext) _Color(ctx context.Context, sel ast.SelectionSet, obj *color.RGBA) graphql.Marshaler {
fields := graphql.CollectFields(ec.RequestContext, sel, colorImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("Color")
case "R":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Color_R(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "G":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Color_G(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "B":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Color_B(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var commentImplementors = []string{"Comment", "Authored"}
func (ec *executionContext) _Comment(ctx context.Context, sel ast.SelectionSet, obj *bug.Comment) graphql.Marshaler {
@ -8211,6 +8485,56 @@ func (ec *executionContext) _IdentityEdge(ctx context.Context, sel ast.Selection
return out
}
var labelImplementors = []string{"Label"}
func (ec *executionContext) _Label(ctx context.Context, sel ast.SelectionSet, obj *bug.Label) graphql.Marshaler {
fields := graphql.CollectFields(ec.RequestContext, sel, labelImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("Label")
case "name":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Label_name(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "color":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Label_color(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var labelChangeOperationImplementors = []string{"LabelChangeOperation", "Operation", "Authored"}
func (ec *executionContext) _LabelChangeOperation(ctx context.Context, sel ast.SelectionSet, obj *bug.LabelChangeOperation) graphql.Marshaler {
@ -9330,6 +9654,20 @@ func (ec *executionContext) marshalNBugEdge2ᚖgithubᚗcomᚋMichaelMureᚋgit
return ec._BugEdge(ctx, sel, v)
}
func (ec *executionContext) marshalNColor2imageᚋcolorᚐRGBA(ctx context.Context, sel ast.SelectionSet, v color.RGBA) graphql.Marshaler {
return ec._Color(ctx, sel, &v)
}
func (ec *executionContext) marshalNColor2ᚖimageᚋcolorᚐRGBA(ctx context.Context, sel ast.SelectionSet, v *color.RGBA) graphql.Marshaler {
if v == nil {
if !ec.HasError(graphql.GetResolverContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._Color(ctx, sel, v)
}
func (ec *executionContext) marshalNComment2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐComment(ctx context.Context, sel ast.SelectionSet, v bug.Comment) graphql.Marshaler {
return ec._Comment(ctx, sel, &v)
}
@ -9645,41 +9983,44 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti
return res
}
func (ec *executionContext) unmarshalNLabel2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐLabel(ctx context.Context, v interface{}) (bug.Label, error) {
var res bug.Label
return res, res.UnmarshalGQL(v)
}
func (ec *executionContext) marshalNLabel2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐLabel(ctx context.Context, sel ast.SelectionSet, v bug.Label) graphql.Marshaler {
return v
}
func (ec *executionContext) unmarshalNLabel2ᚕgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐLabel(ctx context.Context, v interface{}) ([]bug.Label, error) {
var vSlice []interface{}
if v != nil {
if tmp1, ok := v.([]interface{}); ok {
vSlice = tmp1
} else {
vSlice = []interface{}{v}
}
}
var err error
res := make([]bug.Label, len(vSlice))
for i := range vSlice {
res[i], err = ec.unmarshalNLabel2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐLabel(ctx, vSlice[i])
if err != nil {
return nil, err
}
}
return res, nil
return ec._Label(ctx, sel, &v)
}
func (ec *executionContext) marshalNLabel2ᚕgithubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐLabel(ctx context.Context, sel ast.SelectionSet, v []bug.Label) graphql.Marshaler {
ret := make(graphql.Array, len(v))
for i := range v {
ret[i] = ec.marshalNLabel2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐLabel(ctx, sel, v[i])
var wg sync.WaitGroup
isLen1 := len(v) == 1
if !isLen1 {
wg.Add(len(v))
}
for i := range v {
i := i
rctx := &graphql.ResolverContext{
Index: &i,
Result: &v[i],
}
ctx := graphql.WithResolverContext(ctx, rctx)
f := func(i int) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = nil
}
}()
if !isLen1 {
defer wg.Done()
}
ret[i] = ec.marshalNLabel2githubᚗcomᚋMichaelMureᚋgitᚑbugᚋbugᚐLabel(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
return ret
}

View File

@ -133,8 +133,22 @@ func TestQueries(t *testing.T) {
status
}
... on LabelChangeOperation {
added
removed
added {
name
color {
R
G
B
}
}
removed {
name
color {
R
G
B
}
}
}
}
}
@ -152,6 +166,13 @@ func TestQueries(t *testing.T) {
DisplayName string `json:"displayName"`
}
type Label struct {
Name string
Color struct {
R, G, B int
}
}
var resp struct {
DefaultRepository struct {
AllBugs struct {
@ -193,8 +214,8 @@ func TestQueries(t *testing.T) {
Message string
Was string
Status string
Added []string
Removed []string
Added []Label
Removed []Label
}
}
}

View File

@ -0,0 +1,24 @@
package resolvers
import (
"context"
"image/color"
"github.com/MichaelMure/git-bug/graphql/graph"
)
var _ graph.ColorResolver = &colorResolver{}
type colorResolver struct{}
func (colorResolver) R(ctx context.Context, obj *color.RGBA) (int, error) {
return int(obj.R), nil
}
func (colorResolver) G(ctx context.Context, obj *color.RGBA) (int, error) {
return int(obj.G), nil
}
func (colorResolver) B(ctx context.Context, obj *color.RGBA) (int, error) {
return int(obj.B), nil
}

View File

@ -0,0 +1,22 @@
package resolvers
import (
"context"
"image/color"
"github.com/MichaelMure/git-bug/bug"
"github.com/MichaelMure/git-bug/graphql/graph"
)
var _ graph.LabelResolver = &labelResolver{}
type labelResolver struct{}
func (labelResolver) Name(ctx context.Context, obj *bug.Label) (string, error) {
return obj.String(), nil
}
func (labelResolver) Color(ctx context.Context, obj *bug.Label) (*color.RGBA, error) {
rgba := obj.RGBA()
return &rgba, nil
}

View File

@ -34,6 +34,14 @@ func (RootResolver) Bug() graph.BugResolver {
return &bugResolver{}
}
func (RootResolver) Color() graph.ColorResolver {
return &colorResolver{}
}
func (RootResolver) Label() graph.LabelResolver {
return &labelResolver{}
}
func (r RootResolver) Identity() graph.IdentityResolver {
return &identityResolver{}
}

View File

@ -1,7 +1,24 @@
scalar Time
scalar Label
scalar Hash
"""Defines a color by red, green and blue components."""
type Color {
"""Red component of the color."""
R: Int!
"""Green component of the color."""
G: Int!
"""Blue component of the color."""
B: Int!
}
"""Label for a bug."""
type Label {
"""The name of the label."""
name: String!
"""Color of the label."""
color: Color!
}
"""Information about pagination in a connection."""
type PageInfo {
"""When paginating forwards, are there more items?"""

File diff suppressed because one or more lines are too long

View File

@ -1,46 +1,29 @@
import React from 'react';
import gql from 'graphql-tag';
import { makeStyles } from '@material-ui/styles';
import {
getContrastRatio,
darken,
} from '@material-ui/core/styles/colorManipulator';
import * as allColors from '@material-ui/core/colors';
import { common } from '@material-ui/core/colors';
// JS's modulo returns negative numbers sometimes.
// This ensures the result is positive.
const mod = (n, m) => ((n % m) + m) % m;
// Minimum contrast between the background and the text color
const contrastThreshold = 2.5;
// Filter out the "common" color
const labelColors = Object.entries(allColors)
.filter(([key, value]) => value !== common)
.map(([key, value]) => value);
// Generate a hash (number) from a string
const hash = string =>
string.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0);
// Get the background color from the label
const getColor = label =>
labelColors[mod(hash(label), labelColors.length)][500];
// Guess the text color based on the background color
const getTextColor = background =>
getContrastRatio(background, common.white) >= contrastThreshold
? common.white // White on dark backgrounds
: common.black; // And black on light ones
const _genStyle = background => ({
backgroundColor: background,
color: getTextColor(background),
borderBottomColor: darken(background, 0.2),
});
const _rgb = color => 'rgb(' + color.R + ',' + color.G + ',' + color.B + ')';
// Generate a style object (text, background and border colors) from the label
const genStyle = label => _genStyle(getColor(label));
// Create a style object from the label RGB colors
const createStyle = color => ({
backgroundColor: _rgb(color),
color: getTextColor(_rgb(color)),
borderBottomColor: darken(_rgb(color), 0.2),
});
const useStyles = makeStyles(theme => ({
label: {
@ -58,10 +41,21 @@ const useStyles = makeStyles(theme => ({
function Label({ label }) {
const classes = useStyles();
return (
<span className={classes.label} style={genStyle(label)}>
{label}
<span className={classes.label} style={createStyle(label.color)}>
{label.name}
</span>
);
}
Label.fragment = gql`
fragment Label on Label {
name
color {
R
G
B
}
}
`;
export default Label;

View File

@ -74,7 +74,7 @@ function Bug({ bug }) {
<ul className={classes.labelList}>
{bug.labels.map(l => (
<li className={classes.label}>
<Label label={l} key={l} />
<Label label={l} key={l.name} />
</li>
))}
</ul>
@ -90,7 +90,9 @@ Bug.fragment = gql`
humanId
status
title
labels
labels {
...Label
}
createdAt
author {
email
@ -98,6 +100,7 @@ Bug.fragment = gql`
displayName
}
}
${Label.fragment}
`;
export default Bug;

View File

@ -49,10 +49,15 @@ LabelChange.fragment = gql`
email
displayName
}
added
removed
added {
...Label
}
removed {
...Label
}
}
}
${Label.fragment}
`;
export default LabelChange;

View File

@ -70,7 +70,7 @@ function BugRow({ bug }) {
{bug.labels.length > 0 && (
<span className={classes.labels}>
{bug.labels.map(l => (
<Label key={l} label={l} />
<Label key={l.name} label={l} />
))}
</span>
)}
@ -94,12 +94,15 @@ BugRow.fragment = gql`
title
status
createdAt
labels
labels {
...Label
}
author {
name
displayName
}
}
${Label.fragment}
`;
export default BugRow;