From 3085ab25ca087e9f56d0704ea855ff376c6a2366 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 17 Aug 2018 21:52:49 -0400 Subject: 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. --- webui/src/Label.js | 69 +++++++++++++++++++++++++++++++------ webui/src/bug/Bug.js | 71 +++++++++++++++++++++----------------- webui/src/list/BugRow.js | 90 +++++++++++++++++++++++++----------------------- 3 files changed, 144 insertions(+), 86 deletions(-) (limited to 'webui') 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}) => {label} +const Label = ({ label, classes }) => ( + + {label} + +); -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 }) => (
{bug.title} {bug.humanId} - - + + opened this bug @@ -56,19 +61,21 @@ const Bug = ({bug, classes}) => (
- +
- Labels - {bug.labels.map(l => ( - - {l} - - ))} + Labels +
    + {bug.labels.map(l => ( +
  • +
  • + ))} +
-) +); 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}) => - - +const Open = ({ className }) => ( + + + +); -const Closed = ({className}) => - - +const Closed = ({ className }) => ( + + + +); -const Status = ({status, className}) => { +const Status = ({ status, className }) => { switch (status) { - case 'OPEN': - return - case 'CLOSED': - return + case "OPEN": + return ; + case "CLOSED": + return ; 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 }) => ( - +
- +
- + {bug.title} - { bug.labels.length > 0 && ( + {bug.labels.length > 0 && ( {bug.labels.map(l => ( - )}
- + {bug.humanId} opened by {bug.author.name} @@ -80,7 +82,7 @@ const BugRow = ({bug, classes}) => (
-) +); 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); -- cgit