]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/community-form.tsx
ecc2b8900fdd40bd72b66c502a739e0283ad6fd2
[lemmy-ui.git] / src / shared / components / community / community-form.tsx
1 import { Component, linkEvent } from "inferno";
2 import { Prompt } from "inferno-router";
3 import {
4   CommunityResponse,
5   CommunityView,
6   CreateCommunity,
7   EditCommunity,
8   UserOperation,
9 } from "lemmy-js-client";
10 import { Subscription } from "rxjs";
11 import { i18n } from "../../i18next";
12 import { WebSocketService } from "../../services";
13 import {
14   authField,
15   capitalizeFirstLetter,
16   randomStr,
17   toast,
18   wsClient,
19   wsJsonToRes,
20   wsSubscribe,
21   wsUserOp,
22 } from "../../utils";
23 import { Icon, Spinner } from "../common/icon";
24 import { ImageUploadForm } from "../common/image-upload-form";
25 import { MarkdownTextArea } from "../common/markdown-textarea";
26
27 interface CommunityFormProps {
28   community_view?: CommunityView; // If a community is given, that means this is an edit
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       nsfw: false,
52       icon: null,
53       banner: null,
54       auth: authField(false),
55     },
56     loading: false,
57   };
58
59   constructor(props: any, context: any) {
60     super(props, context);
61
62     this.state = this.emptyState;
63
64     this.handleCommunityDescriptionChange =
65       this.handleCommunityDescriptionChange.bind(this);
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     let cv = this.props.community_view;
74     if (cv) {
75       this.state.communityForm = {
76         name: cv.community.name,
77         title: cv.community.title,
78         description: cv.community.description,
79         nsfw: cv.community.nsfw,
80         icon: cv.community.icon,
81         banner: cv.community.banner,
82         auth: authField(),
83       };
84     }
85
86     this.parseMessage = this.parseMessage.bind(this);
87     this.subscription = wsSubscribe(this.parseMessage);
88   }
89
90   // TODO this should be checked out
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_view && (
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                   <Icon icon="help-circle" classes="icon-inline" />
131                 </span>
132               </label>
133               <div class="col-12">
134                 <input
135                   type="text"
136                   id="community-name"
137                   class="form-control"
138                   value={this.state.communityForm.name}
139                   onInput={linkEvent(this, this.handleCommunityNameChange)}
140                   required
141                   minLength={3}
142                   maxLength={20}
143                   pattern="[a-z0-9_]+"
144                   title={i18n.t("community_reqs")}
145                 />
146               </div>
147             </div>
148           )}
149           <div class="form-group row">
150             <label class="col-12 col-form-label" htmlFor="community-title">
151               {i18n.t("display_name")}
152               <span
153                 class="pointer unselectable ml-2 text-muted"
154                 data-tippy-content={i18n.t("display_name_explain")}
155               >
156                 <Icon icon="help-circle" classes="icon-inline" />
157               </span>
158             </label>
159             <div class="col-12">
160               <input
161                 type="text"
162                 id="community-title"
163                 value={this.state.communityForm.title}
164                 onInput={linkEvent(this, this.handleCommunityTitleChange)}
165                 class="form-control"
166                 required
167                 minLength={3}
168                 maxLength={100}
169               />
170             </div>
171           </div>
172           <div class="form-group">
173             <label>{i18n.t("icon")}</label>
174             <ImageUploadForm
175               uploadTitle={i18n.t("upload_icon")}
176               imageSrc={this.state.communityForm.icon}
177               onUpload={this.handleIconUpload}
178               onRemove={this.handleIconRemove}
179               rounded
180             />
181           </div>
182           <div class="form-group">
183             <label>{i18n.t("banner")}</label>
184             <ImageUploadForm
185               uploadTitle={i18n.t("upload_banner")}
186               imageSrc={this.state.communityForm.banner}
187               onUpload={this.handleBannerUpload}
188               onRemove={this.handleBannerRemove}
189             />
190           </div>
191           <div class="form-group row">
192             <label class="col-12 col-form-label" htmlFor={this.id}>
193               {i18n.t("sidebar")}
194             </label>
195             <div class="col-12">
196               <MarkdownTextArea
197                 initialContent={this.state.communityForm.description}
198                 onContentChange={this.handleCommunityDescriptionChange}
199               />
200             </div>
201           </div>
202
203           {this.props.enableNsfw && (
204             <div class="form-group row">
205               <div class="col-12">
206                 <div class="form-check">
207                   <input
208                     class="form-check-input"
209                     id="community-nsfw"
210                     type="checkbox"
211                     checked={this.state.communityForm.nsfw}
212                     onChange={linkEvent(this, this.handleCommunityNsfwChange)}
213                   />
214                   <label class="form-check-label" htmlFor="community-nsfw">
215                     {i18n.t("nsfw")}
216                   </label>
217                 </div>
218               </div>
219             </div>
220           )}
221           <div class="form-group row">
222             <div class="col-12">
223               <button
224                 type="submit"
225                 class="btn btn-secondary mr-2"
226                 disabled={this.state.loading}
227               >
228                 {this.state.loading ? (
229                   <Spinner />
230                 ) : this.props.community_view ? (
231                   capitalizeFirstLetter(i18n.t("save"))
232                 ) : (
233                   capitalizeFirstLetter(i18n.t("create"))
234                 )}
235               </button>
236               {this.props.community_view && (
237                 <button
238                   type="button"
239                   class="btn btn-secondary"
240                   onClick={linkEvent(this, this.handleCancel)}
241                 >
242                   {i18n.t("cancel")}
243                 </button>
244               )}
245             </div>
246           </div>
247         </form>
248       </>
249     );
250   }
251
252   handleCreateCommunitySubmit(i: CommunityForm, event: any) {
253     event.preventDefault();
254     i.state.loading = true;
255     if (i.props.community_view) {
256       let form: EditCommunity = {
257         ...i.state.communityForm,
258         community_id: i.props.community_view.community.id,
259       };
260       WebSocketService.Instance.send(wsClient.editCommunity(form));
261     } else {
262       WebSocketService.Instance.send(
263         wsClient.createCommunity(i.state.communityForm)
264       );
265     }
266     i.setState(i.state);
267   }
268
269   handleCommunityNameChange(i: CommunityForm, event: any) {
270     i.state.communityForm.name = event.target.value;
271     i.setState(i.state);
272   }
273
274   handleCommunityTitleChange(i: CommunityForm, event: any) {
275     i.state.communityForm.title = event.target.value;
276     i.setState(i.state);
277   }
278
279   handleCommunityDescriptionChange(val: string) {
280     this.state.communityForm.description = val;
281     this.setState(this.state);
282   }
283
284   handleCommunityNsfwChange(i: CommunityForm, event: any) {
285     i.state.communityForm.nsfw = event.target.checked;
286     i.setState(i.state);
287   }
288
289   handleCancel(i: CommunityForm) {
290     i.props.onCancel();
291   }
292
293   handleIconUpload(url: string) {
294     this.state.communityForm.icon = url;
295     this.setState(this.state);
296   }
297
298   handleIconRemove() {
299     this.state.communityForm.icon = "";
300     this.setState(this.state);
301   }
302
303   handleBannerUpload(url: string) {
304     this.state.communityForm.banner = url;
305     this.setState(this.state);
306   }
307
308   handleBannerRemove() {
309     this.state.communityForm.banner = "";
310     this.setState(this.state);
311   }
312
313   parseMessage(msg: any) {
314     let op = wsUserOp(msg);
315     console.log(msg);
316     if (msg.error) {
317       toast(i18n.t(msg.error), "danger");
318       this.state.loading = false;
319       this.setState(this.state);
320       return;
321     } else if (op == UserOperation.CreateCommunity) {
322       let data = wsJsonToRes<CommunityResponse>(msg).data;
323       this.state.loading = false;
324       this.props.onCreate(data.community_view);
325     } else if (op == UserOperation.EditCommunity) {
326       let data = wsJsonToRes<CommunityResponse>(msg).data;
327       this.state.loading = false;
328       this.props.onEdit(data.community_view);
329     }
330   }
331 }