]> Untitled Git - lemmy.git/blob - ui/src/components/user-details.tsx
Merge branch 'main' into inbox-refactoring-merge
[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   UserDetailsView,
16   CommentResponse,
17   BanUserResponse,
18   PostResponse,
19 } 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: string;
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   }
83
84   componentDidUpdate(lastProps: UserDetailsProps) {
85     for (const key of Object.keys(lastProps)) {
86       if (lastProps[key] !== this.props[key]) {
87         this.fetchUserData();
88         break;
89       }
90     }
91     setupTippy();
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 (SortType[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                   post={i.data as Post}
154                   admins={this.props.admins}
155                   showCommunity
156                   enableDownvotes={this.props.enableDownvotes}
157                   enableNsfw={this.props.enableNsfw}
158                 />
159               ) : (
160                 <CommentNodes
161                   nodes={[{ comment: i.data as Comment }]}
162                   admins={this.props.admins}
163                   noBorder
164                   noIndent
165                   showCommunity
166                   showContext
167                   enableDownvotes={this.props.enableDownvotes}
168                 />
169               )}
170             </div>
171             <hr class="my-3" />
172           </>
173         ))}
174       </div>
175     );
176   }
177
178   comments() {
179     return (
180       <div>
181         <CommentNodes
182           nodes={commentsToFlatNodes(this.state.comments)}
183           admins={this.props.admins}
184           noIndent
185           showCommunity
186           showContext
187           enableDownvotes={this.props.enableDownvotes}
188         />
189       </div>
190     );
191   }
192
193   posts() {
194     return (
195       <div>
196         {this.state.posts.map(post => (
197           <>
198             <PostListing
199               post={post}
200               admins={this.props.admins}
201               showCommunity
202               enableDownvotes={this.props.enableDownvotes}
203               enableNsfw={this.props.enableNsfw}
204             />
205             <hr class="my-3" />
206           </>
207         ))}
208       </div>
209     );
210   }
211
212   paginator() {
213     return (
214       <div class="my-2">
215         {this.props.page > 1 && (
216           <button
217             class="btn btn-secondary mr-1"
218             onClick={linkEvent(this, this.prevPage)}
219           >
220             {i18n.t('prev')}
221           </button>
222         )}
223         {this.state.comments.length + this.state.posts.length > 0 && (
224           <button
225             class="btn btn-secondary"
226             onClick={linkEvent(this, this.nextPage)}
227           >
228             {i18n.t('next')}
229           </button>
230         )}
231       </div>
232     );
233   }
234
235   nextPage(i: UserDetails) {
236     i.props.onPageChange(i.props.page + 1);
237   }
238
239   prevPage(i: UserDetails) {
240     i.props.onPageChange(i.props.page - 1);
241   }
242
243   parseMessage(msg: WebSocketJsonResponse) {
244     console.log(msg);
245     const res = wsJsonToRes(msg);
246
247     if (msg.error) {
248       toast(i18n.t(msg.error), 'danger');
249       if (msg.error == 'couldnt_find_that_username_or_email') {
250         this.context.router.history.push('/');
251       }
252       return;
253     } else if (msg.reconnect) {
254       this.fetchUserData();
255     } else if (res.op == UserOperation.GetUserDetails) {
256       const data = res.data as UserDetailsResponse;
257       this.setState({
258         comments: data.comments,
259         follows: data.follows,
260         moderates: data.moderates,
261         posts: data.posts,
262       });
263     } else if (res.op == UserOperation.CreateCommentLike) {
264       const data = res.data as CommentResponse;
265       createCommentLikeRes(data, this.state.comments);
266       this.setState({
267         comments: this.state.comments,
268       });
269     } else if (
270       res.op == UserOperation.EditComment ||
271       res.op == UserOperation.DeleteComment ||
272       res.op == UserOperation.RemoveComment
273     ) {
274       const data = res.data as CommentResponse;
275       editCommentRes(data, this.state.comments);
276       this.setState({
277         comments: this.state.comments,
278       });
279     } else if (res.op == UserOperation.CreateComment) {
280       const data = res.data as CommentResponse;
281       if (
282         UserService.Instance.user &&
283         data.comment.creator_id == UserService.Instance.user.id
284       ) {
285         toast(i18n.t('reply_sent'));
286       }
287     } else if (res.op == UserOperation.SaveComment) {
288       const data = res.data as CommentResponse;
289       saveCommentRes(data, this.state.comments);
290       this.setState({
291         comments: this.state.comments,
292       });
293     } else if (res.op == UserOperation.CreatePostLike) {
294       const data = res.data as PostResponse;
295       createPostLikeFindRes(data, this.state.posts);
296       this.setState({
297         posts: this.state.posts,
298       });
299     } else if (res.op == UserOperation.BanUser) {
300       const data = res.data as BanUserResponse;
301       this.state.comments
302         .filter(c => c.creator_id == data.user.id)
303         .forEach(c => (c.banned = data.banned));
304       this.state.posts
305         .filter(c => c.creator_id == data.user.id)
306         .forEach(c => (c.banned = data.banned));
307       this.setState({
308         posts: this.state.posts,
309         comments: this.state.comments,
310       });
311     }
312   }
313 }