]> Untitled Git - lemmy-ui.git/blob - src/shared/components/common/vote-buttons.tsx
fix submodule error
[lemmy-ui.git] / src / shared / components / common / vote-buttons.tsx
1 import { myAuthRequired, newVote, showScores } from "@utils/app";
2 import { numToSI } from "@utils/helpers";
3 import classNames from "classnames";
4 import { Component, linkEvent } from "inferno";
5 import {
6   CommentAggregates,
7   CreateCommentLike,
8   CreatePostLike,
9   PostAggregates,
10 } from "lemmy-js-client";
11 import { VoteContentType, VoteType } from "../../interfaces";
12 import { I18NextService } from "../../services";
13 import { Icon, Spinner } from "../common/icon";
14
15 interface VoteButtonsProps {
16   voteContentType: VoteContentType;
17   id: number;
18   onVote: (i: CreateCommentLike | CreatePostLike) => void;
19   enableDownvotes?: boolean;
20   counts: CommentAggregates | PostAggregates;
21   my_vote?: number;
22 }
23
24 interface VoteButtonsState {
25   upvoteLoading: boolean;
26   downvoteLoading: boolean;
27 }
28
29 const tippy = (counts: CommentAggregates | PostAggregates): string => {
30   const points = I18NextService.i18n.t("number_of_points", {
31     count: Number(counts.score),
32     formattedCount: Number(counts.score),
33   });
34
35   const upvotes = I18NextService.i18n.t("number_of_upvotes", {
36     count: Number(counts.upvotes),
37     formattedCount: Number(counts.upvotes),
38   });
39
40   const downvotes = I18NextService.i18n.t("number_of_downvotes", {
41     count: Number(counts.downvotes),
42     formattedCount: Number(counts.downvotes),
43   });
44
45   return `${points} • ${upvotes} • ${downvotes}`;
46 };
47
48 const handleUpvote = (i: VoteButtons) => {
49   i.setState({ upvoteLoading: true });
50
51   switch (i.props.voteContentType) {
52     case VoteContentType.Comment:
53       i.props.onVote({
54         comment_id: i.props.id,
55         score: newVote(VoteType.Upvote, i.props.my_vote),
56         auth: myAuthRequired(),
57       });
58       break;
59     case VoteContentType.Post:
60     default:
61       i.props.onVote({
62         post_id: i.props.id,
63         score: newVote(VoteType.Upvote, i.props.my_vote),
64         auth: myAuthRequired(),
65       });
66   }
67 };
68
69 const handleDownvote = (i: VoteButtons) => {
70   i.setState({ downvoteLoading: true });
71   switch (i.props.voteContentType) {
72     case VoteContentType.Comment:
73       i.props.onVote({
74         comment_id: i.props.id,
75         score: newVote(VoteType.Downvote, i.props.my_vote),
76         auth: myAuthRequired(),
77       });
78       break;
79     case VoteContentType.Post:
80     default:
81       i.props.onVote({
82         post_id: i.props.id,
83         score: newVote(VoteType.Downvote, i.props.my_vote),
84         auth: myAuthRequired(),
85       });
86   }
87 };
88
89 export class VoteButtonsCompact extends Component<
90   VoteButtonsProps,
91   VoteButtonsState
92 > {
93   state: VoteButtonsState = {
94     upvoteLoading: false,
95     downvoteLoading: false,
96   };
97
98   constructor(props: any, context: any) {
99     super(props, context);
100   }
101
102   componentWillReceiveProps(nextProps: VoteButtonsProps) {
103     if (this.props !== nextProps) {
104       this.setState({
105         upvoteLoading: false,
106         downvoteLoading: false,
107       });
108     }
109   }
110
111   render() {
112     return (
113       <>
114         <button
115           type="button"
116           className={`btn btn-animate btn-sm btn-link py-0 px-1 ${
117             this.props.my_vote === 1 ? "text-info" : "text-muted"
118           }`}
119           data-tippy-content={tippy(this.props.counts)}
120           onClick={linkEvent(this, handleUpvote)}
121           aria-label={I18NextService.i18n.t("upvote")}
122           aria-pressed={this.props.my_vote === 1}
123         >
124           {this.state.upvoteLoading ? (
125             <Spinner />
126           ) : (
127             <>
128               <Icon icon="arrow-up1" classes="icon-inline small" />
129               {showScores() && (
130                 <span className="ms-2">
131                   {numToSI(this.props.counts.upvotes)}
132                 </span>
133               )}
134             </>
135           )}
136         </button>
137         {this.props.enableDownvotes && (
138           <button
139             type="button"
140             className={`ms-2 btn btn-sm btn-link btn-animate btn py-0 px-1 ${
141               this.props.my_vote === -1 ? "text-danger" : "text-muted"
142             }`}
143             onClick={linkEvent(this, handleDownvote)}
144             data-tippy-content={tippy(this.props.counts)}
145             aria-label={I18NextService.i18n.t("downvote")}
146             aria-pressed={this.props.my_vote === -1}
147           >
148             {this.state.downvoteLoading ? (
149               <Spinner />
150             ) : (
151               <>
152                 <Icon icon="arrow-down1" classes="icon-inline small" />
153                 {showScores() && (
154                   <span
155                     className={classNames("ms-2", {
156                       invisible: this.props.counts.downvotes === 0,
157                     })}
158                   >
159                     {numToSI(this.props.counts.downvotes)}
160                   </span>
161                 )}
162               </>
163             )}
164           </button>
165         )}
166       </>
167     );
168   }
169 }
170
171 export class VoteButtons extends Component<VoteButtonsProps, VoteButtonsState> {
172   state: VoteButtonsState = {
173     upvoteLoading: false,
174     downvoteLoading: false,
175   };
176
177   constructor(props: any, context: any) {
178     super(props, context);
179   }
180
181   componentWillReceiveProps(nextProps: VoteButtonsProps) {
182     if (this.props !== nextProps) {
183       this.setState({
184         upvoteLoading: false,
185         downvoteLoading: false,
186       });
187     }
188   }
189
190   render() {
191     return (
192       <div className="vote-bar small text-center">
193         <button
194           type="button"
195           className={`btn-animate btn btn-link p-0 ${
196             this.props.my_vote === 1 ? "text-info" : "text-muted"
197           }`}
198           onClick={linkEvent(this, handleUpvote)}
199           data-tippy-content={I18NextService.i18n.t("upvote")}
200           aria-label={I18NextService.i18n.t("upvote")}
201           aria-pressed={this.props.my_vote === 1}
202         >
203           {this.state.upvoteLoading ? (
204             <Spinner />
205           ) : (
206             <Icon icon="arrow-up1" classes="upvote" />
207           )}
208         </button>
209         {showScores() ? (
210           <div
211             className="unselectable pointer text-muted post-score"
212             data-tippy-content={tippy(this.props.counts)}
213           >
214             {numToSI(this.props.counts.score)}
215           </div>
216         ) : (
217           <div className="p-1"></div>
218         )}
219         {this.props.enableDownvotes && (
220           <button
221             type="button"
222             className={`btn-animate btn btn-link p-0 ${
223               this.props.my_vote === -1 ? "text-danger" : "text-muted"
224             }`}
225             onClick={linkEvent(this, handleDownvote)}
226             data-tippy-content={I18NextService.i18n.t("downvote")}
227             aria-label={I18NextService.i18n.t("downvote")}
228             aria-pressed={this.props.my_vote === -1}
229           >
230             {this.state.downvoteLoading ? (
231               <Spinner />
232             ) : (
233               <Icon icon="arrow-down1" classes="downvote" />
234             )}
235           </button>
236         )}
237       </div>
238     );
239   }
240 }