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