Skip to content

Commit

Permalink
Endpoint customizer (#2908)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
paulmelnikow authored and calebcartwright committed Feb 15, 2019
1 parent 24945ad commit 90f8ad5
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 143 deletions.
159 changes: 159 additions & 0 deletions frontend/components/customizer/customizer.js
Original file line number Diff line number Diff line change
@@ -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>
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,27 +104,34 @@ 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,
[name]: value,
}

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) {
Expand Down
143 changes: 10 additions & 133 deletions frontend/components/markup-modal/markup-modal-content.js
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 },
Expand All @@ -140,39 +28,28 @@ 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: {
title,
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>
</>
)
}
}
Loading

0 comments on commit 90f8ad5

Please sign in to comment.