Files
shields/frontend/components/customizer/path-builder.js
Paul Melnikow 90f8ad5b73 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
2019-02-14 19:08:56 -06:00

190 lines
4.4 KiB
JavaScript

import React from 'react'
import PropTypes from 'prop-types'
import styled, { css } from 'styled-components'
import pathToRegexp from 'path-to-regexp'
import humanizeString from 'humanize-string'
import { noAutocorrect, StyledInput } from '../common'
import {
BuilderContainer,
BuilderLabel,
BuilderCaption,
} from './builder-common'
const PathBuilderColumn = styled.span`
height: 58px;
float: left;
display: flex;
flex-direction: column;
margin: 5px 0;
${({ withHorizPadding }) =>
withHorizPadding &&
css`
padding: 0 8px;
`};
`
const PathLiteral = styled.div`
margin-top: 20px;
${({ isFirstToken }) =>
isFirstToken &&
css`
margin-left: 3px;
`};
`
const NamedParamLabel = styled(BuilderLabel)`
height: 20px;
width: 100%;
text-align: center;
`
const NamedParamInput = styled(StyledInput)`
width: 100%;
text-align: center;
margin-bottom: 10px;
`
const NamedParamCaption = styled(BuilderCaption)`
width: 100%;
text-align: center;
`
export default class PathBuilder extends React.Component {
static propTypes = {
pattern: PropTypes.string.isRequired,
exampleParams: PropTypes.object.isRequired,
onChange: PropTypes.func,
}
constructor(props) {
super(props)
const { pattern } = props
const tokens = pathToRegexp.parse(pattern)
const namedParams = {}
// `pathToRegexp.parse()` returns a mixed array of strings for literals
// and objects for parameters. Filter out the literals and work with the
// objects.
tokens
.filter(t => typeof t !== 'string')
.forEach(({ name }) => {
namedParams[name] = ''
})
this.state = {
tokens,
namedParams,
}
}
static constructPath({ tokens, namedParams }) {
let isComplete = true
const path = tokens
.map(token => {
if (typeof token === 'string') {
return token
} else {
const { delimiter, name } = token
let value = namedParams[name]
if (!value) {
isComplete = false
value = `:${name}`
}
return `${delimiter}${value}`
}
})
.join('')
return { path, isComplete }
}
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 { tokens, namedParams: oldNamedParams } = this.state
const namedParams = {
...oldNamedParams,
[name]: value,
}
this.setState({ namedParams })
this.notePathChanged({ tokens, namedParams })
}
renderLiteral(literal, tokenIndex) {
return (
<PathBuilderColumn key={`${tokenIndex}-${literal}`}>
<PathLiteral isFirstToken={tokenIndex === 0}>{literal}</PathLiteral>
</PathBuilderColumn>
)
}
renderNamedParam(token, tokenIndex, namedParamIndex) {
const { delimiter, name } = token
const { exampleParams } = this.props
const exampleValue = exampleParams[name]
const { namedParams } = this.state
const value = namedParams[name]
return (
<React.Fragment key={token.name}>
{this.renderLiteral(delimiter, tokenIndex)}
<PathBuilderColumn withHorizPadding>
<NamedParamLabel htmlFor={name}>
{humanizeString(name)}
</NamedParamLabel>
<NamedParamInput
type="text"
name={name}
value={value}
onChange={this.handleTokenChange}
{...noAutocorrect}
/>
<NamedParamCaption>
{namedParamIndex === 0 ? `e.g. ${exampleValue}` : exampleValue}
</NamedParamCaption>
</PathBuilderColumn>
</React.Fragment>
)
}
render() {
const { tokens } = this.state
let namedParamIndex = 0
return (
<BuilderContainer>
{tokens.map((token, tokenIndex) =>
typeof token === 'string'
? this.renderLiteral(token, tokenIndex)
: this.renderNamedParam(token, tokenIndex, namedParamIndex++)
)}
</BuilderContainer>
)
}
}