]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/community-form.tsx
Fix site setup and login. Fixes #699 (#702)
[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: undefined,
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     cForm.auth = auth().unwrap();
297
298     i.props.community_view.match({
299       some: cv => {
300         let form = new EditCommunity({
301           community_id: cv.community.id,
302           title: Some(cForm.title),
303           description: cForm.description,
304           icon: cForm.icon,
305           banner: cForm.banner,
306           nsfw: cForm.nsfw,
307           posting_restricted_to_mods: cForm.posting_restricted_to_mods,
308           auth: cForm.auth,
309         });
310
311         WebSocketService.Instance.send(wsClient.editCommunity(form));
312       },
313       none: () => {
314         WebSocketService.Instance.send(
315           wsClient.createCommunity(i.state.communityForm)
316         );
317       },
318     });
319     i.setState(i.state);
320   }
321
322   handleCommunityNameChange(i: CommunityForm, event: any) {
323     i.state.communityForm.name = event.target.value;
324     i.setState(i.state);
325   }
326
327   handleCommunityTitleChange(i: CommunityForm, event: any) {
328     i.state.communityForm.title = event.target.value;
329     i.setState(i.state);
330   }
331
332   handleCommunityDescriptionChange(val: string) {
333     this.state.communityForm.description = Some(val);
334     this.setState(this.state);
335   }
336
337   handleCommunityNsfwChange(i: CommunityForm, event: any) {
338     i.state.communityForm.nsfw = event.target.checked;
339     i.setState(i.state);
340   }
341
342   handleCommunityPostingRestrictedToMods(i: CommunityForm, event: any) {
343     i.state.communityForm.posting_restricted_to_mods = event.target.checked;
344     i.setState(i.state);
345   }
346
347   handleCancel(i: CommunityForm) {
348     i.props.onCancel();
349   }
350
351   handleIconUpload(url: string) {
352     this.state.communityForm.icon = Some(url);
353     this.setState(this.state);
354   }
355
356   handleIconRemove() {
357     this.state.communityForm.icon = Some("");
358     this.setState(this.state);
359   }
360
361   handleBannerUpload(url: string) {
362     this.state.communityForm.banner = Some(url);
363     this.setState(this.state);
364   }
365
366   handleBannerRemove() {
367     this.state.communityForm.banner = Some("");
368     this.setState(this.state);
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.state.loading = false;
378       this.setState(this.state);
379       return;
380     } else if (op == UserOperation.CreateCommunity) {
381       let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
382       this.state.loading = false;
383       this.props.onCreate(data.community_view);
384
385       // Update myUserInfo
386       let community = data.community_view.community;
387
388       UserService.Instance.myUserInfo.match({
389         some: mui => {
390           let person = mui.local_user_view.person;
391           mui.follows.push({
392             community,
393             follower: person,
394           });
395           mui.moderates.push({
396             community,
397             moderator: person,
398           });
399         },
400         none: void 0,
401       });
402     } else if (op == UserOperation.EditCommunity) {
403       let data = wsJsonToRes<CommunityResponse>(msg, CommunityResponse);
404       this.state.loading = false;
405       this.props.onEdit(data.community_view);
406       let community = data.community_view.community;
407
408       UserService.Instance.myUserInfo.match({
409         some: mui => {
410           let followFound = mui.follows.findIndex(
411             f => f.community.id == community.id
412           );
413           if (followFound) {
414             mui.follows[followFound].community = community;
415           }
416
417           let moderatesFound = mui.moderates.findIndex(
418             f => f.community.id == community.id
419           );
420           if (moderatesFound) {
421             mui.moderates[moderatesFound].community = community;
422           }
423         },
424         none: void 0,
425       });
426     }
427   }
428 }