1
0
mirror of https://github.com/mgerb/classic-wow-forums synced 2026-01-10 17:12:48 +00:00

client - order by functionality done

This commit is contained in:
2018-01-22 19:33:46 -06:00
parent 259c270c51
commit a8e82cdcf5
5 changed files with 92 additions and 59 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

View File

@@ -10,6 +10,8 @@ import { Oauth, pagination } from '../../util';
import './forum.scss'; import './forum.scss';
const stickyImage = require('../../assets/sticky.gif'); const stickyImage = require('../../assets/sticky.gif');
const upArrow = require('../../assets/arrow-up.gif');
const downArrow = require('../../assets/arrow-down.gif');
interface Props extends RouteComponentProps<any> { interface Props extends RouteComponentProps<any> {
userStore: UserStore; userStore: UserStore;
@@ -26,7 +28,17 @@ interface RouteParams {
categoryId: number; categoryId: number;
page: number; page: number;
threadsPerPage: number; threadsPerPage: number;
sortBy: string; 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') @inject('userStore')
@@ -43,17 +55,18 @@ export class Forum extends React.Component<Props, State> {
} }
// easier way to get route params - will provide default values if null // easier way to get route params - will provide default values if null
private get routeParams(): RouteParams { private routeParams(props: Props = this.props): RouteParams {
return { return {
categoryId: parseInt(this.props.match.params['id'], 10), categoryId: parseInt(props.match.params['id'], 10),
page: parseInt(this.props.match.params['page'], 10) || 1, page: parseInt(props.match.params['page'], 10) || 1,
threadsPerPage: parseInt(this.props.match.params['threadsPerPage'], 10) || 25, threadsPerPage: parseInt(props.match.params['threadsPerPage'], 10) || 25,
sortBy: this.props.match.params['sortBy'] || 'Latest Reply', sortBy: props.match.params['sortBy'] || ColumnHeader.lastPost,
sortOrder: props.match.params['sortOrder'] || 'desc',
}; };
} }
componentDidMount() { componentDidMount() {
this.getThreads(this.routeParams.categoryId); this.getThreads(this.routeParams().categoryId);
} }
// update the page if the route params change // update the page if the route params change
@@ -61,22 +74,20 @@ export class Forum extends React.Component<Props, State> {
if (this.props.match.params['id'] !== nextProps.match.params['id']) { if (this.props.match.params['id'] !== nextProps.match.params['id']) {
this.getThreads(nextProps.match.params['id']); this.getThreads(nextProps.match.params['id']);
} else { } else {
// have to grab params from next props this.processThreads(this.state.threads, nextProps);
const page = parseInt(nextProps.match.params['page'], 10) || 1;
const threadsPerPage = parseInt(nextProps.match.params['threadsPerPage'], 10) || 25;
this.processThreads(this.state.threads, page, threadsPerPage);
} }
} }
// fetch threads from server // fetch threads from server
private async getThreads(categoryId: number) { private async getThreads(categoryId: number) {
const threads = await ThreadService.getCategoryThreads(categoryId); const threads = await ThreadService.getCategoryThreads(categoryId);
this.processThreads(threads, this.routeParams.page, this.routeParams.threadsPerPage); this.processThreads(threads);
} }
// process threads and set state // process threads and set state
private processThreads(unorderedThreads: ThreadModel[], page: number, threadsPerPage: number): void { private processThreads(unorderedThreads: ThreadModel[], props: Props = this.props): void {
const threads = this.orderBy(unorderedThreads); const { threadsPerPage, page } = this.routeParams(props);
const threads = this.orderBy(unorderedThreads, props);
const numPages = Math.ceil(threads.length / threadsPerPage); const numPages = Math.ceil(threads.length / threadsPerPage);
const threadIndex = (page - 1) * threadsPerPage; const threadIndex = (page - 1) * threadsPerPage;
@@ -87,13 +98,24 @@ export class Forum extends React.Component<Props, State> {
); );
} }
// TODO: private orderBy(threads: ThreadModel[], props: Props) {
private orderBy(threads: ThreadModel[]) { const { sortBy, sortOrder } = this.routeParams(props);
return orderBy(threads, ['sticky', 'updated_at'], ['desc', 'desc']); 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: string) { private navigateHere(categoryId: number, page: number, threadsPerPage: number, sortBy: ColumnHeader, sortOrder: 'asc' | 'desc') {
const url = `/f/${categoryId}/${page}/${threadsPerPage}/${sortBy}`; const url = `/f/${categoryId}/${page}/${threadsPerPage}/${sortBy}/${sortOrder}`;
this.props.history.push(url); this.props.history.push(url);
} }
@@ -108,14 +130,14 @@ export class Forum extends React.Component<Props, State> {
private onNewTopicClose(cancel: boolean) { private onNewTopicClose(cancel: boolean) {
this.setState({ showEditor: false }); this.setState({ showEditor: false });
if (!cancel) { if (!cancel) {
this.getThreads(this.routeParams.categoryId); this.getThreads(this.routeParams().categoryId);
} }
} }
renderHeader() { renderHeader() {
return ( return (
<div className="forum-header"> <div className="forum-header">
<ForumNav categoryId={this.routeParams.categoryId} {...this.props}/> <ForumNav categoryId={this.routeParams().categoryId} {...this.props}/>
<div style={{ height: '100%' }}> <div style={{ height: '100%' }}>
<LoginButton onNavigate={dest => this.props.history.push(dest)}/> <LoginButton onNavigate={dest => this.props.history.push(dest)}/>
</div> </div>
@@ -145,15 +167,14 @@ export class Forum extends React.Component<Props, State> {
); );
} }
renderCell(content: JSX.Element | string, style: any, center?: boolean, header?: boolean) { renderCell(content: JSX.Element | string, style: any, center?: boolean) {
let classNames: string = ''; let classNames: string = '';
classNames += center ? ' forum-cell--center' : ''; classNames += center ? ' forum-cell--center' : '';
classNames += header ? ' forum-cell--header' : ' forum-cell--body'; return <div className={`forum-cell flex-1 forum-cell--body ${classNames}`} style={style}>{content}</div>;
return <div className={`forum-cell flex-1 ${classNames}`} style={style}>{content}</div>;
} }
renderThreadRows() { renderThreadRows() {
const { categoryId } = this.routeParams; const { categoryId } = this.routeParams();
return this.state.pageThreads.map((thread, index) => { return this.state.pageThreads.map((thread, index) => {
const authorBluePost = thread.user.permissions === 'admin' ? 'blue' : ''; const authorBluePost = thread.user.permissions === 'admin' ? 'blue' : '';
const lastReplyBluePost = thread.last_reply.permissions === 'admin' ? 'blue' : ''; const lastReplyBluePost = thread.last_reply.permissions === 'admin' ? 'blue' : '';
@@ -180,11 +201,11 @@ export class Forum extends React.Component<Props, State> {
} }
renderThreadsPerPageDropdown() { renderThreadsPerPageDropdown() {
const { categoryId, sortBy } = this.routeParams; const { categoryId, sortBy, sortOrder } = this.routeParams();
return ( return (
<select style={{ margin: '0 5px' }} <select style={{ margin: '0 5px' }}
value={this.routeParams.threadsPerPage} value={this.routeParams().threadsPerPage}
onChange={e => this.navigateHere(categoryId, 1, parseInt(e.target.value, 10), sortBy)} onChange={e => this.navigateHere(categoryId, 1, parseInt(e.target.value, 10), sortBy, sortOrder)}
> >
<option value={25}>25</option> <option value={25}>25</option>
<option value={50}>50</option> <option value={50}>50</option>
@@ -193,44 +214,48 @@ export class Forum extends React.Component<Props, State> {
); );
} }
// TOOD:
renderSortByDropdown() {
return (
<select style={{ margin: '0 5px' }}>
<option>Latest Reply</option>
<option>Subject</option>
<option>Author</option>
<option># of Replies</option>
<option># of Views</option>
<option>Creation Date</option>
</select>
);
}
renderHeaderFooter() { renderHeaderFooter() {
const { categoryId, sortBy, threadsPerPage } = this.routeParams; const { categoryId, sortBy, threadsPerPage, sortOrder } = this.routeParams();
return ( return (
<div className="forum-row forum-row--header"> <div className="forum-row forum-row--header">
<div className="forum-cell forum-cell--header forum-cell--header-footer flex-1"> <div className="forum-cell forum-cell--header forum-cell--header-footer flex-1">
<div> <div>
<span style={{ marginRight: '10px' }}>Page:</span> <span style={{ marginRight: '10px' }}>Page:</span>
<PaginationLinks <PaginationLinks
activePage={this.routeParams.page} activePage={this.routeParams().page}
pageLinks={this.state.pageLinks} pageLinks={this.state.pageLinks}
onPageSelect={page => this.navigateHere(categoryId, page, threadsPerPage, sortBy)} onPageSelect={page => this.navigateHere(categoryId, page, threadsPerPage, sortBy, sortOrder)}
/> />
</div> </div>
<div> <div>
<b>Threads/Page:</b> <b>Threads/Page:</b>
{this.renderThreadsPerPageDropdown()} {this.renderThreadsPerPageDropdown()}
<b>Sort by:</b>
{this.renderSortByDropdown()}
</div> </div>
</div> </div>
</div> </div>
); );
} }
renderSortingArrow(show: boolean, sortOrder: string) {
const imgSrc = sortOrder === 'asc' ? upArrow : downArrow;
return show ? <img src={imgSrc}/> : 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 (
<div className={`forum-cell forum-cell--header flex-1 ${centerClass}`} style={style}>
<a onClick={() => this.navigateHere(categoryId, page, threadsPerPage, columnHeader, newSortOrder)}>
<span>{columnHeader}</span>
{this.renderSortingArrow(sortBy === columnHeader, sortOrder)}
</a>
</div>
);
}
renderTable() { renderTable() {
return ( return (
<div style={{ padding: '0 3px' }}> <div style={{ padding: '0 3px' }}>
@@ -241,12 +266,14 @@ export class Forum extends React.Component<Props, State> {
{/* column headers */} {/* column headers */}
<div className="forum-row forum-row--header"> <div className="forum-row forum-row--header">
{this.renderCell(<img src={require('../../assets/flag.gif')}/>, { maxWidth: '50px' }, true, true)} <div className={`forum-cell forum-cell--header flex-1 forum-cell--center`} style={{ maxWidth: '50px' }}>
{this.renderCell(<a>Subject</a>, { minWidth: '200px' }, false, true)} <img src={require('../../assets/flag.gif')}/>
{this.renderCell(<a>Author</a>, { maxWidth: '150px' }, true, true)} </div>
{this.renderCell(<a>Replies</a>, { maxWidth: '150px' }, true, true)} {this.renderHeaderCell(ColumnHeader.subject, { minWidth: '200px' }, false)}
{this.renderCell(<a>Views</a>, { maxWidth: '150px' }, true, true)} {this.renderHeaderCell(ColumnHeader.author, { maxWidth: '150px' }, true)}
{this.renderCell(<a>Last Post</a>, { maxWidth: '200px' }, true, true)} {this.renderHeaderCell(ColumnHeader.replies, { maxWidth: '150px' }, true)}
{this.renderHeaderCell(ColumnHeader.views, { maxWidth: '150px' }, true)}
{this.renderHeaderCell(ColumnHeader.lastPost, { maxWidth: '200px' }, true)}
</div> </div>
{/* table body */} {/* table body */}
@@ -265,7 +292,7 @@ export class Forum extends React.Component<Props, State> {
render() { render() {
return ( return (
<ScrollToTop> <ScrollToTop>
{this.state.showEditor && <Editor categoryId={this.routeParams.categoryId} {this.state.showEditor && <Editor categoryId={this.routeParams().categoryId}
onClose={cancel => this.onNewTopicClose(cancel)}/>} onClose={cancel => this.onNewTopicClose(cancel)}/>}
{this.renderHeader()} {this.renderHeader()}
{this.renderBody()} {this.renderBody()}

View File

@@ -91,12 +91,18 @@ export class Home extends React.Component<Props, State> {
<ContentContainer> <ContentContainer>
<img src={header_forms} /> <img src={header_forms} />
<div> <div>
<b>Welcome to the World of Warcraft community forums!</b> <b>Welcome to the unofficial Classic WoW Forums!</b>
</div> </div>
<p> <p>
Blizzard provides the World of Warcraft community forums for its player to chat, exchange ideas, and submit feedback. Posting on This site is made with the intention of providing a web forum
the World of Warcraft community forums requires a World of Warcraft account. Only customers are allowed to post on these forums, that replicates the World of Warcraft forums as they were
but anyone can read them. Please note that you must adhere to the Forum Guidelines if you wish to post on the forums. back in 2005.
</p>
<p>
<b>DISCLAIMER:</b> This site is in no way affiliated with Blizzard Entertainment, Inc. All rights reserved.
World of Warcraft, Warcraft and Blizzard Entertainment are trademarks or registered trademarks of Blizzard Entertainment,
Inc. in the U.S. and/or other countries.
</p> </p>
<div className="topic-container"> <div className="topic-container">

View File

@@ -44,7 +44,7 @@ export class Routes extends React.Component<Props, State> {
<Switch> <Switch>
<Route exact path="/" component={Home} /> <Route exact path="/" component={Home} />
<Route exact path="/realms" component={Realms} /> <Route exact path="/realms" component={Realms} />
<Route exact path="/f/:id/:page?/:threadsPerPage?/:sortBy?" component={Forum} /> <Route exact path="/f/:id/:page?/:threadsPerPage?/:sortBy?/:sortOrder?" component={Forum} />
<Route exact path="/t/:categoryId/:threadId/:page?" component={Thread} /> <Route exact path="/t/:categoryId/:threadId/:page?" component={Thread} />
<Route exact path="/oauth" component={Oauth} /> <Route exact path="/oauth" component={Oauth} />
<Route exact path="/user-account" component={UserAccount} /> <Route exact path="/user-account" component={UserAccount} />