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