]> Untitled Git - lemmy.git/blob - ui/src/components/user-details.tsx
Merge remote-tracking branch 'weblate/master'
[lemmy.git] / ui / src / components / user-details.tsx
1 import { Component, linkEvent } from 'inferno';
2 import { WebSocketService, UserService } from '../services';
3 import { Subscription } from 'rxjs';
4 import { retryWhen, delay, take, last } from 'rxjs/operators';
5 import { i18n } from '../i18next';
6 import {
7   UserOperation,
8   Post,
9   Comment,
10   CommunityUser,
11   SortType,
12   UserDetailsResponse,
13   UserView,
14   WebSocketJsonResponse,
15   UserDetailsView,
16   CommentResponse,
17   BanUserResponse,
18   PostResponse,
19   AddAdminResponse,
20 } from '../interfaces';
21 import {
22   wsJsonToRes,
23   toast,
24   commentsToFlatNodes,
25   setupTippy,
26   editCommentRes,
27   saveCommentRes,
28   createCommentLikeRes,
29   createPostLikeFindRes,
30 } from '../utils';
31 import { PostListing } from './post-listing';
32 import { CommentNodes } from './comment-nodes';
33
34 interface UserDetailsProps {
35   username?: string;
36   user_id?: number;
37   page: number;
38   limit: number;
39   sort: string;
40   enableDownvotes: boolean;
41   enableNsfw: boolean;
42   view: UserDetailsView;
43   onPageChange(page: number): number | any;
44 }
45
46 interface UserDetailsState {
47   follows: Array<CommunityUser>;
48   moderates: Array<CommunityUser>;
49   comments: Array<Comment>;
50   posts: Array<Post>;
51   saved?: Array<Post>;
52   admins: Array<UserView>;
53 }
54
55 export class UserDetails extends Component<UserDetailsProps, UserDetailsState> {
56   private subscription: Subscription;
57   constructor(props: any, context: any) {
58     super(props, context);
59
60     this.state = {
61       follows: [],
62       moderates: [],
63       comments: [],
64       posts: [],
65       saved: [],
66       admins: [],
67     };
68
69     this.subscription = WebSocketService.Instance.subject
70       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
71       .subscribe(
72         msg => this.parseMessage(msg),
73         err => console.error(err),
74         () => console.log('complete')
75       );
76   }
77
78   componentWillUnmount() {
79     this.subscription.unsubscribe();
80   }
81
82   componentDidMount() {
83     this.fetchUserData();
84   }
85
86   componentDidUpdate(lastProps: UserDetailsProps) {
87     for (const key of Object.keys(lastProps)) {
88       if (lastProps[key] !== this.props[key]) {
89         this.fetchUserData();
90         break;
91       }
92     }
93     setupTippy();
94   }
95
96   fetchUserData() {
97     WebSocketService.Instance.getUserDetails({
98       user_id: this.props.user_id,
99       username: this.props.username,
100       sort: this.props.sort,
101       saved_only: this.props.view === UserDetailsView.Saved,
102       page: this.props.page,
103       limit: this.props.limit,
104     });
105   }
106
107   render() {
108     return (
109       <div>
110         {this.viewSelector(this.props.view)}
111         {this.paginator()}
112       </div>
113     );
114   }
115
116   viewSelector(view: UserDetailsView) {
117     if (view === UserDetailsView.Overview || view === UserDetailsView.Saved) {
118       return this.overview();
119     }
120     if (view === UserDetailsView.Comments) {
121       return this.comments();
122     }
123     if (view === UserDetailsView.Posts) {
124       return this.posts();
125     }
126   }
127
128   overview() {
129     const comments = this.state.comments.map((c: Comment) => {
130       return { type: 'comments', data: c };
131     });
132     const posts = this.state.posts.map((p: Post) => {
133       return { type: 'posts', data: p };
134     });
135
136     const combined: Array<{ type: string; data: Comment | Post }> = [
137       ...comments,
138       ...posts,
139     ];
140
141     // Sort it
142     if (SortType[this.props.sort] === SortType.New) {
143       combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
144     } else {
145       combined.sort((a, b) => b.data.score - a.data.score);
146     }
147
148     return (
149       <div>
150         {combined.map(i => (
151           <div>
152             {i.type === 'posts' ? (
153               <PostListing
154                 post={i.data as Post}
155                 admins={this.state.admins}
156                 showCommunity
157                 enableDownvotes={this.props.enableDownvotes}
158                 enableNsfw={this.props.enableNsfw}
159               />
160             ) : (
161               <CommentNodes
162                 nodes={[{ comment: i.data as Comment }]}
163                 admins={this.state.admins}
164                 noIndent
165                 showContext
166                 enableDownvotes={this.props.enableDownvotes}
167               />
168             )}
169           </div>
170         ))}
171       </div>
172     );
173   }
174
175   comments() {
176     return (
177       <div>
178         <CommentNodes
179           nodes={commentsToFlatNodes(this.state.comments)}
180           admins={this.state.admins}
181           noIndent
182           showContext
183           enableDownvotes={this.props.enableDownvotes}
184         />
185       </div>
186     );
187   }
188
189   posts() {
190     return (
191       <div>
192         {this.state.posts.map(post => (
193           <PostListing
194             post={post}
195             admins={this.state.admins}
196             showCommunity
197             enableDownvotes={this.props.enableDownvotes}
198             enableNsfw={this.props.enableNsfw}
199           />
200         ))}
201       </div>
202     );
203   }
204
205   paginator() {
206     return (
207       <div class="my-2">
208         {this.props.page > 1 && (
209           <button
210             class="btn btn-sm btn-secondary mr-1"
211             onClick={linkEvent(this, this.prevPage)}
212           >
213             {i18n.t('prev')}
214           </button>
215         )}
216         {this.state.comments.length + this.state.posts.length > 0 && (
217           <button
218             class="btn btn-sm btn-secondary"
219             onClick={linkEvent(this, this.nextPage)}
220           >
221             {i18n.t('next')}
222           </button>
223         )}
224       </div>
225     );
226   }
227
228   nextPage(i: UserDetails) {
229     i.props.onPageChange(i.props.page + 1);
230   }
231
232   prevPage(i: UserDetails) {
233     i.props.onPageChange(i.props.page - 1);
234   }
235
236   parseMessage(msg: WebSocketJsonResponse) {
237     const res = wsJsonToRes(msg);
238
239     if (msg.error) {
240       toast(i18n.t(msg.error), 'danger');
241       if (msg.error == 'couldnt_find_that_username_or_email') {
242         this.context.router.history.push('/');
243       }
244       return;
245     } else if (msg.reconnect) {
246       this.fetchUserData();
247     } else if (res.op == UserOperation.GetUserDetails) {
248       const data = res.data as UserDetailsResponse;
249       this.setState({
250         comments: data.comments,
251         follows: data.follows,
252         moderates: data.moderates,
253         posts: data.posts,
254         admins: data.admins,
255       });
256     } else if (res.op == UserOperation.CreateCommentLike) {
257       const data = res.data as CommentResponse;
258       createCommentLikeRes(data, this.state.comments);
259       this.setState({
260         comments: this.state.comments,
261       });
262     } else if (res.op == UserOperation.EditComment) {
263       const data = res.data as CommentResponse;
264       editCommentRes(data, this.state.comments);
265       this.setState({
266         comments: this.state.comments,
267       });
268     } else if (res.op == UserOperation.CreateComment) {
269       const data = res.data as CommentResponse;
270       if (
271         UserService.Instance.user &&
272         data.comment.creator_id == UserService.Instance.user.id
273       ) {
274         toast(i18n.t('reply_sent'));
275       }
276     } else if (res.op == UserOperation.SaveComment) {
277       const data = res.data as CommentResponse;
278       saveCommentRes(data, this.state.comments);
279       this.setState({
280         comments: this.state.comments,
281       });
282     } else if (res.op == UserOperation.CreatePostLike) {
283       const data = res.data as PostResponse;
284       createPostLikeFindRes(data, this.state.posts);
285       this.setState({
286         posts: this.state.posts,
287       });
288     } else if (res.op == UserOperation.BanUser) {
289       const data = res.data as BanUserResponse;
290       this.state.comments
291         .filter(c => c.creator_id == data.user.id)
292         .forEach(c => (c.banned = data.banned));
293       this.state.posts
294         .filter(c => c.creator_id == data.user.id)
295         .forEach(c => (c.banned = data.banned));
296       this.setState({
297         posts: this.state.posts,
298         comments: this.state.comments,
299       });
300     } else if (res.op == UserOperation.AddAdmin) {
301       const data = res.data as AddAdminResponse;
302       this.setState({
303         admins: data.admins,
304       });
305     }
306   }
307 }