import {
colors,
constants as sharedConstants,
Container,
EtherscanLinkSuffixes,
HeaderSizes,
Link,
MarkdownSection,
Networks,
SectionHeader,
utils as sharedUtils,
} from '@0xproject/react-shared';
import {
DocAgnosticFormat,
Event,
ExternalExportToLink,
Property,
SolidityMethod,
TypeDefinitionByName,
TypescriptFunction,
TypescriptMethod,
} from '@0xproject/types';
import * as _ from 'lodash';
import * as React from 'react';
import * as semver from 'semver';
import { DocsInfo } from '../docs_info';
import { AddressByContractName, SupportedDocJson } from '../types';
import { constants } from '../utils/constants';
import { Badge } from './badge';
import { Comment } from './comment';
import { EventDefinition } from './event_definition';
import { PropertyBlock } from './property_block';
import { SignatureBlock } from './signature_block';
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 DocReferenceProps {
selectedVersion: string;
availableVersions: string[];
docsInfo: DocsInfo;
sourceUrl: string;
docAgnosticFormat?: DocAgnosticFormat;
}
export interface DocReferenceState {
isHoveringSidebar: boolean;
}
export class DocReference extends React.Component<DocReferenceProps, DocReferenceState> {
constructor(props: DocReferenceProps) {
super(props);
this.state = {
isHoveringSidebar: false,
};
}
public componentDidMount(): void {
window.addEventListener('hashchange', this._onHashChanged.bind(this), false);
}
public componentWillUnmount(): void {
window.removeEventListener('hashchange', this._onHashChanged.bind(this), false);
}
public componentDidUpdate(prevProps: DocReferenceProps, _prevState: DocReferenceState): void {
if (!_.isEqual(prevProps.docAgnosticFormat, this.props.docAgnosticFormat)) {
const hash = window.location.hash.slice(1);
sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID);
}
}
public render(): React.ReactNode {
const subMenus = _.values(this.props.docsInfo.markdownMenu);
const orderedSectionNames = _.flatten(subMenus);
const typeDefinitionByName = this.props.docsInfo.getTypeDefinitionsByName(this.props.docAgnosticFormat);
const renderedSections = _.map(orderedSectionNames, this._renderSection.bind(this, typeDefinitionByName));
return (
<div>
<div id={sharedConstants.SCROLL_TOP_ID} />
{renderedSections}
</div>
);
}
private _renderSection(typeDefinitionByName: TypeDefinitionByName, sectionName: string): React.ReactNode {
const markdownVersions = _.keys(this.props.docsInfo.sectionNameToMarkdownByVersion);
const eligibleVersions = _.filter(markdownVersions, mdVersion => {
return semver.lte(mdVersion, this.props.selectedVersion);
});
if (_.isEmpty(eligibleVersions)) {
throw new Error(
`No eligible markdown sections found for ${this.props.docsInfo.displayName} version ${
this.props.selectedVersion
}.`,
);
}
const sortedEligibleVersions = eligibleVersions.sort(semver.rcompare.bind(semver));
const closestVersion = sortedEligibleVersions[0];
const markdownFileIfExists = this.props.docsInfo.sectionNameToMarkdownByVersion[closestVersion][sectionName];
if (!_.isUndefined(markdownFileIfExists)) {
// Special-case replace the `introduction` sectionName with the package name
const isIntroductionSection = sectionName === 'introduction';
const finalSectionName = isIntroductionSection ? this.props.docsInfo.displayName : sectionName;
const headerSize = isIntroductionSection ? HeaderSizes.H1 : HeaderSizes.H3;
return (
<MarkdownSection
key={`markdown-section-${sectionName}`}
sectionName={finalSectionName}
headerSize={headerSize}
markdownContent={markdownFileIfExists}
shouldReformatTitle={false}
/>
);
}
const docSection = this.props.docAgnosticFormat[sectionName];
if (_.isUndefined(docSection)) {
return null;
}
const isExportedFunctionSection =
docSection.functions.length === 1 &&
_.isEmpty(docSection.types) &&
_.isEmpty(docSection.methods) &&
_.isEmpty(docSection.constructors) &&
_.isEmpty(docSection.properties) &&
_.isEmpty(docSection.events);
const sortedTypes = _.sortBy(docSection.types, 'name');
const typeDefs = _.map(sortedTypes, (customType, i) => {
return (
<TypeDefinition
sectionName={sectionName}
key={`type-${customType.name}-${i}`}
customType={customType}
docsInfo={this.props.docsInfo}
typeDefinitionByName={typeDefinitionByName}
isInPopover={false}
/>
);
});
const sortedProperties = _.sortBy(docSection.properties, 'name');
const propertyDefs = _.map(
sortedProperties,
this._renderProperty.bind(this, sectionName, typeDefinitionByName),
);
const sortedMethods = _.sortBy(docSection.methods, 'name');
const methodDefs = _.map(sortedMethods, method => {
return this._renderSignatureBlocks(method, sectionName, typeDefinitionByName);
});
const sortedFunctions = _.sortBy(docSection.functions, 'name');
const functionDefs = _.map(sortedFunctions, func => {
return this._renderSignatureBlocks(func, sectionName, 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} />}
{!_.isEmpty(docSection.constructors) && (
<div>
<h2 style={headerStyle}>Constructor</h2>
{this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)}
</div>
)}
{!_.isEmpty(docSection.properties) && (
<div>
<h2 style={headerStyle}>Properties</h2>
<div>{propertyDefs}</div>
</div>
)}
{!_.isEmpty(docSection.methods) && (
<div>
<h2 style={headerStyle}>Methods</h2>
<div>{methodDefs}</div>
</div>
)}
{!_.isEmpty(docSection.functions) && (
<div>
{!isExportedFunctionSection && <h2 style={headerStyle}>Functions</h2>}
<div>{functionDefs}</div>
</div>
)}
{!_.isUndefined(docSection.events) &&
docSection.events.length > 0 && (
<div>
<h2 style={headerStyle}>Events</h2>
<div>{eventDefs}</div>
</div>
)}
{!_.isUndefined(docSection.externalExportToLink) &&
this._renderExternalExports(docSection.externalExportToLink)}
{!_.isUndefined(typeDefs) &&
typeDefs.length > 0 && (
<div>
<div>{typeDefs}</div>
</div>
)}
<Container
width={'100%'}
height={'1px'}
backgroundColor={colors.grey300}
marginTop={'32px'}
marginBottom={'12px'}
/>
</div>
);
}
private _renderExternalExports(externalExportToLink: ExternalExportToLink): React.ReactNode {
const externalExports = _.map(externalExportToLink, (link: string, exportName: string) => {
return (
<div className="pt2" key={`external-export-${exportName}`}>
<code className={`hljs ${constants.TYPE_TO_SYNTAX[this.props.docsInfo.type]}`}>
{`import { `}
<Link to={link} shouldOpenInNewTab={true} fontColor={colors.lightBlueA700}>
{exportName}
</Link>
{` } from '${this.props.docsInfo.packageName}'`}
</code>
</div>
);
});
return <div>{externalExports}</div>;
}
private _renderNetworkBadgesIfExists(sectionName: string): React.ReactNode {
if (this.props.docsInfo.type !== SupportedDocJson.SolDoc) {
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 (
<div style={{ marginTop: 8 }}>
<Link
key={`badge-${networkName}-${sectionName}`}
to={linkIfExists}
shouldOpenInNewTab={true}
fontColor={colors.white}
>
<Badge title={networkName} backgroundColor={networkNameToColor[networkName]} />
</Link>
</div>
);
},
);
return badges;
}
private _renderConstructors(
constructors: SolidityMethod[] | TypescriptMethod[],
sectionName: string,
typeDefinitionByName: TypeDefinitionByName,
): React.ReactNode {
const constructorDefs = _.map(constructors, constructor => {
return this._renderSignatureBlocks(constructor, sectionName, typeDefinitionByName);
});
return <div>{constructorDefs}</div>;
}
private _renderProperty(
sectionName: string,
typeDefinitionByName: TypeDefinitionByName,
property: Property,
): React.ReactNode {
return (
<PropertyBlock
key={`property-${property.name}-${property.type.name}`}
property={property}
sectionName={sectionName}
docsInfo={this.props.docsInfo}
sourceUrl={this.props.sourceUrl}
selectedVersion={this.props.selectedVersion}
typeDefinitionByName={typeDefinitionByName}
/>
);
}
private _renderSignatureBlocks(
method: SolidityMethod | TypescriptFunction | TypescriptMethod,
sectionName: string,
typeDefinitionByName: TypeDefinitionByName,
): React.ReactNode {
return (
<SignatureBlock
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>): void {
this.setState({
isHoveringSidebar: true,
});
}
private _onSidebarHoverOff(): void {
this.setState({
isHoveringSidebar: false,
});
}
private _onHashChanged(_event: any): void {
const hash = window.location.hash.slice(1);
sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID);
}
}