261 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			261 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {
 | 
						|
    colors,
 | 
						|
    constants as sharedConstants,
 | 
						|
    HeaderSizes,
 | 
						|
    MarkdownSection,
 | 
						|
    NestedSidebarMenu,
 | 
						|
    SectionHeader,
 | 
						|
    Styles,
 | 
						|
    utils as sharedUtils,
 | 
						|
} from '@0xproject/react-shared';
 | 
						|
import { logUtils } from '@0xproject/utils';
 | 
						|
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 { SidebarHeader } from 'ts/components/sidebar_header';
 | 
						|
import { TopBar } from 'ts/components/top_bar/top_bar';
 | 
						|
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
						|
import { Article, ArticlesBySection, WebsitePaths } from 'ts/types';
 | 
						|
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<WikiProps, WikiState> {
 | 
						|
    private _wikiBackoffTimeoutId: number;
 | 
						|
    private _isUnmounted: boolean;
 | 
						|
    constructor(props: WikiProps) {
 | 
						|
        super(props);
 | 
						|
        this._isUnmounted = false;
 | 
						|
        this.state = {
 | 
						|
            articlesBySection: undefined,
 | 
						|
            isHoveringSidebar: false,
 | 
						|
        };
 | 
						|
    }
 | 
						|
    public componentDidMount() {
 | 
						|
        window.addEventListener('hashchange', this._onHashChanged.bind(this), false);
 | 
						|
    }
 | 
						|
    public componentWillMount() {
 | 
						|
        // tslint:disable-next-line:no-floating-promises
 | 
						|
        this._fetchArticlesBySectionAsync();
 | 
						|
    }
 | 
						|
    public componentWillUnmount() {
 | 
						|
        this._isUnmounted = true;
 | 
						|
        clearTimeout(this._wikiBackoffTimeoutId);
 | 
						|
        window.removeEventListener('hashchange', this._onHashChanged.bind(this), false);
 | 
						|
    }
 | 
						|
    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',
 | 
						|
        };
 | 
						|
        const sidebarHeader = <SidebarHeader title="Wiki" iconUrl="/images/doc_icons/wiki.png" />;
 | 
						|
        return (
 | 
						|
            <div>
 | 
						|
                <DocumentTitle title="0x Protocol Wiki" />
 | 
						|
                <TopBar
 | 
						|
                    blockchainIsLoaded={false}
 | 
						|
                    location={this.props.location}
 | 
						|
                    menuSubsectionsBySection={menuSubsectionsBySection}
 | 
						|
                    translate={this.props.translate}
 | 
						|
                    sidebarHeader={sidebarHeader}
 | 
						|
                />
 | 
						|
                {_.isUndefined(this.state.articlesBySection) ? (
 | 
						|
                    <div className="col col-12" style={mainContainersStyle}>
 | 
						|
                        <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 wiki...
 | 
						|
                            </div>
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                ) : (
 | 
						|
                    <div style={{ width: '100%', height: '100%', backgroundColor: colors.gray40 }}>
 | 
						|
                        <div
 | 
						|
                            className="mx-auto max-width-4 flex"
 | 
						|
                            style={{ color: colors.grey800, height: `calc(100vh - ${TOP_BAR_HEIGHT}px)` }}
 | 
						|
                        >
 | 
						|
                            <div
 | 
						|
                                className="relative lg-pl0 md-pl1 sm-hide xs-hide"
 | 
						|
                                style={{ height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`, width: '36%' }}
 | 
						|
                            >
 | 
						|
                                <div
 | 
						|
                                    className="absolute"
 | 
						|
                                    style={{
 | 
						|
                                        ...styles.menuContainer,
 | 
						|
                                        ...mainContainersStyle,
 | 
						|
                                        height: 'calc(100vh - 76px)',
 | 
						|
                                    }}
 | 
						|
                                    onMouseEnter={this._onSidebarHover.bind(this)}
 | 
						|
                                    onMouseLeave={this._onSidebarHoverOff.bind(this)}
 | 
						|
                                >
 | 
						|
                                    <NestedSidebarMenu
 | 
						|
                                        topLevelMenu={menuSubsectionsBySection}
 | 
						|
                                        menuSubsectionsBySection={menuSubsectionsBySection}
 | 
						|
                                        sidebarHeader={sidebarHeader}
 | 
						|
                                    />
 | 
						|
                                </div>
 | 
						|
                            </div>
 | 
						|
                            <div
 | 
						|
                                className="relative"
 | 
						|
                                style={{
 | 
						|
                                    width: '100%',
 | 
						|
                                    height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`,
 | 
						|
                                    backgroundColor: 'white',
 | 
						|
                                }}
 | 
						|
                            >
 | 
						|
                                <div
 | 
						|
                                    id={sharedConstants.SCROLL_CONTAINER_ID}
 | 
						|
                                    style={{ ...mainContainersStyle, overflow: 'auto' }}
 | 
						|
                                    className="absolute"
 | 
						|
                                >
 | 
						|
                                    <div id={sharedConstants.SCROLL_TOP_ID} />
 | 
						|
                                    <div id="wiki" style={{ paddingRight: 2 }}>
 | 
						|
                                        {this._renderWikiArticles()}
 | 
						|
                                    </div>
 | 
						|
                                </div>
 | 
						|
                            </div>
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                )}
 | 
						|
            </div>
 | 
						|
        );
 | 
						|
    }
 | 
						|
    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 (
 | 
						|
                <div key={`markdown-section-${article.title}`}>
 | 
						|
                    <MarkdownSection
 | 
						|
                        sectionName={article.title}
 | 
						|
                        markdownContent={article.content}
 | 
						|
                        headerSize={HeaderSizes.H2}
 | 
						|
                        githubLink={githubLink}
 | 
						|
                    />
 | 
						|
                    <div className="clearfix mb3 mt2 p3 mx-auto lg-flex md-flex sm-pb4" style={{ maxWidth: 390 }}>
 | 
						|
                        <div className="sm-col sm-col-12 sm-center" style={{ opacity: 0.4, lineHeight: 2.5 }}>
 | 
						|
                            See a way to improve this article?
 | 
						|
                        </div>
 | 
						|
                        <div className="sm-col sm-col-12 lg-col-7 md-col-7 sm-center sm-pt2">
 | 
						|
                            <RaisedButton href={githubLink} target="_blank" label="Edit on Github" />
 | 
						|
                        </div>
 | 
						|
                    </div>
 | 
						|
                </div>
 | 
						|
            );
 | 
						|
        });
 | 
						|
        return (
 | 
						|
            <div key={`section-${sectionName}`} className="py2 md-px1 sm-px2">
 | 
						|
                {/* <div className="pl2">
 | 
						|
                    <SectionHeader sectionName={sectionName} headerSize={HeaderSizes.H1} />
 | 
						|
                </div> */}
 | 
						|
                {renderedArticles}
 | 
						|
            </div>
 | 
						|
        );
 | 
						|
    }
 | 
						|
    private async _fetchArticlesBySectionAsync(): Promise<void> {
 | 
						|
        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();
 | 
						|
            logUtils.log(`Failed to load wiki: ${response.status} ${errMsg}`);
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        const articlesBySection = await response.json();
 | 
						|
        if (!this._isUnmounted) {
 | 
						|
            this.setState(
 | 
						|
                {
 | 
						|
                    articlesBySection,
 | 
						|
                },
 | 
						|
                async () => {
 | 
						|
                    await utils.onPageLoadAsync();
 | 
						|
                    const hash = this.props.location.hash.slice(1);
 | 
						|
                    sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID);
 | 
						|
                },
 | 
						|
            );
 | 
						|
        }
 | 
						|
    }
 | 
						|
    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<HTMLInputElement>) {
 | 
						|
        this.setState({
 | 
						|
            isHoveringSidebar: true,
 | 
						|
        });
 | 
						|
    }
 | 
						|
    private _onSidebarHoverOff() {
 | 
						|
        this.setState({
 | 
						|
            isHoveringSidebar: false,
 | 
						|
        });
 | 
						|
    }
 | 
						|
    private _onHashChanged(event: any) {
 | 
						|
        const hash = window.location.hash.slice(1);
 | 
						|
        sharedUtils.scrollToHash(hash, sharedConstants.SCROLL_CONTAINER_ID);
 | 
						|
    }
 | 
						|
}
 |