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/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 { private _isUnmounted: boolean; constructor(props: DocumentationAllProps) { super(props); this._isUnmounted = false; 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 componentWillUnmount() { this._isUnmounted = true; } public render() { const menuSubsectionsBySection = _.isUndefined(this.state.docAgnosticFormat) ? {} : this.props.docsInfo.getMenuSubsectionsBySection(this.state.docAgnosticFormat); return (
{_.isUndefined(this.state.docAgnosticFormat) ? (
Loading documentation...
) : (

{this.props.docsInfo.displayName}

{this._renderDocumentation()}
)}
); } 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 ( ); } const docSection = this.state.docAgnosticFormat[sectionName]; if (_.isUndefined(docSection)) { return null; } const sortedTypes = _.sortBy(docSection.types, 'name'); const typeDefs = _.map(sortedTypes, customType => { return ( ); }); 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 ( ); }); return (
{this._renderNetworkBadgesIfExists(sectionName)}
{docSection.comment && } {docSection.constructors.length > 0 && this.props.docsInfo.isVisibleConstructor(sectionName) && (

Constructor

{this._renderConstructors(docSection.constructors, sectionName, typeDefinitionByName)}
)} {docSection.properties.length > 0 && (

Properties

{propertyDefs}
)} {docSection.methods.length > 0 && (

Methods

{methodDefs}
)} {!_.isUndefined(docSection.events) && docSection.events.length > 0 && (

Events

{eventDefs}
)} {!_.isUndefined(typeDefs) && typeDefs.length > 0 && (
{typeDefs}
)}
); } 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 ( ); }, ); 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
{constructorDefs}
; } private _renderProperty(sectionName: string, property: Property): React.ReactNode { return (
{property.name}: {property.source && ( )} {property.comment && }
); } private _renderMethodBlocks( method: SolidityMethod | TypescriptMethod, sectionName: string, isConstructor: boolean, typeDefinitionByName: TypeDefinitionByName, ): React.ReactNode { return ( ); } 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 { 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); if (!this._isUnmounted) { this.setState( { docAgnosticFormat, }, () => { this._scrollToHash(); }, ); } } }