import React from 'react'; import { Link, RouteComponentProps } from 'react-router-dom'; import { inject, observer } from 'mobx-react'; import { orderBy } from 'lodash'; import { ThreadService } from '../../services'; import { Editor, ForumNav, LoginButton, PaginationLinks, ScrollToTop } from '../../components'; import { ThreadModel } from '../../model'; import { UserStore } from '../../stores/user-store'; import { Oauth, pagination } from '../../util'; import './forum.scss'; const stickyImage = require('../../assets/sticky.gif'); const upArrow = require('../../assets/arrow-up.gif'); const downArrow = require('../../assets/arrow-down.gif'); interface Props extends RouteComponentProps { userStore: UserStore; } interface State { showEditor: boolean; threads: ThreadModel[]; pageThreads: ThreadModel[]; pageLinks: (number | string)[]; } interface RouteParams { categoryId: number; page: number; threadsPerPage: number; sortBy: ColumnHeader; sortOrder: 'asc' | 'desc'; } // TODO: refactor this on back end to match UI enum ColumnHeader { subject = 'Subject', author = 'Author', replies = 'Replies', views = 'Views', lastPost= 'Last Post', } @inject('userStore') @observer export class Forum extends React.Component { constructor(props: Props) { super(props); this.state = { showEditor: false, threads: [], pageThreads: [], pageLinks: [], }; } // easier way to get route params - will provide default values if null private routeParams(props: Props = this.props): RouteParams { return { categoryId: parseInt(props.match.params['id'], 10), page: parseInt(props.match.params['page'], 10) || 1, threadsPerPage: parseInt(props.match.params['threadsPerPage'], 10) || 25, sortBy: props.match.params['sortBy'] || ColumnHeader.lastPost, sortOrder: props.match.params['sortOrder'] || 'desc', }; } componentDidMount() { this.getThreads(this.routeParams().categoryId); } // update the page if the route params change componentWillReceiveProps(nextProps: Props) { if (this.props.match.params['id'] !== nextProps.match.params['id']) { this.getThreads(nextProps.match.params['id']); } else { this.processThreads(this.state.threads, nextProps); } } // fetch threads from server private async getThreads(categoryId: number) { const threads = await ThreadService.getCategoryThreads(categoryId); this.processThreads(threads); } // process threads and set state private processThreads(unorderedThreads: ThreadModel[], props: Props = this.props): void { const { threadsPerPage, page } = this.routeParams(props); const threads = this.orderBy(unorderedThreads, props); const numPages = Math.ceil(threads.length / threadsPerPage); const threadIndex = (page - 1) * threadsPerPage; this.setState({ threads, pageThreads: [...threads].splice(threadIndex, threadsPerPage), pageLinks: pagination(page, numPages) }, ); } private orderBy(threads: ThreadModel[], props: Props) { const { sortBy, sortOrder } = this.routeParams(props); const titleMap: any = { [ColumnHeader.subject]: (t: any) => t.title.toLowerCase(), [ColumnHeader.author]: (t: any) => { return t.user.character_name ? t.user.character_name.toLowerCase() : t.user.battletag.toLowerCase(); }, [ColumnHeader.replies]: 'reply_count', [ColumnHeader.views]: 'view_count', [ColumnHeader.lastPost]: 'updated_at', }; // always sort sticky to top return orderBy(threads, ['sticky', titleMap[sortBy]], ['desc', sortOrder]); } private navigateHere(categoryId: number, page: number, threadsPerPage: number, sortBy: ColumnHeader, sortOrder: 'asc' | 'desc') { const url = `/f/${categoryId}/${page}/${threadsPerPage}/${sortBy}/${sortOrder}`; this.props.history.push(url); } private onNewTopic() { if (this.props.userStore.user) { this.setState({ showEditor: true }); } else { Oauth.openOuathWindow(); } } private onNewTopicClose(cancel: boolean) { this.setState({ showEditor: false }); if (!cancel) { this.getThreads(this.routeParams().categoryId); } } renderHeader() { return (
this.props.history.push(dest)}/>
); } renderBody() { return (
this.onNewTopic()}/>
{this.renderTable()}
); } renderCell(content: JSX.Element | string, style: any, center?: boolean) { let classNames: string = ''; classNames += center ? ' forum-cell--center' : ''; return
{content}
; } renderThreadRows() { const { categoryId } = this.routeParams(); return this.state.pageThreads.map((thread, index) => { const authorBluePost = thread.user.permissions === 'admin' ? 'blue' : ''; const lastReplyBluePost = thread.last_reply.permissions === 'admin' ? 'blue' : ''; const sticky = thread.sticky ? : ''; return (
{this.renderCell(sticky, { maxWidth: '50px' }, true)} {this.renderCell( {thread.title}, { minWidth: '200px' }, )} {this.renderCell({thread.user.character_name || thread.user.battletag}, { maxWidth: '150px' })} {this.renderCell({thread.reply_count}, { maxWidth: '150px' }, true)} {this.renderCell({thread.view_count}, { maxWidth: '150px' }, true)} {this.renderCell(
by {thread.last_reply.character_name || thread.last_reply.battletag}
, { maxWidth: '200px' }, )}
); }); } renderThreadsPerPageDropdown() { const { categoryId, sortBy, sortOrder } = this.routeParams(); return ( ); } renderHeaderFooter() { const { categoryId, sortBy, threadsPerPage, sortOrder } = this.routeParams(); return (
Page: this.navigateHere(categoryId, page, threadsPerPage, sortBy, sortOrder)} />
Threads/Page: {this.renderThreadsPerPageDropdown()}
); } renderSortingArrow(show: boolean, sortOrder: string) { const imgSrc = sortOrder === 'asc' ? upArrow : downArrow; return show ? : null; } renderHeaderCell(columnHeader: ColumnHeader, style: any, center: boolean) { const { categoryId, page, threadsPerPage, sortBy, sortOrder } = this.routeParams(); const newSortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; const centerClass = center ? 'forum-cell--center' : ''; return (
this.navigateHere(categoryId, page, threadsPerPage, columnHeader, newSortOrder)}> {columnHeader} {this.renderSortingArrow(sortBy === columnHeader, sortOrder)}
); } renderTable() { return (
{/* header */} {this.renderHeaderFooter()} {/* column headers */}
{this.renderHeaderCell(ColumnHeader.subject, { minWidth: '200px' }, false)} {this.renderHeaderCell(ColumnHeader.author, { maxWidth: '150px' }, true)} {this.renderHeaderCell(ColumnHeader.replies, { maxWidth: '150px' }, true)} {this.renderHeaderCell(ColumnHeader.views, { maxWidth: '150px' }, true)} {this.renderHeaderCell(ColumnHeader.lastPost, { maxWidth: '200px' }, true)}
{/* table body */} {this.renderThreadRows()} {/* footer */} {this.renderHeaderFooter()}
); } render() { return ( {this.state.showEditor && this.onNewTopicClose(cancel)}/>} {this.renderHeader()} {this.renderBody()} ); } }