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