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