* development: Update and standardize contracts README Add to CHANGELOG Refactor toBaseUnitAmount so that it throws if user supplies unitAmount with too many decimals Make assertion stricter so that one cannot submit invalid baseUnit amounts to `toUnitAmount` Add some missed underscores, update changelog and comments Add new underscore-privates rule to @0xproject/tslint-config and fix lint errors # Conflicts: # packages/website/ts/pages/documentation/documentation.tsx # packages/website/ts/pages/shared/nested_sidebar_menu.tsx
398 lines
16 KiB
TypeScript
398 lines
16 KiB
TypeScript
import findVersions = require('find-versions');
|
|
import * as _ from 'lodash';
|
|
import CircularProgress from 'material-ui/CircularProgress';
|
|
import * as React from 'react';
|
|
import DocumentTitle = require('react-document-title');
|
|
import {
|
|
scroller,
|
|
} from 'react-scroll';
|
|
import semverSort = require('semver-sort');
|
|
import {TopBar} from 'ts/components/top_bar';
|
|
import {Badge} from 'ts/components/ui/badge';
|
|
import {Comment} from 'ts/pages/documentation/comment';
|
|
import {DocsInfo} from 'ts/pages/documentation/docs_info';
|
|
import {EventDefinition} from 'ts/pages/documentation/event_definition';
|
|
import {MethodBlock} from 'ts/pages/documentation/method_block';
|
|
import {SourceLink} from 'ts/pages/documentation/source_link';
|
|
import {Type} from 'ts/pages/documentation/type';
|
|
import {TypeDefinition} from 'ts/pages/documentation/type_definition';
|
|
import {MarkdownSection} from 'ts/pages/shared/markdown_section';
|
|
import {NestedSidebarMenu} from 'ts/pages/shared/nested_sidebar_menu';
|
|
import {SectionHeader} from 'ts/pages/shared/section_header';
|
|
import {Dispatcher} from 'ts/redux/dispatcher';
|
|
import {
|
|
AddressByContractName,
|
|
DocAgnosticFormat,
|
|
DoxityDocObj,
|
|
EtherscanLinkSuffixes,
|
|
Event,
|
|
Networks,
|
|
Property,
|
|
SolidityMethod,
|
|
Styles,
|
|
TypeDefinitionByName,
|
|
TypescriptMethod,
|
|
} from 'ts/types';
|
|
import {colors} from 'ts/utils/colors';
|
|
import {configs} from 'ts/utils/configs';
|
|
import {constants} from 'ts/utils/constants';
|
|
import {docUtils} from 'ts/utils/doc_utils';
|
|
import {utils} from 'ts/utils/utils';
|
|
|
|
const SCROLL_TOP_ID = 'docsScrollTop';
|
|
|
|
const networkNameToColor: {[network: string]: string} = {
|
|
[Networks.kovan]: colors.purple,
|
|
[Networks.ropsten]: colors.red,
|
|
[Networks.mainnet]: colors.turquois,
|
|
};
|
|
|
|
export interface DocumentationAllProps {
|
|
source: string;
|
|
location: Location;
|
|
dispatcher: Dispatcher;
|
|
docsVersion: string;
|
|
availableDocVersions: string[];
|
|
docsInfo: DocsInfo;
|
|
}
|
|
|
|
interface DocumentationState {
|
|
docAgnosticFormat?: DocAgnosticFormat;
|
|
}
|
|
|
|
const styles: Styles = {
|
|
mainContainers: {
|
|
position: 'absolute',
|
|
top: 1,
|
|
left: 0,
|
|
bottom: 0,
|
|
right: 0,
|
|
overflowZ: 'hidden',
|
|
overflowY: 'scroll',
|
|
minHeight: 'calc(100vh - 1px)',
|
|
WebkitOverflowScrolling: 'touch',
|
|
},
|
|
menuContainer: {
|
|
borderColor: colors.grey300,
|
|
maxWidth: 330,
|
|
marginLeft: 20,
|
|
},
|
|
};
|
|
|
|
export class Documentation extends
|
|
React.Component<DocumentationAllProps, DocumentationState> {
|
|
constructor(props: DocumentationAllProps) {
|
|
super(props);
|
|
this.state = {
|
|
docAgnosticFormat: undefined,
|
|
};
|
|
}
|
|
public componentWillMount() {
|
|
const pathName = this.props.location.pathname;
|
|
const lastSegment = pathName.substr(pathName.lastIndexOf('/') + 1);
|
|
const versions = findVersions(lastSegment);
|
|
const preferredVersionIfExists = versions.length > 0 ? versions[0] : undefined;
|
|
// tslint:disable-next-line:no-floating-promises
|
|
this._fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists);
|
|
}
|
|
public render() {
|
|
const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) ?
|
|
{} :
|
|
this.props.docsInfo.getMenuSubsectionsBySection(this.state.docAgnosticFormat);
|
|
return (
|
|
<div>
|
|
<DocumentTitle title={`${this.props.docsInfo.displayName} Documentation`}/>
|
|
<TopBar
|
|
blockchainIsLoaded={false}
|
|
location={this.props.location}
|
|
docsVersion={this.props.docsVersion}
|
|
availableDocVersions={this.props.availableDocVersions}
|
|
menu={this.props.docsInfo.getMenu(this.props.docsVersion)}
|
|
menuSubsectionsBySection={menuSubsectionsBySection}
|
|
shouldFullWidth={true}
|
|
docsInfo={this.props.docsInfo}
|
|
/>
|
|
{_.isUndefined(this.state.docAgnosticFormat) ?
|
|
<div
|
|
className="col col-12"
|
|
style={styles.mainContainers}
|
|
>
|
|
<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> :
|
|
<div
|
|
className="mx-auto flex"
|
|
style={{color: colors.grey800, height: 43}}
|
|
>
|
|
<div className="relative col md-col-3 lg-col-3 lg-pl0 md-pl1 sm-hide xs-hide">
|
|
<div
|
|
className="border-right absolute"
|
|
style={{...styles.menuContainer, ...styles.mainContainers}}
|
|
>
|
|
<NestedSidebarMenu
|
|
selectedVersion={this.props.docsVersion}
|
|
versions={this.props.availableDocVersions}
|
|
topLevelMenu={this.props.docsInfo.getMenu(this.props.docsVersion)}
|
|
menuSubsectionsBySection={menuSubsectionsBySection}
|
|
docPath={this.props.docsInfo.websitePath}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="relative col lg-col-9 md-col-9 sm-col-12 col-12">
|
|
<div
|
|
id="documentation"
|
|
style={styles.mainContainers}
|
|
className="absolute"
|
|
>
|
|
<div id={SCROLL_TOP_ID} />
|
|
<h1 className="md-pl2 sm-pl3">
|
|
<a href={this.props.docsInfo.packageUrl} target="_blank">
|
|
{this.props.docsInfo.displayName}
|
|
</a>
|
|
</h1>
|
|
{this._renderDocumentation()}
|
|
</div>
|
|
</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.state.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.state.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}
|
|
/>
|
|
);
|
|
});
|
|
return (
|
|
<div
|
|
key={`section-${sectionName}`}
|
|
className="py2 pr3 md-pl2 sm-pl3"
|
|
>
|
|
<div className="flex">
|
|
<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 className="thin">Constructor</h2>
|
|
{this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)}
|
|
</div>
|
|
}
|
|
{docSection.properties.length > 0 &&
|
|
<div>
|
|
<h2 className="thin">Properties</h2>
|
|
<div>{propertyDefs}</div>
|
|
</div>
|
|
}
|
|
{docSection.methods.length > 0 &&
|
|
<div>
|
|
<h2 className="thin">Methods</h2>
|
|
<div>{methodDefs}</div>
|
|
</div>
|
|
}
|
|
{!_.isUndefined(docSection.events) && docSection.events.length > 0 &&
|
|
<div>
|
|
<h2 className="thin">Events</h2>
|
|
<div>{eventDefs}</div>
|
|
</div>
|
|
}
|
|
{!_.isUndefined(typeDefs) && typeDefs.length > 0 &&
|
|
<div>
|
|
<div>{typeDefs}</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
);
|
|
}
|
|
private _renderNetworkBadgesIfExists(sectionName: string) {
|
|
const networkToAddressByContractName = configs.CONTRACT_ADDRESS[this.props.docsVersion];
|
|
const badges = _.map(networkToAddressByContractName,
|
|
(addressByContractName: AddressByContractName, networkName: string) => {
|
|
const contractAddress = addressByContractName[sectionName];
|
|
if (_.isUndefined(contractAddress)) {
|
|
return null;
|
|
}
|
|
const linkIfExists = utils.getEtherScanLinkIfExists(
|
|
contractAddress, constants.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">
|
|
{property.name}:
|
|
<Type
|
|
type={property.type}
|
|
sectionName={sectionName}
|
|
docsInfo={this.props.docsInfo}
|
|
/>
|
|
</code>
|
|
{property.source &&
|
|
<SourceLink
|
|
version={this.props.docsVersion}
|
|
source={property.source}
|
|
baseUrl={this.props.docsInfo.packageUrl}
|
|
subPackageName={this.props.docsInfo.subPackageName}
|
|
/>
|
|
}
|
|
{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.docsVersion}
|
|
docsInfo={this.props.docsInfo}
|
|
/>
|
|
);
|
|
}
|
|
private _scrollToHash(): void {
|
|
const hashWithPrefix = this.props.location.hash;
|
|
let hash = hashWithPrefix.slice(1);
|
|
if (_.isEmpty(hash)) {
|
|
hash = SCROLL_TOP_ID; // scroll to the top
|
|
}
|
|
|
|
scroller.scrollTo(hash, {duration: 0, offset: 0, containerId: 'documentation'});
|
|
}
|
|
private async _fetchJSONDocsFireAndForgetAsync(preferredVersionIfExists?: string): Promise<void> {
|
|
const versionToFileName = await docUtils.getVersionToFileNameAsync(this.props.docsInfo.docsJsonRoot);
|
|
const versions = _.keys(versionToFileName);
|
|
this.props.dispatcher.updateAvailableDocVersions(versions);
|
|
const sortedVersions = semverSort.desc(versions);
|
|
const latestVersion = sortedVersions[0];
|
|
|
|
let versionToFetch = latestVersion;
|
|
if (!_.isUndefined(preferredVersionIfExists)) {
|
|
const preferredVersionFileNameIfExists = versionToFileName[preferredVersionIfExists];
|
|
if (!_.isUndefined(preferredVersionFileNameIfExists)) {
|
|
versionToFetch = preferredVersionIfExists;
|
|
}
|
|
}
|
|
this.props.dispatcher.updateCurrentDocsVersion(versionToFetch);
|
|
|
|
const versionFileNameToFetch = versionToFileName[versionToFetch];
|
|
const versionDocObj = await docUtils.getJSONDocFileAsync(
|
|
versionFileNameToFetch, this.props.docsInfo.docsJsonRoot,
|
|
);
|
|
const docAgnosticFormat = this.props.docsInfo.convertToDocAgnosticFormat(versionDocObj as DoxityDocObj);
|
|
|
|
this.setState({
|
|
docAgnosticFormat,
|
|
}, () => {
|
|
this._scrollToHash();
|
|
});
|
|
}
|
|
}
|