import * as _ from 'lodash'; import CircularProgress from 'material-ui/CircularProgress'; import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; import DocumentTitle = require('react-document-title'); import { scroller } from 'react-scroll'; import { TopBar } from 'ts/components/top_bar/top_bar'; 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 { Article, ArticlesBySection, HeaderSizes, Styles, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; const TOP_BAR_HEIGHT = 60; const WIKI_NOT_READY_BACKOUT_TIMEOUT_MS = 5000; export interface WikiProps { source: string; location: Location; dispatcher: Dispatcher; translate: Translate; } interface WikiState { articlesBySection: ArticlesBySection; isHoveringSidebar: boolean; } const styles: Styles = { mainContainers: { position: 'absolute', top: 1, left: 0, bottom: 0, right: 0, overflowZ: 'hidden', height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`, WebkitOverflowScrolling: 'touch', }, menuContainer: { borderColor: colors.grey300, maxWidth: 330, backgroundColor: colors.gray40, }, }; export class Wiki extends React.Component { private _wikiBackoffTimeoutId: number; private _isUnmounted: boolean; constructor(props: WikiProps) { super(props); this._isUnmounted = false; this.state = { articlesBySection: undefined, isHoveringSidebar: false, }; } public componentWillMount() { // tslint:disable-next-line:no-floating-promises this._fetchArticlesBySectionAsync(); } public componentWillUnmount() { this._isUnmounted = true; clearTimeout(this._wikiBackoffTimeoutId); } public render() { const menuSubsectionsBySection = _.isUndefined(this.state.articlesBySection) ? {} : this._getMenuSubsectionsBySection(this.state.articlesBySection); const mainContainersStyle: React.CSSProperties = { ...styles.mainContainers, overflow: this.state.isHoveringSidebar ? 'auto' : 'hidden', }; return (
{_.isUndefined(this.state.articlesBySection) ? (
Loading wiki...
) : (
{this._renderWikiArticles()}
)}
); } private _renderWikiArticles(): React.ReactNode { const sectionNames = _.keys(this.state.articlesBySection); const sections = _.map(sectionNames, sectionName => this._renderSection(sectionName)); return sections; } private _renderSection(sectionName: string) { const articles = this.state.articlesBySection[sectionName]; const renderedArticles = _.map(articles, (article: Article) => { const githubLink = `${constants.URL_GITHUB_WIKI}/edit/master/${sectionName}/${article.fileName}`; return (
See a way to improve this article?
); }); return (
{/*
*/} {renderedArticles}
); } private _scrollToHash(): void { const hashWithPrefix = this.props.location.hash; let hash = hashWithPrefix.slice(1); if (_.isEmpty(hash)) { hash = '0xProtocolWiki'; // scroll to the top } scroller.scrollTo(hash, { duration: 0, offset: 0, containerId: 'documentation', }); } private async _fetchArticlesBySectionAsync(): Promise { const endpoint = `${configs.BACKEND_BASE_URL}${WebsitePaths.Wiki}`; const response = await fetch(endpoint); if (response.status === 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; } if (response.status !== 200) { // TODO: Show the user an error message when the wiki fail to load const errMsg = await response.text(); utils.consoleLog(`Failed to load wiki: ${response.status} ${errMsg}`); return; } const articlesBySection = await response.json(); if (!this._isUnmounted) { this.setState( { articlesBySection, }, () => { this._scrollToHash(); }, ); } } private _getMenuSubsectionsBySection(articlesBySection: ArticlesBySection) { const sectionNames = _.keys(articlesBySection); const menuSubsectionsBySection: { [section: string]: string[] } = {}; for (const sectionName of sectionNames) { const articles = articlesBySection[sectionName]; const articleNames = _.map(articles, article => article.title); menuSubsectionsBySection[sectionName] = articleNames; } return menuSubsectionsBySection; } private _onSidebarHover(event: React.FormEvent) { this.setState({ isHoveringSidebar: true, }); } private _onSidebarHoverOff() { this.setState({ isHoveringSidebar: false, }); } }