]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/communities.tsx
Re-organized components folder. (#339)
[lemmy-ui.git] / src / shared / components / community / communities.tsx
1 import { Component, linkEvent } from "inferno";
2 import {
3   CommunityResponse,
4   CommunityView,
5   FollowCommunity,
6   ListCommunities,
7   ListCommunitiesResponse,
8   ListingType,
9   SiteView,
10   SortType,
11   UserOperation,
12 } from "lemmy-js-client";
13 import { Subscription } from "rxjs";
14 import { InitialFetchRequest } from "shared/interfaces";
15 import { i18n } from "../../i18next";
16 import { WebSocketService } from "../../services";
17 import {
18   authField,
19   getPageFromProps,
20   isBrowser,
21   setIsoData,
22   setOptionalAuth,
23   toast,
24   wsClient,
25   wsJsonToRes,
26   wsSubscribe,
27   wsUserOp,
28 } from "../../utils";
29 import { HtmlTags } from "../common/html-tags";
30 import { Spinner } from "../common/icon";
31 import { Paginator } from "../common/paginator";
32 import { CommunityLink } from "./community-link";
33
34 const communityLimit = 100;
35
36 interface CommunitiesState {
37   communities: CommunityView[];
38   page: number;
39   loading: boolean;
40   site_view: SiteView;
41   searchText: string;
42 }
43
44 interface CommunitiesProps {
45   page: number;
46 }
47
48 export class Communities extends Component<any, CommunitiesState> {
49   private subscription: Subscription;
50   private isoData = setIsoData(this.context);
51   private emptyState: CommunitiesState = {
52     communities: [],
53     loading: true,
54     page: getPageFromProps(this.props),
55     site_view: this.isoData.site_res.site_view,
56     searchText: "",
57   };
58
59   constructor(props: any, context: any) {
60     super(props, context);
61     this.state = this.emptyState;
62     this.handlePageChange = this.handlePageChange.bind(this);
63
64     this.parseMessage = this.parseMessage.bind(this);
65     this.subscription = wsSubscribe(this.parseMessage);
66
67     // Only fetch the data if coming from another route
68     if (this.isoData.path == this.context.router.route.match.url) {
69       this.state.communities = this.isoData.routeData[0].communities;
70       this.state.loading = false;
71     } else {
72       this.refetch();
73     }
74   }
75
76   componentWillUnmount() {
77     if (isBrowser()) {
78       this.subscription.unsubscribe();
79     }
80   }
81
82   static getDerivedStateFromProps(props: any): CommunitiesProps {
83     return {
84       page: getPageFromProps(props),
85     };
86   }
87
88   componentDidUpdate(_: any, lastState: CommunitiesState) {
89     if (lastState.page !== this.state.page) {
90       this.setState({ loading: true });
91       this.refetch();
92     }
93   }
94
95   get documentTitle(): string {
96     return `${i18n.t("communities")} - ${this.state.site_view.site.name}`;
97   }
98
99   render() {
100     return (
101       <div class="container">
102         <HtmlTags
103           title={this.documentTitle}
104           path={this.context.router.route.match.url}
105         />
106         {this.state.loading ? (
107           <h5>
108             <Spinner large />
109           </h5>
110         ) : (
111           <div>
112             <div class="row">
113               <div class="col-md-6">
114                 <h4>{i18n.t("list_of_communities")}</h4>
115               </div>
116               <div class="col-md-6">
117                 <div class="float-md-right">{this.searchForm()}</div>
118               </div>
119             </div>
120
121             <div class="table-responsive">
122               <table id="community_table" class="table table-sm table-hover">
123                 <thead class="pointer">
124                   <tr>
125                     <th>{i18n.t("name")}</th>
126                     <th class="text-right">{i18n.t("subscribers")}</th>
127                     <th class="text-right">
128                       {i18n.t("users")} / {i18n.t("month")}
129                     </th>
130                     <th class="text-right d-none d-lg-table-cell">
131                       {i18n.t("posts")}
132                     </th>
133                     <th class="text-right d-none d-lg-table-cell">
134                       {i18n.t("comments")}
135                     </th>
136                     <th></th>
137                   </tr>
138                 </thead>
139                 <tbody>
140                   {this.state.communities.map(cv => (
141                     <tr>
142                       <td>
143                         <CommunityLink community={cv.community} />
144                       </td>
145                       <td class="text-right">{cv.counts.subscribers}</td>
146                       <td class="text-right">{cv.counts.users_active_month}</td>
147                       <td class="text-right d-none d-lg-table-cell">
148                         {cv.counts.posts}
149                       </td>
150                       <td class="text-right d-none d-lg-table-cell">
151                         {cv.counts.comments}
152                       </td>
153                       <td class="text-right">
154                         {cv.subscribed ? (
155                           <button
156                             class="btn btn-link d-inline-block"
157                             onClick={linkEvent(
158                               cv.community.id,
159                               this.handleUnsubscribe
160                             )}
161                           >
162                             {i18n.t("unsubscribe")}
163                           </button>
164                         ) : (
165                           <button
166                             class="btn btn-link d-inline-block"
167                             onClick={linkEvent(
168                               cv.community.id,
169                               this.handleSubscribe
170                             )}
171                           >
172                             {i18n.t("subscribe")}
173                           </button>
174                         )}
175                       </td>
176                     </tr>
177                   ))}
178                 </tbody>
179               </table>
180             </div>
181             <Paginator
182               page={this.state.page}
183               onChange={this.handlePageChange}
184             />
185           </div>
186         )}
187       </div>
188     );
189   }
190
191   searchForm() {
192     return (
193       <form
194         class="form-inline"
195         onSubmit={linkEvent(this, this.handleSearchSubmit)}
196       >
197         <input
198           type="text"
199           id="communities-search"
200           class="form-control mr-2 mb-2"
201           value={this.state.searchText}
202           placeholder={`${i18n.t("search")}...`}
203           onInput={linkEvent(this, this.handleSearchChange)}
204           required
205           minLength={3}
206         />
207         <label class="sr-only" htmlFor="communities-search">
208           {i18n.t("search")}
209         </label>
210         <button type="submit" class="btn btn-secondary mr-2 mb-2">
211           <span>{i18n.t("search")}</span>
212         </button>
213       </form>
214     );
215   }
216
217   updateUrl(paramUpdates: CommunitiesProps) {
218     const page = paramUpdates.page || this.state.page;
219     this.props.history.push(`/communities/page/${page}`);
220   }
221
222   handlePageChange(page: number) {
223     this.updateUrl({ page });
224   }
225
226   handleUnsubscribe(communityId: number) {
227     let form: FollowCommunity = {
228       community_id: communityId,
229       follow: false,
230       auth: authField(),
231     };
232     WebSocketService.Instance.send(wsClient.followCommunity(form));
233   }
234
235   handleSubscribe(communityId: number) {
236     let form: FollowCommunity = {
237       community_id: communityId,
238       follow: true,
239       auth: authField(),
240     };
241     WebSocketService.Instance.send(wsClient.followCommunity(form));
242   }
243
244   handleSearchChange(i: Communities, event: any) {
245     i.setState({ searchText: event.target.value });
246   }
247
248   handleSearchSubmit(i: Communities) {
249     const searchParamEncoded = encodeURIComponent(i.state.searchText);
250     i.context.router.history.push(
251       `/search/q/${searchParamEncoded}/type/Communities/sort/TopAll/listing_type/All/community_id/0/creator_id/0/page/1`
252     );
253   }
254
255   refetch() {
256     let listCommunitiesForm: ListCommunities = {
257       type_: ListingType.All,
258       sort: SortType.TopMonth,
259       limit: communityLimit,
260       page: this.state.page,
261       auth: authField(false),
262     };
263
264     WebSocketService.Instance.send(
265       wsClient.listCommunities(listCommunitiesForm)
266     );
267   }
268
269   static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
270     let pathSplit = req.path.split("/");
271     let page = pathSplit[3] ? Number(pathSplit[3]) : 1;
272     let listCommunitiesForm: ListCommunities = {
273       type_: ListingType.All,
274       sort: SortType.TopMonth,
275       limit: communityLimit,
276       page,
277     };
278     setOptionalAuth(listCommunitiesForm, req.auth);
279
280     return [req.client.listCommunities(listCommunitiesForm)];
281   }
282
283   parseMessage(msg: any) {
284     let op = wsUserOp(msg);
285     console.log(msg);
286     if (msg.error) {
287       toast(i18n.t(msg.error), "danger");
288       return;
289     } else if (op == UserOperation.ListCommunities) {
290       let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
291       this.state.communities = data.communities;
292       this.state.loading = false;
293       window.scrollTo(0, 0);
294       this.setState(this.state);
295     } else if (op == UserOperation.FollowCommunity) {
296       let data = wsJsonToRes<CommunityResponse>(msg).data;
297       let found = this.state.communities.find(
298         c => c.community.id == data.community_view.community.id
299       );
300       found.subscribed = data.community_view.subscribed;
301       found.counts.subscribers = data.community_view.counts.subscribers;
302       this.setState(this.state);
303     }
304   }
305 }