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