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