-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
I rewrote the frontend in React using a module bundler. It's matched feature-for-feature with the current frontend, with only slight changes in the styling. I did not fuss about making the styling identical; the badge popup looks particularly different. This makes the front end much easier to develop. I'm really looking forward to implementing #701, to which this paves the way. This makes light use of Next.js, which provides webpack config and dev/build tooling. We’ll probably replace it with create-react-app or our own webpack setup because unfortunately it comes with a lot of runtime overhead (the build is 400k). Let’s open new issues for bugs and features, and track other follow-ups here: https://github.com/badges/shields/projects/1
- Loading branch information
1 parent
a0cd930
commit 4b5bf03
Showing
32 changed files
with
5,925 additions
and
1,049 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
/build | ||
/coverage |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
env: | ||
browser: true | ||
|
||
parser: "babel-eslint" | ||
|
||
parserOptions: | ||
sourceType: "module" | ||
|
||
extends: | ||
- "standard-jsx" | ||
- "standard-react" | ||
- "./.eslintrc-preferred.yml" |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -88,3 +88,4 @@ typings/ | |
|
||
# Temporary build artifacts. | ||
/build | ||
.next |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1 @@ | ||
extends: | ||
- 'standard-jsx' | ||
- '../.eslintrc-preferred.yml' | ||
|
||
parserOptions: | ||
ecmaFeatures: | ||
jsx: true | ||
extends: '../.eslintrc-frontend.yml' |
This file was deleted.
Oops, something went wrong.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { URL } from '../lib/url-api'; | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import classNames from 'classnames'; | ||
|
||
function resolveUri (uri, baseUri, options) { | ||
const { longCache } = options || {}; | ||
const result = new URL(uri, baseUri); | ||
if (longCache) { | ||
result.searchParams.maxAge = '2592000'; | ||
} | ||
return result.href; | ||
} | ||
|
||
const Badge = ({ title, previewUri, exampleUri, documentation, baseUri, longCache, onClick }) => { | ||
const handleClick = onClick ? | ||
() => onClick({ title, previewUri, exampleUri, documentation }) | ||
: undefined; | ||
|
||
const previewImage = previewUri | ||
? (<img | ||
className={classNames('badge-img', { clickable: onClick })} | ||
onClick={handleClick} | ||
src={resolveUri(previewUri, baseUri, { longCache } )} | ||
alt="" /> | ||
) : '\u00a0'; // non-breaking space | ||
const resolvedExampleUri = resolveUri( | ||
exampleUri || previewUri, | ||
baseUri || 'https://img.shields.io/', | ||
{ longCache: false }); | ||
|
||
return ( | ||
<tr> | ||
<th className={classNames({ clickable: onClick })} onClick={handleClick}> | ||
{ title }: | ||
</th> | ||
<td>{ previewImage }</td> | ||
<td> | ||
<code className={classNames({ clickable: onClick })} onClick={handleClick}> | ||
{ resolvedExampleUri } | ||
</code> | ||
</td> | ||
</tr> | ||
); | ||
}; | ||
Badge.propTypes = { | ||
title: PropTypes.string.isRequired, | ||
previewUri: PropTypes.string, | ||
exampleUri: PropTypes.string, | ||
documentation: PropTypes.string, | ||
baseUri: PropTypes.string.isRequired, | ||
longCache: PropTypes.bool.isRequired, | ||
onClick: PropTypes.func.isRequired, | ||
}; | ||
|
||
const Category = ({ category, examples, baseUri, longCache, onClick }) => ( | ||
<div> | ||
<h3 id={category.id}>{ category.name }</h3> | ||
<table className="badge"> | ||
<tbody> | ||
{ | ||
examples.map((badgeData, i) => ( | ||
<Badge | ||
key={i} | ||
{...badgeData} | ||
baseUri={baseUri} | ||
longCache={longCache} | ||
onClick={onClick} /> | ||
)) | ||
} | ||
</tbody> | ||
</table> | ||
</div> | ||
); | ||
Category.propTypes = { | ||
category: PropTypes.shape({ | ||
id: PropTypes.string.isRequired, | ||
name: PropTypes.string.isRequired, | ||
}).isRequired, | ||
examples: PropTypes.arrayOf(PropTypes.shape({ | ||
title: PropTypes.string.isRequired, | ||
previewUri: PropTypes.string, | ||
exampleUri: PropTypes.string, | ||
documentation: PropTypes.string, | ||
})).isRequired, | ||
baseUri: PropTypes.string.isRequired, | ||
longCache: PropTypes.bool.isRequired, | ||
onClick: PropTypes.func.isRequired, | ||
}; | ||
|
||
const BadgeExamples = ({ examples, baseUri, longCache, onClick }) => ( | ||
<div> | ||
{ | ||
examples.map((categoryData, i) => ( | ||
<Category | ||
key={i} | ||
{...categoryData} | ||
baseUri={baseUri} | ||
longCache={longCache} | ||
onClick={onClick} /> | ||
)) | ||
} | ||
</div> | ||
); | ||
BadgeExamples.propTypes = { | ||
examples: PropTypes.arrayOf(PropTypes.shape({ | ||
category: Category.propTypes.category, | ||
examples: Category.propTypes.examples, | ||
})), | ||
baseUri: PropTypes.string.isRequired, | ||
longCache: PropTypes.bool.isRequired, | ||
onClick: PropTypes.func.isRequired, | ||
}; | ||
|
||
module.exports = { | ||
Badge, | ||
BadgeExamples, | ||
resolveUri, | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
export default class DynamicBadgeMaker extends React.Component { | ||
static propTypes = { | ||
baseUri: PropTypes.string.isRequired, | ||
}; | ||
|
||
state = { | ||
type: 'json', | ||
label: '', | ||
uri: '', | ||
colorB: '', | ||
prefix: '', | ||
suffix: '', | ||
query: '', | ||
}; | ||
|
||
makeBadgeUri () { | ||
const result = new URL(`/dynamic/${this.state.type}.svg`, this.props.baseUri); | ||
const searchParams = [ | ||
'label', | ||
'uri', | ||
'colorB', | ||
'prefix', | ||
'suffix', | ||
'query', | ||
]; | ||
searchParams.forEach(k => { | ||
result.searchParams.set(k, this.state[k]); | ||
}); | ||
return result.href; | ||
} | ||
|
||
handleSubmit(e) { | ||
e.preventDefault(); | ||
document.location = this.makeBadgeUri(); | ||
} | ||
|
||
get isValid() { | ||
const { label, uri, query } = this.state; | ||
return label && uri && query; | ||
} | ||
|
||
render() { | ||
return ( | ||
<form onSubmit={e => this.handleSubmit(e)}> | ||
<input | ||
className="short" | ||
value={this.state.type} | ||
readOnly | ||
list="dynamic-type" /> | ||
<datalist id="dynamic-type"> | ||
<option value="json" /> | ||
</datalist> | ||
<input | ||
className="short" | ||
value={this.state.label} | ||
onChange={event => this.setState({ label: event.target.value })} | ||
placeholder="label" /> | ||
<input | ||
className="short" | ||
value={this.state.uri} | ||
onChange={event => this.setState({ uri: event.target.value })} | ||
placeholder="uri" /> | ||
<input | ||
className="short" | ||
value={this.state.query} | ||
onChange={event => this.setState({ query: event.target.value })} | ||
placeholder="$.data.subdata" /> | ||
<input | ||
className="short" | ||
value={this.state.color} | ||
onChange={event => this.setState({ color: event.target.value })} | ||
placeholder="hex color" /> | ||
<input | ||
className="short" | ||
value={this.state.prefix} | ||
onChange={event => this.setState({ prefix: event.target.value })} | ||
placeholder="prefix" /> | ||
<input | ||
className="short" | ||
value={this.state.suffix} | ||
onChange={event => this.setState({ suffix: event.target.value })} | ||
placeholder="suffix" /> | ||
<button disabled={! this.isValid}>Make Badge</button> | ||
</form> | ||
); | ||
} | ||
} |
Oops, something went wrong.