aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorQuentin Gliech <quentingliech@gmail.com>2018-08-17 21:52:49 -0400
committerQuentin Gliech <quentingliech@gmail.com>2018-08-17 21:54:14 -0400
commit3085ab25ca087e9f56d0704ea855ff376c6a2366 (patch)
treeb1d9810e516e23b46ec1aa3c0c17976def847571
parentf8f48b975adba688ff0bdee5725468e69f5fc999 (diff)
downloadgit-bug-3085ab25ca087e9f56d0704ea855ff376c6a2366.tar.gz
Different colors for labels based on their hash
webui: A hash is computed on each label, which is then used to determine the label's color.
-rw-r--r--webui/src/Label.js69
-rw-r--r--webui/src/bug/Bug.js71
-rw-r--r--webui/src/list/BugRow.js90
3 files changed, 144 insertions, 86 deletions
diff --git a/webui/src/Label.js b/webui/src/Label.js
index 93a9a358..8981142d 100644
--- a/webui/src/Label.js
+++ b/webui/src/Label.js
@@ -1,15 +1,64 @@
-import React from 'react'
-import { withStyles } from '@material-ui/core/styles'
+import React from "react";
+import { withStyles } from "@material-ui/core/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)
+});
+
+// Generate a style object (text, background and border colors) from the label
+const genStyle = label => _genStyle(getColor(label));
const styles = theme => ({
label: {
- padding: '0 4px',
- margin: '0 1px',
- backgroundColor: '#da9898',
- borderRadius: '3px'
- },
-})
+ ...theme.typography.body2,
+ padding: "0 6px",
+ fontSize: "0.9em",
+ margin: "0 1px",
+ borderRadius: "3px",
+ display: "inline-block",
+ borderBottom: "solid 1.5px",
+ verticalAlign: "bottom"
+ }
+});
-const Label = ({label, classes}) => <span className={classes.label}>{label}</span>
+const Label = ({ label, classes }) => (
+ <span className={classes.label} style={genStyle(label)}>
+ {label}
+ </span>
+);
-export default withStyles(styles)(Label)
+export default withStyles(styles)(Label);
diff --git a/webui/src/bug/Bug.js b/webui/src/bug/Bug.js
index 34fc3ad4..c6edda35 100644
--- a/webui/src/bug/Bug.js
+++ b/webui/src/bug/Bug.js
@@ -1,15 +1,16 @@
-import { withStyles } from '@material-ui/core/styles'
-import Typography from '@material-ui/core/Typography/Typography'
-import gql from 'graphql-tag'
-import React from 'react'
-import Author from '../Author'
-import Date from '../Date'
-import TimelineQuery from './TimelineQuery'
+import { withStyles } from "@material-ui/core/styles";
+import Typography from "@material-ui/core/Typography/Typography";
+import gql from "graphql-tag";
+import React from "react";
+import Author from "../Author";
+import Date from "../Date";
+import TimelineQuery from "./TimelineQuery";
+import Label from "../Label";
const styles = theme => ({
main: {
maxWidth: 600,
- margin: 'auto',
+ margin: "auto",
marginTop: theme.spacing.unit * 4
},
header: {},
@@ -18,37 +19,41 @@ const styles = theme => ({
},
id: {
...theme.typography.subheading,
- marginLeft: 15,
+ marginLeft: 15
},
container: {
- display: 'flex',
+ display: "flex",
marginBottom: 30
},
timeline: {
- width: '70%',
+ width: "70%",
marginTop: 20,
- marginRight: 20,
+ marginRight: 20
},
sidebar: {
- width: '30%'
+ width: "30%"
+ },
+ labelList: {
+ listStyle: "none",
+ padding: 0,
+ margin: 0
},
label: {
- backgroundColor: '#da9898',
- borderRadius: '3px',
- paddingLeft: '10px',
- margin: '2px 20px auto 2px',
- fontWeight: 'bold',
+ margin: "4px 0",
+ "& > *": {
+ display: "block"
+ }
}
-})
+});
-const Bug = ({bug, classes}) => (
+const Bug = ({ bug, classes }) => (
<main className={classes.main}>
<div className={classes.header}>
<span className={classes.title}>{bug.title}</span>
<span className={classes.id}>{bug.humanId}</span>
- <Typography color={'textSecondary'}>
- <Author author={bug.author}/>
+ <Typography color={"textSecondary"}>
+ <Author author={bug.author} />
<span> opened this bug </span>
<Date date={bug.createdAt} />
</Typography>
@@ -56,19 +61,21 @@ const Bug = ({bug, classes}) => (
<div className={classes.container}>
<div className={classes.timeline}>
- <TimelineQuery id={bug.id}/>
+ <TimelineQuery id={bug.id} />
</div>
<div className={classes.sidebar}>
- <Typography variant={'subheading'}>Labels</Typography>
- {bug.labels.map(l => (
- <Typography key={l} className={classes.label}>
- {l}
- </Typography>
- ))}
+ <Typography variant={"subheading"}>Labels</Typography>
+ <ul className={classes.labelList}>
+ {bug.labels.map(l => (
+ <li className={classes.label}>
+ <Label label={l} key={l} />
+ </li>
+ ))}
+ </ul>
</div>
</div>
</main>
-)
+);
Bug.fragment = gql`
fragment Bug on Bug {
@@ -83,6 +90,6 @@ Bug.fragment = gql`
name
}
}
-`
+`;
-export default withStyles(styles)(Bug)
+export default withStyles(styles)(Bug);
diff --git a/webui/src/list/BugRow.js b/webui/src/list/BugRow.js
index 9f55809d..05eafe92 100644
--- a/webui/src/list/BugRow.js
+++ b/webui/src/list/BugRow.js
@@ -1,78 +1,80 @@
-import { withStyles } from '@material-ui/core/styles'
-import TableCell from '@material-ui/core/TableCell/TableCell'
-import TableRow from '@material-ui/core/TableRow/TableRow'
-import Tooltip from '@material-ui/core/Tooltip/Tooltip'
-import Typography from '@material-ui/core/Typography'
-import ErrorOutline from '@material-ui/icons/ErrorOutline'
-import gql from 'graphql-tag'
-import React from 'react'
-import { Link } from 'react-router-dom'
-import Date from '../Date'
-import Label from '../Label'
+import { withStyles } from "@material-ui/core/styles";
+import TableCell from "@material-ui/core/TableCell/TableCell";
+import TableRow from "@material-ui/core/TableRow/TableRow";
+import Tooltip from "@material-ui/core/Tooltip/Tooltip";
+import Typography from "@material-ui/core/Typography";
+import ErrorOutline from "@material-ui/icons/ErrorOutline";
+import gql from "graphql-tag";
+import React from "react";
+import { Link } from "react-router-dom";
+import Date from "../Date";
+import Label from "../Label";
-const Open = ({className}) => <Tooltip title="Open">
- <ErrorOutline nativeColor='#28a745' className={className}/>
-</Tooltip>
+const Open = ({ className }) => (
+ <Tooltip title="Open">
+ <ErrorOutline nativeColor="#28a745" className={className} />
+ </Tooltip>
+);
-const Closed = ({className}) => <Tooltip title="Closed">
- <ErrorOutline nativeColor='#cb2431' className={className}/>
-</Tooltip>
+const Closed = ({ className }) => (
+ <Tooltip title="Closed">
+ <ErrorOutline nativeColor="#cb2431" className={className} />
+ </Tooltip>
+);
-const Status = ({status, className}) => {
+const Status = ({ status, className }) => {
switch (status) {
- case 'OPEN':
- return <Open className={className}/>
- case 'CLOSED':
- return <Closed className={className}/>
+ case "OPEN":
+ return <Open className={className} />;
+ case "CLOSED":
+ return <Closed className={className} />;
default:
- return 'unknown status ' + status
+ return "unknown status " + status;
}
-}
+};
const styles = theme => ({
cell: {
- display: 'flex',
- alignItems: 'center',
- '& a': {
- textDecoration: 'none'
+ display: "flex",
+ alignItems: "center",
+ "& a": {
+ textDecoration: "none"
}
},
status: {
margin: 10
},
expand: {
- width: '100%'
+ width: "100%"
},
title: {
- display: 'inline',
+ display: "inline"
},
labels: {
- ...theme.typography.body2,
- display: 'inline',
- paddingLeft: theme.spacing.unit,
+ paddingLeft: theme.spacing.unit
}
-})
+});
-const BugRow = ({bug, classes}) => (
+const BugRow = ({ bug, classes }) => (
<TableRow hover>
<TableCell className={classes.cell}>
- <Status status={bug.status} className={classes.status}/>
+ <Status status={bug.status} className={classes.status} />
<div className={classes.expand}>
- <Link to={'bug/' + bug.humanId}>
+ <Link to={"bug/" + bug.humanId}>
<div className={classes.expand}>
- <Typography variant={'title'} className={classes.title}>
+ <Typography variant={"title"} className={classes.title}>
{bug.title}
</Typography>
- { bug.labels.length > 0 && (
+ {bug.labels.length > 0 && (
<span className={classes.labels}>
{bug.labels.map(l => (
- <Label key={l} label={l}/>
+ <Label key={l} label={l} />
))}
</span>
)}
</div>
</Link>
- <Typography color={'textSecondary'}>
+ <Typography color={"textSecondary"}>
{bug.humanId} opened
<Date date={bug.createdAt} />
by {bug.author.name}
@@ -80,7 +82,7 @@ const BugRow = ({bug, classes}) => (
</div>
</TableCell>
</TableRow>
-)
+);
BugRow.fragment = gql`
fragment BugRow on Bug {
@@ -94,6 +96,6 @@ BugRow.fragment = gql`
name
}
}
-`
+`;
-export default withStyles(styles)(BugRow)
+export default withStyles(styles)(BugRow);