diff options
author | Fabio Berger <me@fabioberger.com> | 2018-03-09 15:39:38 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-03-09 15:39:38 +0800 |
commit | 9699ee4eff8a6594bd862883cac35de80dfbcf56 (patch) | |
tree | 01c5a092bd0180101dcc012e3d21320d9addb073 /packages/react-docs/src/ts/components | |
parent | da277f5b2743c666a9a66e4fadf6678edd44fd69 (diff) | |
parent | 0eeaac1f2b5095441f7cd04bc948515d600d1f3a (diff) | |
download | dexon-0x-contracts-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar dexon-0x-contracts-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar.gz dexon-0x-contracts-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar.bz2 dexon-0x-contracts-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar.lz dexon-0x-contracts-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar.xz dexon-0x-contracts-9699ee4eff8a6594bd862883cac35de80dfbcf56.tar.zst dexon-0x-contracts-9699ee4eff8a6594bd862883cac35de80dfbcf56.zip |
Merge branch 'development' into addPackagePublishConfig
* development: (94 commits)
Update CHANGELOG
Add solc 0.4.20 and 0.4.21
Prettier sra-report README
Add new packages to top level README
Updated @0xproject/utils in top level package.json
Publish
Updated CHANGELOGs
Detail tests in the README
Add support for ropsten and rinkeby
Fix yarn.lock
Update list of packages and organize them alphabetically
Fix prettier issues
Add support for going back to previous hashes via the browser back button to wiki
Scroll to previous hashed elements when user clicks back button
Add back strict null checks to react-shared package and fix issues
remove ability to have implicit dependencies and add missing deps
update license
remove no-implicit-this
Add example & screenshot to npmignore
Remove `;` to be nice to windows users
...
Diffstat (limited to 'packages/react-docs/src/ts/components')
-rw-r--r-- | packages/react-docs/src/ts/components/badge.tsx | 56 | ||||
-rw-r--r-- | packages/react-docs/src/ts/components/comment.tsx | 23 | ||||
-rw-r--r-- | packages/react-docs/src/ts/components/custom_enum.tsx | 33 | ||||
-rw-r--r-- | packages/react-docs/src/ts/components/documentation.tsx | 375 | ||||
-rw-r--r-- | packages/react-docs/src/ts/components/enum.tsx | 23 | ||||
-rw-r--r-- | packages/react-docs/src/ts/components/event_definition.tsx | 84 | ||||
-rw-r--r-- | packages/react-docs/src/ts/components/interface.tsx | 63 | ||||
-rw-r--r-- | packages/react-docs/src/ts/components/method_block.tsx | 150 | ||||
-rw-r--r-- | packages/react-docs/src/ts/components/method_signature.tsx | 128 | ||||
-rw-r--r-- | packages/react-docs/src/ts/components/source_link.tsx | 23 | ||||
-rw-r--r-- | packages/react-docs/src/ts/components/type.tsx | 227 | ||||
-rw-r--r-- | packages/react-docs/src/ts/components/type_definition.tsx | 131 |
12 files changed, 1316 insertions, 0 deletions
diff --git a/packages/react-docs/src/ts/components/badge.tsx b/packages/react-docs/src/ts/components/badge.tsx new file mode 100644 index 000000000..b342f2dca --- /dev/null +++ b/packages/react-docs/src/ts/components/badge.tsx @@ -0,0 +1,56 @@ +import { Styles } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +const styles: Styles = { + badge: { + width: 50, + fontSize: 11, + height: 10, + borderRadius: 5, + lineHeight: 0.9, + fontFamily: 'Roboto Mono', + marginLeft: 3, + marginRight: 3, + }, +}; + +export interface BadgeProps { + title: string; + backgroundColor: string; +} + +export interface BadgeState { + isHovering: boolean; +} + +export class Badge extends React.Component<BadgeProps, BadgeState> { + constructor(props: BadgeProps) { + super(props); + this.state = { + isHovering: false, + }; + } + public render() { + const badgeStyle = { + ...styles.badge, + backgroundColor: this.props.backgroundColor, + opacity: this.state.isHovering ? 0.7 : 1, + }; + return ( + <div + className="p1 center" + style={badgeStyle} + onMouseOver={this._setHoverState.bind(this, true)} + onMouseOut={this._setHoverState.bind(this, false)} + > + {this.props.title} + </div> + ); + } + private _setHoverState(isHovering: boolean) { + this.setState({ + isHovering, + }); + } +} diff --git a/packages/react-docs/src/ts/components/comment.tsx b/packages/react-docs/src/ts/components/comment.tsx new file mode 100644 index 000000000..0d63d4d31 --- /dev/null +++ b/packages/react-docs/src/ts/components/comment.tsx @@ -0,0 +1,23 @@ +import { MarkdownCodeBlock } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactMarkdown from 'react-markdown'; + +export interface CommentProps { + comment: string; + className?: string; +} + +const defaultProps = { + className: '', +}; + +export const Comment: React.SFC<CommentProps> = (props: CommentProps) => { + return ( + <div className={`${props.className} comment`}> + <ReactMarkdown source={props.comment} renderers={{ code: MarkdownCodeBlock }} /> + </div> + ); +}; + +Comment.defaultProps = defaultProps; diff --git a/packages/react-docs/src/ts/components/custom_enum.tsx b/packages/react-docs/src/ts/components/custom_enum.tsx new file mode 100644 index 000000000..deb33ff1d --- /dev/null +++ b/packages/react-docs/src/ts/components/custom_enum.tsx @@ -0,0 +1,33 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { CustomType } from '../types'; +import { utils } from '../utils/utils'; + +const STRING_ENUM_CODE_PREFIX = ' strEnum('; + +export interface CustomEnumProps { + type: CustomType; +} + +// This component renders custom string enums that was a work-around for versions of +// TypeScript <2.4.0 that did not support them natively. We keep it around to support +// older versions of 0x.js <0.9.0 +export function CustomEnum(props: CustomEnumProps) { + const type = props.type; + if (!_.startsWith(type.defaultValue, STRING_ENUM_CODE_PREFIX)) { + utils.consoleLog('We do not yet support `Variable` types that are not strEnums'); + return null; + } + // Remove the prefix and postfix, leaving only the strEnum values without quotes. + const enumValues = type.defaultValue.slice(10, -3).replace(/'/g, ''); + return ( + <span> + {`{`} + {'\t'} + {enumValues} + <br /> + {`}`} + </span> + ); +} diff --git a/packages/react-docs/src/ts/components/documentation.tsx b/packages/react-docs/src/ts/components/documentation.tsx new file mode 100644 index 000000000..b46358159 --- /dev/null +++ b/packages/react-docs/src/ts/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); + } +} diff --git a/packages/react-docs/src/ts/components/enum.tsx b/packages/react-docs/src/ts/components/enum.tsx new file mode 100644 index 000000000..37f82f26e --- /dev/null +++ b/packages/react-docs/src/ts/components/enum.tsx @@ -0,0 +1,23 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { EnumValue } from '../types'; + +export interface EnumProps { + values: EnumValue[]; +} + +export function Enum(props: EnumProps) { + const values = _.map(props.values, (value, i) => { + const defaultValueIfAny = !_.isUndefined(value.defaultValue) ? ` = ${value.defaultValue}` : ''; + return `\n\t${value.name}${defaultValueIfAny},`; + }); + return ( + <span> + {`{`} + {values} + <br /> + {`}`} + </span> + ); +} diff --git a/packages/react-docs/src/ts/components/event_definition.tsx b/packages/react-docs/src/ts/components/event_definition.tsx new file mode 100644 index 000000000..67729ac87 --- /dev/null +++ b/packages/react-docs/src/ts/components/event_definition.tsx @@ -0,0 +1,84 @@ +import { AnchorTitle, colors, HeaderSizes } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { DocsInfo } from '../docs_info'; +import { Event, EventArg } from '../types'; + +import { Type } from './type'; + +export interface EventDefinitionProps { + event: Event; + sectionName: string; + docsInfo: DocsInfo; +} + +export interface EventDefinitionState { + shouldShowAnchor: boolean; +} + +export class EventDefinition extends React.Component<EventDefinitionProps, EventDefinitionState> { + constructor(props: EventDefinitionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const event = this.props.event; + const id = `${this.props.sectionName}-${event.name}`; + return ( + <div + id={id} + className="pb2" + style={{ overflow: 'hidden', width: '100%' }} + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <AnchorTitle + headerSize={HeaderSizes.H3} + title={`Event ${event.name}`} + id={id} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + <div style={{ fontSize: 16 }}> + <pre> + <code className="hljs solidity">{this._renderEventCode()}</code> + </pre> + </div> + </div> + ); + } + private _renderEventCode() { + const indexed = <span style={{ color: colors.green }}> indexed</span>; + const eventArgs = _.map(this.props.event.eventArgs, (eventArg: EventArg) => { + const type = ( + <Type type={eventArg.type} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} /> + ); + return ( + <span key={`eventArg-${eventArg.name}`}> + {eventArg.name} + {eventArg.isIndexed ? indexed : ''}: {type}, + </span> + ); + }); + const argList = _.reduce(eventArgs, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '\n\t', curr]; + }); + return ( + <span> + {`{`} + <br /> + {'\t'} + {argList} + <br /> + {`}`} + </span> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/react-docs/src/ts/components/interface.tsx b/packages/react-docs/src/ts/components/interface.tsx new file mode 100644 index 000000000..01f4942ef --- /dev/null +++ b/packages/react-docs/src/ts/components/interface.tsx @@ -0,0 +1,63 @@ +import * as _ from 'lodash'; +import * as React from 'react'; + +import { DocsInfo } from '../docs_info'; +import { CustomType, TypeDocTypes } from '../types'; + +import { MethodSignature } from './method_signature'; +import { Type } from './type'; + +export interface InterfaceProps { + type: CustomType; + sectionName: string; + docsInfo: DocsInfo; +} + +export function Interface(props: InterfaceProps) { + const type = props.type; + const properties = _.map(type.children, property => { + return ( + <span key={`property-${property.name}-${property.type}-${type.name}`}> + {property.name}:{' '} + {property.type.typeDocType !== TypeDocTypes.Reflection ? ( + <Type type={property.type} sectionName={props.sectionName} docsInfo={props.docsInfo} /> + ) : ( + <MethodSignature + method={property.type.method} + sectionName={props.sectionName} + shouldHideMethodName={true} + shouldUseArrowSyntax={true} + docsInfo={props.docsInfo} + /> + )}, + </span> + ); + }); + const hasIndexSignature = !_.isUndefined(type.indexSignature); + if (hasIndexSignature) { + const is = type.indexSignature; + const param = ( + <span key={`indexSigParams-${is.keyName}-${is.keyType}-${type.name}`}> + {is.keyName}: <Type type={is.keyType} sectionName={props.sectionName} docsInfo={props.docsInfo} /> + </span> + ); + properties.push( + <span key={`indexSignature-${type.name}-${is.keyType.name}`}> + [{param}]: {is.valueName}, + </span>, + ); + } + const propertyList = _.reduce(properties, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '\n\t', curr]; + }); + return ( + <span> + {`{`} + <br /> + {'\t'} + {propertyList} + <br /> + {`}`} + </span> + ); +} diff --git a/packages/react-docs/src/ts/components/method_block.tsx b/packages/react-docs/src/ts/components/method_block.tsx new file mode 100644 index 000000000..44a1db8af --- /dev/null +++ b/packages/react-docs/src/ts/components/method_block.tsx @@ -0,0 +1,150 @@ +import { AnchorTitle, colors, HeaderSizes, Styles } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { DocsInfo } from '../docs_info'; +import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from '../types'; +import { constants } from '../utils/constants'; +import { typeDocUtils } from '../utils/typedoc_utils'; + +import { Comment } from './comment'; +import { MethodSignature } from './method_signature'; +import { SourceLink } from './source_link'; + +export interface MethodBlockProps { + method: SolidityMethod | TypescriptMethod; + sectionName: string; + libraryVersion: string; + typeDefinitionByName: TypeDefinitionByName; + docsInfo: DocsInfo; + sourceUrl: string; +} + +export interface MethodBlockState { + shouldShowAnchor: boolean; +} + +const styles: Styles = { + chip: { + fontSize: 13, + backgroundColor: colors.lightBlueA700, + color: colors.white, + height: 11, + borderRadius: 14, + lineHeight: 0.9, + }, +}; + +export class MethodBlock extends React.Component<MethodBlockProps, MethodBlockState> { + constructor(props: MethodBlockProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const method = this.props.method; + if (typeDocUtils.isPrivateOrProtectedProperty(method.name)) { + return null; + } + + return ( + <div + id={`${this.props.sectionName}-${method.name}`} + style={{ overflow: 'hidden', width: '100%' }} + className="pb4" + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + {!method.isConstructor && ( + <div className="flex pb2 pt2"> + {(method as TypescriptMethod).isStatic && this._renderChip('Static')} + {(method as SolidityMethod).isConstant && this._renderChip('Constant')} + {(method as SolidityMethod).isPayable && this._renderChip('Payable')} + <div style={{ lineHeight: 1.3 }}> + <AnchorTitle + headerSize={HeaderSizes.H3} + title={method.name} + id={`${this.props.sectionName}-${method.name}`} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + </div> + </div> + )} + <code className={`hljs ${constants.TYPE_TO_SYNTAX[this.props.docsInfo.type]}`}> + <MethodSignature + method={method} + sectionName={this.props.sectionName} + typeDefinitionByName={this.props.typeDefinitionByName} + docsInfo={this.props.docsInfo} + /> + </code> + {(method as TypescriptMethod).source && ( + <SourceLink + version={this.props.libraryVersion} + source={(method as TypescriptMethod).source} + sourceUrl={this.props.sourceUrl} + /> + )} + {method.comment && <Comment comment={method.comment} className="py2" />} + {method.parameters && + !_.isEmpty(method.parameters) && ( + <div> + <h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}> + ARGUMENTS + </h4> + {this._renderParameterDescriptions(method.parameters)} + </div> + )} + {method.returnComment && ( + <div className="pt1 comment"> + <h4 className="pb1 thin" style={{ borderBottom: '1px solid #e1e8ed' }}> + RETURNS + </h4> + <Comment comment={method.returnComment} /> + </div> + )} + </div> + ); + } + private _renderChip(text: string) { + return ( + <div className="p1 mr1" style={styles.chip}> + {text} + </div> + ); + } + private _renderParameterDescriptions(parameters: Parameter[]) { + const descriptions = _.map(parameters, parameter => { + const isOptional = parameter.isOptional; + return ( + <div + key={`param-description-${parameter.name}`} + className="flex pb1 mb2" + style={{ borderBottom: '1px solid #f0f4f7' }} + > + <div className="pl2 col lg-col-4 md-col-4 sm-col-12 col-12"> + <div + className="bold" + style={{ overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis' }} + > + {parameter.name} + </div> + <div className="pt1" style={{ color: colors.grey, fontSize: 14 }}> + {isOptional && 'optional'} + </div> + </div> + <div className="col lg-col-8 md-col-8 sm-col-12 col-12" style={{ paddingLeft: 5 }}> + {parameter.comment && <Comment comment={parameter.comment} />} + </div> + </div> + ); + }); + return descriptions; + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} diff --git a/packages/react-docs/src/ts/components/method_signature.tsx b/packages/react-docs/src/ts/components/method_signature.tsx new file mode 100644 index 000000000..1400182ea --- /dev/null +++ b/packages/react-docs/src/ts/components/method_signature.tsx @@ -0,0 +1,128 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +import { DocsInfo } from '../docs_info'; +import { Parameter, SolidityMethod, TypeDefinitionByName, TypescriptMethod } from '../types'; +import { constants } from '../utils/constants'; + +import { Type } from './type'; + +export interface MethodSignatureProps { + method: TypescriptMethod | SolidityMethod; + sectionName: string; + shouldHideMethodName?: boolean; + shouldUseArrowSyntax?: boolean; + typeDefinitionByName?: TypeDefinitionByName; + docsInfo: DocsInfo; +} + +const defaultProps = { + shouldHideMethodName: false, + shouldUseArrowSyntax: false, +}; + +export const MethodSignature: React.SFC<MethodSignatureProps> = (props: MethodSignatureProps) => { + const sectionName = constants.TYPES_SECTION_NAME; + const parameters = renderParameters(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); + const paramStringArray: any[] = []; + // HACK: For now we don't put params on newlines if there are less then 2 of them. + // Ideally we would check the character length of the resulting method signature and + // if it exceeds the available space, put params on their own lines. + const hasMoreThenTwoParams = parameters.length > 2; + _.each(parameters, (param: React.ReactNode, i: number) => { + const finalParam = hasMoreThenTwoParams ? ( + <span className="pl2" key={`param-${i}`}> + {param} + </span> + ) : ( + param + ); + paramStringArray.push(finalParam); + const comma = hasMoreThenTwoParams ? ( + <span key={`param-comma-${i}`}> + , <br /> + </span> + ) : ( + ', ' + ); + paramStringArray.push(comma); + }); + if (!hasMoreThenTwoParams) { + paramStringArray.pop(); + } + const methodName = props.shouldHideMethodName ? '' : props.method.name; + const typeParameterIfExists = _.isUndefined((props.method as TypescriptMethod).typeParameter) + ? undefined + : renderTypeParameter(props.method, props.docsInfo, sectionName, props.typeDefinitionByName); + return ( + <span style={{ fontSize: 15 }}> + {props.method.callPath} + {methodName} + {typeParameterIfExists}({hasMoreThenTwoParams && <br />} + {paramStringArray}) + {props.method.returnType && ( + <span> + {props.shouldUseArrowSyntax ? ' => ' : ': '}{' '} + <Type + type={props.method.returnType} + sectionName={sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + /> + </span> + )} + </span> + ); +}; + +MethodSignature.defaultProps = defaultProps; + +function renderParameters( + method: TypescriptMethod | SolidityMethod, + docsInfo: DocsInfo, + sectionName: string, + typeDefinitionByName?: TypeDefinitionByName, +) { + const parameters = method.parameters; + const params = _.map(parameters, (p: Parameter) => { + const isOptional = p.isOptional; + const type = ( + <Type + type={p.type} + sectionName={sectionName} + typeDefinitionByName={typeDefinitionByName} + docsInfo={docsInfo} + /> + ); + return ( + <span key={`param-${p.type}-${p.name}`}> + {p.name} + {isOptional && '?'}: {type} + </span> + ); + }); + return params; +} + +function renderTypeParameter( + method: TypescriptMethod, + docsInfo: DocsInfo, + sectionName: string, + typeDefinitionByName?: TypeDefinitionByName, +) { + const typeParameter = method.typeParameter; + const typeParam = ( + <span> + {`<${typeParameter.name} extends `} + <Type + type={typeParameter.type} + sectionName={sectionName} + typeDefinitionByName={typeDefinitionByName} + docsInfo={docsInfo} + /> + {`>`} + </span> + ); + return typeParam; +} diff --git a/packages/react-docs/src/ts/components/source_link.tsx b/packages/react-docs/src/ts/components/source_link.tsx new file mode 100644 index 000000000..89956a507 --- /dev/null +++ b/packages/react-docs/src/ts/components/source_link.tsx @@ -0,0 +1,23 @@ +import { colors } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { Source } from '../types'; + +export interface SourceLinkProps { + source: Source; + sourceUrl: string; + version: string; +} + +export function SourceLink(props: SourceLinkProps) { + const src = props.source; + const sourceCodeUrl = `${props.sourceUrl}/${src.fileName}#L${src.line}`; + return ( + <div className="pt2" style={{ fontSize: 14 }}> + <a href={sourceCodeUrl} target="_blank" className="underline" style={{ color: colors.grey }}> + Source + </a> + </div> + ); +} diff --git a/packages/react-docs/src/ts/components/type.tsx b/packages/react-docs/src/ts/components/type.tsx new file mode 100644 index 000000000..56425a5df --- /dev/null +++ b/packages/react-docs/src/ts/components/type.tsx @@ -0,0 +1,227 @@ +import { colors, constants as sharedConstants, utils as sharedUtils } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; +import { Link as ScrollLink } from 'react-scroll'; +import * as ReactTooltip from 'react-tooltip'; + +import { DocsInfo } from '../docs_info'; +import { Type as TypeDef, TypeDefinitionByName, TypeDocTypes } from '../types'; +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; + +import { TypeDefinition } from './type_definition'; + +const typeToSection: { [typeName: string]: string } = { + ExchangeWrapper: 'exchange', + TokenWrapper: 'token', + TokenRegistryWrapper: 'tokenRegistry', + EtherTokenWrapper: 'etherToken', + ProxyWrapper: 'proxy', + TokenTransferProxyWrapper: 'proxy', + OrderStateWatcher: 'orderWatcher', +}; + +export interface TypeProps { + type: TypeDef; + docsInfo: DocsInfo; + sectionName: string; + typeDefinitionByName?: TypeDefinitionByName; +} + +// The return type needs to be `any` here so that we can recursively define <Type /> components within +// <Type /> components (e.g when rendering the union type). +export function Type(props: TypeProps): any { + const type = props.type; + const isReference = type.typeDocType === TypeDocTypes.Reference; + const isArray = type.typeDocType === TypeDocTypes.Array; + let typeNameColor = 'inherit'; + let typeName: string | React.ReactNode; + let typeArgs: React.ReactNode[] = []; + switch (type.typeDocType) { + case TypeDocTypes.Intrinsic: + case TypeDocTypes.Unknown: + typeName = type.name; + typeNameColor = colors.orange; + break; + + case TypeDocTypes.Reference: + typeName = type.name; + typeArgs = _.map(type.typeArguments, (arg: TypeDef) => { + if (arg.typeDocType === TypeDocTypes.Array) { + const key = `type-${arg.elementType.name}-${arg.elementType.typeDocType}`; + return ( + <span> + <Type + key={key} + type={arg.elementType} + sectionName={props.sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + />[] + </span> + ); + } else { + const subType = ( + <Type + key={`type-${arg.name}-${arg.value}-${arg.typeDocType}`} + type={arg} + sectionName={props.sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + /> + ); + return subType; + } + }); + break; + + case TypeDocTypes.StringLiteral: + typeName = `'${type.value}'`; + typeNameColor = colors.green; + break; + + case TypeDocTypes.Array: + typeName = type.elementType.name; + break; + + case TypeDocTypes.Union: + const unionTypes = _.map(type.types, t => { + return ( + <Type + key={`type-${t.name}-${t.value}-${t.typeDocType}`} + type={t} + sectionName={props.sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + /> + ); + }); + typeName = _.reduce(unionTypes, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '|', curr]; + }); + break; + + case TypeDocTypes.TypeParameter: + typeName = type.name; + break; + + case TypeDocTypes.Intersection: + const intersectionsTypes = _.map(type.types, t => { + return ( + <Type + key={`type-${t.name}-${t.value}-${t.typeDocType}`} + type={t} + sectionName={props.sectionName} + typeDefinitionByName={props.typeDefinitionByName} + docsInfo={props.docsInfo} + /> + ); + }); + typeName = _.reduce(intersectionsTypes, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, '&', curr]; + }); + break; + + default: + throw utils.spawnSwitchErr('type.typeDocType', type.typeDocType); + } + // HACK: Normalize BigNumber to simply BigNumber. For some reason the type + // name is unpredictably one or the other. + if (typeName === 'BigNumber') { + typeName = 'BigNumber'; + } + const commaSeparatedTypeArgs = _.reduce(typeArgs, (prev: React.ReactNode, curr: React.ReactNode) => { + return [prev, ', ', curr]; + }); + + let typeNameUrlIfExists; + let typePrefixIfExists; + let sectionNameIfExists; + if (!_.isUndefined(props.docsInfo.typeConfigs)) { + typeNameUrlIfExists = !_.isUndefined(props.docsInfo.typeConfigs.typeNameToExternalLink) + ? props.docsInfo.typeConfigs.typeNameToExternalLink[typeName as string] + : undefined; + typePrefixIfExists = !_.isUndefined(props.docsInfo.typeConfigs.typeNameToPrefix) + ? props.docsInfo.typeConfigs.typeNameToPrefix[typeName as string] + : undefined; + sectionNameIfExists = !_.isUndefined(props.docsInfo.typeConfigs.typeNameToDocSection) + ? props.docsInfo.typeConfigs.typeNameToDocSection[typeName as string] + : undefined; + } + if (!_.isUndefined(typeNameUrlIfExists)) { + typeName = ( + <a + href={typeNameUrlIfExists} + target="_blank" + className="text-decoration-none" + style={{ color: colors.lightBlueA700 }} + > + {!_.isUndefined(typePrefixIfExists) ? `${typePrefixIfExists}.` : ''} + {typeName} + </a> + ); + } else if ( + (isReference || isArray) && + (props.docsInfo.isPublicType(typeName as string) || !_.isUndefined(sectionNameIfExists)) + ) { + const id = Math.random().toString(); + const typeDefinitionAnchorId = _.isUndefined(sectionNameIfExists) + ? `${props.sectionName}-${typeName}` + : sectionNameIfExists; + let typeDefinition; + if (props.typeDefinitionByName) { + typeDefinition = props.typeDefinitionByName[typeName as string]; + } + typeName = ( + <ScrollLink + to={typeDefinitionAnchorId} + offset={0} + duration={sharedConstants.DOCS_SCROLL_DURATION_MS} + containerId={sharedConstants.DOCS_CONTAINER_ID} + > + {_.isUndefined(typeDefinition) || sharedUtils.isUserOnMobile() ? ( + <span + onClick={sharedUtils.setUrlHash.bind(null, typeDefinitionAnchorId)} + style={{ color: colors.lightBlueA700, cursor: 'pointer' }} + > + {typeName} + </span> + ) : ( + <span + data-tip={true} + data-for={id} + onClick={sharedUtils.setUrlHash.bind(null, typeDefinitionAnchorId)} + style={{ + color: colors.lightBlueA700, + cursor: 'pointer', + display: 'inline-block', + }} + > + {typeName} + <ReactTooltip type="light" effect="solid" id={id} className="typeTooltip"> + <TypeDefinition + sectionName={props.sectionName} + customType={typeDefinition} + shouldAddId={false} + docsInfo={props.docsInfo} + /> + </ReactTooltip> + </span> + )} + </ScrollLink> + ); + } + return ( + <span> + <span style={{ color: typeNameColor }}>{typeName}</span> + {isArray && '[]'} + {!_.isEmpty(typeArgs) && ( + <span> + {'<'} + {commaSeparatedTypeArgs} + {'>'} + </span> + )} + </span> + ); +} diff --git a/packages/react-docs/src/ts/components/type_definition.tsx b/packages/react-docs/src/ts/components/type_definition.tsx new file mode 100644 index 000000000..68ef4c465 --- /dev/null +++ b/packages/react-docs/src/ts/components/type_definition.tsx @@ -0,0 +1,131 @@ +import { AnchorTitle, colors, HeaderSizes } from '@0xproject/react-shared'; +import * as _ from 'lodash'; +import * as React from 'react'; + +import { DocsInfo } from '../docs_info'; +import { CustomType, CustomTypeChild, KindString, TypeDocTypes } from '../types'; +import { constants } from '../utils/constants'; +import { utils } from '../utils/utils'; + +import { Comment } from './comment'; +import { CustomEnum } from './custom_enum'; +import { Enum } from './enum'; +import { Interface } from './interface'; +import { MethodSignature } from './method_signature'; +import { Type } from './type'; + +export interface TypeDefinitionProps { + sectionName: string; + customType: CustomType; + shouldAddId?: boolean; + docsInfo: DocsInfo; +} + +export interface TypeDefinitionState { + shouldShowAnchor: boolean; +} + +export class TypeDefinition extends React.Component<TypeDefinitionProps, TypeDefinitionState> { + public static defaultProps: Partial<TypeDefinitionProps> = { + shouldAddId: true, + }; + constructor(props: TypeDefinitionProps) { + super(props); + this.state = { + shouldShowAnchor: false, + }; + } + public render() { + const customType = this.props.customType; + if (!this.props.docsInfo.isPublicType(customType.name)) { + return null; // no-op + } + + let typePrefix: string; + let codeSnippet: React.ReactNode; + switch (customType.kindString) { + case KindString.Interface: + typePrefix = 'Interface'; + codeSnippet = ( + <Interface type={customType} sectionName={this.props.sectionName} docsInfo={this.props.docsInfo} /> + ); + break; + + case KindString.Variable: + typePrefix = 'Enum'; + codeSnippet = <CustomEnum type={customType} />; + break; + + case KindString.Enumeration: + typePrefix = 'Enum'; + const enumValues = _.map(customType.children, (c: CustomTypeChild) => { + return { + name: c.name, + defaultValue: c.defaultValue, + }; + }); + codeSnippet = <Enum values={enumValues} />; + break; + + case KindString.TypeAlias: + typePrefix = 'Type Alias'; + codeSnippet = ( + <span> + <span style={{ color: colors.lightPurple }}>type</span> {customType.name} ={' '} + {customType.type.typeDocType !== TypeDocTypes.Reflection ? ( + <Type + type={customType.type} + sectionName={this.props.sectionName} + docsInfo={this.props.docsInfo} + /> + ) : ( + <MethodSignature + method={customType.type.method} + sectionName={this.props.sectionName} + shouldHideMethodName={true} + shouldUseArrowSyntax={true} + docsInfo={this.props.docsInfo} + /> + )} + </span> + ); + break; + + default: + throw utils.spawnSwitchErr('type.kindString', customType.kindString); + } + + const typeDefinitionAnchorId = `${this.props.sectionName}-${customType.name}`; + return ( + <div + id={this.props.shouldAddId ? typeDefinitionAnchorId : ''} + className="pb2" + style={{ overflow: 'hidden', width: '100%' }} + onMouseOver={this._setAnchorVisibility.bind(this, true)} + onMouseOut={this._setAnchorVisibility.bind(this, false)} + > + <AnchorTitle + headerSize={HeaderSizes.H3} + title={`${typePrefix} ${customType.name}`} + id={this.props.shouldAddId ? typeDefinitionAnchorId : ''} + shouldShowAnchor={this.state.shouldShowAnchor} + /> + <div style={{ fontSize: 16 }}> + <pre> + <code className={`hljs ${constants.TYPE_TO_SYNTAX[this.props.docsInfo.type]}`}> + {codeSnippet} + </code> + </pre> + </div> + <div style={{ maxWidth: 620 }}> + {customType.comment && <Comment comment={customType.comment} className="py2" />} + </div> + </div> + ); + } + private _setAnchorVisibility(shouldShowAnchor: boolean) { + this.setState({ + shouldShowAnchor, + }); + } +} |