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