]> Untitled Git - lemmy.git/blob - ui/src/components/community.tsx
9f96ac51edc1e0e22feb3cf175c57395399483d1
[lemmy.git] / ui / src / components / community.tsx
1 import { Component, linkEvent } from 'inferno';
2 import { Subscription } from 'rxjs';
3 import { retryWhen, delay, take } from 'rxjs/operators';
4 import {
5   UserOperation,
6   Community as CommunityI,
7   GetCommunityResponse,
8   CommunityResponse,
9   CommunityUser,
10   UserView,
11   SortType,
12   Post,
13   GetPostsForm,
14   GetCommunityForm,
15   ListingType,
16   GetPostsResponse,
17   PostResponse,
18   AddModToCommunityResponse,
19   BanFromCommunityResponse,
20   WebSocketJsonResponse,
21 } from '../interfaces';
22 import { WebSocketService, UserService } from '../services';
23 import { PostListings } from './post-listings';
24 import { SortSelect } from './sort-select';
25 import { Sidebar } from './sidebar';
26 import { wsJsonToRes, routeSortTypeToEnum, fetchLimit, toast } from '../utils';
27 import { i18n } from '../i18next';
28
29 interface State {
30   community: CommunityI;
31   communityId: number;
32   communityName: string;
33   moderators: Array<CommunityUser>;
34   admins: Array<UserView>;
35   online: number;
36   loading: boolean;
37   posts: Array<Post>;
38   sort: SortType;
39   page: number;
40 }
41
42 export class Community extends Component<any, State> {
43   private subscription: Subscription;
44   private emptyState: State = {
45     community: {
46       id: null,
47       name: null,
48       title: null,
49       category_id: null,
50       category_name: null,
51       creator_id: null,
52       creator_name: null,
53       number_of_subscribers: null,
54       number_of_posts: null,
55       number_of_comments: null,
56       published: null,
57       removed: null,
58       nsfw: false,
59       deleted: null,
60     },
61     moderators: [],
62     admins: [],
63     communityId: Number(this.props.match.params.id),
64     communityName: this.props.match.params.name,
65     online: null,
66     loading: true,
67     posts: [],
68     sort: this.getSortTypeFromProps(this.props),
69     page: this.getPageFromProps(this.props),
70   };
71
72   getSortTypeFromProps(props: any): SortType {
73     return props.match.params.sort
74       ? routeSortTypeToEnum(props.match.params.sort)
75       : UserService.Instance.user
76       ? UserService.Instance.user.default_sort_type
77       : SortType.Hot;
78   }
79
80   getPageFromProps(props: any): number {
81     return props.match.params.page ? Number(props.match.params.page) : 1;
82   }
83
84   constructor(props: any, context: any) {
85     super(props, context);
86
87     this.state = this.emptyState;
88     this.handleSortChange = this.handleSortChange.bind(this);
89
90     this.subscription = WebSocketService.Instance.subject
91       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
92       .subscribe(
93         msg => this.parseMessage(msg),
94         err => console.error(err),
95         () => console.log('complete')
96       );
97
98     let form: GetCommunityForm = {
99       id: this.state.communityId ? this.state.communityId : null,
100       name: this.state.communityName ? this.state.communityName : null,
101     };
102     WebSocketService.Instance.getCommunity(form);
103   }
104
105   componentWillUnmount() {
106     this.subscription.unsubscribe();
107   }
108
109   // Necessary for back button for some reason
110   componentWillReceiveProps(nextProps: any) {
111     if (
112       nextProps.history.action == 'POP' ||
113       nextProps.history.action == 'PUSH'
114     ) {
115       this.state.sort = this.getSortTypeFromProps(nextProps);
116       this.state.page = this.getPageFromProps(nextProps);
117       this.setState(this.state);
118       this.fetchPosts();
119     }
120   }
121
122   render() {
123     return (
124       <div class="container">
125         {this.state.loading ? (
126           <h5>
127             <svg class="icon icon-spinner spin">
128               <use xlinkHref="#icon-spinner"></use>
129             </svg>
130           </h5>
131         ) : (
132           <div class="row">
133             <div class="col-12 col-md-8">
134               <h5>
135                 {this.state.community.title}
136                 {this.state.community.removed && (
137                   <small className="ml-2 text-muted font-italic">
138                     {i18n.t('removed')}
139                   </small>
140                 )}
141                 {this.state.community.nsfw && (
142                   <small className="ml-2 text-muted font-italic">
143                     {i18n.t('nsfw')}
144                   </small>
145                 )}
146               </h5>
147               {this.selects()}
148               <PostListings posts={this.state.posts} />
149               {this.paginator()}
150             </div>
151             <div class="col-12 col-md-4">
152               <Sidebar
153                 community={this.state.community}
154                 moderators={this.state.moderators}
155                 admins={this.state.admins}
156                 online={this.state.online}
157               />
158             </div>
159           </div>
160         )}
161       </div>
162     );
163   }
164
165   selects() {
166     return (
167       <div class="mb-2">
168         <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
169         <a
170           href={`/feeds/c/${this.state.communityName}.xml?sort=${
171             SortType[this.state.sort]
172           }`}
173           target="_blank"
174         >
175           <svg class="icon mx-2 text-muted small">
176             <use xlinkHref="#icon-rss">#</use>
177           </svg>
178         </a>
179       </div>
180     );
181   }
182
183   paginator() {
184     return (
185       <div class="my-2">
186         {this.state.page > 1 && (
187           <button
188             class="btn btn-sm btn-secondary mr-1"
189             onClick={linkEvent(this, this.prevPage)}
190           >
191             {i18n.t('prev')}
192           </button>
193         )}
194         {this.state.posts.length == fetchLimit && (
195           <button
196             class="btn btn-sm btn-secondary"
197             onClick={linkEvent(this, this.nextPage)}
198           >
199             {i18n.t('next')}
200           </button>
201         )}
202       </div>
203     );
204   }
205
206   nextPage(i: Community) {
207     i.state.page++;
208     i.setState(i.state);
209     i.updateUrl();
210     i.fetchPosts();
211     window.scrollTo(0, 0);
212   }
213
214   prevPage(i: Community) {
215     i.state.page--;
216     i.setState(i.state);
217     i.updateUrl();
218     i.fetchPosts();
219     window.scrollTo(0, 0);
220   }
221
222   handleSortChange(val: SortType) {
223     this.state.sort = val;
224     this.state.page = 1;
225     this.state.loading = true;
226     this.setState(this.state);
227     this.updateUrl();
228     this.fetchPosts();
229     window.scrollTo(0, 0);
230   }
231
232   updateUrl() {
233     let sortStr = SortType[this.state.sort].toLowerCase();
234     this.props.history.push(
235       `/c/${this.state.community.name}/sort/${sortStr}/page/${this.state.page}`
236     );
237   }
238
239   fetchPosts() {
240     let getPostsForm: GetPostsForm = {
241       page: this.state.page,
242       limit: fetchLimit,
243       sort: SortType[this.state.sort],
244       type_: ListingType[ListingType.Community],
245       community_id: this.state.community.id,
246     };
247     WebSocketService.Instance.getPosts(getPostsForm);
248   }
249
250   parseMessage(msg: WebSocketJsonResponse) {
251     console.log(msg);
252     let res = wsJsonToRes(msg);
253     if (msg.error) {
254       toast(i18n.t(msg.error), 'danger');
255       this.context.router.history.push('/');
256       return;
257     } else if (res.op == UserOperation.GetCommunity) {
258       let data = res.data as GetCommunityResponse;
259       this.state.community = data.community;
260       this.state.moderators = data.moderators;
261       this.state.admins = data.admins;
262       this.state.online = data.online;
263       document.title = `/c/${this.state.community.name} - ${WebSocketService.Instance.site.name}`;
264       this.setState(this.state);
265       this.fetchPosts();
266     } else if (res.op == UserOperation.EditCommunity) {
267       let data = res.data as CommunityResponse;
268       this.state.community = data.community;
269       this.setState(this.state);
270     } else if (res.op == UserOperation.FollowCommunity) {
271       let data = res.data as CommunityResponse;
272       this.state.community.subscribed = data.community.subscribed;
273       this.state.community.number_of_subscribers =
274         data.community.number_of_subscribers;
275       this.setState(this.state);
276     } else if (res.op == UserOperation.GetPosts) {
277       let data = res.data as GetPostsResponse;
278       this.state.posts = data.posts;
279       this.state.loading = false;
280       this.setState(this.state);
281     } else if (res.op == UserOperation.EditPost) {
282       let data = res.data as PostResponse;
283       let found = this.state.posts.find(c => c.id == data.post.id);
284
285       found.url = data.post.url;
286       found.name = data.post.name;
287       found.nsfw = data.post.nsfw;
288
289       this.setState(this.state);
290     } else if (res.op == UserOperation.CreatePost) {
291       let data = res.data as PostResponse;
292       this.state.posts.unshift(data.post);
293       this.setState(this.state);
294     } else if (res.op == UserOperation.CreatePostLike) {
295       let data = res.data as PostResponse;
296       let found = this.state.posts.find(c => c.id == data.post.id);
297
298       found.score = data.post.score;
299       found.upvotes = data.post.upvotes;
300       found.downvotes = data.post.downvotes;
301       if (data.post.my_vote !== null) {
302         found.my_vote = data.post.my_vote;
303         found.upvoteLoading = false;
304         found.downvoteLoading = false;
305       }
306
307       this.setState(this.state);
308     } else if (res.op == UserOperation.AddModToCommunity) {
309       let data = res.data as AddModToCommunityResponse;
310       this.state.moderators = data.moderators;
311       this.setState(this.state);
312     } else if (res.op == UserOperation.BanFromCommunity) {
313       let data = res.data as BanFromCommunityResponse;
314
315       this.state.posts
316         .filter(p => p.creator_id == data.user.id)
317         .forEach(p => (p.banned = data.banned));
318
319       this.setState(this.state);
320     }
321   }
322 }