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