]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/reports.tsx
Merge branch 'main' into route-data-refactor
[lemmy-ui.git] / src / shared / components / person / reports.tsx
1 import { Component, linkEvent } from "inferno";
2 import {
3   CommentReportResponse,
4   CommentReportView,
5   GetSiteResponse,
6   ListCommentReports,
7   ListCommentReportsResponse,
8   ListPostReports,
9   ListPostReportsResponse,
10   ListPrivateMessageReports,
11   ListPrivateMessageReportsResponse,
12   PostReportResponse,
13   PostReportView,
14   PrivateMessageReportResponse,
15   PrivateMessageReportView,
16   UserOperation,
17   wsJsonToRes,
18   wsUserOp,
19 } from "lemmy-js-client";
20 import { Subscription } from "rxjs";
21 import { i18n } from "../../i18next";
22 import { InitialFetchRequest } from "../../interfaces";
23 import { UserService, WebSocketService } from "../../services";
24 import {
25   WithPromiseKeys,
26   amAdmin,
27   fetchLimit,
28   isBrowser,
29   myAuth,
30   setIsoData,
31   setupTippy,
32   toast,
33   updateCommentReportRes,
34   updatePostReportRes,
35   updatePrivateMessageReportRes,
36   wsClient,
37   wsSubscribe,
38 } from "../../utils";
39 import { CommentReport } from "../comment/comment-report";
40 import { HtmlTags } from "../common/html-tags";
41 import { Spinner } from "../common/icon";
42 import { Paginator } from "../common/paginator";
43 import { PostReport } from "../post/post-report";
44 import { PrivateMessageReport } from "../private_message/private-message-report";
45
46 enum UnreadOrAll {
47   Unread,
48   All,
49 }
50
51 enum MessageType {
52   All,
53   CommentReport,
54   PostReport,
55   PrivateMessageReport,
56 }
57
58 enum MessageEnum {
59   CommentReport,
60   PostReport,
61   PrivateMessageReport,
62 }
63
64 interface ReportsData {
65   commentReportsResponse: ListCommentReportsResponse;
66   postReportsResponse: ListPostReportsResponse;
67   privateMessageReportsResponse?: ListPrivateMessageReportsResponse;
68 }
69
70 type ItemType = {
71   id: number;
72   type_: MessageEnum;
73   view: CommentReportView | PostReportView | PrivateMessageReportView;
74   published: string;
75 };
76
77 interface ReportsState {
78   listCommentReportsResponse?: ListCommentReportsResponse;
79   listPostReportsResponse?: ListPostReportsResponse;
80   listPrivateMessageReportsResponse?: ListPrivateMessageReportsResponse;
81   unreadOrAll: UnreadOrAll;
82   messageType: MessageType;
83   combined: ItemType[];
84   siteRes: GetSiteResponse;
85   page: number;
86   loading: boolean;
87 }
88
89 export class Reports extends Component<any, ReportsState> {
90   private isoData = setIsoData<ReportsData>(this.context);
91   private subscription?: Subscription;
92   state: ReportsState = {
93     unreadOrAll: UnreadOrAll.Unread,
94     messageType: MessageType.All,
95     combined: [],
96     page: 1,
97     siteRes: this.isoData.site_res,
98     loading: true,
99   };
100
101   constructor(props: any, context: any) {
102     super(props, context);
103
104     this.handlePageChange = this.handlePageChange.bind(this);
105
106     this.parseMessage = this.parseMessage.bind(this);
107     this.subscription = wsSubscribe(this.parseMessage);
108
109     // Only fetch the data if coming from another route
110     if (this.isoData.path === this.context.router.route.match.url) {
111       const {
112         commentReportsResponse,
113         postReportsResponse,
114         privateMessageReportsResponse,
115       } = this.isoData.routeData;
116
117       this.state = {
118         ...this.state,
119         listCommentReportsResponse: commentReportsResponse,
120         listPostReportsResponse: postReportsResponse,
121         listPrivateMessageReportsResponse: privateMessageReportsResponse,
122       };
123
124       this.state = {
125         ...this.state,
126         combined: this.buildCombined(),
127         loading: false,
128       };
129     } else {
130       this.refetch();
131     }
132   }
133
134   componentWillUnmount() {
135     if (isBrowser()) {
136       this.subscription?.unsubscribe();
137     }
138   }
139
140   get documentTitle(): string {
141     let mui = UserService.Instance.myUserInfo;
142     return mui
143       ? `@${mui.local_user_view.person.name} ${i18n.t("reports")} - ${
144           this.state.siteRes.site_view.site.name
145         }`
146       : "";
147   }
148
149   render() {
150     return (
151       <div className="container-lg">
152         {this.state.loading ? (
153           <h5>
154             <Spinner large />
155           </h5>
156         ) : (
157           <div className="row">
158             <div className="col-12">
159               <HtmlTags
160                 title={this.documentTitle}
161                 path={this.context.router.route.match.url}
162               />
163               <h5 className="mb-2">{i18n.t("reports")}</h5>
164               {this.selects()}
165               {this.state.messageType == MessageType.All && this.all()}
166               {this.state.messageType == MessageType.CommentReport &&
167                 this.commentReports()}
168               {this.state.messageType == MessageType.PostReport &&
169                 this.postReports()}
170               {this.state.messageType == MessageType.PrivateMessageReport &&
171                 this.privateMessageReports()}
172               <Paginator
173                 page={this.state.page}
174                 onChange={this.handlePageChange}
175               />
176             </div>
177           </div>
178         )}
179       </div>
180     );
181   }
182
183   unreadOrAllRadios() {
184     return (
185       <div className="btn-group btn-group-toggle flex-wrap mb-2">
186         <label
187           className={`btn btn-outline-secondary pointer
188             ${this.state.unreadOrAll == UnreadOrAll.Unread && "active"}
189           `}
190         >
191           <input
192             type="radio"
193             value={UnreadOrAll.Unread}
194             checked={this.state.unreadOrAll == UnreadOrAll.Unread}
195             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
196           />
197           {i18n.t("unread")}
198         </label>
199         <label
200           className={`btn btn-outline-secondary pointer
201             ${this.state.unreadOrAll == UnreadOrAll.All && "active"}
202           `}
203         >
204           <input
205             type="radio"
206             value={UnreadOrAll.All}
207             checked={this.state.unreadOrAll == UnreadOrAll.All}
208             onChange={linkEvent(this, this.handleUnreadOrAllChange)}
209           />
210           {i18n.t("all")}
211         </label>
212       </div>
213     );
214   }
215
216   messageTypeRadios() {
217     return (
218       <div className="btn-group btn-group-toggle flex-wrap mb-2">
219         <label
220           className={`btn btn-outline-secondary pointer
221             ${this.state.messageType == MessageType.All && "active"}
222           `}
223         >
224           <input
225             type="radio"
226             value={MessageType.All}
227             checked={this.state.messageType == MessageType.All}
228             onChange={linkEvent(this, this.handleMessageTypeChange)}
229           />
230           {i18n.t("all")}
231         </label>
232         <label
233           className={`btn btn-outline-secondary pointer
234             ${this.state.messageType == MessageType.CommentReport && "active"}
235           `}
236         >
237           <input
238             type="radio"
239             value={MessageType.CommentReport}
240             checked={this.state.messageType == MessageType.CommentReport}
241             onChange={linkEvent(this, this.handleMessageTypeChange)}
242           />
243           {i18n.t("comments")}
244         </label>
245         <label
246           className={`btn btn-outline-secondary pointer
247             ${this.state.messageType == MessageType.PostReport && "active"}
248           `}
249         >
250           <input
251             type="radio"
252             value={MessageType.PostReport}
253             checked={this.state.messageType == MessageType.PostReport}
254             onChange={linkEvent(this, this.handleMessageTypeChange)}
255           />
256           {i18n.t("posts")}
257         </label>
258         {amAdmin() && (
259           <label
260             className={`btn btn-outline-secondary pointer
261             ${
262               this.state.messageType == MessageType.PrivateMessageReport &&
263               "active"
264             }
265           `}
266           >
267             <input
268               type="radio"
269               value={MessageType.PrivateMessageReport}
270               checked={
271                 this.state.messageType == MessageType.PrivateMessageReport
272               }
273               onChange={linkEvent(this, this.handleMessageTypeChange)}
274             />
275             {i18n.t("messages")}
276           </label>
277         )}
278       </div>
279     );
280   }
281
282   selects() {
283     return (
284       <div className="mb-2">
285         <span className="mr-3">{this.unreadOrAllRadios()}</span>
286         <span className="mr-3">{this.messageTypeRadios()}</span>
287       </div>
288     );
289   }
290
291   commentReportToItemType(r: CommentReportView): ItemType {
292     return {
293       id: r.comment_report.id,
294       type_: MessageEnum.CommentReport,
295       view: r,
296       published: r.comment_report.published,
297     };
298   }
299
300   postReportToItemType(r: PostReportView): ItemType {
301     return {
302       id: r.post_report.id,
303       type_: MessageEnum.PostReport,
304       view: r,
305       published: r.post_report.published,
306     };
307   }
308
309   privateMessageReportToItemType(r: PrivateMessageReportView): ItemType {
310     return {
311       id: r.private_message_report.id,
312       type_: MessageEnum.PrivateMessageReport,
313       view: r,
314       published: r.private_message_report.published,
315     };
316   }
317
318   buildCombined(): ItemType[] {
319     // let comments: ItemType[] = this.state.listCommentReportsResponse
320     //   .map(r => r.comment_reports)
321     //   .unwrapOr([])
322     //   .map(r => this.commentReportToItemType(r));
323     let comments =
324       this.state.listCommentReportsResponse?.comment_reports.map(
325         this.commentReportToItemType
326       ) ?? [];
327     let posts =
328       this.state.listPostReportsResponse?.post_reports.map(
329         this.postReportToItemType
330       ) ?? [];
331     let privateMessages =
332       this.state.listPrivateMessageReportsResponse?.private_message_reports.map(
333         this.privateMessageReportToItemType
334       ) ?? [];
335
336     return [...comments, ...posts, ...privateMessages].sort((a, b) =>
337       b.published.localeCompare(a.published)
338     );
339   }
340
341   renderItemType(i: ItemType) {
342     switch (i.type_) {
343       case MessageEnum.CommentReport:
344         return (
345           <CommentReport key={i.id} report={i.view as CommentReportView} />
346         );
347       case MessageEnum.PostReport:
348         return <PostReport key={i.id} report={i.view as PostReportView} />;
349       case MessageEnum.PrivateMessageReport:
350         return (
351           <PrivateMessageReport
352             key={i.id}
353             report={i.view as PrivateMessageReportView}
354           />
355         );
356       default:
357         return <div />;
358     }
359   }
360
361   all() {
362     return (
363       <div>
364         {this.state.combined.map(i => (
365           <>
366             <hr />
367             {this.renderItemType(i)}
368           </>
369         ))}
370       </div>
371     );
372   }
373
374   commentReports() {
375     let reports = this.state.listCommentReportsResponse?.comment_reports;
376     return (
377       reports && (
378         <div>
379           {reports.map(cr => (
380             <>
381               <hr />
382               <CommentReport key={cr.comment_report.id} report={cr} />
383             </>
384           ))}
385         </div>
386       )
387     );
388   }
389
390   postReports() {
391     let reports = this.state.listPostReportsResponse?.post_reports;
392     return (
393       reports && (
394         <div>
395           {reports.map(pr => (
396             <>
397               <hr />
398               <PostReport key={pr.post_report.id} report={pr} />
399             </>
400           ))}
401         </div>
402       )
403     );
404   }
405
406   privateMessageReports() {
407     let reports =
408       this.state.listPrivateMessageReportsResponse?.private_message_reports;
409     return (
410       reports && (
411         <div>
412           {reports.map(pmr => (
413             <>
414               <hr />
415               <PrivateMessageReport
416                 key={pmr.private_message_report.id}
417                 report={pmr}
418               />
419             </>
420           ))}
421         </div>
422       )
423     );
424   }
425
426   handlePageChange(page: number) {
427     this.setState({ page });
428     this.refetch();
429   }
430
431   handleUnreadOrAllChange(i: Reports, event: any) {
432     i.setState({ unreadOrAll: Number(event.target.value), page: 1 });
433     i.refetch();
434   }
435
436   handleMessageTypeChange(i: Reports, event: any) {
437     i.setState({ messageType: Number(event.target.value), page: 1 });
438     i.refetch();
439   }
440
441   static fetchInitialData({
442     auth,
443     client,
444   }: InitialFetchRequest): WithPromiseKeys<ReportsData> {
445     const unresolved_only = true;
446     const page = 1;
447     const limit = fetchLimit;
448
449     const commentReportsForm: ListCommentReports = {
450       unresolved_only,
451       page,
452       limit,
453       auth: auth as string,
454     };
455
456     const postReportsForm: ListPostReports = {
457       unresolved_only,
458       page,
459       limit,
460       auth: auth as string,
461     };
462
463     const data: WithPromiseKeys<ReportsData> = {
464       commentReportsResponse: client.listCommentReports(commentReportsForm),
465       postReportsResponse: client.listPostReports(postReportsForm),
466     };
467
468     if (amAdmin()) {
469       const privateMessageReportsForm: ListPrivateMessageReports = {
470         unresolved_only,
471         page,
472         limit,
473         auth: auth as string,
474       };
475
476       data.privateMessageReportsResponse = client.listPrivateMessageReports(
477         privateMessageReportsForm
478       );
479     }
480
481     return data;
482   }
483
484   refetch() {
485     const unresolved_only = this.state.unreadOrAll === UnreadOrAll.Unread;
486     const page = this.state.page;
487     const limit = fetchLimit;
488     const auth = myAuth();
489
490     if (auth) {
491       const commentReportsForm: ListCommentReports = {
492         unresolved_only,
493         page,
494         limit,
495         auth,
496       };
497
498       WebSocketService.Instance.send(
499         wsClient.listCommentReports(commentReportsForm)
500       );
501
502       const postReportsForm: ListPostReports = {
503         unresolved_only,
504         page,
505         limit,
506         auth,
507       };
508
509       WebSocketService.Instance.send(wsClient.listPostReports(postReportsForm));
510
511       if (amAdmin()) {
512         const privateMessageReportsForm: ListPrivateMessageReports = {
513           unresolved_only,
514           page,
515           limit,
516           auth,
517         };
518         WebSocketService.Instance.send(
519           wsClient.listPrivateMessageReports(privateMessageReportsForm)
520         );
521       }
522     }
523   }
524
525   parseMessage(msg: any) {
526     let op = wsUserOp(msg);
527     console.log(msg);
528     if (msg.error) {
529       toast(i18n.t(msg.error), "danger");
530       return;
531     } else if (msg.reconnect) {
532       this.refetch();
533     } else if (op == UserOperation.ListCommentReports) {
534       let data = wsJsonToRes<ListCommentReportsResponse>(msg);
535       this.setState({ listCommentReportsResponse: data });
536       this.setState({ combined: this.buildCombined(), loading: false });
537       // this.sendUnreadCount();
538       window.scrollTo(0, 0);
539       setupTippy();
540     } else if (op == UserOperation.ListPostReports) {
541       let data = wsJsonToRes<ListPostReportsResponse>(msg);
542       this.setState({ listPostReportsResponse: data });
543       this.setState({ combined: this.buildCombined(), loading: false });
544       // this.sendUnreadCount();
545       window.scrollTo(0, 0);
546       setupTippy();
547     } else if (op == UserOperation.ListPrivateMessageReports) {
548       let data = wsJsonToRes<ListPrivateMessageReportsResponse>(msg);
549       this.setState({ listPrivateMessageReportsResponse: data });
550       this.setState({ combined: this.buildCombined(), loading: false });
551       // this.sendUnreadCount();
552       window.scrollTo(0, 0);
553       setupTippy();
554     } else if (op == UserOperation.ResolvePostReport) {
555       let data = wsJsonToRes<PostReportResponse>(msg);
556       updatePostReportRes(
557         data.post_report_view,
558         this.state.listPostReportsResponse?.post_reports
559       );
560       let urcs = UserService.Instance.unreadReportCountSub;
561       if (data.post_report_view.post_report.resolved) {
562         urcs.next(urcs.getValue() - 1);
563       } else {
564         urcs.next(urcs.getValue() + 1);
565       }
566       this.setState(this.state);
567     } else if (op == UserOperation.ResolveCommentReport) {
568       let data = wsJsonToRes<CommentReportResponse>(msg);
569       updateCommentReportRes(
570         data.comment_report_view,
571         this.state.listCommentReportsResponse?.comment_reports
572       );
573       let urcs = UserService.Instance.unreadReportCountSub;
574       if (data.comment_report_view.comment_report.resolved) {
575         urcs.next(urcs.getValue() - 1);
576       } else {
577         urcs.next(urcs.getValue() + 1);
578       }
579       this.setState(this.state);
580     } else if (op == UserOperation.ResolvePrivateMessageReport) {
581       let data = wsJsonToRes<PrivateMessageReportResponse>(msg);
582       updatePrivateMessageReportRes(
583         data.private_message_report_view,
584         this.state.listPrivateMessageReportsResponse?.private_message_reports
585       );
586       let urcs = UserService.Instance.unreadReportCountSub;
587       if (data.private_message_report_view.private_message_report.resolved) {
588         urcs.next(urcs.getValue() - 1);
589       } else {
590         urcs.next(urcs.getValue() + 1);
591       }
592       this.setState(this.state);
593     }
594   }
595 }