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