]> Untitled Git - lemmy.git/blob - ui/src/components/user.tsx
Adding emoji support
[lemmy.git] / ui / src / components / user.tsx
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';
11
12 enum View {
13   Overview, Comments, Posts, Saved
14 }
15
16 interface UserState {
17   user: UserView;
18   user_id: number;
19   username: string;
20   follows: Array<CommunityUser>;
21   moderates: Array<CommunityUser>;
22   comments: Array<Comment>;
23   posts: Array<Post>;
24   saved?: Array<Post>;
25   view: View;
26   sort: SortType;
27   page: number;
28   loading: boolean;
29 }
30
31 export class User extends Component<any, UserState> {
32
33   private subscription: Subscription;
34   private emptyState: UserState = {
35     user: {
36       id: null,
37       name: null,
38       fedi_name: null,
39       published: null,
40       number_of_posts: null,
41       post_score: null,
42       number_of_comments: null,
43       comment_score: null,
44     },
45     user_id: null,
46     username: null,
47     follows: [],
48     moderates: [],
49     comments: [],
50     posts: [],
51     loading: true,
52     view: this.getViewFromProps(this.props),
53     sort: this.getSortTypeFromProps(this.props),
54     page: this.getPageFromProps(this.props),
55   }
56
57   constructor(props: any, context: any) {
58     super(props, context);
59
60     this.state = this.emptyState;
61
62     this.state.user_id = Number(this.props.match.params.id);
63     this.state.username = this.props.match.params.username;
64
65     this.subscription = WebSocketService.Instance.subject
66     .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
67     .subscribe(
68       (msg) => this.parseMessage(msg),
69         (err) => console.error(err),
70         () => console.log('complete')
71     );
72
73     this.refetch();
74   }
75
76   getViewFromProps(props: any): View {
77     return (props.match.params.view) ? 
78       View[capitalizeFirstLetter(props.match.params.view)] : 
79       View.Overview;
80   }
81
82   getSortTypeFromProps(props: any): SortType {
83     return (props.match.params.sort) ? 
84       routeSortTypeToEnum(props.match.params.sort) : 
85       SortType.New;
86   }
87
88   getPageFromProps(props: any): number {
89     return (props.match.params.page) ? Number(props.match.params.page) : 1;
90   }
91
92   componentWillUnmount() {
93     this.subscription.unsubscribe();
94   }
95
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);
103       this.refetch();
104     }
105   }
106
107   render() {
108     return (
109       <div class="container">
110         {this.state.loading ? 
111         <h5><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> : 
112         <div class="row">
113           <div class="col-12 col-md-9">
114             <h5>/u/{this.state.user.name}</h5>
115             {this.selects()}
116             {this.state.view == View.Overview &&
117               this.overview()
118             }
119             {this.state.view == View.Comments &&
120               this.comments()
121             }
122             {this.state.view == View.Posts &&
123               this.posts()
124             }
125             {this.state.view == View.Saved &&
126               this.overview()
127             }
128             {this.paginator()}
129           </div>
130           <div class="col-12 col-md-3">
131             {this.userInfo()}
132             {this.moderates()}
133             {this.follows()}
134           </div>
135         </div>
136         }
137       </div>
138     )
139   }
140
141   selects() {
142     return (
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>
150         </select>
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>
159         </select>
160       </div>
161     )
162
163   }
164
165   overview() {
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}});
169
170     combined.push(...comments);
171     combined.push(...posts);
172
173     // Sort it
174     if (this.state.sort == SortType.New) {
175       combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
176     } else {
177       combined.sort((a, b) => b.data.score - a.data.score);
178     }
179
180     return (
181       <div>
182         {combined.map(i =>
183           <div>
184             {i.type_ == "posts"
185               ? <PostListing post={i.data as Post} showCommunity viewOnly />
186               : <CommentNodes nodes={[{comment: i.data as Comment}]} noIndent />
187             }
188           </div>
189                      )
190         }
191       </div>
192     )
193   }
194
195   comments() {
196     return (
197       <div>
198         {this.state.comments.map(comment => 
199           <CommentNodes nodes={[{comment: comment}]} noIndent viewOnly />
200         )}
201       </div>
202     );
203   }
204
205   posts() {
206     return (
207       <div>
208         {this.state.posts.map(post => 
209           <PostListing post={post} showCommunity viewOnly />
210         )}
211       </div>
212     );
213   }
214
215   userInfo() {
216     let user = this.state.user;
217     return (
218       <div>
219         <h5>{user.name}</h5>
220         <div>Joined <MomentTime data={user} /></div>
221         <table class="table table-bordered table-sm mt-2">
222           <tr>
223             <td>{user.post_score} points</td>
224             <td>{user.number_of_posts} posts</td>
225           </tr>
226           <tr>
227             <td>{user.comment_score} points</td>
228             <td>{user.number_of_comments} comments</td>
229           </tr>
230         </table>
231         <hr />
232       </div>
233     )
234   }
235
236   moderates() {
237     return (
238       <div>
239         {this.state.moderates.length > 0 &&
240           <div>
241             <h5>Moderates</h5>
242             <ul class="list-unstyled"> 
243               {this.state.moderates.map(community =>
244                 <li><Link to={`/c/${community.community_name}`}>{community.community_name}</Link></li>
245               )}
246             </ul>
247           </div>
248         }
249       </div>
250     )
251   }
252
253   follows() {
254     return (
255       <div>
256         {this.state.follows.length > 0 &&
257           <div>
258             <hr />
259             <h5>Subscribed</h5>
260             <ul class="list-unstyled"> 
261               {this.state.follows.map(community =>
262                 <li><Link to={`/c/${community.community_name}`}>{community.community_name}</Link></li>
263               )}
264             </ul>
265           </div>
266         }
267       </div>
268     )
269   }
270
271   paginator() {
272     return (
273       <div class="mt-2">
274         {this.state.page > 1 && 
275           <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}>Prev</button>
276         }
277         <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}>Next</button>
278       </div>
279     );
280   }
281
282   updateUrl() {
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}`);
286   }
287
288   nextPage(i: User) { 
289     i.state.page++;
290     i.setState(i.state);
291     i.updateUrl();
292     i.refetch();
293   }
294
295   prevPage(i: User) { 
296     i.state.page--;
297     i.setState(i.state);
298     i.updateUrl();
299     i.refetch();
300   }
301
302   refetch() {
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,
309       limit: fetchLimit,
310     };
311     WebSocketService.Instance.getUserDetails(form);
312   }
313
314   handleSortChange(i: User, event: any) {
315     i.state.sort = Number(event.target.value);
316     i.state.page = 1;
317     i.setState(i.state);
318     i.updateUrl();
319     i.refetch();
320   }
321
322   handleViewChange(i: User, event: any) {
323     i.state.view = Number(event.target.value);
324     i.state.page = 1;
325     i.setState(i.state);
326     i.updateUrl();
327     i.refetch();
328   }
329
330   parseMessage(msg: any) {
331     console.log(msg);
332     let op: UserOperation = msgOp(msg);
333     if (msg.error) {
334       alert(msg.error);
335       return;
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;
349
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;
358
359       this.setState(this.state);
360     } else if (op == UserOperation.CreateComment) {
361       // let res: CommentResponse = msg;
362       alert('Reply sent');
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);
379     }
380   }
381 }
382