]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/home.tsx
Fix site setup and login. Fixes #699 (#702)
[lemmy-ui.git] / src / shared / components / home / home.tsx
1 import { None, Option, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import { T } from "inferno-i18next-dess";
4 import { Link } from "inferno-router";
5 import {
6   AddAdminResponse,
7   BanPersonResponse,
8   BlockPersonResponse,
9   CommentReportResponse,
10   CommentResponse,
11   CommentView,
12   CommunityView,
13   GetComments,
14   GetCommentsResponse,
15   GetPosts,
16   GetPostsResponse,
17   GetSiteResponse,
18   ListCommunities,
19   ListCommunitiesResponse,
20   ListingType,
21   PostReportResponse,
22   PostResponse,
23   PostView,
24   PurgeItemResponse,
25   SiteResponse,
26   SortType,
27   UserOperation,
28   wsJsonToRes,
29   wsUserOp,
30 } from "lemmy-js-client";
31 import { Subscription } from "rxjs";
32 import { i18n } from "../../i18next";
33 import { DataType, InitialFetchRequest } from "../../interfaces";
34 import { UserService, WebSocketService } from "../../services";
35 import {
36   auth,
37   commentsToFlatNodes,
38   createCommentLikeRes,
39   createPostLikeFindRes,
40   editCommentRes,
41   editPostFindRes,
42   enableDownvotes,
43   enableNsfw,
44   fetchLimit,
45   getDataTypeFromProps,
46   getListingTypeFromProps,
47   getPageFromProps,
48   getSortTypeFromProps,
49   isBrowser,
50   notifyPost,
51   relTags,
52   restoreScrollPosition,
53   saveCommentRes,
54   saveScrollPosition,
55   setIsoData,
56   setupTippy,
57   showLocal,
58   toast,
59   trendingFetchLimit,
60   updatePersonBlock,
61   wsClient,
62   wsSubscribe,
63 } from "../../utils";
64 import { CommentNodes } from "../comment/comment-nodes";
65 import { DataTypeSelect } from "../common/data-type-select";
66 import { HtmlTags } from "../common/html-tags";
67 import { Icon, Spinner } from "../common/icon";
68 import { ListingTypeSelect } from "../common/listing-type-select";
69 import { Paginator } from "../common/paginator";
70 import { SortSelect } from "../common/sort-select";
71 import { CommunityLink } from "../community/community-link";
72 import { PostListings } from "../post/post-listings";
73 import { SiteSidebar } from "./site-sidebar";
74
75 interface HomeState {
76   trendingCommunities: CommunityView[];
77   siteRes: GetSiteResponse;
78   posts: PostView[];
79   comments: CommentView[];
80   listingType: ListingType;
81   dataType: DataType;
82   sort: SortType;
83   page: number;
84   showSubscribedMobile: boolean;
85   showTrendingMobile: boolean;
86   showSidebarMobile: boolean;
87   subscribedCollapsed: boolean;
88   loading: boolean;
89 }
90
91 interface HomeProps {
92   listingType: ListingType;
93   dataType: DataType;
94   sort: SortType;
95   page: number;
96 }
97
98 interface UrlParams {
99   listingType?: ListingType;
100   dataType?: string;
101   sort?: SortType;
102   page?: number;
103 }
104
105 export class Home extends Component<any, HomeState> {
106   private isoData = setIsoData(
107     this.context,
108     GetPostsResponse,
109     GetCommentsResponse,
110     ListCommunitiesResponse
111   );
112   private subscription: Subscription;
113   private emptyState: HomeState = {
114     trendingCommunities: [],
115     siteRes: this.isoData.site_res,
116     showSubscribedMobile: false,
117     showTrendingMobile: false,
118     showSidebarMobile: false,
119     subscribedCollapsed: false,
120     loading: true,
121     posts: [],
122     comments: [],
123     listingType: getListingTypeFromProps(
124       this.props,
125       ListingType[
126         this.isoData.site_res.site_view.match({
127           some: type_ => type_.site.default_post_listing_type,
128           none: ListingType.Local,
129         })
130       ]
131     ),
132     dataType: getDataTypeFromProps(this.props),
133     sort: getSortTypeFromProps(this.props),
134     page: getPageFromProps(this.props),
135   };
136
137   constructor(props: any, context: any) {
138     super(props, context);
139
140     this.state = this.emptyState;
141     this.handleSortChange = this.handleSortChange.bind(this);
142     this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
143     this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
144     this.handlePageChange = this.handlePageChange.bind(this);
145
146     this.parseMessage = this.parseMessage.bind(this);
147     this.subscription = wsSubscribe(this.parseMessage);
148
149     // Only fetch the data if coming from another route
150     if (this.isoData.path == this.context.router.route.match.url) {
151       let postsRes = Some(this.isoData.routeData[0] as GetPostsResponse);
152       let commentsRes = Some(this.isoData.routeData[1] as GetCommentsResponse);
153       let trendingRes = this.isoData.routeData[2] as ListCommunitiesResponse;
154
155       postsRes.match({
156         some: pvs => (this.state.posts = pvs.posts),
157         none: void 0,
158       });
159       commentsRes.match({
160         some: cvs => (this.state.comments = cvs.comments),
161         none: void 0,
162       });
163       this.state.trendingCommunities = trendingRes.communities;
164
165       if (isBrowser()) {
166         WebSocketService.Instance.send(
167           wsClient.communityJoin({ community_id: 0 })
168         );
169       }
170       this.state.loading = false;
171     } else {
172       this.fetchTrendingCommunities();
173       this.fetchData();
174     }
175   }
176
177   fetchTrendingCommunities() {
178     let listCommunitiesForm = new ListCommunities({
179       type_: Some(ListingType.Local),
180       sort: Some(SortType.Hot),
181       limit: Some(trendingFetchLimit),
182       page: None,
183       auth: auth(false).ok(),
184     });
185     WebSocketService.Instance.send(
186       wsClient.listCommunities(listCommunitiesForm)
187     );
188   }
189
190   componentDidMount() {
191     // This means it hasn't been set up yet
192     if (this.state.siteRes.site_view.isNone()) {
193       this.context.router.history.push("/setup");
194     }
195     setupTippy();
196   }
197
198   componentWillUnmount() {
199     saveScrollPosition(this.context);
200     this.subscription.unsubscribe();
201   }
202
203   static getDerivedStateFromProps(props: any): HomeProps {
204     return {
205       listingType: getListingTypeFromProps(props, ListingType.Local),
206       dataType: getDataTypeFromProps(props),
207       sort: getSortTypeFromProps(props),
208       page: getPageFromProps(props),
209     };
210   }
211
212   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
213     let pathSplit = req.path.split("/");
214     let dataType: DataType = pathSplit[3]
215       ? DataType[pathSplit[3]]
216       : DataType.Post;
217
218     // TODO figure out auth default_listingType, default_sort_type
219     let type_: Option<ListingType> = Some(
220       pathSplit[5]
221         ? ListingType[pathSplit[5]]
222         : UserService.Instance.myUserInfo.match({
223             some: mui =>
224               Object.values(ListingType)[
225                 mui.local_user_view.local_user.default_listing_type
226               ],
227             none: ListingType.Local,
228           })
229     );
230     let sort: Option<SortType> = Some(
231       pathSplit[7]
232         ? SortType[pathSplit[7]]
233         : UserService.Instance.myUserInfo.match({
234             some: mui =>
235               Object.values(SortType)[
236                 mui.local_user_view.local_user.default_sort_type
237               ],
238             none: SortType.Active,
239           })
240     );
241
242     let page = Some(pathSplit[9] ? Number(pathSplit[9]) : 1);
243
244     let promises: Promise<any>[] = [];
245
246     if (dataType == DataType.Post) {
247       let getPostsForm = new GetPosts({
248         community_id: None,
249         community_name: None,
250         type_,
251         page,
252         limit: Some(fetchLimit),
253         sort,
254         saved_only: Some(false),
255         auth: req.auth,
256       });
257
258       promises.push(req.client.getPosts(getPostsForm));
259       promises.push(Promise.resolve());
260     } else {
261       let getCommentsForm = new GetComments({
262         community_id: None,
263         community_name: None,
264         page,
265         limit: Some(fetchLimit),
266         sort,
267         type_,
268         saved_only: Some(false),
269         auth: req.auth,
270       });
271       promises.push(Promise.resolve());
272       promises.push(req.client.getComments(getCommentsForm));
273     }
274
275     let trendingCommunitiesForm = new ListCommunities({
276       type_: Some(ListingType.Local),
277       sort: Some(SortType.Hot),
278       limit: Some(trendingFetchLimit),
279       page: None,
280       auth: req.auth,
281     });
282     promises.push(req.client.listCommunities(trendingCommunitiesForm));
283
284     return promises;
285   }
286
287   componentDidUpdate(_: any, lastState: HomeState) {
288     if (
289       lastState.listingType !== this.state.listingType ||
290       lastState.dataType !== this.state.dataType ||
291       lastState.sort !== this.state.sort ||
292       lastState.page !== this.state.page
293     ) {
294       this.setState({ loading: true });
295       this.fetchData();
296     }
297   }
298
299   get documentTitle(): string {
300     return this.state.siteRes.site_view.match({
301       some: siteView =>
302         siteView.site.description.match({
303           some: desc => `${siteView.site.name} - ${desc}`,
304           none: siteView.site.name,
305         }),
306       none: "Lemmy",
307     });
308   }
309
310   render() {
311     return (
312       <div class="container">
313         <HtmlTags
314           title={this.documentTitle}
315           path={this.context.router.route.match.url}
316           description={None}
317           image={None}
318         />
319         {this.state.siteRes.site_view.isSome() && (
320           <div class="row">
321             <main role="main" class="col-12 col-md-8">
322               <div class="d-block d-md-none">{this.mobileView()}</div>
323               {this.posts()}
324             </main>
325             <aside class="d-none d-md-block col-md-4">{this.mySidebar()}</aside>
326           </div>
327         )}
328       </div>
329     );
330   }
331
332   get hasFollows(): boolean {
333     return UserService.Instance.myUserInfo.match({
334       some: mui => mui.follows.length > 0,
335       none: false,
336     });
337   }
338
339   mobileView() {
340     let siteRes = this.state.siteRes;
341     return (
342       <div class="row">
343         <div class="col-12">
344           {this.hasFollows && (
345             <button
346               class="btn btn-secondary d-inline-block mb-2 mr-3"
347               onClick={linkEvent(this, this.handleShowSubscribedMobile)}
348             >
349               {i18n.t("subscribed")}{" "}
350               <Icon
351                 icon={
352                   this.state.showSubscribedMobile
353                     ? `minus-square`
354                     : `plus-square`
355                 }
356                 classes="icon-inline"
357               />
358             </button>
359           )}
360           <button
361             class="btn btn-secondary d-inline-block mb-2 mr-3"
362             onClick={linkEvent(this, this.handleShowTrendingMobile)}
363           >
364             {i18n.t("trending")}{" "}
365             <Icon
366               icon={
367                 this.state.showTrendingMobile ? `minus-square` : `plus-square`
368               }
369               classes="icon-inline"
370             />
371           </button>
372           <button
373             class="btn btn-secondary d-inline-block mb-2 mr-3"
374             onClick={linkEvent(this, this.handleShowSidebarMobile)}
375           >
376             {i18n.t("sidebar")}{" "}
377             <Icon
378               icon={
379                 this.state.showSidebarMobile ? `minus-square` : `plus-square`
380               }
381               classes="icon-inline"
382             />
383           </button>
384           {this.state.showSidebarMobile &&
385             siteRes.site_view.match({
386               some: siteView => (
387                 <SiteSidebar
388                   site={siteView.site}
389                   admins={Some(siteRes.admins)}
390                   counts={Some(siteView.counts)}
391                   online={Some(siteRes.online)}
392                   showLocal={showLocal(this.isoData)}
393                 />
394               ),
395               none: <></>,
396             })}
397           {this.state.showTrendingMobile && (
398             <div class="col-12 card border-secondary mb-3">
399               <div class="card-body">{this.trendingCommunities()}</div>
400             </div>
401           )}
402           {this.state.showSubscribedMobile && (
403             <div class="col-12 card border-secondary mb-3">
404               <div class="card-body">{this.subscribedCommunities()}</div>
405             </div>
406           )}
407         </div>
408       </div>
409     );
410   }
411
412   mySidebar() {
413     let siteRes = this.state.siteRes;
414     return (
415       <div>
416         {!this.state.loading && (
417           <div>
418             <div class="card border-secondary mb-3">
419               <div class="card-body">
420                 {this.trendingCommunities()}
421                 {this.createCommunityButton()}
422                 {this.exploreCommunitiesButton()}
423               </div>
424             </div>
425             {siteRes.site_view.match({
426               some: siteView => (
427                 <SiteSidebar
428                   site={siteView.site}
429                   admins={Some(siteRes.admins)}
430                   counts={Some(siteView.counts)}
431                   online={Some(siteRes.online)}
432                   showLocal={showLocal(this.isoData)}
433                 />
434               ),
435               none: <></>,
436             })}
437             {this.hasFollows && (
438               <div class="card border-secondary mb-3">
439                 <div class="card-body">{this.subscribedCommunities()}</div>
440               </div>
441             )}
442           </div>
443         )}
444       </div>
445     );
446   }
447
448   createCommunityButton() {
449     return (
450       <Link className="mt-2 btn btn-secondary btn-block" to="/create_community">
451         {i18n.t("create_a_community")}
452       </Link>
453     );
454   }
455
456   exploreCommunitiesButton() {
457     return (
458       <Link className="btn btn-secondary btn-block" to="/communities">
459         {i18n.t("explore_communities")}
460       </Link>
461     );
462   }
463
464   trendingCommunities() {
465     return (
466       <div>
467         <h5>
468           <T i18nKey="trending_communities">
469             #
470             <Link className="text-body" to="/communities">
471               #
472             </Link>
473           </T>
474         </h5>
475         <ul class="list-inline mb-0">
476           {this.state.trendingCommunities.map(cv => (
477             <li class="list-inline-item d-inline-block">
478               <CommunityLink community={cv.community} />
479             </li>
480           ))}
481         </ul>
482       </div>
483     );
484   }
485
486   subscribedCommunities() {
487     return (
488       <div>
489         <h5>
490           <T class="d-inline" i18nKey="subscribed_to_communities">
491             #
492             <Link className="text-body" to="/communities">
493               #
494             </Link>
495           </T>
496           <button
497             class="btn btn-sm text-muted"
498             onClick={linkEvent(this, this.handleCollapseSubscribe)}
499             aria-label={i18n.t("collapse")}
500             data-tippy-content={i18n.t("collapse")}
501           >
502             {this.state.subscribedCollapsed ? (
503               <Icon icon="plus-square" classes="icon-inline" />
504             ) : (
505               <Icon icon="minus-square" classes="icon-inline" />
506             )}
507           </button>
508         </h5>
509         {!this.state.subscribedCollapsed && (
510           <ul class="list-inline mb-0">
511             {UserService.Instance.myUserInfo
512               .map(m => m.follows)
513               .unwrapOr([])
514               .map(cfv => (
515                 <li class="list-inline-item d-inline-block">
516                   <CommunityLink community={cfv.community} />
517                 </li>
518               ))}
519           </ul>
520         )}
521       </div>
522     );
523   }
524
525   updateUrl(paramUpdates: UrlParams) {
526     const listingTypeStr = paramUpdates.listingType || this.state.listingType;
527     const dataTypeStr = paramUpdates.dataType || DataType[this.state.dataType];
528     const sortStr = paramUpdates.sort || this.state.sort;
529     const page = paramUpdates.page || this.state.page;
530     this.props.history.push(
531       `/home/data_type/${dataTypeStr}/listing_type/${listingTypeStr}/sort/${sortStr}/page/${page}`
532     );
533   }
534
535   posts() {
536     return (
537       <div class="main-content-wrapper">
538         {this.state.loading ? (
539           <h5>
540             <Spinner large />
541           </h5>
542         ) : (
543           <div>
544             {this.selects()}
545             {this.listings()}
546             <Paginator
547               page={this.state.page}
548               onChange={this.handlePageChange}
549             />
550           </div>
551         )}
552       </div>
553     );
554   }
555
556   listings() {
557     return this.state.dataType == DataType.Post ? (
558       <PostListings
559         posts={this.state.posts}
560         showCommunity
561         removeDuplicates
562         enableDownvotes={enableDownvotes(this.state.siteRes)}
563         enableNsfw={enableNsfw(this.state.siteRes)}
564       />
565     ) : (
566       <CommentNodes
567         nodes={commentsToFlatNodes(this.state.comments)}
568         moderators={None}
569         admins={None}
570         maxCommentsShown={None}
571         noIndent
572         showCommunity
573         showContext
574         enableDownvotes={enableDownvotes(this.state.siteRes)}
575       />
576     );
577   }
578
579   selects() {
580     let allRss = `/feeds/all.xml?sort=${this.state.sort}`;
581     let localRss = `/feeds/local.xml?sort=${this.state.sort}`;
582     let frontRss = auth(false)
583       .ok()
584       .map(auth => `/feeds/front/${auth}.xml?sort=${this.state.sort}`);
585
586     return (
587       <div className="mb-3">
588         <span class="mr-3">
589           <DataTypeSelect
590             type_={this.state.dataType}
591             onChange={this.handleDataTypeChange}
592           />
593         </span>
594         <span class="mr-3">
595           <ListingTypeSelect
596             type_={this.state.listingType}
597             showLocal={showLocal(this.isoData)}
598             showSubscribed
599             onChange={this.handleListingTypeChange}
600           />
601         </span>
602         <span class="mr-2">
603           <SortSelect sort={this.state.sort} onChange={this.handleSortChange} />
604         </span>
605         {this.state.listingType == ListingType.All && (
606           <>
607             <a href={allRss} rel={relTags} title="RSS">
608               <Icon icon="rss" classes="text-muted small" />
609             </a>
610             <link rel="alternate" type="application/atom+xml" href={allRss} />
611           </>
612         )}
613         {this.state.listingType == ListingType.Local && (
614           <>
615             <a href={localRss} rel={relTags} title="RSS">
616               <Icon icon="rss" classes="text-muted small" />
617             </a>
618             <link rel="alternate" type="application/atom+xml" href={localRss} />
619           </>
620         )}
621         {this.state.listingType == ListingType.Subscribed &&
622           frontRss.match({
623             some: rss => (
624               <>
625                 <a href={rss} title="RSS" rel={relTags}>
626                   <Icon icon="rss" classes="text-muted small" />
627                 </a>
628                 <link rel="alternate" type="application/atom+xml" href={rss} />
629               </>
630             ),
631             none: <></>,
632           })}
633       </div>
634     );
635   }
636
637   handleShowSubscribedMobile(i: Home) {
638     i.state.showSubscribedMobile = !i.state.showSubscribedMobile;
639     i.setState(i.state);
640   }
641
642   handleShowTrendingMobile(i: Home) {
643     i.state.showTrendingMobile = !i.state.showTrendingMobile;
644     i.setState(i.state);
645   }
646
647   handleShowSidebarMobile(i: Home) {
648     i.state.showSidebarMobile = !i.state.showSidebarMobile;
649     i.setState(i.state);
650   }
651
652   handleCollapseSubscribe(i: Home) {
653     i.state.subscribedCollapsed = !i.state.subscribedCollapsed;
654     i.setState(i.state);
655   }
656
657   handlePageChange(page: number) {
658     this.updateUrl({ page });
659     window.scrollTo(0, 0);
660   }
661
662   handleSortChange(val: SortType) {
663     this.updateUrl({ sort: val, page: 1 });
664     window.scrollTo(0, 0);
665   }
666
667   handleListingTypeChange(val: ListingType) {
668     this.updateUrl({ listingType: val, page: 1 });
669     window.scrollTo(0, 0);
670   }
671
672   handleDataTypeChange(val: DataType) {
673     this.updateUrl({ dataType: DataType[val], page: 1 });
674     window.scrollTo(0, 0);
675   }
676
677   fetchData() {
678     if (this.state.dataType == DataType.Post) {
679       let getPostsForm = new GetPosts({
680         community_id: None,
681         community_name: None,
682         page: Some(this.state.page),
683         limit: Some(fetchLimit),
684         sort: Some(this.state.sort),
685         saved_only: Some(false),
686         auth: auth(false).ok(),
687         type_: Some(this.state.listingType),
688       });
689
690       WebSocketService.Instance.send(wsClient.getPosts(getPostsForm));
691     } else {
692       let getCommentsForm = new GetComments({
693         community_id: None,
694         community_name: None,
695         page: Some(this.state.page),
696         limit: Some(fetchLimit),
697         sort: Some(this.state.sort),
698         saved_only: Some(false),
699         auth: auth(false).ok(),
700         type_: Some(this.state.listingType),
701       });
702       WebSocketService.Instance.send(wsClient.getComments(getCommentsForm));
703     }
704   }
705
706   parseMessage(msg: any) {
707     let op = wsUserOp(msg);
708     console.log(msg);
709     if (msg.error) {
710       toast(i18n.t(msg.error), "danger");
711       return;
712     } else if (msg.reconnect) {
713       WebSocketService.Instance.send(
714         wsClient.communityJoin({ community_id: 0 })
715       );
716       this.fetchData();
717     } else if (op == UserOperation.ListCommunities) {
718       let data = wsJsonToRes<ListCommunitiesResponse>(
719         msg,
720         ListCommunitiesResponse
721       );
722       this.state.trendingCommunities = data.communities;
723       this.setState(this.state);
724     } else if (op == UserOperation.EditSite) {
725       let data = wsJsonToRes<SiteResponse>(msg, SiteResponse);
726       this.state.siteRes.site_view = Some(data.site_view);
727       this.setState(this.state);
728       toast(i18n.t("site_saved"));
729     } else if (op == UserOperation.GetPosts) {
730       let data = wsJsonToRes<GetPostsResponse>(msg, GetPostsResponse);
731       this.state.posts = data.posts;
732       this.state.loading = false;
733       this.setState(this.state);
734       WebSocketService.Instance.send(
735         wsClient.communityJoin({ community_id: 0 })
736       );
737       restoreScrollPosition(this.context);
738       setupTippy();
739     } else if (op == UserOperation.CreatePost) {
740       let data = wsJsonToRes<PostResponse>(msg, PostResponse);
741       // NSFW check
742       let nsfw = data.post_view.post.nsfw || data.post_view.community.nsfw;
743       let nsfwCheck =
744         !nsfw ||
745         (nsfw &&
746           UserService.Instance.myUserInfo
747             .map(m => m.local_user_view.local_user.show_nsfw)
748             .unwrapOr(false));
749
750       let showPostNotifs = UserService.Instance.myUserInfo
751         .map(m => m.local_user_view.local_user.show_new_post_notifs)
752         .unwrapOr(false);
753
754       // Only push these if you're on the first page, and you pass the nsfw check
755       if (this.state.page == 1 && nsfwCheck) {
756         // If you're on subscribed, only push it if you're subscribed.
757         if (this.state.listingType == ListingType.Subscribed) {
758           if (
759             UserService.Instance.myUserInfo
760               .map(m => m.follows)
761               .unwrapOr([])
762               .map(c => c.community.id)
763               .includes(data.post_view.community.id)
764           ) {
765             this.state.posts.unshift(data.post_view);
766             if (showPostNotifs) {
767               notifyPost(data.post_view, this.context.router);
768             }
769           }
770         } else if (this.state.listingType == ListingType.Local) {
771           // If you're on the local view, only push it if its local
772           if (data.post_view.post.local) {
773             this.state.posts.unshift(data.post_view);
774             if (showPostNotifs) {
775               notifyPost(data.post_view, this.context.router);
776             }
777           }
778         } else {
779           this.state.posts.unshift(data.post_view);
780           if (showPostNotifs) {
781             notifyPost(data.post_view, this.context.router);
782           }
783         }
784         this.setState(this.state);
785       }
786     } else if (
787       op == UserOperation.EditPost ||
788       op == UserOperation.DeletePost ||
789       op == UserOperation.RemovePost ||
790       op == UserOperation.LockPost ||
791       op == UserOperation.StickyPost ||
792       op == UserOperation.SavePost
793     ) {
794       let data = wsJsonToRes<PostResponse>(msg, PostResponse);
795       editPostFindRes(data.post_view, this.state.posts);
796       this.setState(this.state);
797     } else if (op == UserOperation.CreatePostLike) {
798       let data = wsJsonToRes<PostResponse>(msg, PostResponse);
799       createPostLikeFindRes(data.post_view, this.state.posts);
800       this.setState(this.state);
801     } else if (op == UserOperation.AddAdmin) {
802       let data = wsJsonToRes<AddAdminResponse>(msg, AddAdminResponse);
803       this.state.siteRes.admins = data.admins;
804       this.setState(this.state);
805     } else if (op == UserOperation.BanPerson) {
806       let data = wsJsonToRes<BanPersonResponse>(msg, BanPersonResponse);
807       this.state.posts
808         .filter(p => p.creator.id == data.person_view.person.id)
809         .forEach(p => (p.creator.banned = data.banned));
810
811       this.setState(this.state);
812     } else if (op == UserOperation.GetComments) {
813       let data = wsJsonToRes<GetCommentsResponse>(msg, GetCommentsResponse);
814       this.state.comments = data.comments;
815       this.state.loading = false;
816       this.setState(this.state);
817     } else if (
818       op == UserOperation.EditComment ||
819       op == UserOperation.DeleteComment ||
820       op == UserOperation.RemoveComment
821     ) {
822       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
823       editCommentRes(data.comment_view, this.state.comments);
824       this.setState(this.state);
825     } else if (op == UserOperation.CreateComment) {
826       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
827
828       // Necessary since it might be a user reply
829       if (data.form_id) {
830         // If you're on subscribed, only push it if you're subscribed.
831         if (this.state.listingType == ListingType.Subscribed) {
832           if (
833             UserService.Instance.myUserInfo
834               .map(m => m.follows)
835               .unwrapOr([])
836               .map(c => c.community.id)
837               .includes(data.comment_view.community.id)
838           ) {
839             this.state.comments.unshift(data.comment_view);
840           }
841         } else {
842           this.state.comments.unshift(data.comment_view);
843         }
844         this.setState(this.state);
845       }
846     } else if (op == UserOperation.SaveComment) {
847       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
848       saveCommentRes(data.comment_view, this.state.comments);
849       this.setState(this.state);
850     } else if (op == UserOperation.CreateCommentLike) {
851       let data = wsJsonToRes<CommentResponse>(msg, CommentResponse);
852       createCommentLikeRes(data.comment_view, this.state.comments);
853       this.setState(this.state);
854     } else if (op == UserOperation.BlockPerson) {
855       let data = wsJsonToRes<BlockPersonResponse>(msg, BlockPersonResponse);
856       updatePersonBlock(data);
857     } else if (op == UserOperation.CreatePostReport) {
858       let data = wsJsonToRes<PostReportResponse>(msg, PostReportResponse);
859       if (data) {
860         toast(i18n.t("report_created"));
861       }
862     } else if (op == UserOperation.CreateCommentReport) {
863       let data = wsJsonToRes<CommentReportResponse>(msg, CommentReportResponse);
864       if (data) {
865         toast(i18n.t("report_created"));
866       }
867     } else if (
868       op == UserOperation.PurgePerson ||
869       op == UserOperation.PurgePost ||
870       op == UserOperation.PurgeComment ||
871       op == UserOperation.PurgeCommunity
872     ) {
873       let data = wsJsonToRes<PurgeItemResponse>(msg, PurgeItemResponse);
874       if (data.success) {
875         toast(i18n.t("purge_success"));
876         this.context.router.history.push(`/`);
877       }
878     }
879   }
880 }