]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/site-form.tsx
Custom themes (#584)
[lemmy-ui.git] / src / shared / components / home / site-form.tsx
1 import { Component, linkEvent } from "inferno";
2 import { Prompt } from "inferno-router";
3 import { CreateSite, EditSite, Site } from "lemmy-js-client";
4 import { i18n } from "../../i18next";
5 import { WebSocketService } from "../../services";
6 import {
7   authField,
8   capitalizeFirstLetter,
9   fetchThemeList,
10   wsClient,
11 } from "../../utils";
12 import { Spinner } from "../common/icon";
13 import { ImageUploadForm } from "../common/image-upload-form";
14 import { MarkdownTextArea } from "../common/markdown-textarea";
15
16 interface SiteFormProps {
17   site?: Site; // If a site is given, that means this is an edit
18   onCancel?(): any;
19 }
20
21 interface SiteFormState {
22   siteForm: EditSite;
23   loading: boolean;
24   themeList: string[];
25 }
26
27 export class SiteForm extends Component<SiteFormProps, SiteFormState> {
28   private emptyState: SiteFormState = {
29     siteForm: {
30       enable_downvotes: true,
31       open_registration: true,
32       enable_nsfw: true,
33       name: null,
34       icon: null,
35       banner: null,
36       require_email_verification: null,
37       require_application: null,
38       application_question: null,
39       private_instance: null,
40       default_theme: null,
41       auth: authField(),
42     },
43     loading: false,
44     themeList: [],
45   };
46
47   constructor(props: any, context: any) {
48     super(props, context);
49
50     this.state = this.emptyState;
51     this.handleSiteSidebarChange = this.handleSiteSidebarChange.bind(this);
52     this.handleSiteApplicationQuestionChange =
53       this.handleSiteApplicationQuestionChange.bind(this);
54
55     this.handleIconUpload = this.handleIconUpload.bind(this);
56     this.handleIconRemove = this.handleIconRemove.bind(this);
57
58     this.handleBannerUpload = this.handleBannerUpload.bind(this);
59     this.handleBannerRemove = this.handleBannerRemove.bind(this);
60
61     if (this.props.site) {
62       let site = this.props.site;
63       this.state.siteForm = {
64         name: site.name,
65         sidebar: site.sidebar,
66         description: site.description,
67         enable_downvotes: site.enable_downvotes,
68         open_registration: site.open_registration,
69         enable_nsfw: site.enable_nsfw,
70         community_creation_admin_only: site.community_creation_admin_only,
71         icon: site.icon,
72         banner: site.banner,
73         require_email_verification: site.require_email_verification,
74         require_application: site.require_application,
75         application_question: site.application_question,
76         private_instance: site.private_instance,
77         default_theme: site.default_theme,
78         auth: authField(),
79       };
80     }
81   }
82
83   async componentDidMount() {
84     this.state.themeList = await fetchThemeList();
85     this.setState(this.state);
86   }
87
88   // Necessary to stop the loading
89   componentWillReceiveProps() {
90     this.state.loading = false;
91     this.setState(this.state);
92   }
93
94   componentDidUpdate() {
95     if (
96       !this.state.loading &&
97       !this.props.site &&
98       (this.state.siteForm.name ||
99         this.state.siteForm.sidebar ||
100         this.state.siteForm.application_question ||
101         this.state.siteForm.description)
102     ) {
103       window.onbeforeunload = () => true;
104     } else {
105       window.onbeforeunload = undefined;
106     }
107   }
108
109   componentWillUnmount() {
110     window.onbeforeunload = null;
111   }
112
113   render() {
114     return (
115       <>
116         <Prompt
117           when={
118             !this.state.loading &&
119             !this.props.site &&
120             (this.state.siteForm.name ||
121               this.state.siteForm.sidebar ||
122               this.state.siteForm.application_question ||
123               this.state.siteForm.description)
124           }
125           message={i18n.t("block_leaving")}
126         />
127         <form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}>
128           <h5>{`${
129             this.props.site
130               ? capitalizeFirstLetter(i18n.t("save"))
131               : capitalizeFirstLetter(i18n.t("name"))
132           } ${i18n.t("your_site")}`}</h5>
133           <div class="form-group row">
134             <label class="col-12 col-form-label" htmlFor="create-site-name">
135               {i18n.t("name")}
136             </label>
137             <div class="col-12">
138               <input
139                 type="text"
140                 id="create-site-name"
141                 class="form-control"
142                 value={this.state.siteForm.name}
143                 onInput={linkEvent(this, this.handleSiteNameChange)}
144                 required
145                 minLength={3}
146                 maxLength={20}
147               />
148             </div>
149           </div>
150           <div class="form-group">
151             <label>{i18n.t("icon")}</label>
152             <ImageUploadForm
153               uploadTitle={i18n.t("upload_icon")}
154               imageSrc={this.state.siteForm.icon}
155               onUpload={this.handleIconUpload}
156               onRemove={this.handleIconRemove}
157               rounded
158             />
159           </div>
160           <div class="form-group">
161             <label>{i18n.t("banner")}</label>
162             <ImageUploadForm
163               uploadTitle={i18n.t("upload_banner")}
164               imageSrc={this.state.siteForm.banner}
165               onUpload={this.handleBannerUpload}
166               onRemove={this.handleBannerRemove}
167             />
168           </div>
169           <div class="form-group row">
170             <label class="col-12 col-form-label" htmlFor="site-desc">
171               {i18n.t("description")}
172             </label>
173             <div class="col-12">
174               <input
175                 type="text"
176                 class="form-control"
177                 id="site-desc"
178                 value={this.state.siteForm.description}
179                 onInput={linkEvent(this, this.handleSiteDescChange)}
180                 maxLength={150}
181               />
182             </div>
183           </div>
184           <div class="form-group row">
185             <label class="col-12 col-form-label">{i18n.t("sidebar")}</label>
186             <div class="col-12">
187               <MarkdownTextArea
188                 initialContent={this.state.siteForm.sidebar}
189                 onContentChange={this.handleSiteSidebarChange}
190                 hideNavigationWarnings
191               />
192             </div>
193           </div>
194           {this.state.siteForm.require_application && (
195             <div class="form-group row">
196               <label class="col-12 col-form-label">
197                 {i18n.t("application_questionnaire")}
198               </label>
199               <div class="col-12">
200                 <MarkdownTextArea
201                   initialContent={this.state.siteForm.application_question}
202                   onContentChange={this.handleSiteApplicationQuestionChange}
203                   hideNavigationWarnings
204                 />
205               </div>
206             </div>
207           )}
208           <div class="form-group row">
209             <div class="col-12">
210               <div class="form-check">
211                 <input
212                   class="form-check-input"
213                   id="create-site-downvotes"
214                   type="checkbox"
215                   checked={this.state.siteForm.enable_downvotes}
216                   onChange={linkEvent(
217                     this,
218                     this.handleSiteEnableDownvotesChange
219                   )}
220                 />
221                 <label class="form-check-label" htmlFor="create-site-downvotes">
222                   {i18n.t("enable_downvotes")}
223                 </label>
224               </div>
225             </div>
226           </div>
227           <div class="form-group row">
228             <div class="col-12">
229               <div class="form-check">
230                 <input
231                   class="form-check-input"
232                   id="create-site-enable-nsfw"
233                   type="checkbox"
234                   checked={this.state.siteForm.enable_nsfw}
235                   onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
236                 />
237                 <label
238                   class="form-check-label"
239                   htmlFor="create-site-enable-nsfw"
240                 >
241                   {i18n.t("enable_nsfw")}
242                 </label>
243               </div>
244             </div>
245           </div>
246           <div class="form-group row">
247             <div class="col-12">
248               <div class="form-check">
249                 <input
250                   class="form-check-input"
251                   id="create-site-open-registration"
252                   type="checkbox"
253                   checked={this.state.siteForm.open_registration}
254                   onChange={linkEvent(
255                     this,
256                     this.handleSiteOpenRegistrationChange
257                   )}
258                 />
259                 <label
260                   class="form-check-label"
261                   htmlFor="create-site-open-registration"
262                 >
263                   {i18n.t("open_registration")}
264                 </label>
265               </div>
266             </div>
267           </div>
268           <div class="form-group row">
269             <div class="col-12">
270               <div class="form-check">
271                 <input
272                   class="form-check-input"
273                   id="create-site-community-creation-admin-only"
274                   type="checkbox"
275                   checked={this.state.siteForm.community_creation_admin_only}
276                   onChange={linkEvent(
277                     this,
278                     this.handleSiteCommunityCreationAdminOnly
279                   )}
280                 />
281                 <label
282                   class="form-check-label"
283                   htmlFor="create-site-community-creation-admin-only"
284                 >
285                   {i18n.t("community_creation_admin_only")}
286                 </label>
287               </div>
288             </div>
289           </div>
290           <div class="form-group row">
291             <div class="col-12">
292               <div class="form-check">
293                 <input
294                   class="form-check-input"
295                   id="create-site-require-email-verification"
296                   type="checkbox"
297                   checked={this.state.siteForm.require_email_verification}
298                   onChange={linkEvent(
299                     this,
300                     this.handleSiteRequireEmailVerification
301                   )}
302                 />
303                 <label
304                   class="form-check-label"
305                   htmlFor="create-site-require-email-verification"
306                 >
307                   {i18n.t("require_email_verification")}
308                 </label>
309               </div>
310             </div>
311           </div>
312           <div class="form-group row">
313             <div class="col-12">
314               <div class="form-check">
315                 <input
316                   class="form-check-input"
317                   id="create-site-require-application"
318                   type="checkbox"
319                   checked={this.state.siteForm.require_application}
320                   onChange={linkEvent(this, this.handleSiteRequireApplication)}
321                 />
322                 <label
323                   class="form-check-label"
324                   htmlFor="create-site-require-application"
325                 >
326                   {i18n.t("require_registration_application")}
327                 </label>
328               </div>
329             </div>
330           </div>
331           <div class="form-group row">
332             <div class="col-12">
333               <label
334                 class="form-check-label mr-2"
335                 htmlFor="create-site-default-theme"
336               >
337                 {i18n.t("theme")}
338               </label>
339               <select
340                 id="create-site-default-theme"
341                 value={this.state.siteForm.default_theme}
342                 onChange={linkEvent(this, this.handleSiteDefaultTheme)}
343                 class="custom-select w-auto"
344               >
345                 <option value="browser">{i18n.t("browser_default")}</option>
346                 {this.state.themeList.map(theme => (
347                   <option value={theme}>{theme}</option>
348                 ))}
349               </select>
350             </div>
351           </div>
352           <div class="form-group row">
353             <div class="col-12">
354               <div class="form-check">
355                 <input
356                   class="form-check-input"
357                   id="create-site-private-instance"
358                   type="checkbox"
359                   value={this.state.siteForm.default_theme}
360                   onChange={linkEvent(this, this.handleSitePrivateInstance)}
361                 />
362                 <label
363                   class="form-check-label"
364                   htmlFor="create-site-private-instance"
365                 >
366                   {i18n.t("private_instance")}
367                 </label>
368               </div>
369             </div>
370           </div>
371           <div class="form-group row">
372             <div class="col-12">
373               <button
374                 type="submit"
375                 class="btn btn-secondary mr-2"
376                 disabled={this.state.loading}
377               >
378                 {this.state.loading ? (
379                   <Spinner />
380                 ) : this.props.site ? (
381                   capitalizeFirstLetter(i18n.t("save"))
382                 ) : (
383                   capitalizeFirstLetter(i18n.t("create"))
384                 )}
385               </button>
386               {this.props.site && (
387                 <button
388                   type="button"
389                   class="btn btn-secondary"
390                   onClick={linkEvent(this, this.handleCancel)}
391                 >
392                   {i18n.t("cancel")}
393                 </button>
394               )}
395             </div>
396           </div>
397         </form>
398       </>
399     );
400   }
401
402   handleCreateSiteSubmit(i: SiteForm, event: any) {
403     event.preventDefault();
404     i.state.loading = true;
405     if (i.props.site) {
406       WebSocketService.Instance.send(wsClient.editSite(i.state.siteForm));
407     } else {
408       let form: CreateSite = {
409         name: i.state.siteForm.name || "My site",
410         ...i.state.siteForm,
411       };
412       WebSocketService.Instance.send(wsClient.createSite(form));
413     }
414     i.setState(i.state);
415   }
416
417   handleSiteNameChange(i: SiteForm, event: any) {
418     i.state.siteForm.name = event.target.value;
419     i.setState(i.state);
420   }
421
422   handleSiteSidebarChange(val: string) {
423     this.state.siteForm.sidebar = val;
424     this.setState(this.state);
425   }
426
427   handleSiteApplicationQuestionChange(val: string) {
428     this.state.siteForm.application_question = val;
429     this.setState(this.state);
430   }
431
432   handleSiteDescChange(i: SiteForm, event: any) {
433     i.state.siteForm.description = event.target.value;
434     i.setState(i.state);
435   }
436
437   handleSiteEnableNsfwChange(i: SiteForm, event: any) {
438     i.state.siteForm.enable_nsfw = event.target.checked;
439     i.setState(i.state);
440   }
441
442   handleSiteOpenRegistrationChange(i: SiteForm, event: any) {
443     i.state.siteForm.open_registration = event.target.checked;
444     i.setState(i.state);
445   }
446
447   handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) {
448     i.state.siteForm.community_creation_admin_only = event.target.checked;
449     i.setState(i.state);
450   }
451
452   handleSiteEnableDownvotesChange(i: SiteForm, event: any) {
453     i.state.siteForm.enable_downvotes = event.target.checked;
454     i.setState(i.state);
455   }
456
457   handleSiteRequireApplication(i: SiteForm, event: any) {
458     i.state.siteForm.require_application = event.target.checked;
459     i.setState(i.state);
460   }
461
462   handleSiteRequireEmailVerification(i: SiteForm, event: any) {
463     i.state.siteForm.require_email_verification = event.target.checked;
464     i.setState(i.state);
465   }
466
467   handleSitePrivateInstance(i: SiteForm, event: any) {
468     i.state.siteForm.private_instance = event.target.checked;
469     i.setState(i.state);
470   }
471
472   handleSiteDefaultTheme(i: SiteForm, event: any) {
473     i.state.siteForm.default_theme = event.target.value;
474     i.setState(i.state);
475   }
476
477   handleCancel(i: SiteForm) {
478     i.props.onCancel();
479   }
480
481   handleIconUpload(url: string) {
482     this.state.siteForm.icon = url;
483     this.setState(this.state);
484   }
485
486   handleIconRemove() {
487     this.state.siteForm.icon = "";
488     this.setState(this.state);
489   }
490
491   handleBannerUpload(url: string) {
492     this.state.siteForm.banner = url;
493     this.setState(this.state);
494   }
495
496   handleBannerRemove() {
497     this.state.siteForm.banner = "";
498     this.setState(this.state);
499   }
500 }