diff --git a/ui/src/components/community.tsx b/ui/src/components/community.tsx index ba542582e..53f1222c8 100644 --- a/ui/src/components/community.tsx +++ b/ui/src/components/community.tsx @@ -1,11 +1,11 @@ -import { Component } from 'inferno'; +import { Component, linkEvent } from 'inferno'; import { Subscription } from "rxjs"; 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 { PostListings } from './post-listings'; import { Sidebar } from './sidebar'; -import { msgOp } from '../utils'; +import { msgOp, routeSortTypeToEnum, fetchLimit } from '../utils'; interface State { community: CommunityI; @@ -14,6 +14,9 @@ interface State { moderators: Array; admins: Array; loading: boolean; + posts: Array; + sort: SortType; + page: number; } export class Community extends Component { @@ -38,7 +41,20 @@ export class Community extends Component { admins: [], communityId: Number(this.props.match.params.id), 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) { @@ -66,6 +82,16 @@ export class Community extends Component { 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() { return (
@@ -78,7 +104,9 @@ export class Community extends Component { removed } - {this.state.community && } + {this.selects()} + + {this.paginator()}
{ ) } + selects() { + return ( +
+ +
+ ) + } + + paginator() { + return ( +
+ {this.state.page > 1 && + + } + +
+ ); + } + + 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) { console.log(msg); @@ -105,9 +199,9 @@ export class Community extends Component { this.state.community = res.community; this.state.moderators = res.moderators; this.state.admins = res.admins; - this.state.loading = false; document.title = `/f/${this.state.community.name} - Lemmy`; this.setState(this.state); + this.fetchPosts(); } else if (op == UserOperation.EditCommunity) { let res: CommunityResponse = msg; this.state.community = res.community; @@ -117,6 +211,19 @@ export class Community extends Component { this.state.community.subscribed = res.community.subscribed; this.state.community.number_of_subscribers = res.community.number_of_subscribers; 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); } } } diff --git a/ui/src/components/home.tsx b/ui/src/components/home.tsx index cebe222b7..e69de29bb 100644 --- a/ui/src/components/home.tsx +++ b/ui/src/components/home.tsx @@ -1,24 +0,0 @@ -import { Component } from 'inferno'; -import { Main } from './main'; -import { ListingType } from '../interfaces'; - -export class Home extends Component { - - constructor(props: any, context: any) { - super(props, context); - } - - render() { - return ( -
- ) - } - - componentDidMount() { - document.title = "Lemmy"; - } - - listType(): ListingType { - return (this.props.match.path == '/all') ? ListingType.All : ListingType.Subscribed; - } -} diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx index 6911ca40b..3b937453c 100644 --- a/ui/src/components/main.tsx +++ b/ui/src/components/main.tsx @@ -2,16 +2,11 @@ import { Component, linkEvent } from 'inferno'; import { Link } from 'inferno-router'; import { Subscription } from "rxjs"; 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 { PostListings } from './post-listings'; import { SiteForm } from './site-form'; -import { msgOp, repoUrl, mdToHtml } from '../utils'; - - -interface MainProps { - type: ListingType; -} +import { msgOp, repoUrl, mdToHtml, fetchLimit, routeSortTypeToEnum, routeListingTypeToEnum } from '../utils'; interface MainState { subscribedCommunities: Array; @@ -19,9 +14,13 @@ interface MainState { site: GetSiteResponse; showEditSite: boolean; loading: boolean; + posts: Array; + type_: ListingType; + sort: SortType; + page: number; } -export class Main extends Component { +export class Main extends Component { private subscription: Subscription; private emptyState: MainState = { @@ -43,7 +42,29 @@ export class Main extends Component { banned: [], }, 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) { @@ -72,37 +93,51 @@ export class Main extends Component { WebSocketService.Instance.listCommunities(listCommunitiesForm); - this.handleEditCancel = this.handleEditCancel.bind(this); + this.fetchPosts(); } componentWillUnmount() { 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() { return (
- + {this.posts()}
- {this.state.loading ? -
: -
- {this.trendingCommunities()} - {UserService.Instance.user && this.state.subscribedCommunities.length > 0 && -
-
Subscribed forums
-
    - {this.state.subscribedCommunities.map(community => -
  • {community.community_name}
  • - )} -
-
- } - {this.sidebar()} -
+ {!this.state.loading && +
+ {this.trendingCommunities()} + {UserService.Instance.user && this.state.subscribedCommunities.length > 0 && +
+
Subscribed forums
+
    + {this.state.subscribedCommunities.map(community => +
  • {community.community_name}
  • + )} +
+
+ } + {this.sidebar()} +
}
@@ -138,6 +173,12 @@ export class Main extends Component { ) } + 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() { return (
@@ -185,9 +226,61 @@ export class Main extends Component {

Suggest new features or report bugs here.

Made with Rust, Actix, Inferno, Typescript.

+ ) + } + + posts() { + return ( +
+ {this.state.loading ? +
: +
+ {this.selects()} + + {this.paginator()} +
+ } +
) } + selects() { + return ( +
+ + { UserService.Instance.user && + + + } +
+ ) + } + + paginator() { + return ( +
+ {this.state.page > 1 && + + } + +
+ ); + } + get canAdmin(): boolean { 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 { 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) { console.log(msg); let op: UserOperation = msgOp(msg); @@ -211,12 +344,10 @@ export class Main extends Component { } else if (op == UserOperation.GetFollowedCommunities) { let res: GetFollowedCommunitiesResponse = msg; this.state.subscribedCommunities = res.communities; - this.state.loading = false; this.setState(this.state); } else if (op == UserOperation.ListCommunities) { let res: ListCommunitiesResponse = msg; this.state.trendingCommunities = res.communities; - this.state.loading = false; this.setState(this.state); } else if (op == UserOperation.GetSite) { let res: GetSiteResponse = msg; @@ -234,6 +365,19 @@ export class Main extends Component { this.state.site.site = res.site; this.state.showEditSite = false; 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); } } } diff --git a/ui/src/components/post-listings.tsx b/ui/src/components/post-listings.tsx index b60b05d8e..c1126f520 100644 --- a/ui/src/components/post-listings.tsx +++ b/ui/src/components/post-listings.tsx @@ -1,183 +1,28 @@ -import { Component, linkEvent } from 'inferno'; +import { Component } from 'inferno'; import { Link } from 'inferno-router'; -import { Subscription } from "rxjs"; -import { retryWhen, delay, take } from 'rxjs/operators'; -import { UserOperation, Post, GetPostsForm, SortType, ListingType, GetPostsResponse, CreatePostLikeResponse, CommunityUser} from '../interfaces'; -import { WebSocketService, UserService } from '../services'; +import { Post } from '../interfaces'; import { PostListing } from './post-listing'; -import { msgOp, fetchLimit } from '../utils'; interface PostListingsProps { - type?: ListingType; - communityId?: number; -} - -interface PostListingsState { - moderators: Array; posts: Array; - sortType: SortType; - type_: ListingType; - page: number; - loading: boolean; + showCommunity?: boolean; } -export class PostListings extends Component { - - 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 - } +export class PostListings extends Component { constructor(props: any, context: any) { 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() { return (
- {this.state.loading ? -
: -
- {this.selects()} - {this.state.posts.length > 0 - ? this.state.posts.map(post => - ) - :
No Listings. {!this.props.communityId && Subscribe to some forums.}
- } - {this.paginator()} + {this.props.posts.length > 0 ? this.props.posts.map(post => + ) : +
No Listings. {!this.props.showCommunity && Subscribe to some forums.}
}
) } - - selects() { - return ( -
- - {!this.props.communityId && - UserService.Instance.user && - - - } -
- ) - } - - paginator() { - return ( -
- {this.state.page > 1 && - - } - -
- ); - } - - 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); - } - } } - - diff --git a/ui/src/components/user.tsx b/ui/src/components/user.tsx index 1d1f8e638..cccba8526 100644 --- a/ui/src/components/user.tsx +++ b/ui/src/components/user.tsx @@ -4,7 +4,7 @@ import { Subscription } from "rxjs"; import { retryWhen, delay, take } from 'rxjs/operators'; import { UserOperation, Post, Comment, CommunityUser, GetUserDetailsForm, SortType, UserDetailsResponse, UserView, CommentResponse } from '../interfaces'; import { WebSocketService } from '../services'; -import { msgOp, fetchLimit } from '../utils'; +import { msgOp, fetchLimit, routeSortTypeToEnum, capitalizeFirstLetter } from '../utils'; import { PostListing } from './post-listing'; import { CommentNodes } from './comment-nodes'; import { MomentTime } from './moment-time'; @@ -25,6 +25,7 @@ interface UserState { view: View; sort: SortType; page: number; + loading: boolean; } export class User extends Component { @@ -47,9 +48,10 @@ export class User extends Component { moderates: [], comments: [], posts: [], - view: View.Overview, - sort: SortType.New, - page: 1, + loading: true, + view: this.getViewFromProps(this.props), + sort: this.getSortTypeFromProps(this.props), + page: this.getPageFromProps(this.props), } constructor(props: any, context: any) { @@ -71,13 +73,42 @@ export class User extends Component { 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() { 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() { return (
+ {this.state.loading ? +
:
/u/{this.state.user.name}
@@ -102,6 +133,7 @@ export class User extends Component { {this.follows()}
+ }
) } @@ -247,15 +279,23 @@ export class User extends Component { ); } + 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) { i.state.page++; i.setState(i.state); + i.updateUrl(); i.refetch(); } prevPage(i: User) { i.state.page--; i.setState(i.state); + i.updateUrl(); i.refetch(); } @@ -275,6 +315,7 @@ export class User extends Component { i.state.sort = Number(event.target.value); i.state.page = 1; i.setState(i.state); + i.updateUrl(); i.refetch(); } @@ -282,6 +323,7 @@ export class User extends Component { i.state.view = Number(event.target.value); i.state.page = 1; i.setState(i.state); + i.updateUrl(); i.refetch(); } @@ -298,6 +340,7 @@ export class User extends Component { this.state.follows = res.follows; this.state.moderates = res.moderates; this.state.posts = res.posts; + this.state.loading = false; document.title = `/u/${this.state.user.name} - Lemmy`; this.setState(this.state); } else if (op == UserOperation.EditComment) { diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 446705f15..c91a03b0d 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -1,9 +1,8 @@ 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 { Footer } from './components/footer'; -import { Home } from './components/home'; import { Login } from './components/login'; import { CreatePost } from './components/create-post'; import { CreateCommunity } from './components/create-community'; @@ -35,20 +34,22 @@ class Index extends Component { render() { return ( - +
- - + + + + diff --git a/ui/src/utils.ts b/ui/src/utils.ts index f8bf6ea62..4199f09ce 100644 --- a/ui/src/utils.ts +++ b/ui/src/utils.ts @@ -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'; export let repoUrl = 'https://github.com/dessalines/lemmy'; @@ -67,3 +67,29 @@ export function isImage(url: string) { } 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)]; +} +