Merge branch 'paging' into dev. Adding Community, User, and Main

paging.

- Fixes #109
- Fixes #87
This commit is contained in:
Dessalines 2019-04-28 17:46:38 -07:00
commit 6761aa556a
7 changed files with 374 additions and 232 deletions

View file

@ -1,11 +1,11 @@
import { Component } from 'inferno'; import { Component, linkEvent } from 'inferno';
import { Subscription } from "rxjs"; import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, Community as CommunityI, GetCommunityResponse, CommunityResponse, CommunityUser, UserView } from '../interfaces'; import { UserOperation, Community as CommunityI, GetCommunityResponse, CommunityResponse, CommunityUser, UserView, SortType, Post, GetPostsForm, ListingType, GetPostsResponse, CreatePostLikeResponse } from '../interfaces';
import { WebSocketService } from '../services'; import { WebSocketService } from '../services';
import { PostListings } from './post-listings'; import { PostListings } from './post-listings';
import { Sidebar } from './sidebar'; import { Sidebar } from './sidebar';
import { msgOp } from '../utils'; import { msgOp, routeSortTypeToEnum, fetchLimit } from '../utils';
interface State { interface State {
community: CommunityI; community: CommunityI;
@ -14,6 +14,9 @@ interface State {
moderators: Array<CommunityUser>; moderators: Array<CommunityUser>;
admins: Array<UserView>; admins: Array<UserView>;
loading: boolean; loading: boolean;
posts: Array<Post>;
sort: SortType;
page: number;
} }
export class Community extends Component<any, State> { export class Community extends Component<any, State> {
@ -38,7 +41,20 @@ export class Community extends Component<any, State> {
admins: [], admins: [],
communityId: Number(this.props.match.params.id), communityId: Number(this.props.match.params.id),
communityName: this.props.match.params.name, communityName: this.props.match.params.name,
loading: true loading: true,
posts: [],
sort: this.getSortTypeFromProps(this.props),
page: this.getPageFromProps(this.props),
}
getSortTypeFromProps(props: any): SortType {
return (props.match.params.sort) ?
routeSortTypeToEnum(props.match.params.sort) :
SortType.Hot;
}
getPageFromProps(props: any): number {
return (props.match.params.page) ? Number(props.match.params.page) : 1;
} }
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -66,6 +82,16 @@ export class Community extends Component<any, State> {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
// Necessary for back button for some reason
componentWillReceiveProps(nextProps: any) {
if (nextProps.history.action == 'POP') {
this.state = this.emptyState;
this.state.sort = this.getSortTypeFromProps(nextProps);
this.state.page = this.getPageFromProps(nextProps);
this.fetchPosts();
}
}
render() { render() {
return ( return (
<div class="container"> <div class="container">
@ -78,7 +104,9 @@ export class Community extends Component<any, State> {
<small className="ml-2 text-muted font-italic">removed</small> <small className="ml-2 text-muted font-italic">removed</small>
} }
</h5> </h5>
{this.state.community && <PostListings communityId={this.state.community.id} />} {this.selects()}
<PostListings posts={this.state.posts} />
{this.paginator()}
</div> </div>
<div class="col-12 col-md-3"> <div class="col-12 col-md-3">
<Sidebar <Sidebar
@ -93,6 +121,72 @@ export class Community extends Component<any, State> {
) )
} }
selects() {
return (
<div className="mb-2">
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto">
<option disabled>Sort Type</option>
<option value={SortType.Hot}>Hot</option>
<option value={SortType.New}>New</option>
<option disabled></option>
<option value={SortType.TopDay}>Top Day</option>
<option value={SortType.TopWeek}>Week</option>
<option value={SortType.TopMonth}>Month</option>
<option value={SortType.TopYear}>Year</option>
<option value={SortType.TopAll}>All</option>
</select>
</div>
)
}
paginator() {
return (
<div class="mt-2">
{this.state.page > 1 &&
<button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button>
}
<button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button>
</div>
);
}
nextPage(i: Community) {
i.state.page++;
i.setState(i.state);
i.updateUrl();
i.fetchPosts();
}
prevPage(i: Community) {
i.state.page--;
i.setState(i.state);
i.updateUrl();
i.fetchPosts();
}
handleSortChange(i: Community, event: any) {
i.state.sort = Number(event.target.value);
i.state.page = 1;
i.setState(i.state);
i.updateUrl();
i.fetchPosts();
}
updateUrl() {
let sortStr = SortType[this.state.sort].toLowerCase();
this.props.history.push(`/f/${this.state.community.name}/sort/${sortStr}/page/${this.state.page}`);
}
fetchPosts() {
let getPostsForm: GetPostsForm = {
page: this.state.page,
limit: fetchLimit,
sort: SortType[this.state.sort],
type_: ListingType[ListingType.Community],
community_id: this.state.community.id,
}
WebSocketService.Instance.getPosts(getPostsForm);
}
parseMessage(msg: any) { parseMessage(msg: any) {
console.log(msg); console.log(msg);
@ -105,9 +199,9 @@ export class Community extends Component<any, State> {
this.state.community = res.community; this.state.community = res.community;
this.state.moderators = res.moderators; this.state.moderators = res.moderators;
this.state.admins = res.admins; this.state.admins = res.admins;
this.state.loading = false;
document.title = `/f/${this.state.community.name} - Lemmy`; document.title = `/f/${this.state.community.name} - Lemmy`;
this.setState(this.state); this.setState(this.state);
this.fetchPosts();
} else if (op == UserOperation.EditCommunity) { } else if (op == UserOperation.EditCommunity) {
let res: CommunityResponse = msg; let res: CommunityResponse = msg;
this.state.community = res.community; this.state.community = res.community;
@ -117,6 +211,19 @@ export class Community extends Component<any, State> {
this.state.community.subscribed = res.community.subscribed; this.state.community.subscribed = res.community.subscribed;
this.state.community.number_of_subscribers = res.community.number_of_subscribers; this.state.community.number_of_subscribers = res.community.number_of_subscribers;
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.GetPosts) {
let res: GetPostsResponse = msg;
this.state.posts = res.posts;
this.state.loading = false;
this.setState(this.state);
} else if (op == UserOperation.CreatePostLike) {
let res: CreatePostLikeResponse = msg;
let found = this.state.posts.find(c => c.id == res.post.id);
found.my_vote = res.post.my_vote;
found.score = res.post.score;
found.upvotes = res.post.upvotes;
found.downvotes = res.post.downvotes;
this.setState(this.state);
} }
} }
} }

View file

@ -1,24 +0,0 @@
import { Component } from 'inferno';
import { Main } from './main';
import { ListingType } from '../interfaces';
export class Home extends Component<any, any> {
constructor(props: any, context: any) {
super(props, context);
}
render() {
return (
<Main type={this.listType()}/>
)
}
componentDidMount() {
document.title = "Lemmy";
}
listType(): ListingType {
return (this.props.match.path == '/all') ? ListingType.All : ListingType.Subscribed;
}
}

View file

@ -2,16 +2,11 @@ import { Component, linkEvent } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { Subscription } from "rxjs"; import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse, ListCommunitiesForm, ListCommunitiesResponse, Community, SortType, GetSiteResponse, ListingType, SiteResponse } from '../interfaces'; import { UserOperation, CommunityUser, GetFollowedCommunitiesResponse, ListCommunitiesForm, ListCommunitiesResponse, Community, SortType, GetSiteResponse, ListingType, SiteResponse, GetPostsResponse, CreatePostLikeResponse, Post, GetPostsForm } from '../interfaces';
import { WebSocketService, UserService } from '../services'; import { WebSocketService, UserService } from '../services';
import { PostListings } from './post-listings'; import { PostListings } from './post-listings';
import { SiteForm } from './site-form'; import { SiteForm } from './site-form';
import { msgOp, repoUrl, mdToHtml } from '../utils'; import { msgOp, repoUrl, mdToHtml, fetchLimit, routeSortTypeToEnum, routeListingTypeToEnum } from '../utils';
interface MainProps {
type: ListingType;
}
interface MainState { interface MainState {
subscribedCommunities: Array<CommunityUser>; subscribedCommunities: Array<CommunityUser>;
@ -19,9 +14,13 @@ interface MainState {
site: GetSiteResponse; site: GetSiteResponse;
showEditSite: boolean; showEditSite: boolean;
loading: boolean; loading: boolean;
posts: Array<Post>;
type_: ListingType;
sort: SortType;
page: number;
} }
export class Main extends Component<MainProps, MainState> { export class Main extends Component<any, MainState> {
private subscription: Subscription; private subscription: Subscription;
private emptyState: MainState = { private emptyState: MainState = {
@ -43,7 +42,29 @@ export class Main extends Component<MainProps, MainState> {
banned: [], banned: [],
}, },
showEditSite: false, showEditSite: false,
loading: true loading: true,
posts: [],
type_: this.getListingTypeFromProps(this.props),
sort: this.getSortTypeFromProps(this.props),
page: this.getPageFromProps(this.props),
}
getListingTypeFromProps(props: any): ListingType {
return (props.match.params.type) ?
routeListingTypeToEnum(props.match.params.type) :
UserService.Instance.user ?
ListingType.Subscribed :
ListingType.All;
}
getSortTypeFromProps(props: any): SortType {
return (props.match.params.sort) ?
routeSortTypeToEnum(props.match.params.sort) :
SortType.Hot;
}
getPageFromProps(props: any): number {
return (props.match.params.page) ? Number(props.match.params.page) : 1;
} }
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -72,37 +93,51 @@ export class Main extends Component<MainProps, MainState> {
WebSocketService.Instance.listCommunities(listCommunitiesForm); WebSocketService.Instance.listCommunities(listCommunitiesForm);
this.handleEditCancel = this.handleEditCancel.bind(this); this.fetchPosts();
} }
componentWillUnmount() { componentWillUnmount() {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
componentDidMount() {
document.title = "Lemmy";
}
// Necessary for back button for some reason
componentWillReceiveProps(nextProps: any) {
if (nextProps.history.action == 'POP') {
this.state = this.emptyState;
this.state.type_ = this.getListingTypeFromProps(nextProps);
this.state.sort = this.getSortTypeFromProps(nextProps);
this.state.page = this.getPageFromProps(nextProps);
this.fetchPosts();
}
}
render() { render() {
return ( return (
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12 col-md-8"> <div class="col-12 col-md-8">
<PostListings type={this.props.type} /> {this.posts()}
</div> </div>
<div class="col-12 col-md-4"> <div class="col-12 col-md-4">
{this.state.loading ? {!this.state.loading &&
<h5><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> : <div>
<div> {this.trendingCommunities()}
{this.trendingCommunities()} {UserService.Instance.user && this.state.subscribedCommunities.length > 0 &&
{UserService.Instance.user && this.state.subscribedCommunities.length > 0 && <div>
<div> <h5>Subscribed forums</h5>
<h5>Subscribed forums</h5> <ul class="list-inline">
<ul class="list-inline"> {this.state.subscribedCommunities.map(community =>
{this.state.subscribedCommunities.map(community => <li class="list-inline-item"><Link to={`/f/${community.community_name}`}>{community.community_name}</Link></li>
<li class="list-inline-item"><Link to={`/f/${community.community_name}`}>{community.community_name}</Link></li> )}
)} </ul>
</ul> </div>
</div> }
} {this.sidebar()}
{this.sidebar()} </div>
</div>
} }
</div> </div>
</div> </div>
@ -138,6 +173,12 @@ export class Main extends Component<MainProps, MainState> {
) )
} }
updateUrl() {
let typeStr = ListingType[this.state.type_].toLowerCase();
let sortStr = SortType[this.state.sort].toLowerCase();
this.props.history.push(`/home/type/${typeStr}/sort/${sortStr}/page/${this.state.page}`);
}
siteInfo() { siteInfo() {
return ( return (
<div> <div>
@ -185,9 +226,61 @@ export class Main extends Component<MainProps, MainState> {
<p>Suggest new features or report bugs <a href={repoUrl}>here.</a></p> <p>Suggest new features or report bugs <a href={repoUrl}>here.</a></p>
<p>Made with <a href="https://www.rust-lang.org">Rust</a>, <a href="https://actix.rs/">Actix</a>, <a href="https://www.infernojs.org">Inferno</a>, <a href="https://www.typescriptlang.org/">Typescript</a>.</p> <p>Made with <a href="https://www.rust-lang.org">Rust</a>, <a href="https://actix.rs/">Actix</a>, <a href="https://www.infernojs.org">Inferno</a>, <a href="https://www.typescriptlang.org/">Typescript</a>.</p>
</div> </div>
)
}
posts() {
return (
<div>
{this.state.loading ?
<h5><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> :
<div>
{this.selects()}
<PostListings posts={this.state.posts} showCommunity />
{this.paginator()}
</div>
}
</div>
) )
} }
selects() {
return (
<div className="mb-2">
<select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto">
<option disabled>Sort Type</option>
<option value={SortType.Hot}>Hot</option>
<option value={SortType.New}>New</option>
<option disabled></option>
<option value={SortType.TopDay}>Top Day</option>
<option value={SortType.TopWeek}>Week</option>
<option value={SortType.TopMonth}>Month</option>
<option value={SortType.TopYear}>Year</option>
<option value={SortType.TopAll}>All</option>
</select>
{ UserService.Instance.user &&
<select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="ml-2 custom-select w-auto">
<option disabled>Type</option>
<option value={ListingType.All}>All</option>
<option value={ListingType.Subscribed}>Subscribed</option>
</select>
}
</div>
)
}
paginator() {
return (
<div class="mt-2">
{this.state.page > 1 &&
<button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button>
}
<button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button>
</div>
);
}
get canAdmin(): boolean { get canAdmin(): boolean {
return UserService.Instance.user && this.state.site.admins.map(a => a.id).includes(UserService.Instance.user.id); return UserService.Instance.user && this.state.site.admins.map(a => a.id).includes(UserService.Instance.user.id);
} }
@ -202,6 +295,46 @@ export class Main extends Component<MainProps, MainState> {
this.setState(this.state); this.setState(this.state);
} }
nextPage(i: Main) {
i.state.page++;
i.setState(i.state);
i.updateUrl();
i.fetchPosts();
}
prevPage(i: Main) {
i.state.page--;
i.setState(i.state);
i.updateUrl();
i.fetchPosts();
}
handleSortChange(i: Main, event: any) {
i.state.sort = Number(event.target.value);
i.state.page = 1;
i.setState(i.state);
i.updateUrl();
i.fetchPosts();
}
handleTypeChange(i: Main, event: any) {
i.state.type_ = Number(event.target.value);
i.state.page = 1;
i.setState(i.state);
i.updateUrl();
i.fetchPosts();
}
fetchPosts() {
let getPostsForm: GetPostsForm = {
page: this.state.page,
limit: fetchLimit,
sort: SortType[this.state.sort],
type_: ListingType[this.state.type_]
}
WebSocketService.Instance.getPosts(getPostsForm);
}
parseMessage(msg: any) { parseMessage(msg: any) {
console.log(msg); console.log(msg);
let op: UserOperation = msgOp(msg); let op: UserOperation = msgOp(msg);
@ -211,12 +344,10 @@ export class Main extends Component<MainProps, MainState> {
} else if (op == UserOperation.GetFollowedCommunities) { } else if (op == UserOperation.GetFollowedCommunities) {
let res: GetFollowedCommunitiesResponse = msg; let res: GetFollowedCommunitiesResponse = msg;
this.state.subscribedCommunities = res.communities; this.state.subscribedCommunities = res.communities;
this.state.loading = false;
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.ListCommunities) { } else if (op == UserOperation.ListCommunities) {
let res: ListCommunitiesResponse = msg; let res: ListCommunitiesResponse = msg;
this.state.trendingCommunities = res.communities; this.state.trendingCommunities = res.communities;
this.state.loading = false;
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.GetSite) { } else if (op == UserOperation.GetSite) {
let res: GetSiteResponse = msg; let res: GetSiteResponse = msg;
@ -234,6 +365,19 @@ export class Main extends Component<MainProps, MainState> {
this.state.site.site = res.site; this.state.site.site = res.site;
this.state.showEditSite = false; this.state.showEditSite = false;
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.GetPosts) {
let res: GetPostsResponse = msg;
this.state.posts = res.posts;
this.state.loading = false;
this.setState(this.state);
} else if (op == UserOperation.CreatePostLike) {
let res: CreatePostLikeResponse = msg;
let found = this.state.posts.find(c => c.id == res.post.id);
found.my_vote = res.post.my_vote;
found.score = res.post.score;
found.upvotes = res.post.upvotes;
found.downvotes = res.post.downvotes;
this.setState(this.state);
} }
} }
} }

View file

@ -1,183 +1,28 @@
import { Component, linkEvent } from 'inferno'; import { Component } from 'inferno';
import { Link } from 'inferno-router'; import { Link } from 'inferno-router';
import { Subscription } from "rxjs"; import { Post } from '../interfaces';
import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, Post, GetPostsForm, SortType, ListingType, GetPostsResponse, CreatePostLikeResponse, CommunityUser} from '../interfaces';
import { WebSocketService, UserService } from '../services';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { msgOp, fetchLimit } from '../utils';
interface PostListingsProps { interface PostListingsProps {
type?: ListingType;
communityId?: number;
}
interface PostListingsState {
moderators: Array<CommunityUser>;
posts: Array<Post>; posts: Array<Post>;
sortType: SortType; showCommunity?: boolean;
type_: ListingType;
page: number;
loading: boolean;
} }
export class PostListings extends Component<PostListingsProps, PostListingsState> { export class PostListings extends Component<PostListingsProps, any> {
private subscription: Subscription;
private emptyState: PostListingsState = {
moderators: [],
posts: [],
sortType: SortType.Hot,
type_: (this.props.type !== undefined && UserService.Instance.user) ? this.props.type :
this.props.communityId
? ListingType.Community
: UserService.Instance.user
? ListingType.Subscribed
: ListingType.All,
page: 1,
loading: true
}
constructor(props: any, context: any) { constructor(props: any, context: any) {
super(props, context); super(props, context);
this.state = this.emptyState;
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
(msg) => this.parseMessage(msg),
(err) => console.error(err),
() => console.log('complete')
);
this.refetch();
}
componentWillUnmount() {
this.subscription.unsubscribe();
} }
render() { render() {
return ( return (
<div> <div>
{this.state.loading ? {this.props.posts.length > 0 ? this.props.posts.map(post =>
<h5><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> : <PostListing post={post} showCommunity={this.props.showCommunity} />) :
<div> <div>No Listings. {!this.props.showCommunity && <span>Subscribe to some <Link to="/communities">forums</Link>.</span>}
{this.selects()}
{this.state.posts.length > 0
? this.state.posts.map(post =>
<PostListing post={post} showCommunity={!this.props.communityId}/>)
: <div>No Listings. {!this.props.communityId && <span>Subscribe to some <Link to="/communities">forums</Link>.</span>}</div>
}
{this.paginator()}
</div> </div>
} }
</div> </div>
) )
} }
selects() {
return (
<div className="mb-2">
<select value={this.state.sortType} onChange={linkEvent(this, this.handleSortChange)} class="custom-select w-auto">
<option disabled>Sort Type</option>
<option value={SortType.Hot}>Hot</option>
<option value={SortType.New}>New</option>
<option disabled></option>
<option value={SortType.TopDay}>Top Day</option>
<option value={SortType.TopWeek}>Week</option>
<option value={SortType.TopMonth}>Month</option>
<option value={SortType.TopYear}>Year</option>
<option value={SortType.TopAll}>All</option>
</select>
{!this.props.communityId &&
UserService.Instance.user &&
<select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="ml-2 custom-select w-auto">
<option disabled>Type</option>
<option value={ListingType.All}>All</option>
<option value={ListingType.Subscribed}>Subscribed</option>
</select>
}
</div>
)
}
paginator() {
return (
<div class="mt-2">
{this.state.page > 1 &&
<button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button>
}
<button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button>
</div>
);
}
nextPage(i: PostListings) {
i.state.page++;
i.setState(i.state);
i.refetch();
}
prevPage(i: PostListings) {
i.state.page--;
i.setState(i.state);
i.refetch();
}
handleSortChange(i: PostListings, event: any) {
i.state.sortType = Number(event.target.value);
i.state.page = 1;
i.setState(i.state);
i.refetch();
}
refetch() {
let getPostsForm: GetPostsForm = {
community_id: this.props.communityId,
page: this.state.page,
limit: fetchLimit,
sort: SortType[this.state.sortType],
type_: ListingType[this.state.type_]
}
WebSocketService.Instance.getPosts(getPostsForm);
}
handleTypeChange(i: PostListings, event: any) {
i.state.type_ = Number(event.target.value);
i.state.page = 1;
if (i.state.type_ == ListingType.All) {
i.context.router.history.push('/all');
} else {
i.context.router.history.push('/');
}
i.setState(i.state);
i.refetch();
}
parseMessage(msg: any) {
console.log(msg);
let op: UserOperation = msgOp(msg);
if (msg.error) {
alert(msg.error);
return;
} else if (op == UserOperation.GetPosts) {
let res: GetPostsResponse = msg;
this.state.posts = res.posts;
this.state.loading = false;
this.setState(this.state);
} else if (op == UserOperation.CreatePostLike) {
let res: CreatePostLikeResponse = msg;
let found = this.state.posts.find(c => c.id == res.post.id);
found.my_vote = res.post.my_vote;
found.score = res.post.score;
found.upvotes = res.post.upvotes;
found.downvotes = res.post.downvotes;
this.setState(this.state);
}
}
} }

View file

@ -4,7 +4,7 @@ import { Subscription } from "rxjs";
import { retryWhen, delay, take } from 'rxjs/operators'; import { retryWhen, delay, take } from 'rxjs/operators';
import { UserOperation, Post, Comment, CommunityUser, GetUserDetailsForm, SortType, UserDetailsResponse, UserView, CommentResponse } from '../interfaces'; import { UserOperation, Post, Comment, CommunityUser, GetUserDetailsForm, SortType, UserDetailsResponse, UserView, CommentResponse } from '../interfaces';
import { WebSocketService } from '../services'; import { WebSocketService } from '../services';
import { msgOp, fetchLimit } from '../utils'; import { msgOp, fetchLimit, routeSortTypeToEnum, capitalizeFirstLetter } from '../utils';
import { PostListing } from './post-listing'; import { PostListing } from './post-listing';
import { CommentNodes } from './comment-nodes'; import { CommentNodes } from './comment-nodes';
import { MomentTime } from './moment-time'; import { MomentTime } from './moment-time';
@ -25,6 +25,7 @@ interface UserState {
view: View; view: View;
sort: SortType; sort: SortType;
page: number; page: number;
loading: boolean;
} }
export class User extends Component<any, UserState> { export class User extends Component<any, UserState> {
@ -47,9 +48,10 @@ export class User extends Component<any, UserState> {
moderates: [], moderates: [],
comments: [], comments: [],
posts: [], posts: [],
view: View.Overview, loading: true,
sort: SortType.New, view: this.getViewFromProps(this.props),
page: 1, sort: this.getSortTypeFromProps(this.props),
page: this.getPageFromProps(this.props),
} }
constructor(props: any, context: any) { constructor(props: any, context: any) {
@ -71,13 +73,42 @@ export class User extends Component<any, UserState> {
this.refetch(); this.refetch();
} }
getViewFromProps(props: any): View {
return (props.match.params.view) ?
View[capitalizeFirstLetter(props.match.params.view)] :
View.Overview;
}
getSortTypeFromProps(props: any): SortType {
return (props.match.params.sort) ?
routeSortTypeToEnum(props.match.params.sort) :
SortType.New;
}
getPageFromProps(props: any): number {
return (props.match.params.page) ? Number(props.match.params.page) : 1;
}
componentWillUnmount() { componentWillUnmount() {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
// Necessary for back button for some reason
componentWillReceiveProps(nextProps: any) {
if (nextProps.history.action == 'POP') {
this.state = this.emptyState;
this.state.view = this.getViewFromProps(nextProps);
this.state.sort = this.getSortTypeFromProps(nextProps);
this.state.page = this.getPageFromProps(nextProps);
this.refetch();
}
}
render() { render() {
return ( return (
<div class="container"> <div class="container">
{this.state.loading ?
<h5><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> :
<div class="row"> <div class="row">
<div class="col-12 col-md-9"> <div class="col-12 col-md-9">
<h5>/u/{this.state.user.name}</h5> <h5>/u/{this.state.user.name}</h5>
@ -102,6 +133,7 @@ export class User extends Component<any, UserState> {
{this.follows()} {this.follows()}
</div> </div>
</div> </div>
}
</div> </div>
) )
} }
@ -247,15 +279,23 @@ export class User extends Component<any, UserState> {
); );
} }
updateUrl() {
let viewStr = View[this.state.view].toLowerCase();
let sortStr = SortType[this.state.sort].toLowerCase();
this.props.history.push(`/u/${this.state.user.name}/view/${viewStr}/sort/${sortStr}/page/${this.state.page}`);
}
nextPage(i: User) { nextPage(i: User) {
i.state.page++; i.state.page++;
i.setState(i.state); i.setState(i.state);
i.updateUrl();
i.refetch(); i.refetch();
} }
prevPage(i: User) { prevPage(i: User) {
i.state.page--; i.state.page--;
i.setState(i.state); i.setState(i.state);
i.updateUrl();
i.refetch(); i.refetch();
} }
@ -275,6 +315,7 @@ export class User extends Component<any, UserState> {
i.state.sort = Number(event.target.value); i.state.sort = Number(event.target.value);
i.state.page = 1; i.state.page = 1;
i.setState(i.state); i.setState(i.state);
i.updateUrl();
i.refetch(); i.refetch();
} }
@ -282,6 +323,7 @@ export class User extends Component<any, UserState> {
i.state.view = Number(event.target.value); i.state.view = Number(event.target.value);
i.state.page = 1; i.state.page = 1;
i.setState(i.state); i.setState(i.state);
i.updateUrl();
i.refetch(); i.refetch();
} }
@ -298,6 +340,7 @@ export class User extends Component<any, UserState> {
this.state.follows = res.follows; this.state.follows = res.follows;
this.state.moderates = res.moderates; this.state.moderates = res.moderates;
this.state.posts = res.posts; this.state.posts = res.posts;
this.state.loading = false;
document.title = `/u/${this.state.user.name} - Lemmy`; document.title = `/u/${this.state.user.name} - Lemmy`;
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.EditComment) { } else if (op == UserOperation.EditComment) {

View file

@ -1,9 +1,8 @@
import { render, Component } from 'inferno'; import { render, Component } from 'inferno';
import { HashRouter, Route, Switch } from 'inferno-router'; import { HashRouter, BrowserRouter, Route, Switch } from 'inferno-router';
import { Main } from './components/main';
import { Navbar } from './components/navbar'; import { Navbar } from './components/navbar';
import { Footer } from './components/footer'; import { Footer } from './components/footer';
import { Home } from './components/home';
import { Login } from './components/login'; import { Login } from './components/login';
import { CreatePost } from './components/create-post'; import { CreatePost } from './components/create-post';
import { CreateCommunity } from './components/create-community'; import { CreateCommunity } from './components/create-community';
@ -35,20 +34,22 @@ class Index extends Component<any, any> {
render() { render() {
return ( return (
<HashRouter> <BrowserRouter>
<Navbar /> <Navbar />
<div class="mt-3 p-0"> <div class="mt-3 p-0">
<Switch> <Switch>
<Route exact path="/all" component={Home} /> <Route path={`/home/type/:type/sort/:sort/page/:page`} component={Main} />
<Route exact path="/" component={Home} /> <Route exact path={`/`} component={Main} />
<Route path={`/login`} component={Login} /> <Route path={`/login`} component={Login} />
<Route path={`/create_post`} component={CreatePost} /> <Route path={`/create_post`} component={CreatePost} />
<Route path={`/create_community`} component={CreateCommunity} /> <Route path={`/create_community`} component={CreateCommunity} />
<Route path={`/communities`} component={Communities} /> <Route path={`/communities`} component={Communities} />
<Route path={`/post/:id/comment/:comment_id`} component={Post} /> <Route path={`/post/:id/comment/:comment_id`} component={Post} />
<Route path={`/post/:id`} component={Post} /> <Route path={`/post/:id`} component={Post} />
<Route path={`/f/:name/sort/:sort/page/:page`} component={Community} />
<Route path={`/community/:id`} component={Community} /> <Route path={`/community/:id`} component={Community} />
<Route path={`/f/:name`} component={Community} /> <Route path={`/f/:name`} component={Community} />
<Route path={`/u/:username/view/:view/sort/:sort/page/:page`} component={User} />
<Route path={`/user/:id`} component={User} /> <Route path={`/user/:id`} component={User} />
<Route path={`/u/:username`} component={User} /> <Route path={`/u/:username`} component={User} />
<Route path={`/inbox`} component={Inbox} /> <Route path={`/inbox`} component={Inbox} />

View file

@ -1,4 +1,4 @@
import { UserOperation, Comment, User } from './interfaces'; import { UserOperation, Comment, User, SortType, ListingType } from './interfaces';
import * as markdown_it from 'markdown-it'; import * as markdown_it from 'markdown-it';
export let repoUrl = 'https://github.com/dessalines/lemmy'; export let repoUrl = 'https://github.com/dessalines/lemmy';
@ -67,3 +67,29 @@ export function isImage(url: string) {
} }
export let fetchLimit: number = 20; export let fetchLimit: number = 20;
export function capitalizeFirstLetter(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function routeSortTypeToEnum(sort: string): SortType {
if (sort == 'new') {
return SortType.New;
} else if (sort == 'hot') {
return SortType.Hot;
} else if (sort == 'topday') {
return SortType.TopDay;
} else if (sort == 'topweek') {
return SortType.TopWeek;
} else if (sort == 'topmonth') {
return SortType.TopMonth;
} else if (sort == 'topall') {
return SortType.TopAll;
}
}
export function routeListingTypeToEnum(type: string): ListingType {
return ListingType[capitalizeFirstLetter(type)];
}