]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/community-form.tsx
d1f5f7534c18f6eebeafc0806509d7d66f62d46a
[lemmy-ui.git] / src / shared / components / community / community-form.tsx
1 import { None, Option, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import { Prompt } from "inferno-router";
4 import {
5   CommunityResponse,
6   CommunityView,
7   CreateCommunity,
8   EditCommunity,
9   toUndefined,
10   UserOperation,
11   wsJsonToRes,
12   wsUserOp,
13 } from "lemmy-js-client";
14 import { Subscription } from "rxjs";
15 import { i18n } from "../../i18next";
16 import { UserService, WebSocketService } from "../../services";
17 import {
18   auth,
19   capitalizeFirstLetter,
20   randomStr,
21   wsClient,
22   wsSubscribe,
23 } from "../../utils";
24 import { Icon, Spinner } from "../common/icon";
25 import { ImageUploadForm } from "../common/image-upload-form";
26 import { MarkdownTextArea } from "../common/markdown-textarea";
27
28 interface CommunityFormProps {
29   community_view: Option<CommunityView>; // If a community is given, that means this is an edit
30   onCancel?(): any;
31   onCreate?(community: CommunityView): any;
32   onEdit?(community: CommunityView): any;
33   enableNsfw?: boolean;
34 }
35
36 interface CommunityFormState {
37   communityForm: CreateCommunity;
38   loading: boolean;
39 }
40
41 export class CommunityForm extends Component<
42   CommunityFormProps,
43   CommunityFormState
44 > {
45   private id = `community-form-${randomStr()}`;
46   private subscription: Subscription;
47
48   private emptyState: CommunityFormState = {
49     communityForm: new CreateCommunity({
50       name: undefined,
51       title: undefined,
52       description: None,
53       nsfw: None,
54       icon: None,
55       banner: None,
56       posting_restricted_to_mods: None,
57       auth: undefined,
58     }),
59     loading: false,
60   };
61
62   constructor(props: any, context: any) {
63     super(props, context);
64
65     this.state = this.emptyState;
66
67     this.handleCommunityDescriptionChange =
68       this.handleCommunityDescriptionChange.bind(this);
69
70     this.handleIconUpload = this.handleIconUpload.bind(this);
71     this.handleIconRemove = this.handleIconRemove.bind(this);
72
73     this.handleBannerUpload = this.handleBannerUpload.bind(this);
74     this.handleBannerRemove = this.handleBannerRemove.bind(this);
75
76     this.props.community_view.match({
77       some: cv => {
78         this.state.communityForm = new CreateCommunity({
79           name: cv.community.name,
80           title: cv.community.title,
81           description: cv.community.description,
82           nsfw: Some(cv.community.nsfw),
83           icon: cv.community.icon,
84           banner: cv.community.banner,
85           posting_restricted_to_mods: Some(
86             cv.community.posting_restricted_to_mods
87           ),
88           auth: auth().unwrap(),
89         });
90       },
91       none: void 0,
92     });
93
94     this.parseMessage = this.parseMessage.bind(this);
95     this.subscription = wsSubscribe(this.parseMessage);
96   }
97
98   componentDidUpdate() {
99     if (
100       !this.state.loading &&
101       (this.state.communityForm.name ||
102         this.state.communityForm.title ||
103         this.state.communityForm.description.isSome())
104     ) {
105       window.onbeforeunload = () => true;
106     } else {
107       window.onbeforeunload = undefined;
108     }
109   }
110
111   componentWillUnmount() {
112     this.subscription.unsubscribe();
113     window.onbeforeunload = null;
114   }
115
116   render() {
117     return (
118       <>
119         <Prompt
120           when={
121             !this.state.loading &&
122             (this.state.communityForm.name ||
123               this.state.communityForm.title ||
124               this.state.communityForm.description.isSome())
125           }
126           message={i18n.t("block_leaving")}
127         />
128         <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}>
129           {this.props.community_view.isNone() && (
130             <div class="form-group row">
131               <label
132                 class="col-12 col-sm-2 col-form-label"
133                 htmlFor="community-name"
134               >
135                 {i18n.t("name")}
136                 <span
137                   class="position-absolute pointer unselectable ml-2 text-muted"
138                   data-tippy-content={i18n.t("name_explain")}
139                 >
140                   <Icon icon="help-circle" classes="icon-inline" />
141                 </span>
142               </label>
143               <div class="col-12 col-sm-10">
144                 <input
145                   type="text"
146                   id="community-name"
147                   class="form-control"
148                   value={this.state.communityForm.name}
149                   onInput={linkEvent(this, this.handleCommunityNameChange)}
150                   required
151                   minLength={3}
152                   pattern="[a-z0-9_]+"
153                   title={i18n.t("community_reqs")}
154                 />
155               </div>
156             </div>
157           )}
158           <div class="form-group row">
159             <label
160               class="col-12 col-sm-2 col-form-label"
161               htmlFor="community-title"
162             >
163               {i18n.t("display_name")}
164               <span
165                 class="position-absolute pointer unselectable ml-2 text-muted"
166                 data-tippy-content={i18n.t("display_name_explain")}
167               >
168                 <Icon icon="help-circle" classes="icon-inline" />
169               </span>
170             </label>
171             <div class="col-12 col-sm-10">
172               <input
173                 type="text"
174                 id="community-title"
175                 value={this.state.communityForm.title}
176                 onInput={linkEvent(this, this.handleCommunityTitleChange)}
177                 class="form-control"
178                 required
179                 minLength={3}
180                 maxLength={100}
181               />
182             </div>
183           </div>
184           <div class="form-group row">
185             <label class="col-12 col-sm-2">{i18n.t("icon")}</label>
186             <div class="col-12 col-sm-10">
187               <ImageUploadForm
188                 uploadTitle={i18n.t("upload_icon")}
189                 imageSrc={this.state.communityForm.icon}
190                 onUpload={this.handleIconUpload}
191                 onRemove={this.handleIconRemove}
192                 rounded
193               />
194             </div>
195           </div>
196           <div class="form-group row">
197             <label class="col-12 col-sm-2">{i18n.t("banner")}</label>
198             <div class="col-12 col-sm-10">
199               <ImageUploadForm
200                 uploadTitle={i18n.t("upload_banner")}
201                 imageSrc={this.state.communityForm.banner}
202                 onUpload={this.handleBannerUpload}
203                 onRemove={this.handleBannerRemove}
204               />
205             </div>
206           </div>
207           <div class="form-group row">
208             <label class="col-12 col-sm-2 col-form-label" htmlFor={this.id}>
209               {i18n.t("sidebar")}
210             </label>
211             <div class="col-12 col-sm-10">
212               <MarkdownTextArea
213                 initialContent={this.state.communityForm.description}
214                 placeholder={Some("description")}
215                 buttonTitle={None}
216                 maxLength={None}
217                 onContentChange={this.handleCommunityDescriptionChange}
218               />
219             </div>
220           </div>
221
222           {this.props.enableNsfw && (
223             <div class="form-group row">
224               <legend class="col-form-label col-sm-2 pt-0">
225                 {i18n.t("nsfw")}
226               </legend>
227               <div class="col-10">
228                 <div class="form-check">
229                   <input
230                     class="form-check-input position-static"
231                     id="community-nsfw"
232                     type="checkbox"
233                     checked={toUndefined(this.state.communityForm.nsfw)}
234                     onChange={linkEvent(this, this.handleCommunityNsfwChange)}
235                   />
236                 </div>
237               </div>
238             </div>
239           )}
240           <div class="form-group row">
241             <legend class="col-form-label col-6 pt-0">
242               {i18n.t("only_mods_can_post_in_community")}
243             </legend>
244             <div class="col-6">
245               <div class="form-check">
246                 <input
247                   class="form-check-input position-static"
248                   id="community-only-mods-can-post"
249                   type="checkbox"
250                   checked={toUndefined(
251                     this.state.communityForm.posting_restricted_to_mods
252                   )}
253                   onChange={linkEvent(
254                     this,
255                     this.handleCommunityPostingRestrictedToMods
256                   )}
257                 />
258               </div>
259             </div>
260           </div>
261           <div class="form-group row">
262             <div class="col-12">
263               <button
264                 type="submit"
265                 class="btn btn-secondary mr-2"
266                 disabled={this.state.loading}
267               >
268                 {this.state.loading ? (
269                   <Spinner />
270                 ) : this.props.community_view.isSome() ? (
271                   capitalizeFirstLetter(i18n.t("save"))
272                 ) : (
273                   capitalizeFirstLetter(i18n.t("create"))
274                 )}
275               </button>
276               {this.props.community_view.isSome() && (
277                 <button
278                   type="button"
279                   class="btn btn-secondary"
280                   onClick={linkEvent(this, this.handleCancel)}
281                 >
282                   {i18n.t("cancel")}
283                 </button>
284               )}
285             </div>
286           </div>
287         </form>
288       </>
289     );
290   }
291
292   handleCreateCommunitySubmit(i: CommunityForm, event: any) {
293     event.preventDefault();
294     i.state.loading = true;
295     let cForm = i.state.communityForm;
296
297     i.props.community_view.match({
298       some: cv => {
299         let form = new EditCommunity({
300           community_id: cv.community.id,
301           title: Some(cForm.title),
302           description: cForm.description,
303           icon: cForm.icon,
304           banner: cForm.banner,
305           nsfw: cForm.nsfw,
306           posting_restricted_to_mods: cForm.posting_restricted_to_mods,
307           auth: auth().unwrap(),
308         });
309
310         WebSocketService.Instance.send(wsClient.editCommunity(form));
311       },
312       none: () => {
313         WebSocketService.Instance.send(
314           wsClient.createCommunity(i.state.communityForm)
315         );
316       },
317     });
318     i.setState(i.state);
319   }
320
321   handleCommunityNameChange(i: CommunityForm, event: any) {
322     i.state.communityForm.name = event.target.value;
323     i.setState(i.state);
324   }
325
326   handleCommunityTitleChange(i: CommunityForm, event: any) {
327     i.state.communityForm.title = event.target.value;
328     i.setState(i.state);
329   }
330
331   handleCommunityDescriptionChange(val: string) {
332     this.state.communityForm.description = Some(val);
333     this.setState(this.state);
334   }
335
336   handleCommunityNsfwChange(i: CommunityForm, event: any) {
337     i.state.communityForm.nsfw = event.target.checked;
338     i.setState(i.state);
339   }
340
341   handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) {
342     i.state.communityForm.posting_restricted_to_mods = event.target.checked;
343     i.setState(i.state);
344   }
345
346   handleCancel(i: CommunityForm) {
347     i.props.onCancel();
348   }
349
350   handleIconUpload(url: string) {
351     this.state.communityForm.icon = Some(url);
352     this.setState(this.state);
353   }
354
355   handleIconRemove() {
356     this.state.communityForm.icon = Some("");
357     this.setState(this.state);
358   }
359
360   handleBannerUpload(url: string) {
361     this.state.communityForm.banner = Some(url);
362     this.setState(this.state);
363   }
364
365   handleBannerRemove() {
366     this.state.communityForm.banner = Some("");
367     this.setState(this.state);
368   }
369
370   parseMessage(msg: any) {
371     let op = wsUserOp(msg);
372     console.log(msg);
373     if (msg.error) {
374       // Errors handled by top level pages
375       // toast(i18n.t(msg.error), "danger");
376       this.state.loading = false;
377       this.setState(this.state);
378       return;
379     } else if (op == UserOperation.CreateCommunity) {
380       let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
381       this.state.loading = false;
382       this.props.onCreate(data.community_view);
383
384       // Update myUserInfo
385       let community = data.community_view.community;
386
387       UserService.Instance.myUserInfo.match({
388         some: mui => {
389           let person = mui.local_user_view.person;
390           mui.follows.push({
391             community,
392             follower: person,
393           });
394           mui.moderates.push({
395             community,
396             moderator: person,
397           });
398         },
399         none: void 0,
400       });
401     } else if (op == UserOperation.EditCommunity) {
402       let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
403       this.state.loading = false;
404       this.props.onEdit(data.community_view);
405       let community = data.community_view.community;
406
407       UserService.Instance.myUserInfo.match({
408         some: mui => {
409           let followFound = mui.follows.findIndex(
410             f => f.community.id == community.id
411           );
412           if (followFound) {
413             mui.follows[followFound].community = community;
414           }
415
416           let moderatesFound = mui.moderates.findIndex(
417             f => f.community.id == community.id
418           );
419           if (moderatesFound) {
420             mui.moderates[moderatesFound].community = community;
421           }
422         },
423         none: void 0,
424       });
425     }
426   }
427 }