1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import { Subscription } from "rxjs";
4 import { retryWhen, delay, take } from 'rxjs/operators';
5 import { UserOperation, Post, Comment, CommunityUser, GetUserDetailsForm, SortType, UserDetailsResponse, UserView, CommentResponse } from '../interfaces';
6 import { WebSocketService } from '../services';
7 import { msgOp, fetchLimit, routeSortTypeToEnum, capitalizeFirstLetter } from '../utils';
8 import { PostListing } from './post-listing';
9 import { CommentNodes } from './comment-nodes';
10 import { MomentTime } from './moment-time';
13 Overview, Comments, Posts, Saved
20 follows: Array<CommunityUser>;
21 moderates: Array<CommunityUser>;
22 comments: Array<Comment>;
31 export class User extends Component<any, UserState> {
33 private subscription: Subscription;
34 private emptyState: UserState = {
40 number_of_posts: null,
42 number_of_comments: null,
52 view: this.getViewFromProps(this.props),
53 sort: this.getSortTypeFromProps(this.props),
54 page: this.getPageFromProps(this.props),
57 constructor(props: any, context: any) {
58 super(props, context);
60 this.state = this.emptyState;
62 this.state.user_id = Number(this.props.match.params.id);
63 this.state.username = this.props.match.params.username;
65 this.subscription = WebSocketService.Instance.subject
66 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
68 (msg) => this.parseMessage(msg),
69 (err) => console.error(err),
70 () => console.log('complete')
76 getViewFromProps(props: any): View {
77 return (props.match.params.view) ?
78 View[capitalizeFirstLetter(props.match.params.view)] :
82 getSortTypeFromProps(props: any): SortType {
83 return (props.match.params.sort) ?
84 routeSortTypeToEnum(props.match.params.sort) :
88 getPageFromProps(props: any): number {
89 return (props.match.params.page) ? Number(props.match.params.page) : 1;
92 componentWillUnmount() {
93 this.subscription.unsubscribe();
96 // Necessary for back button for some reason
97 componentWillReceiveProps(nextProps: any) {
98 if (nextProps.history.action == 'POP') {
99 this.state = this.emptyState;
100 this.state.view = this.getViewFromProps(nextProps);
101 this.state.sort = this.getSortTypeFromProps(nextProps);
102 this.state.page = this.getPageFromProps(nextProps);
109 <div class="container">
110 {this.state.loading ?
111 <h5><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> :
113 <div class="col-12 col-md-9">
114 <h5>/u/{this.state.user.name}</h5>
116 {this.state.view == View.Overview &&
119 {this.state.view == View.Comments &&
122 {this.state.view == View.Posts &&
125 {this.state.view == View.Saved &&
130 <div class="col-12 col-md-3">
143 <div className="mb-2">
144 <select value={this.state.view} onChange={linkEvent(this, this.handleViewChange)} class="custom-select custom-select-sm w-auto">
145 <option disabled>View</option>
146 <option value={View.Overview}>Overview</option>
147 <option value={View.Comments}>Comments</option>
148 <option value={View.Posts}>Posts</option>
149 <option value={View.Saved}>Saved</option>
151 <select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select custom-select-sm w-auto ml-2">
152 <option disabled>Sort Type</option>
153 <option value={SortType.New}>New</option>
154 <option value={SortType.TopDay}>Top Day</option>
155 <option value={SortType.TopWeek}>Week</option>
156 <option value={SortType.TopMonth}>Month</option>
157 <option value={SortType.TopYear}>Year</option>
158 <option value={SortType.TopAll}>All</option>
166 let combined: Array<{type_: string, data: Comment | Post}> = [];
167 let comments = this.state.comments.map(e => {return {type_: "comments", data: e}});
168 let posts = this.state.posts.map(e => {return {type_: "posts", data: e}});
170 combined.push(...comments);
171 combined.push(...posts);
174 if (this.state.sort == SortType.New) {
175 combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
177 combined.sort((a, b) => b.data.score - a.data.score);
185 ? <PostListing post={i.data as Post} showCommunity viewOnly />
186 : <CommentNodes nodes={[{comment: i.data as Comment}]} noIndent />
198 {this.state.comments.map(comment =>
199 <CommentNodes nodes={[{comment: comment}]} noIndent viewOnly />
208 {this.state.posts.map(post =>
209 <PostListing post={post} showCommunity viewOnly />
216 let user = this.state.user;
220 <div>Joined <MomentTime data={user} /></div>
221 <table class="table table-bordered table-sm mt-2">
223 <td>{user.post_score} points</td>
224 <td>{user.number_of_posts} posts</td>
227 <td>{user.comment_score} points</td>
228 <td>{user.number_of_comments} comments</td>
239 {this.state.moderates.length > 0 &&
242 <ul class="list-unstyled">
243 {this.state.moderates.map(community =>
244 <li><Link to={`/c/${community.community_name}`}>{community.community_name}</Link></li>
256 {this.state.follows.length > 0 &&
260 <ul class="list-unstyled">
261 {this.state.follows.map(community =>
262 <li><Link to={`/c/${community.community_name}`}>{community.community_name}</Link></li>
274 {this.state.page > 1 &&
275 <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button>
277 <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button>
283 let viewStr = View[this.state.view].toLowerCase();
284 let sortStr = SortType[this.state.sort].toLowerCase();
285 this.props.history.push(`/u/${this.state.user.name}/view/${viewStr}/sort/${sortStr}/page/${this.state.page}`);
303 let form: GetUserDetailsForm = {
304 user_id: this.state.user_id,
305 username: this.state.username,
306 sort: SortType[this.state.sort],
307 saved_only: this.state.view == View.Saved,
308 page: this.state.page,
311 WebSocketService.Instance.getUserDetails(form);
314 handleSortChange(i: User, event: any) {
315 i.state.sort = Number(event.target.value);
322 handleViewChange(i: User, event: any) {
323 i.state.view = Number(event.target.value);
330 parseMessage(msg: any) {
332 let op: UserOperation = msgOp(msg);
336 } else if (op == UserOperation.GetUserDetails) {
337 let res: UserDetailsResponse = msg;
338 this.state.user = res.user;
339 this.state.comments = res.comments;
340 this.state.follows = res.follows;
341 this.state.moderates = res.moderates;
342 this.state.posts = res.posts;
343 this.state.loading = false;
344 document.title = `/u/${this.state.user.name} - Lemmy`;
345 window.scrollTo(0,0);
346 this.setState(this.state);
347 } else if (op == UserOperation.EditComment) {
348 let res: CommentResponse = msg;
350 let found = this.state.comments.find(c => c.id == res.comment.id);
351 found.content = res.comment.content;
352 found.updated = res.comment.updated;
353 found.removed = res.comment.removed;
354 found.deleted = res.comment.deleted;
355 found.upvotes = res.comment.upvotes;
356 found.downvotes = res.comment.downvotes;
357 found.score = res.comment.score;
359 this.setState(this.state);
360 } else if (op == UserOperation.CreateComment) {
361 // let res: CommentResponse = msg;
363 // this.state.comments.unshift(res.comment); // TODO do this right
364 // this.setState(this.state);
365 } else if (op == UserOperation.SaveComment) {
366 let res: CommentResponse = msg;
367 let found = this.state.comments.find(c => c.id == res.comment.id);
368 found.saved = res.comment.saved;
369 this.setState(this.state);
370 } else if (op == UserOperation.CreateCommentLike) {
371 let res: CommentResponse = msg;
372 let found: Comment = this.state.comments.find(c => c.id === res.comment.id);
373 found.score = res.comment.score;
374 found.upvotes = res.comment.upvotes;
375 found.downvotes = res.comment.downvotes;
376 if (res.comment.my_vote !== null)
377 found.my_vote = res.comment.my_vote;
378 this.setState(this.state);