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, UserSettingsForm, LoginResponse } from '../interfaces';
6 import { WebSocketService, UserService } 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 import { i18n } from '../i18next';
12 import { T } from 'inferno-i18next';
15 Overview, Comments, Posts, Saved
22 follows: Array<CommunityUser>;
23 moderates: Array<CommunityUser>;
24 comments: Array<Comment>;
31 userSettingsForm: UserSettingsForm;
32 userSettingsLoading: boolean;
35 export class User extends Component<any, UserState> {
37 private subscription: Subscription;
38 private emptyState: UserState = {
44 number_of_posts: null,
46 number_of_comments: null,
56 view: this.getViewFromProps(this.props),
57 sort: this.getSortTypeFromProps(this.props),
58 page: this.getPageFromProps(this.props),
63 userSettingsLoading: null,
66 constructor(props: any, context: any) {
67 super(props, context);
69 this.state = this.emptyState;
71 this.state.user_id = Number(this.props.match.params.id);
72 this.state.username = this.props.match.params.username;
74 this.subscription = WebSocketService.Instance.subject
75 .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
77 (msg) => this.parseMessage(msg),
78 (err) => console.error(err),
79 () => console.log('complete')
86 return UserService.Instance.user && UserService.Instance.user.id == this.state.user.id;
89 getViewFromProps(props: any): View {
90 return (props.match.params.view) ?
91 View[capitalizeFirstLetter(props.match.params.view)] :
95 getSortTypeFromProps(props: any): SortType {
96 return (props.match.params.sort) ?
97 routeSortTypeToEnum(props.match.params.sort) :
101 getPageFromProps(props: any): number {
102 return (props.match.params.page) ? Number(props.match.params.page) : 1;
105 componentWillUnmount() {
106 this.subscription.unsubscribe();
109 // Necessary for back button for some reason
110 componentWillReceiveProps(nextProps: any) {
111 if (nextProps.history.action == 'POP') {
112 this.state = this.emptyState;
113 this.state.view = this.getViewFromProps(nextProps);
114 this.state.sort = this.getSortTypeFromProps(nextProps);
115 this.state.page = this.getPageFromProps(nextProps);
122 <div class="container">
123 {this.state.loading ?
124 <h5><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h5> :
126 <div class="col-12 col-md-8">
127 <h5>/u/{this.state.user.name}</h5>
129 {this.state.view == View.Overview &&
132 {this.state.view == View.Comments &&
135 {this.state.view == View.Posts &&
138 {this.state.view == View.Saved &&
143 <div class="col-12 col-md-4">
145 {this.isCurrentUser &&
159 <div className="mb-2">
160 <select value={this.state.view} onChange={linkEvent(this, this.handleViewChange)} class="custom-select custom-select-sm w-auto">
161 <option disabled><T i18nKey="view">#</T></option>
162 <option value={View.Overview}><T i18nKey="overview">#</T></option>
163 <option value={View.Comments}><T i18nKey="comments">#</T></option>
164 <option value={View.Posts}><T i18nKey="posts">#</T></option>
165 <option value={View.Saved}><T i18nKey="saved">#</T></option>
167 <select value={this.state.sort} onChange={linkEvent(this, this.handleSortChange)} class="custom-select custom-select-sm w-auto ml-2">
168 <option disabled><T i18nKey="sort_type">#</T></option>
169 <option value={SortType.New}><T i18nKey="new">#</T></option>
170 <option value={SortType.TopDay}><T i18nKey="top_day">#</T></option>
171 <option value={SortType.TopWeek}><T i18nKey="week">#</T></option>
172 <option value={SortType.TopMonth}><T i18nKey="month">#</T></option>
173 <option value={SortType.TopYear}><T i18nKey="year">#</T></option>
174 <option value={SortType.TopAll}><T i18nKey="all">#</T></option>
182 let combined: Array<{type_: string, data: Comment | Post}> = [];
183 let comments = this.state.comments.map(e => {return {type_: "comments", data: e}});
184 let posts = this.state.posts.map(e => {return {type_: "posts", data: e}});
186 combined.push(...comments);
187 combined.push(...posts);
190 if (this.state.sort == SortType.New) {
191 combined.sort((a, b) => b.data.published.localeCompare(a.data.published));
193 combined.sort((a, b) => b.data.score - a.data.score);
201 ? <PostListing post={i.data as Post} showCommunity viewOnly />
202 : <CommentNodes nodes={[{comment: i.data as Comment}]} noIndent />
214 {this.state.comments.map(comment =>
215 <CommentNodes nodes={[{comment: comment}]} noIndent viewOnly />
224 {this.state.posts.map(post =>
225 <PostListing post={post} showCommunity viewOnly />
232 let user = this.state.user;
235 <div class="card border-secondary mb-3">
236 <div class="card-body">
238 <div>{i18n.t('joined')} <MomentTime data={user} /></div>
239 <div class="table-responsive">
240 <table class="table table-bordered table-sm mt-2 mb-0">
242 <td><T i18nKey="number_of_points" interpolation={{count: user.post_score}}>#</T></td>
243 <td><T i18nKey="number_of_posts" interpolation={{count: user.number_of_posts}}>#</T></td>
246 <td><T i18nKey="number_of_points" interpolation={{count: user.comment_score}}>#</T></td>
247 <td><T i18nKey="number_of_comments" interpolation={{count: user.number_of_comments}}>#</T></td>
260 <div class="card border-secondary mb-3">
261 <div class="card-body">
262 <h5><T i18nKey="settings">#</T></h5>
263 <form onSubmit={linkEvent(this, this.handleUserSettingsSubmit)}>
264 <div class="form-group row">
266 <div class="form-check">
267 <input class="form-check-input" type="checkbox" checked={this.state.userSettingsForm.show_nsfw} onChange={linkEvent(this, this.handleUserSettingsShowNsfwChange)}/>
268 <label class="form-check-label"><T i18nKey="show_nsfw">#</T></label>
272 <div class="form-group row mb-0">
274 <button type="submit" class="btn btn-secondary">{this.state.userSettingsLoading ?
275 <svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : capitalizeFirstLetter(i18n.t('save'))}</button>
288 <div class="card border-secondary mb-3">
289 <div class="card-body">
290 {this.state.moderates.length > 0 &&
292 <h5><T i18nKey="moderates">#</T></h5>
293 <ul class="list-unstyled mb-0">
294 {this.state.moderates.map(community =>
295 <li><Link to={`/c/${community.community_name}`}>{community.community_name}</Link></li>
309 {this.state.follows.length > 0 &&
310 <div class="card border-secondary mb-3">
311 <div class="card-body">
312 <h5><T i18nKey="subscribed">#</T></h5>
313 <ul class="list-unstyled mb-0">
314 {this.state.follows.map(community =>
315 <li><Link to={`/c/${community.community_name}`}>{community.community_name}</Link></li>
328 {this.state.page > 1 &&
329 <button class="btn btn-sm btn-secondary mr-1" onClick={linkEvent(this, this.prevPage)}><T i18nKey="prev">#</T></button>
331 <button class="btn btn-sm btn-secondary" onClick={linkEvent(this, this.nextPage)}><T i18nKey="next">#</T></button>
337 let viewStr = View[this.state.view].toLowerCase();
338 let sortStr = SortType[this.state.sort].toLowerCase();
339 this.props.history.push(`/u/${this.state.user.name}/view/${viewStr}/sort/${sortStr}/page/${this.state.page}`);
357 let form: GetUserDetailsForm = {
358 user_id: this.state.user_id,
359 username: this.state.username,
360 sort: SortType[this.state.sort],
361 saved_only: this.state.view == View.Saved,
362 page: this.state.page,
365 WebSocketService.Instance.getUserDetails(form);
368 handleSortChange(i: User, event: any) {
369 i.state.sort = Number(event.target.value);
376 handleViewChange(i: User, event: any) {
377 i.state.view = Number(event.target.value);
384 handleUserSettingsShowNsfwChange(i: User, event: any) {
385 i.state.userSettingsForm.show_nsfw = event.target.checked;
389 handleUserSettingsSubmit(i: User, event: any) {
390 event.preventDefault();
391 i.state.userSettingsLoading = true;
394 WebSocketService.Instance.saveUserSettings(i.state.userSettingsForm);
397 parseMessage(msg: any) {
399 let op: UserOperation = msgOp(msg);
401 alert(i18n.t(msg.error));
403 } else if (op == UserOperation.GetUserDetails) {
404 let res: UserDetailsResponse = msg;
405 this.state.user = res.user;
406 this.state.comments = res.comments;
407 this.state.follows = res.follows;
408 this.state.moderates = res.moderates;
409 this.state.posts = res.posts;
410 this.state.loading = false;
411 if (this.isCurrentUser) {
412 this.state.userSettingsForm.show_nsfw = UserService.Instance.user.show_nsfw;
414 document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
415 window.scrollTo(0,0);
416 this.setState(this.state);
417 } else if (op == UserOperation.EditComment) {
418 let res: CommentResponse = msg;
420 let found = this.state.comments.find(c => c.id == res.comment.id);
421 found.content = res.comment.content;
422 found.updated = res.comment.updated;
423 found.removed = res.comment.removed;
424 found.deleted = res.comment.deleted;
425 found.upvotes = res.comment.upvotes;
426 found.downvotes = res.comment.downvotes;
427 found.score = res.comment.score;
429 this.setState(this.state);
430 } else if (op == UserOperation.CreateComment) {
431 // let res: CommentResponse = msg;
432 alert(i18n.t('reply_sent'));
433 // this.state.comments.unshift(res.comment); // TODO do this right
434 // this.setState(this.state);
435 } else if (op == UserOperation.SaveComment) {
436 let res: CommentResponse = msg;
437 let found = this.state.comments.find(c => c.id == res.comment.id);
438 found.saved = res.comment.saved;
439 this.setState(this.state);
440 } else if (op == UserOperation.CreateCommentLike) {
441 let res: CommentResponse = msg;
442 let found: Comment = this.state.comments.find(c => c.id === res.comment.id);
443 found.score = res.comment.score;
444 found.upvotes = res.comment.upvotes;
445 found.downvotes = res.comment.downvotes;
446 if (res.comment.my_vote !== null)
447 found.my_vote = res.comment.my_vote;
448 this.setState(this.state);
449 } else if (op == UserOperation.SaveUserSettings) {
450 this.state = this.emptyState;
451 this.state.userSettingsLoading = false;
452 this.setState(this.state);
453 let res: LoginResponse = msg;
454 UserService.Instance.login(res);