Skip to content

Instantly share code, notes, and snippets.

@atomize
Last active August 19, 2019 04:02
Show Gist options
  • Save atomize/9242a96b10903f2c5866eb0cb48be6b8 to your computer and use it in GitHub Desktop.
Save atomize/9242a96b10903f2c5866eb0cb48be6b8 to your computer and use it in GitHub Desktop.
Github Issues in React browser only
const invertColor = (hex, bw) => {
if (hex.indexOf('#') === 0) { hex = hex.slice(1) }
if (hex.length === 3) { hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] }
if (hex.length !== 6) { throw new Error('Invalid HEX color.') }
var r = parseInt(hex.slice(0, 2), 16),
g = parseInt(hex.slice(2, 4), 16),
b = parseInt(hex.slice(4, 6), 16);
if (bw) {
return (r * 0.299 + g * 0.587 + b * 0.114) > 186 ? '#000000' : '#FFFFFF'
}
r = (255 - r).toString(16); g = (255 - g).toString(16); b = (255 - b).toString(16);
return "#" + padZero(r) + padZero(g) + padZero(b)
}
const getTimeAgoString = (timestamp) => {
const SECOND = 1000,
MINUTE = SECOND * 60,
HOUR = MINUTE * 60,
DAY = HOUR * 24,
MONTH = DAY * 30,
YEAR = DAY * 365;
const elapsed = Date.now() - timestamp,
getElapsedString = (value, unit) => {
const round = Math.round(elapsed / value);
return `${round} ${unit}${round > 1
? 's'
: ''} ago`;
};
if (elapsed < MINUTE) {
return getElapsedString(SECOND, 'second');
}
if (elapsed < HOUR) {
return getElapsedString(MINUTE, 'minute');
}
if (elapsed < DAY) {
return getElapsedString(HOUR, 'hour');
}
if (elapsed < MONTH) {
return getElapsedString(DAY, 'day');
}
if (elapsed < YEAR) {
return getElapsedString(MONTH, 'month');
}
return getElapsedString(YEAR, 'year');
}
const encodeQueryString = (params) => {
const keys = Object.keys(params)
return keys.length
? "?" + keys
.map(key => encodeURIComponent(key)
+ "=" + encodeURIComponent(params[key]))
.join("&")
: ""
}
const converter = new showdown.Converter({
tables: true,
strikethrough: true,
ghCompatibleHeaderId: true,
literalMidWordUnderscores: true,
ghCodeBlocks: true,
tasklists: true,
ghMentions: true,
ghMentionsLink: 'https://github.com/{u}'
})
'use strict';
const Comments = ({ data }) => {
let jsx = <span class={data.comments < 1 ? "is-hidden" : "comments"}>
<i class="far fa-comment-alt"></i> {data.comments < 0 ? "" : data.comments}
</span>
return (jsx);
}
const Labels = ({ labels }) => {
let labelMap = labels.map((label, index) => {
return <span key={index} style={{ backgroundColor: "#" + label.color, color: invertColor("#" + label.color, true) }} class={"tag " + (labels.length === 0 ? "is-hidden" : "")}>{label.name}</span>
})
return (
<React.Fragment>
{labelMap}
</React.Fragment>
)
}
const Milestones = ({ milestone, _addclass }) => {
let jsx = <span class={!milestone ? "is-hidden" : "milestone" + " " + (_addclass ? [..._addclass] : "")}>
<i class="fas fa-map-signs"></i> {!milestone ? "" : milestone.title}
</span>
return jsx;
}
const StackedIcons = ({ type, state }) => {
const iconColor = (state === "closed" ? "has-text-danger" : "has-text-success")
let jsx = type ? <span class="fa-stack" >
<i class={"fas fa-exclamation fa-stack-1x " + iconColor}></i>
<i class={"fas fa-check fa-stack-1x check " + iconColor + " " + (state === "open" ? "is-hidden" : "")}></i>
<i class={"far fa-circle fa-stack-2x fa-fw " + iconColor}></i>
</span>
: <i class={"fas fa-code-branch fa-2x fa-fw " + iconColor} aria-hidden="true"></i>
return (jsx)
}
const TimeAgoComponent = ({ data }) => {
let timeAgoString = `${getTimeAgoString(new Date(data.created_at))} by ${data.user.login}`
const assigneesString = `${(data.assignees.length > 0 ?
` --- Assigned to: ${data.assignees.map(x => { return " " + x.login })}`
: "")}`
return (
<React.Fragment>
{timeAgoString + assigneesString}
</React.Fragment>
)
}
class GithubIssues extends React.Component {
constructor() {
super();
this.state = {
issues: [],
loading: true,
error: null,
showBody: {},
};
// fix the this value
this.getIssues = this.getIssues.bind(this);
}
componentWillMount() {
this.getIssues();
}
shouldComponentUpdate(nextState) {
if (this.state.showBody !== nextState.showBody) {
return true;
}
return false;
}
getIssues() {
const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()
const headers = {
headers: {
Accept: "application/vnd.github.v3+json",
Authorization: "a185f5058a1b89eede21d810611ff54860c5f9c3"
}
};
const params = encodeQueryString({ state: "all", since: since, per_page: 1000 })
const url = `https://api.github.com/repos/angular/angular/issues${params}`
fetch(url, headers)
.then(response => {
if (response.ok) return response.json();
throw new Error('Request failed.');
})
.then(data => {
let obj = () => {
let obj =
data.map(issues => { return obj[("_" + issues.id).toString()] = false })
}
const open = data.filter(issues => issues.state !== "closed").length
const closed = data.length - open
document.getElementById('open').innerHTML = open
document.getElementById('closed').innerHTML = closed
this.setState({
issues: data,
loading: false,
error: null,
showBody: obj,
});
})
.catch(error => {
this.setState({
loading: false,
error: error
});
});
}
renderLoading() {
return <progress class="progress is-small is-dark" max="100"></progress>;
}
renderError() {
return (
<div>
Uh oh: {this.state.error.message}
</div>
);
}
renderIssues() {
if (this.state.error) {
return this.renderError();
}
const isVisible = (id) => {
return this.state.showBody["_" + id] ? " container bodyContainer" : " is-hidden"
}
const handleClick = (e) => {
e.preventDefault();
let obj = Object.create(this.state.showBody)
const id = e.currentTarget.dataset.id;
obj["_" + id] = this.state.showBody["_" + id] === true ? false : true
this.setState({ showBody: obj })
if (obj["_" + id]) {
this.state.issues.map(issues => {
if (issues.id == id) {
return document.querySelector(`._${id}`).innerHTML = converter.makeHtml(issues.body)
}
})
}
}
return (
< div >
{
this.state.issues.map(function (data, index) {
return <a key={index} class="panel-block">
<span data-id={data.id} class="panel-ovr" onClick={handleClick} >
<span class="columns is-mobile is-multiline is-vcentered is-2">
<span class=" column is-1">
<span class="panel-icon icon is-medium">
<StackedIcons type={!data.pull_request} state={data.state} />
</span>
</span>
<span class="column is-11 info-container">
<span class="_title">{data.title}</span>
<br class="is-hidden-tablet" />
<span class="time is-hidden-tablet">
<TimeAgoComponent data={data} />
<Milestones milestone={data.milestone} _addclass={["is-hidden-mobile"]} />
</span>
<br class="is-hidden-tablet" />
<Labels labels={data.labels} />
<br class="is-hidden-mobile" />
<span class="time is-hidden-mobile">
<TimeAgoComponent data={data} />
<Milestones milestone={data.milestone} />
<Comments data={data} />
</span>
</span>
<span class="column show-body has-text-right is-pulled-right">
<i class="fas fa-eye"></i>
</span>
</span>
</span>
<span class={"_" + data.id + (isVisible(data.id))}></span>
</a>;
})
} </div>
);
}
render() {
return (< div > {this.state.loading ?
this.renderLoading()
: this.renderIssues()} </div>);
}
}
ReactDOM.render(
<GithubIssues />,
document.getElementById('issues')
);
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Angular Issues</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css">
<link rel="stylesheet" href="style.css">
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
<script src="https://unpkg.com/react@16/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.0/showdown.min.js"></script>
<script src="appHelpers.js"></script>
<script src="GithubIssues.js" type="text/babel" defer></script>
</head>
<body>
<section class="section">
<div class="container">
<nav class="panel">
<p class="panel-heading">
<span>
Open:
<span id="open">
<i class="fas fa-spinner fa-pulse"></i>
</span>
</span>
<span>&nbsp;&nbsp;Closed:
<span id="closed">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<span class="is-pulled-right is-hidden-mobile">Angular issues for the past 7 days </span>
</p>
<div id="issues"><progress class="progress is-small is-dark" value="0" max="100"></progress></div>
</nav>
</div>
</section>
</body>
</html>
.panel-icon {
line-height: 0.5rem;
text-align: center;
vertical-align: middle;
display: inline;
}
.fa-check {
font-size: 100%;
margin: -0.01rem 0 0 0.9rem;
}
.tag {
margin: 2px;
height: 1.2em !important;
}
.time {
font-size: 0.78rem;
width: 100%;
color: #586069 !important;
}
.bodyContainer {
max-height: fit-content;
display: block;
background-color: #ffffff;
margin: 10px;
overflow-wrap: normal;
overflow: hidden;
padding: 10px !important;
}
.bodyContainer>li,
ol,
p,
pre,
textarea,
ul {
padding: inherit;
margin: inherit !important;
}
.panel-block,
.panel-heading,
.panel-tabs {
border-bottom: 1px solid #dbdbdb;
border-left: 1px solid #dbdbdb;
border-right: 1px solid #dbdbdb;
}
.panel-block {
flex-flow: wrap;
align-items: center;
color: #363636;
padding: .5em .75em;
width: 100%;
margin: 0px !important;
}
.panel-ovr {
border: none !important;
margin: 0px !important;
width: 100%;
max-width: 100%;
line-height: 1.25;
}
._title {
text-overflow: ellipsis;
overflow-x: hidden;
max-width: 90%;
overflow-wrap: normal;
color: #363636 !important;
margin-right: 12px;
font-weight: 900;
}
.milestone,
.comments {
margin-left: 10px;
}
.show-body {
padding: 5px;
}
.panel-icon-container {
margin-right: -3.5% !important;
}
.info-container {
margin-left: -4%;
line-height: 1.2;
}
@media screen and (max-width: 769px) {
.panel-icon-container {
margin-right: 0 !important;
}
.info-container {
margin-left: 0% !important;
padding-left: 30px
}
.milestone {
margin-left: 0px !important;
}
/* .panel-icon {
margin-left: -8px;
} */
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment