Last active
August 19, 2019 04:02
-
-
Save atomize/9242a96b10903f2c5866eb0cb48be6b8 to your computer and use it in GitHub Desktop.
Github Issues in React browser only
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}' | |
}) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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') | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> 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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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