diff options
Diffstat (limited to 'packages/react-docs/src/components/documentation.tsx')
-rw-r--r-- | packages/react-docs/src/components/documentation.tsx | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/packages/react-docs/src/components/documentation.tsx b/packages/react-docs/src/components/documentation.tsx new file mode 100644 index 000000000..b46358159 --- /dev/null +++ b/packages/react-docs/src/components/documentation.tsx @@ -0,0 +1,375 @@ +import { + colors, + constants as sharedConstants, + EtherscanLinkSuffixes, + MarkdownSection, + MenuSubsectionsBySection, + NestedSidebarMenu, + Networks, + SectionHeader, + Styles, + utils as sharedUtils, +} from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import CircularProgress from 'material-ui/CircularProgress'; +import * as React from 'react'; +import { scroller } from 'react-scroll'; + +import { DocsInfo } from '../docs_info'; +import { + AddressByContractName, + DocAgnosticFormat, + DoxityDocObj, + Event, + Property, + SolidityMethod, + SupportedDocJson, + TypeDefinitionByName, + TypescriptMethod, +} from '../types'; +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; + +import { Badge } from './badge'; +import { Comment } from './comment'; +import { EventDefinition } from './event_definition'; +import { MethodBlock } from './method_block'; +import { SourceLink } from './source_link'; +import { Type } from './type'; +import { TypeDefinition } from './type_definition'; + +const networkNameToColor: { [network: string]: string } = { + [Networks.Kovan]: colors.purple, + [Networks.Ropsten]: colors.red, + [Networks.Mainnet]: colors.turquois, + [Networks.Rinkeby]: colors.darkYellow, +}; + +export interface DocumentationProps { + selectedVersion: string; + availableVersions: string[]; + docsInfo: DocsInfo; + sourceUrl: string; + onVersionSelected: (semver: string) => void; + docAgnosticFormat?: DocAgnosticFormat; + sidebarHeader?: React.ReactNode; + topBarHeight?: number; +} + +export interface DocumentationState { + isHoveringSidebar: boolean; +} + +export class Documentation extends React.Component<DocumentationProps, DocumentationState> { + public static defaultProps: Partial<DocumentationProps> = { + topBarHeight: 0, + }; + constructor(props: DocumentationProps) { + super(props); + this.state = { + isHoveringSidebar: false, + }; + } + public componentDidMount() { + window.addEventListener('hashchange', this._onHashChanged.bind(this), false); + } + public componentWillUnmount() { + window.removeEventListener('hashchange', this._onHashChanged.bind(this), false); + } + public componentDidUpdate(prevProps: DocumentationProps, prevState: DocumentationState) { + if (!_.isEqual(prevProps.docAgnosticFormat, this.props.docAgnosticFormat)) { + const hash = window.location.hash.slice(1); + sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); + } + } + public render() { + const styles: Styles = { + mainContainers: { + position: 'absolute', + top: 1, + left: 0, + bottom: 0, + right: 0, + overflowZ: 'hidden', + overflowY: 'scroll', + minHeight: `calc(100vh - ${this.props.topBarHeight}px)`, + WebkitOverflowScrolling: 'touch', + }, + menuContainer: { + borderColor: colors.grey300, + maxWidth: 330, + marginLeft: 20, + }, + }; + const menuSubsectionsBySection = this.props.docsInfo.getMenuSubsectionsBySection(this.props.docAgnosticFormat); + return ( + <div> + {_.isUndefined(this.props.docAgnosticFormat) ? ( + this._renderLoading(styles.mainContainers) + ) : ( + <div style={{ width: '100%', height: '100%', backgroundColor: colors.gray40 }}> + <div + className="mx-auto max-width-4 flex" + style={{ color: colors.grey800, height: `calc(100vh - ${this.props.topBarHeight}px)` }} + > + <div + className="relative sm-hide xs-hide" + style={{ width: '36%', height: `calc(100vh - ${this.props.topBarHeight}px)` }} + > + <div + className="border-right absolute" + style={{ + ...styles.menuContainer, + ...styles.mainContainers, + height: `calc(100vh - ${this.props.topBarHeight}px)`, + overflow: this.state.isHoveringSidebar ? 'auto' : 'hidden', + }} + onMouseEnter={this._onSidebarHover.bind(this)} + onMouseLeave={this._onSidebarHoverOff.bind(this)} + > + <NestedSidebarMenu + selectedVersion={this.props.selectedVersion} + versions={this.props.availableVersions} + sidebarHeader={this.props.sidebarHeader} + topLevelMenu={this.props.docsInfo.getMenu(this.props.selectedVersion)} + menuSubsectionsBySection={menuSubsectionsBySection} + onVersionSelected={this.props.onVersionSelected} + /> + </div> + </div> + <div + className="relative col lg-col-9 md-col-9 sm-col-12 col-12" + style={{ backgroundColor: colors.white }} + > + <div + id={sharedConstants.SCROLL_CONTAINER_ID} + style={styles.mainContainers} + className="absolute px1" + > + <div id={sharedConstants.SCROLL_TOP_ID} /> + {this._renderDocumentation()} + </div> + </div> + </div> + </div> + )} + </div> + ); + } + private _renderLoading(mainContainersStyles: React.CSSProperties) { + return ( + <div className="col col-12" style={mainContainersStyles}> + <div + className="relative sm-px2 sm-pt2 sm-m1" + style={{ height: 122, top: '50%', transform: 'translateY(-50%)' }} + > + <div className="center pb2"> + <CircularProgress size={40} thickness={5} /> + </div> + <div className="center pt2" style={{ paddingBottom: 11 }}> + Loading documentation... + </div> + </div> + </div> + ); + } + private _renderDocumentation(): React.ReactNode { + const subMenus = _.values(this.props.docsInfo.getMenu()); + const orderedSectionNames = _.flatten(subMenus); + + const typeDefinitionByName = this.props.docsInfo.getTypeDefinitionsByName(this.props.docAgnosticFormat); + const renderedSections = _.map(orderedSectionNames, this._renderSection.bind(this, typeDefinitionByName)); + + return renderedSections; + } + private _renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode { + const markdownFileIfExists = this.props.docsInfo.sectionNameToMarkdown[sectionName]; + if (!_.isUndefined(markdownFileIfExists)) { + return ( + <MarkdownSection + key={`markdown-section-${sectionName}`} + sectionName={sectionName} + markdownContent={markdownFileIfExists} + /> + ); + } + + const docSection = this.props.docAgnosticFormat[sectionName]; + if (_.isUndefined(docSection)) { + return null; + } + + const sortedTypes = _.sortBy(docSection.types, 'name'); + const typeDefs = _.map(sortedTypes, customType => { + return ( + <TypeDefinition + sectionName={sectionName} + key={`type-${customType.name}`} + customType={customType} + docsInfo={this.props.docsInfo} + /> + ); + }); + + const sortedProperties = _.sortBy(docSection.properties, 'name'); + const propertyDefs = _.map(sortedProperties, this._renderProperty.bind(this, sectionName)); + + const sortedMethods = _.sortBy(docSection.methods, 'name'); + const methodDefs = _.map(sortedMethods, method => { + const isConstructor = false; + return this._renderMethodBlocks(method, sectionName, isConstructor, typeDefinitionByName); + }); + + const sortedEvents = _.sortBy(docSection.events, 'name'); + const eventDefs = _.map(sortedEvents, (event: Event, i: number) => { + return ( + <EventDefinition + key={`event-${event.name}-${i}`} + event={event} + sectionName={sectionName} + docsInfo={this.props.docsInfo} + /> + ); + }); + const headerStyle: React.CSSProperties = { + fontWeight: 100, + }; + return ( + <div key={`section-${sectionName}`} className="py2 pr3 md-pl2 sm-pl3"> + <div className="flex pb2"> + <div style={{ marginRight: 7 }}> + <SectionHeader sectionName={sectionName} /> + </div> + {this._renderNetworkBadgesIfExists(sectionName)} + </div> + {docSection.comment && <Comment comment={docSection.comment} />} + {docSection.constructors.length > 0 && + this.props.docsInfo.isVisibleConstructor(sectionName) && ( + <div> + <h2 style={headerStyle}>Constructor</h2> + {this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)} + </div> + )} + {docSection.properties.length > 0 && ( + <div> + <h2 style={headerStyle}>Properties</h2> + <div>{propertyDefs}</div> + </div> + )} + {docSection.methods.length > 0 && ( + <div> + <h2 style={headerStyle}>Methods</h2> + <div>{methodDefs}</div> + </div> + )} + {!_.isUndefined(docSection.events) && + docSection.events.length > 0 && ( + <div> + <h2 style={headerStyle}>Events</h2> + <div>{eventDefs}</div> + </div> + )} + {!_.isUndefined(typeDefs) && + typeDefs.length > 0 && ( + <div> + <div>{typeDefs}</div> + </div> + )} + </div> + ); + } + private _renderNetworkBadgesIfExists(sectionName: string) { + if (this.props.docsInfo.type !== SupportedDocJson.Doxity) { + return null; + } + + const networkToAddressByContractName = this.props.docsInfo.contractsByVersionByNetworkId[ + this.props.selectedVersion + ]; + const badges = _.map( + networkToAddressByContractName, + (addressByContractName: AddressByContractName, networkName: string) => { + const contractAddress = addressByContractName[sectionName]; + if (_.isUndefined(contractAddress)) { + return null; + } + const linkIfExists = sharedUtils.getEtherScanLinkIfExists( + contractAddress, + sharedConstants.NETWORK_ID_BY_NAME[networkName], + EtherscanLinkSuffixes.Address, + ); + return ( + <a + key={`badge-${networkName}-${sectionName}`} + href={linkIfExists} + target="_blank" + style={{ color: colors.white, textDecoration: 'none' }} + > + <Badge title={networkName} backgroundColor={networkNameToColor[networkName]} /> + </a> + ); + }, + ); + return badges; + } + private _renderConstructors( + constructors: SolidityMethod[] | TypescriptMethod[], + sectionName: string, + typeDefinitionByName: TypeDefinitionByName, + ): React.ReactNode { + const constructorDefs = _.map(constructors, constructor => { + return this._renderMethodBlocks(constructor, sectionName, constructor.isConstructor, typeDefinitionByName); + }); + return <div>{constructorDefs}</div>; + } + private _renderProperty(sectionName: string, property: Property): React.ReactNode { + return ( + <div key={`property-${property.name}-${property.type.name}`} className="pb3"> + <code className={`hljs ${constants.TYPE_TO_SYNTAX[this.props.docsInfo.type]}`}> + {property.name}: + <Type type={property.type} sectionName={sectionName} docsInfo={this.props.docsInfo} /> + </code> + {property.source && ( + <SourceLink + version={this.props.selectedVersion} + source={property.source} + sourceUrl={this.props.sourceUrl} + /> + )} + {property.comment && <Comment comment={property.comment} className="py2" />} + </div> + ); + } + private _renderMethodBlocks( + method: SolidityMethod | TypescriptMethod, + sectionName: string, + isConstructor: boolean, + typeDefinitionByName: TypeDefinitionByName, + ): React.ReactNode { + return ( + <MethodBlock + key={`method-${method.name}-${sectionName}`} + sectionName={sectionName} + method={method} + typeDefinitionByName={typeDefinitionByName} + libraryVersion={this.props.selectedVersion} + docsInfo={this.props.docsInfo} + sourceUrl={this.props.sourceUrl} + /> + ); + } + private _onSidebarHover(event: React.FormEvent<HTMLInputElement>) { + this.setState({ + isHoveringSidebar: true, + }); + } + private _onSidebarHoverOff() { + this.setState({ + isHoveringSidebar: false, + }); + } + private _onHashChanged(event: any) { + const hash = window.location.hash.slice(1); + sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); + } +} |