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