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 ( {literal} ) } 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 ( {this.renderLiteral(delimiter, tokenIndex)} {humanizeString(name)} {namedParamIndex === 0 ? `e.g. ${exampleValue}` : exampleValue} ) } render() { const { tokens } = this.state let namedParamIndex = 0 return ( {tokens.map((token, tokenIndex) => typeof token === 'string' ? this.renderLiteral(token, tokenIndex) : this.renderNamedParam(token, tokenIndex, namedParamIndex++) )} ) } }