Skip to content

Commit

Permalink
Split DocExplorer into smaller files (#243)
Browse files Browse the repository at this point in the history
* Extract TypeLink out of DocExplorer

* Extract SearchBox out of DocExplorer

* Extract SearchDoc out of DocExplorer, renaming it to SearchResults

Changed the name to obviate the need for the comment describing what th
component is for.

* Extract SchemaDoc from DocExplorer

* Extract MarkdownContent from DocExplorer

* Extract TypeDoc from DocExplorer

* Extract FieldDoc from DocExplorer
  • Loading branch information
wincent authored and asiandrummer committed Dec 10, 2016
1 parent e005447 commit f45fbde
Show file tree
Hide file tree
Showing 8 changed files with 584 additions and 511 deletions.
518 changes: 7 additions & 511 deletions src/components/DocExplorer.js

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions src/components/DocExplorer/FieldDoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE-examples file in the root directory of this source tree.
*/

import React, { PropTypes } from 'react';

import MarkdownContent from './MarkdownContent';
import TypeLink from './TypeLink';

export default class FieldDoc extends React.Component {
static propTypes = {
field: PropTypes.object,
onClickType: PropTypes.func,
}

shouldComponentUpdate(nextProps) {
return this.props.field !== nextProps.field;
}

render() {
const field = this.props.field;

let argsDef;
if (field.args && field.args.length > 0) {
argsDef = (
<div className="doc-category">
<div className="doc-category-title">
{'arguments'}
</div>
{field.args.map(arg =>
<div key={arg.name} className="doc-category-item">
<div>
<span className="arg-name">{arg.name}</span>
{': '}
<TypeLink type={arg.type} onClick={this.props.onClickType} />
</div>
<MarkdownContent
className="doc-value-description"
markdown={arg.description}
/>
</div>
)}
</div>
);
}

return (
<div>
<MarkdownContent
className="doc-type-description"
markdown={field.description || 'No Description'}
/>
{
field.deprecationReason &&
<MarkdownContent
className="doc-alert-text"
markdown={field.deprecationReason}
/>
}
<div className="doc-category">
<div className="doc-category-title">
{'type'}
</div>
<TypeLink type={field.type} onClick={this.props.onClickType} />
</div>
{argsDef}
</div>
);
}
}
36 changes: 36 additions & 0 deletions src/components/DocExplorer/MarkdownContent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE-examples file in the root directory of this source tree.
*/

import React, { PropTypes } from 'react';
import Marked from 'marked';

export default class MarkdownContent extends React.Component {
static propTypes = {
markdown: PropTypes.string,
className: PropTypes.string,
}

shouldComponentUpdate(nextProps) {
return this.props.markdown !== nextProps.markdown;
}

render() {
const markdown = this.props.markdown;
if (!markdown) {
return <div />;
}

const html = Marked(markdown, { sanitize: true });
return (
<div
className={this.props.className}
dangerouslySetInnerHTML={{ __html: html }}
/>
);
}
}
72 changes: 72 additions & 0 deletions src/components/DocExplorer/SchemaDoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE-examples file in the root directory of this source tree.
*/

import React, { PropTypes } from 'react';

import TypeLink from './TypeLink';
import MarkdownContent from './MarkdownContent';

// Render the top level Schema
export default class SchemaDoc extends React.Component {
static propTypes = {
schema: PropTypes.object,
onClickType: PropTypes.func,
}

shouldComponentUpdate(nextProps) {
return this.props.schema !== nextProps.schema;
}

render() {
const schema = this.props.schema;
const queryType = schema.getQueryType();
const mutationType = schema.getMutationType && schema.getMutationType();
const subscriptionType =
schema.getSubscriptionType && schema.getSubscriptionType();

return (
<div>
<MarkdownContent
className="doc-type-description"
markdown={
'A GraphQL schema provides a root type for each kind of operation.'
}
/>
<div className="doc-category">
<div className="doc-category-title">
{'root types'}
</div>
<div className="doc-category-item">
<span className="keyword">{'query'}</span>
{': '}
<TypeLink type={queryType} onClick={this.props.onClickType} />
</div>
{
mutationType &&
<div className="doc-category-item">
<span className="keyword">{'mutation'}</span>
{': '}
<TypeLink type={mutationType} onClick={this.props.onClickType} />
</div>
}
{
subscriptionType &&
<div className="doc-category-item">
<span className="keyword">{'subscription'}</span>
{': '}
<TypeLink
type={subscriptionType}
onClick={this.props.onClickType}
/>
</div>
}
</div>
</div>
);
}
}
56 changes: 56 additions & 0 deletions src/components/DocExplorer/SearchBox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE-examples file in the root directory of this source tree.
*/

import React, { PropTypes } from 'react';

import debounce from '../../utility/debounce';

export default class SearchBox extends React.Component {
static propTypes = {
isShown: PropTypes.bool,
onSearch: PropTypes.func,
}

constructor(props) {
super(props);

this.state = { value: '' };

this._debouncedOnSearch = debounce(200, () => {
this.props.onSearch(this.state.value);
});
}

shouldComponentUpdate(nextProps, nextState) {
return nextProps.isShown !== this.props.isShown ||
nextState.value !== this.state.value;
}

render() {
return (
<div>
{
this.props.isShown &&
<label className="search-box-outer">
<input className="search-box-input"
onChange={this.handleChange}
type="text"
value={this.state.value}
placeholder="Search the schema ..."
/>
</label>
}
</div>
);
}

handleChange = event => {
this.setState({ value: event.target.value });
this._debouncedOnSearch();
}
}
135 changes: 135 additions & 0 deletions src/components/DocExplorer/SearchResults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE-examples file in the root directory of this source tree.
*/

import React, { PropTypes } from 'react';

import TypeLink from './TypeLink';

export default class SearchResults extends React.Component {
static propTypes = {
schema: PropTypes.object,
searchValue: PropTypes.string,
onClickType: PropTypes.func,
onClickField: PropTypes.func,
}

shouldComponentUpdate(nextProps) {
return this.props.schema !== nextProps.schema ||
this.props.searchValue !== nextProps.searchValue;
}

render() {
const searchValue = this.props.searchValue;
const schema = this.props.schema;
const onClickType = this.props.onClickType;
const onClickField = this.props.onClickField;

const typeMap = schema.getTypeMap();

const matchedTypes = [];
const matchedFields = [];

const typeNames = Object.keys(typeMap);
for (const typeName of typeNames) {
if (matchedTypes.length + matchedFields.length >= 100) {
break;
}

const type = typeMap[typeName];
const matchedOn = [];
if (this._isMatch(typeName, searchValue)) {
matchedOn.push('Type Name');
}

if (matchedOn.length) {
matchedTypes.push(
<div className="doc-category-item">
<TypeLink type={type} onClick={onClickType} />
</div>
);
}

if (type.getFields) {
const fields = type.getFields();
Object.keys(fields).forEach(fieldName => {
const field = fields[fieldName];
if (this._isMatch(fieldName, searchValue)) {
matchedFields.push(
<div className="doc-category-item">
<a className="field-name"
onClick={event => onClickField(field, type, event)}>
{field.name}
</a>
{' on '}
<TypeLink type={type} onClick={onClickType} />
</div>
);
} else if (field.args && field.args.length) {
const matches =
field.args.filter(arg => this._isMatch(arg.name, searchValue));
if (matches.length > 0) {
matchedFields.push(
<div className="doc-category-item">
<a className="field-name"
onClick={event => onClickField(field, type, event)}>
{field.name}
</a>
{'('}
<span>
{matches.map(arg =>
<span className="arg" key={arg.name}>
<span className="arg-name">{arg.name}</span>
{': '}
<TypeLink type={arg.type} onClick={onClickType} />
</span>
)}
</span>
{')'}
{' on '}
<TypeLink type={type} onClick={onClickType} />
</div>
);
}
}
});
}
}

if (matchedTypes.length === 0 && matchedFields.length === 0) {
return (
<span className="doc-alert-text">
{'No results found.'}
</span>
);
}

return (
<div>
<div className="doc-category">
{
(matchedTypes.length > 0 || matchedFields.length > 0) &&
<div className="doc-category-title">
{'search results'}
</div>
}
{matchedTypes}
{matchedFields}
</div>
</div>
);
}

_isMatch(sourceText, searchValue) {
try {
const escaped = searchValue.replace(/[^_0-9A-Za-z]/g, ch => '\\' + ch);
return sourceText.search(new RegExp(escaped, 'i')) !== -1;
} catch (e) {
return sourceText.toLowerCase().indexOf(searchValue.toLowerCase()) !== -1;
}
}
}
Loading

0 comments on commit f45fbde

Please sign in to comment.