import { ALink, colors, constants as sharedConstants, HeaderSizes, Link, MarkdownSection, utils as sharedUtils, } from '@0x/react-shared'; import { ObjectMap } from '@0x/types'; import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import * as React from 'react'; import { SidebarHeader } from 'ts/components/documentation/sidebar_header'; import { NestedSidebarMenu } from 'ts/components/nested_sidebar_menu'; import { Button } from 'ts/components/ui/button'; import { Container } from 'ts/components/ui/container'; import { DevelopersPage } from 'ts/pages/documentation/developers_page'; import { Dispatcher } from 'ts/redux/dispatcher'; import { Article, ArticlesBySection, Deco, Key, ScreenWidths } from 'ts/types'; import { backendClient } from 'ts/utils/backend_client'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; const WIKI_NOT_READY_BACKOUT_TIMEOUT_MS = 5000; export interface WikiProps { source: string; location: Location; dispatcher: Dispatcher; translate: Translate; screenWidth: ScreenWidths; } interface WikiState { articlesBySection: ArticlesBySection; isHoveringSidebar: boolean; } export class Wiki extends React.Component<WikiProps, WikiState> { private _wikiBackoffTimeoutId: number; private _isUnmounted: boolean; constructor(props: WikiProps) { super(props); this._isUnmounted = false; this.state = { articlesBySection: undefined, isHoveringSidebar: false, }; } public componentWillMount(): void { // tslint:disable-next-line:no-floating-promises this._fetchArticlesBySectionAsync(); } public componentWillUnmount(): void { this._isUnmounted = true; clearTimeout(this._wikiBackoffTimeoutId); } public render(): React.ReactNode { const sectionNameToLinks = _.isUndefined(this.state.articlesBySection) ? {} : this._getSectionNameToLinks(this.state.articlesBySection); const mainContent = _.isUndefined(this.state.articlesBySection) ? ( <div className="flex justify-center">{this._renderLoading()}</div> ) : ( <div id="wiki" style={{ paddingRight: 2 }}> {this._renderWikiArticles()} </div> ); const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; const sidebar = _.isUndefined(this.state.articlesBySection) ? ( <div /> ) : ( <NestedSidebarMenu sidebarHeader={isSmallScreen ? this._renderSidebarHeader() : undefined} sectionNameToLinks={sectionNameToLinks} screenWidth={this.props.screenWidth} /> ); return ( <DevelopersPage sidebar={sidebar} mainContent={mainContent} location={this.props.location} screenWidth={this.props.screenWidth} translate={this.props.translate} dispatcher={this.props.dispatcher} /> ); } private _renderSidebarHeader(): React.ReactNode { const menuItems = _.map(constants.DEVELOPER_TOPBAR_LINKS, menuItemInfo => { return ( <Link key={`menu-item-${menuItemInfo.title}`} to={menuItemInfo.to} shouldOpenInNewTab={menuItemInfo.shouldOpenInNewTab} > <Button borderRadius="4px" padding="0.4em 0.375em" width="100%" fontColor={colors.grey800} fontSize="14px" textAlign="left" > {this.props.translate.get(menuItemInfo.title as Key, Deco.Cap)} </Button> </Link> ); }); const wikiTitle = this.props.translate.get(Key.Wiki, Deco.Cap); return ( <Container> <SidebarHeader screenWidth={this.props.screenWidth} title={wikiTitle} /> {menuItems} </Container> ); } private _renderLoading(): React.ReactNode { return ( <Container className="pt4"> <Container className="center pb2"> <CircularProgress size={40} thickness={5} /> </Container> <Container className="center pt2" paddingBottom="11px"> Loading wiki... </Container> </Container> ); } private _renderWikiArticles(): React.ReactNode { const sectionNames = _.keys(this.state.articlesBySection); const sections = _.map(sectionNames, sectionName => this._renderSection(sectionName)); return sections; } private _renderSection(sectionName: string): React.ReactNode { const articles = this.state.articlesBySection[sectionName]; const renderedArticles = _.map(articles, (article: Article) => { const githubLink = `${constants.URL_GITHUB_WIKI}/edit/master/${sectionName}/${article.fileName}`; return ( <div key={`markdown-section-${article.title}`}> <MarkdownSection sectionName={article.title} markdownContent={article.content} headerSize={HeaderSizes.H2} githubLink={githubLink} /> </div> ); }); return <div key={`section-${sectionName}`}>{renderedArticles}</div>; } private async _fetchArticlesBySectionAsync(): Promise<void> { try { const articlesBySection = await backendClient.getWikiArticlesBySectionAsync(); if (!this._isUnmounted) { this.setState( { articlesBySection, }, async () => { await utils.onPageLoadPromise; const hash = this.props.location.hash.slice(1); sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID); }, ); } } catch (err) { const errMsg = `${err}`; if (_.includes(errMsg, `${constants.HTTP_NO_CONTENT_STATUS_CODE}`)) { // We need to backoff and try fetching again later this._wikiBackoffTimeoutId = window.setTimeout(() => { // tslint:disable-next-line:no-floating-promises this._fetchArticlesBySectionAsync(); }, WIKI_NOT_READY_BACKOUT_TIMEOUT_MS); return; } } } private _getSectionNameToLinks(articlesBySection: ArticlesBySection): ObjectMap<ALink[]> { const sectionNames = _.keys(articlesBySection); const sectionNameToLinks: ObjectMap<ALink[]> = {}; for (const sectionName of sectionNames) { const articles = articlesBySection[sectionName]; const articleLinks = _.map(articles, article => { return { to: sharedUtils.getIdFromName(article.title), title: article.title, }; }); sectionNameToLinks[sectionName] = articleLinks; } return sectionNameToLinks; } }