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