]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community-form.tsx
Create community, Create post, and instances pages done.
[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 { retryWhen, delay, take } from 'rxjs/operators';
5 import {
6   CommunityForm as CommunityFormI,
7   UserOperation,
8   Category,
9   CommunityResponse,
10   WebSocketJsonResponse,
11   Community,
12 } from 'lemmy-js-client';
13 import { WebSocketService } from '../services';
14 import { wsJsonToRes, capitalizeFirstLetter, toast, randomStr } from '../utils';
15 import { i18n } from '../i18next';
16
17 import { MarkdownTextArea } from './markdown-textarea';
18 import { ImageUploadForm } from './image-upload-form';
19
20 interface CommunityFormProps {
21   community?: Community; // If a community is given, that means this is an edit
22   categories: Category[];
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   loading: boolean;
32 }
33
34 export class CommunityForm extends Component<
35   CommunityFormProps,
36   CommunityFormState
37 > {
38   private id = `community-form-${randomStr()}`;
39   private subscription: Subscription;
40
41   private emptyState: CommunityFormState = {
42     communityForm: {
43       name: null,
44       title: null,
45       category_id: this.props.categories[0].id,
46       nsfw: false,
47       icon: null,
48       banner: null,
49     },
50     loading: false,
51   };
52
53   constructor(props: any, context: any) {
54     super(props, context);
55
56     this.state = this.emptyState;
57
58     this.handleCommunityDescriptionChange = this.handleCommunityDescriptionChange.bind(
59       this
60     );
61
62     this.handleIconUpload = this.handleIconUpload.bind(this);
63     this.handleIconRemove = this.handleIconRemove.bind(this);
64
65     this.handleBannerUpload = this.handleBannerUpload.bind(this);
66     this.handleBannerRemove = this.handleBannerRemove.bind(this);
67
68     if (this.props.community) {
69       this.state.communityForm = {
70         name: this.props.community.name,
71         title: this.props.community.title,
72         category_id: this.props.community.category_id,
73         description: this.props.community.description,
74         edit_id: this.props.community.id,
75         nsfw: this.props.community.nsfw,
76         icon: this.props.community.icon,
77         banner: this.props.community.banner,
78         auth: null,
79       };
80     }
81
82     this.subscription = WebSocketService.Instance.subject
83       .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
84       .subscribe(
85         msg => this.parseMessage(msg),
86         err => console.error(err),
87         () => console.log('complete')
88       );
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 }