Endpoint customizer (#2908)

* Endpoint page: improve formatting

Cherry-picked from #2906 (conflicts with that)

Partly addresses #2837 but does not resolve it

* Add badge customizer to the endpoint page

* Clean lint
This commit is contained in:
Paul Melnikow
2019-02-14 21:08:56 -04:00
committed by Caleb Cartwright
parent 24945adfed
commit 90f8ad5b73
9 changed files with 195 additions and 143 deletions

View File

@@ -0,0 +1,159 @@
import React from 'react'
import PropTypes from 'prop-types'
import clipboardCopy from 'clipboard-copy'
import { staticBadgeUrl } from '../../lib/badge-url'
import { generateMarkup } from '../../lib/generate-image-markup'
import { Badge } from '../common'
import PathBuilder from './path-builder'
import QueryStringBuilder from './query-string-builder'
import RequestMarkupButtom from './request-markup-button'
import CopiedContentIndicator from './copied-content-indicator'
export default class Customizer extends React.Component {
static propTypes = {
// This is an item from the `examples` array within the
// `serviceDefinition` schema.
// https://github.com/badges/shields/blob/master/services/service-definitions.js
baseUrl: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
pattern: PropTypes.string.isRequired,
exampleNamedParams: PropTypes.object.isRequired,
exampleQueryParams: PropTypes.object.isRequired,
defaultStyle: PropTypes.string,
}
indicatorRef = React.createRef()
state = {
path: '',
link: '',
message: undefined,
}
get baseUrl() {
const { baseUrl } = this.props
if (baseUrl) {
return baseUrl
} else {
// Default to the current hostname for when there is no `BASE_URL` set
// at build time (as in most PaaS deploys).
const { protocol, hostname } = window.location
return `${protocol}//${hostname}`
}
}
generateBuiltBadgeUrl() {
const { baseUrl } = this
const { path, queryString } = this.state
const suffix = queryString ? `?${queryString}` : ''
return `${baseUrl}${path}.svg${suffix}`
}
renderLivePreview() {
// There are some usability issues here. It would be better if the message
// changed from a validation error to a loading message once the
// parameters were filled in, and also switched back to loading when the
// parameters changed.
const { baseUrl } = this.props
const { pathIsComplete } = this.state
let src
if (pathIsComplete) {
src = this.generateBuiltBadgeUrl()
} else {
src = staticBadgeUrl(
baseUrl,
'preview',
'some parameters missing',
'lightgray'
)
}
return (
<p>
<Badge display="block" src={src} />
</p>
)
}
copyMarkup = async markupFormat => {
const { title } = this.props
const { link } = this.state
const builtBadgeUrl = this.generateBuiltBadgeUrl()
const markup = generateMarkup({
badgeUrl: builtBadgeUrl,
link,
title,
markupFormat,
})
try {
await clipboardCopy(markup)
} catch (e) {
this.setState({
message: 'Copy failed',
markup,
})
return
}
this.setState({ markup })
this.indicatorRef.current.trigger()
}
renderMarkupAndLivePreview() {
const { indicatorRef } = this
const { markup, message, pathIsComplete } = this.state
return (
<div>
{this.renderLivePreview()}
<CopiedContentIndicator ref={indicatorRef} copiedContent="Copied">
<RequestMarkupButtom
isDisabled={!pathIsComplete}
onMarkupRequested={this.copyMarkup}
/>
</CopiedContentIndicator>
{message && (
<div>
<p>{message}</p>
<p>Markup: {markup}</p>
</div>
)}
</div>
)
}
handlePathChange = ({ path, isComplete }) => {
this.setState({ path, pathIsComplete: isComplete })
}
handleQueryStringChange = ({ queryString, isComplete }) => {
this.setState({ queryString })
}
render() {
const {
pattern,
exampleNamedParams,
exampleQueryParams,
defaultStyle,
} = this.props
return (
<form action="">
<PathBuilder
pattern={pattern}
exampleParams={exampleNamedParams}
onChange={this.handlePathChange}
/>
<QueryStringBuilder
exampleParams={exampleQueryParams}
defaultStyle={defaultStyle}
onChange={this.handleQueryStringChange}
/>
<div>{this.renderMarkupAndLivePreview()}</div>
</form>
)
}
}

View File

@@ -104,14 +104,26 @@ export default class PathBuilder extends React.Component {
return { path, isComplete }
}
getPath(namedParams) {
const { tokens } = this.state
return this.constructor.constructPath({ tokens, namedParams })
notePathChanged({ tokens, namedParams }) {
const { onChange } = this.props
if (onChange) {
const { path, isComplete } = this.constructor.constructPath({
tokens,
namedParams,
})
onChange({ path, isComplete })
}
}
componentDidMount() {
// Ensure the default style is applied right away.
const { tokens, namedParams } = this.state
this.notePathChanged({ tokens, namedParams })
}
handleTokenChange = evt => {
const { name, value } = evt.target
const { namedParams: oldNamedParams } = this.state
const { tokens, namedParams: oldNamedParams } = this.state
const namedParams = {
...oldNamedParams,
@@ -119,12 +131,7 @@ export default class PathBuilder extends React.Component {
}
this.setState({ namedParams })
const { onChange } = this.props
if (onChange) {
const { path, isComplete } = this.getPath(namedParams)
onChange({ path, isComplete })
}
this.notePathChanged({ tokens, namedParams })
}
renderLiteral(literal, tokenIndex) {

View File

@@ -1,14 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import clipboardCopy from 'clipboard-copy'
import { staticBadgeUrl } from '../../lib/badge-url'
import { generateMarkup } from '../../lib/generate-image-markup'
import { H3, Badge } from '../common'
import PathBuilder from './path-builder'
import QueryStringBuilder from './query-string-builder'
import RequestMarkupButtom from './request-markup-button'
import CopiedContentIndicator from './copied-content-indicator'
import { H3 } from '../common'
import Customizer from '../customizer/customizer'
const Documentation = styled.div`
max-width: 800px;
@@ -24,112 +18,6 @@ export default class MarkupModalContent extends React.Component {
baseUrl: PropTypes.string.isRequired,
}
indicatorRef = React.createRef()
state = {
path: '',
link: '',
message: undefined,
}
get baseUrl() {
const { baseUrl } = this.props
if (baseUrl) {
return baseUrl
} else {
// Default to the current hostname for when there is no `BASE_URL` set
// at build time (as in most PaaS deploys).
const { protocol, hostname } = window.location
return `${protocol}//${hostname}`
}
}
generateBuiltBadgeUrl() {
const { baseUrl } = this
const { path, queryString } = this.state
const suffix = queryString ? `?${queryString}` : ''
return `${baseUrl}${path}.svg${suffix}`
}
renderLivePreview() {
// There are some usability issues here. It would be better if the message
// changed from a validation error to a loading message once the
// parameters were filled in, and also switched back to loading when the
// parameters changed.
const { baseUrl } = this.props
const { pathIsComplete } = this.state
let src
if (pathIsComplete) {
src = this.generateBuiltBadgeUrl()
} else {
src = staticBadgeUrl(
baseUrl,
'preview',
'some parameters missing',
'lightgray'
)
}
return (
<p>
<Badge display="block" src={src} />
</p>
)
}
copyMarkup = async markupFormat => {
const {
example: {
example: { title },
},
} = this.props
const { link } = this.state
const builtBadgeUrl = this.generateBuiltBadgeUrl()
const markup = generateMarkup({
badgeUrl: builtBadgeUrl,
link,
title,
markupFormat,
})
try {
await clipboardCopy(markup)
} catch (e) {
this.setState({
message: 'Copy failed',
markup,
})
return
}
this.setState({ markup })
this.indicatorRef.current.trigger()
}
renderMarkupAndLivePreview() {
const { indicatorRef } = this
const { markup, message, pathIsComplete } = this.state
return (
<div>
{this.renderLivePreview()}
<CopiedContentIndicator ref={indicatorRef} copiedContent="Copied">
<RequestMarkupButtom
isDisabled={!pathIsComplete}
onMarkupRequested={this.copyMarkup}
/>
</CopiedContentIndicator>
{message && (
<div>
<p>{message}</p>
<p>Markup: {markup}</p>
</div>
)}
</div>
)
}
renderDocumentation() {
const {
example: { documentation },
@@ -140,14 +28,6 @@ export default class MarkupModalContent extends React.Component {
) : null
}
handlePathChange = ({ path, isComplete }) => {
this.setState({ path, pathIsComplete: isComplete })
}
handleQueryStringChange = ({ queryString, isComplete }) => {
this.setState({ queryString })
}
render() {
const {
example: {
@@ -155,24 +35,21 @@ export default class MarkupModalContent extends React.Component {
example: { pattern, namedParams, queryParams },
preview: { style: defaultStyle },
},
baseUrl,
} = this.props
return (
<form action="">
<>
<H3>{title}</H3>
{this.renderDocumentation()}
<PathBuilder
<Customizer
baseUrl={baseUrl}
title={title}
pattern={pattern}
exampleParams={namedParams}
onChange={this.handlePathChange}
/>
<QueryStringBuilder
exampleParams={queryParams}
exampleNamedParams={namedParams}
exampleQueryParams={queryParams}
defaultStyle={defaultStyle}
onChange={this.handleQueryStringChange}
/>
<div>{this.renderMarkupAndLivePreview()}</div>
</form>
</>
)
}
}

View File

@@ -8,6 +8,7 @@ import Header from '../components/header'
import Footer from '../components/footer'
import { BaseFont, GlobalStyle, H3, Badge } from '../components/common'
import { Snippet } from '../components/snippet'
import Customizer from '../components/customizer/customizer'
const MainContainer = styled(BaseFont)`
text-align: center;
@@ -221,6 +222,14 @@ const EndpointPage = () => (
overridden by the user via the query string, but only to a longer value.
</dd>
</Schema>
<h4>Customize and test</h4>
<Customizer
baseUrl={baseUrl}
title="Custom badge"
pattern="/badge/endpoint"
exampleNamedParams={{}}
exampleQueryParams={{ url: 'https://shields.redsparr0w.com/2473/monday' }}
/>
<Footer baseUrl={baseUrl} />
</MainContainer>
)