mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-15 02:01:43 +03:00
Merge pull request #113 from ludovicm67/patch-colors
bug: add label color directly in the core
This commit is contained in:
commit
6e20bf0e73
45
bug/label.go
45
bug/label.go
@ -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
36
bug/label_test.go
Normal 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)
|
||||
}
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
24
graphql/resolvers/color.go
Normal file
24
graphql/resolvers/color.go
Normal 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
|
||||
}
|
22
graphql/resolvers/label.go
Normal file
22
graphql/resolvers/label.go
Normal 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
|
||||
}
|
@ -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{}
|
||||
}
|
||||
|
@ -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
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -49,10 +49,15 @@ LabelChange.fragment = gql`
|
||||
email
|
||||
displayName
|
||||
}
|
||||
added
|
||||
removed
|
||||
added {
|
||||
...Label
|
||||
}
|
||||
removed {
|
||||
...Label
|
||||
}
|
||||
}
|
||||
}
|
||||
${Label.fragment}
|
||||
`;
|
||||
|
||||
export default LabelChange;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user