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