]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/site-form.tsx
Merge branch 'LemmyNet:main' into multiple-images-upload
[lemmy-ui.git] / src / shared / components / home / site-form.tsx
1 import { None, Option, Some } from "@sniptt/monads";
2 import { Component, linkEvent } from "inferno";
3 import { Prompt } from "inferno-router";
4 import {
5   CreateSite,
6   EditSite,
7   ListingType,
8   Site,
9   toUndefined,
10 } from "lemmy-js-client";
11 import { i18n } from "../../i18next";
12 import { WebSocketService } from "../../services";
13 import {
14   auth,
15   capitalizeFirstLetter,
16   fetchThemeList,
17   wsClient,
18 } from "../../utils";
19 import { Spinner } from "../common/icon";
20 import { ImageUploadForm } from "../common/image-upload-form";
21 import { ListingTypeSelect } from "../common/listing-type-select";
22 import { MarkdownTextArea } from "../common/markdown-textarea";
23
24 interface SiteFormProps {
25   site: Option<Site>; // If a site is given, that means this is an edit
26   showLocal?: boolean;
27   onCancel?(): void;
28   onEdit?(): void;
29 }
30
31 interface SiteFormState {
32   siteForm: EditSite;
33   loading: boolean;
34   themeList: Option<string[]>;
35 }
36
37 export class SiteForm extends Component<SiteFormProps, SiteFormState> {
38   private emptyState: SiteFormState = {
39     siteForm: new EditSite({
40       enable_downvotes: Some(true),
41       open_registration: Some(true),
42       enable_nsfw: Some(true),
43       name: None,
44       icon: None,
45       banner: None,
46       require_email_verification: None,
47       require_application: None,
48       application_question: None,
49       private_instance: None,
50       default_theme: None,
51       sidebar: None,
52       default_post_listing_type: None,
53       legal_information: None,
54       description: None,
55       community_creation_admin_only: None,
56       application_email_admins: None,
57       auth: undefined,
58       hide_modlog_mod_names: Some(true),
59     }),
60     loading: false,
61     themeList: None,
62   };
63
64   constructor(props: any, context: any) {
65     super(props, context);
66
67     this.state = this.emptyState;
68     this.handleSiteSidebarChange = this.handleSiteSidebarChange.bind(this);
69     this.handleSiteLegalInfoChange = this.handleSiteLegalInfoChange.bind(this);
70     this.handleSiteApplicationQuestionChange =
71       this.handleSiteApplicationQuestionChange.bind(this);
72
73     this.handleIconUpload = this.handleIconUpload.bind(this);
74     this.handleIconRemove = this.handleIconRemove.bind(this);
75
76     this.handleBannerUpload = this.handleBannerUpload.bind(this);
77     this.handleBannerRemove = this.handleBannerRemove.bind(this);
78
79     this.handleDefaultPostListingTypeChange =
80       this.handleDefaultPostListingTypeChange.bind(this);
81
82     if (this.props.site.isSome()) {
83       let site = this.props.site.unwrap();
84       this.state = {
85         ...this.state,
86         siteForm: new EditSite({
87           name: Some(site.name),
88           sidebar: site.sidebar,
89           description: site.description,
90           enable_downvotes: Some(site.enable_downvotes),
91           open_registration: Some(site.open_registration),
92           enable_nsfw: Some(site.enable_nsfw),
93           community_creation_admin_only: Some(
94             site.community_creation_admin_only
95           ),
96           icon: site.icon,
97           banner: site.banner,
98           require_email_verification: Some(site.require_email_verification),
99           require_application: Some(site.require_application),
100           application_question: site.application_question,
101           private_instance: Some(site.private_instance),
102           default_theme: Some(site.default_theme),
103           default_post_listing_type: Some(site.default_post_listing_type),
104           legal_information: site.legal_information,
105           application_email_admins: Some(site.application_email_admins),
106           hide_modlog_mod_names: site.hide_modlog_mod_names,
107           auth: undefined,
108         }),
109       };
110     }
111   }
112
113   async componentDidMount() {
114     this.setState({ themeList: Some(await fetchThemeList()) });
115   }
116
117   // Necessary to stop the loading
118   componentWillReceiveProps() {
119     this.setState({ loading: false });
120   }
121
122   componentDidUpdate() {
123     if (
124       !this.state.loading &&
125       this.props.site.isNone() &&
126       (this.state.siteForm.name ||
127         this.state.siteForm.sidebar ||
128         this.state.siteForm.application_question ||
129         this.state.siteForm.description)
130     ) {
131       window.onbeforeunload = () => true;
132     } else {
133       window.onbeforeunload = undefined;
134     }
135   }
136
137   componentWillUnmount() {
138     window.onbeforeunload = null;
139   }
140
141   render() {
142     return (
143       <>
144         <Prompt
145           when={
146             !this.state.loading &&
147             this.props.site.isNone() &&
148             (this.state.siteForm.name ||
149               this.state.siteForm.sidebar ||
150               this.state.siteForm.application_question ||
151               this.state.siteForm.description)
152           }
153           message={i18n.t("block_leaving")}
154         />
155         <form onSubmit={linkEvent(this, this.handleCreateSiteSubmit)}>
156           <h5>{`${
157             this.props.site.isSome()
158               ? capitalizeFirstLetter(i18n.t("save"))
159               : capitalizeFirstLetter(i18n.t("name"))
160           } ${i18n.t("your_site")}`}</h5>
161           <div className="form-group row">
162             <label className="col-12 col-form-label" htmlFor="create-site-name">
163               {i18n.t("name")}
164             </label>
165             <div className="col-12">
166               <input
167                 type="text"
168                 id="create-site-name"
169                 className="form-control"
170                 value={toUndefined(this.state.siteForm.name)}
171                 onInput={linkEvent(this, this.handleSiteNameChange)}
172                 required
173                 minLength={3}
174                 maxLength={20}
175               />
176             </div>
177           </div>
178           <div className="form-group">
179             <label>{i18n.t("icon")}</label>
180             <ImageUploadForm
181               uploadTitle={i18n.t("upload_icon")}
182               imageSrc={this.state.siteForm.icon}
183               onUpload={this.handleIconUpload}
184               onRemove={this.handleIconRemove}
185               rounded
186             />
187           </div>
188           <div className="form-group">
189             <label>{i18n.t("banner")}</label>
190             <ImageUploadForm
191               uploadTitle={i18n.t("upload_banner")}
192               imageSrc={this.state.siteForm.banner}
193               onUpload={this.handleBannerUpload}
194               onRemove={this.handleBannerRemove}
195             />
196           </div>
197           <div className="form-group row">
198             <label className="col-12 col-form-label" htmlFor="site-desc">
199               {i18n.t("description")}
200             </label>
201             <div className="col-12">
202               <input
203                 type="text"
204                 className="form-control"
205                 id="site-desc"
206                 value={toUndefined(this.state.siteForm.description)}
207                 onInput={linkEvent(this, this.handleSiteDescChange)}
208                 maxLength={150}
209               />
210             </div>
211           </div>
212           <div className="form-group row">
213             <label className="col-12 col-form-label">{i18n.t("sidebar")}</label>
214             <div className="col-12">
215               <MarkdownTextArea
216                 initialContent={this.state.siteForm.sidebar}
217                 initialLanguageId={None}
218                 placeholder={None}
219                 buttonTitle={None}
220                 maxLength={None}
221                 onContentChange={this.handleSiteSidebarChange}
222                 hideNavigationWarnings
223                 allLanguages={[]}
224               />
225             </div>
226           </div>
227           <div className="form-group row">
228             <label className="col-12 col-form-label">
229               {i18n.t("legal_information")}
230             </label>
231             <div className="col-12">
232               <MarkdownTextArea
233                 initialContent={this.state.siteForm.legal_information}
234                 initialLanguageId={None}
235                 placeholder={None}
236                 buttonTitle={None}
237                 maxLength={None}
238                 onContentChange={this.handleSiteLegalInfoChange}
239                 hideNavigationWarnings
240                 allLanguages={[]}
241               />
242             </div>
243           </div>
244           {this.state.siteForm.require_application.unwrapOr(false) && (
245             <div className="form-group row">
246               <label className="col-12 col-form-label">
247                 {i18n.t("application_questionnaire")}
248               </label>
249               <div className="col-12">
250                 <MarkdownTextArea
251                   initialContent={this.state.siteForm.application_question}
252                   initialLanguageId={None}
253                   placeholder={None}
254                   buttonTitle={None}
255                   maxLength={None}
256                   onContentChange={this.handleSiteApplicationQuestionChange}
257                   hideNavigationWarnings
258                   allLanguages={[]}
259                 />
260               </div>
261             </div>
262           )}
263           <div className="form-group row">
264             <div className="col-12">
265               <div className="form-check">
266                 <input
267                   className="form-check-input"
268                   id="create-site-downvotes"
269                   type="checkbox"
270                   checked={toUndefined(this.state.siteForm.enable_downvotes)}
271                   onChange={linkEvent(
272                     this,
273                     this.handleSiteEnableDownvotesChange
274                   )}
275                 />
276                 <label
277                   className="form-check-label"
278                   htmlFor="create-site-downvotes"
279                 >
280                   {i18n.t("enable_downvotes")}
281                 </label>
282               </div>
283             </div>
284           </div>
285           <div className="form-group row">
286             <div className="col-12">
287               <div className="form-check">
288                 <input
289                   className="form-check-input"
290                   id="create-site-enable-nsfw"
291                   type="checkbox"
292                   checked={toUndefined(this.state.siteForm.enable_nsfw)}
293                   onChange={linkEvent(this, this.handleSiteEnableNsfwChange)}
294                 />
295                 <label
296                   className="form-check-label"
297                   htmlFor="create-site-enable-nsfw"
298                 >
299                   {i18n.t("enable_nsfw")}
300                 </label>
301               </div>
302             </div>
303           </div>
304           <div className="form-group row">
305             <div className="col-12">
306               <div className="form-check">
307                 <input
308                   className="form-check-input"
309                   id="create-site-open-registration"
310                   type="checkbox"
311                   checked={toUndefined(this.state.siteForm.open_registration)}
312                   onChange={linkEvent(
313                     this,
314                     this.handleSiteOpenRegistrationChange
315                   )}
316                 />
317                 <label
318                   className="form-check-label"
319                   htmlFor="create-site-open-registration"
320                 >
321                   {i18n.t("open_registration")}
322                 </label>
323               </div>
324             </div>
325           </div>
326           <div className="form-group row">
327             <div className="col-12">
328               <div className="form-check">
329                 <input
330                   className="form-check-input"
331                   id="create-site-community-creation-admin-only"
332                   type="checkbox"
333                   checked={toUndefined(
334                     this.state.siteForm.community_creation_admin_only
335                   )}
336                   onChange={linkEvent(
337                     this,
338                     this.handleSiteCommunityCreationAdminOnly
339                   )}
340                 />
341                 <label
342                   className="form-check-label"
343                   htmlFor="create-site-community-creation-admin-only"
344                 >
345                   {i18n.t("community_creation_admin_only")}
346                 </label>
347               </div>
348             </div>
349           </div>
350           <div className="form-group row">
351             <div className="col-12">
352               <div className="form-check">
353                 <input
354                   className="form-check-input"
355                   id="create-site-require-email-verification"
356                   type="checkbox"
357                   checked={toUndefined(
358                     this.state.siteForm.require_email_verification
359                   )}
360                   onChange={linkEvent(
361                     this,
362                     this.handleSiteRequireEmailVerification
363                   )}
364                 />
365                 <label
366                   className="form-check-label"
367                   htmlFor="create-site-require-email-verification"
368                 >
369                   {i18n.t("require_email_verification")}
370                 </label>
371               </div>
372             </div>
373           </div>
374           <div className="form-group row">
375             <div className="col-12">
376               <div className="form-check">
377                 <input
378                   className="form-check-input"
379                   id="create-site-require-application"
380                   type="checkbox"
381                   checked={toUndefined(this.state.siteForm.require_application)}
382                   onChange={linkEvent(this, this.handleSiteRequireApplication)}
383                 />
384                 <label
385                   className="form-check-label"
386                   htmlFor="create-site-require-application"
387                 >
388                   {i18n.t("require_registration_application")}
389                 </label>
390               </div>
391             </div>
392           </div>
393           <div className="form-group row">
394             <div className="col-12">
395               <div className="form-check">
396                 <input
397                   className="form-check-input"
398                   id="create-site-application-email-admins"
399                   type="checkbox"
400                   checked={toUndefined(
401                     this.state.siteForm.application_email_admins
402                   )}
403                   onChange={linkEvent(
404                     this,
405                     this.handleSiteApplicationEmailAdmins
406                   )}
407                 />
408                 <label
409                   className="form-check-label"
410                   htmlFor="create-site-email-admins"
411                 >
412                   {i18n.t("application_email_admins")}
413                 </label>
414               </div>
415             </div>
416           </div>
417           <div className="form-group row">
418             <div className="col-12">
419               <label
420                 className="form-check-label mr-2"
421                 htmlFor="create-site-default-theme"
422               >
423                 {i18n.t("theme")}
424               </label>
425               <select
426                 id="create-site-default-theme"
427                 value={toUndefined(this.state.siteForm.default_theme)}
428                 onChange={linkEvent(this, this.handleSiteDefaultTheme)}
429                 className="custom-select w-auto"
430               >
431                 <option value="browser">{i18n.t("browser_default")}</option>
432                 {this.state.themeList.unwrapOr([]).map(theme => (
433                   <option key={theme} value={theme}>
434                     {theme}
435                   </option>
436                 ))}
437               </select>
438             </div>
439           </div>
440           {this.props.showLocal && (
441             <form className="form-group row">
442               <label className="col-sm-3">{i18n.t("listing_type")}</label>
443               <div className="col-sm-9">
444                 <ListingTypeSelect
445                   type_={
446                     ListingType[
447                       this.state.siteForm.default_post_listing_type.unwrapOr(
448                         "Local"
449                       )
450                     ]
451                   }
452                   showLocal
453                   showSubscribed={false}
454                   onChange={this.handleDefaultPostListingTypeChange}
455                 />
456               </div>
457             </form>
458           )}
459           <div className="form-group row">
460             <div className="col-12">
461               <div className="form-check">
462                 <input
463                   className="form-check-input"
464                   id="create-site-private-instance"
465                   type="checkbox"
466                   checked={toUndefined(this.state.siteForm.private_instance)}
467                   onChange={linkEvent(this, this.handleSitePrivateInstance)}
468                 />
469                 <label
470                   className="form-check-label"
471                   htmlFor="create-site-private-instance"
472                 >
473                   {i18n.t("private_instance")}
474                 </label>
475               </div>
476             </div>
477           </div>
478           <div className="form-group row">
479             <div className="col-12">
480               <div className="form-check">
481                 <input
482                   className="form-check-input"
483                   id="create-site-hide-modlog-mod-names"
484                   type="checkbox"
485                   checked={toUndefined(
486                     this.state.siteForm.hide_modlog_mod_names
487                   )}
488                   onChange={linkEvent(this, this.handleSiteHideModlogModNames)}
489                 />
490                 <label
491                   className="form-check-label"
492                   htmlFor="create-site-hide-modlog-mod-names"
493                 >
494                   {i18n.t("hide_modlog_mod_names")}
495                 </label>
496               </div>
497             </div>
498           </div>
499           <div className="form-group row">
500             <div className="col-12">
501               <button
502                 type="submit"
503                 className="btn btn-secondary mr-2"
504                 disabled={this.state.loading}
505               >
506                 {this.state.loading ? (
507                   <Spinner />
508                 ) : this.props.site.isSome() ? (
509                   capitalizeFirstLetter(i18n.t("save"))
510                 ) : (
511                   capitalizeFirstLetter(i18n.t("create"))
512                 )}
513               </button>
514               {this.props.site.isSome() && (
515                 <button
516                   type="button"
517                   className="btn btn-secondary"
518                   onClick={linkEvent(this, this.handleCancel)}
519                 >
520                   {i18n.t("cancel")}
521                 </button>
522               )}
523             </div>
524           </div>
525         </form>
526       </>
527     );
528   }
529
530   handleCreateSiteSubmit(i: SiteForm, event: any) {
531     event.preventDefault();
532     i.setState({ loading: true });
533     i.setState(s => ((s.siteForm.auth = auth().unwrap()), s));
534
535     if (i.props.site.isSome()) {
536       WebSocketService.Instance.send(wsClient.editSite(i.state.siteForm));
537       i.props.onEdit();
538     } else {
539       let sForm = i.state.siteForm;
540       let form = new CreateSite({
541         name: sForm.name.unwrapOr("My site"),
542         sidebar: sForm.sidebar,
543         description: sForm.description,
544         icon: sForm.icon,
545         banner: sForm.banner,
546         community_creation_admin_only: sForm.community_creation_admin_only,
547         enable_nsfw: sForm.enable_nsfw,
548         enable_downvotes: sForm.enable_downvotes,
549         require_application: sForm.require_application,
550         application_question: sForm.application_question,
551         open_registration: sForm.open_registration,
552         require_email_verification: sForm.require_email_verification,
553         private_instance: sForm.private_instance,
554         default_theme: sForm.default_theme,
555         default_post_listing_type: sForm.default_post_listing_type,
556         application_email_admins: sForm.application_email_admins,
557         auth: auth().unwrap(),
558         hide_modlog_mod_names: sForm.hide_modlog_mod_names,
559       });
560       WebSocketService.Instance.send(wsClient.createSite(form));
561     }
562     i.setState(i.state);
563   }
564
565   handleSiteNameChange(i: SiteForm, event: any) {
566     i.state.siteForm.name = Some(event.target.value);
567     i.setState(i.state);
568   }
569
570   handleSiteSidebarChange(val: string) {
571     this.setState(s => ((s.siteForm.sidebar = Some(val)), s));
572   }
573
574   handleSiteLegalInfoChange(val: string) {
575     this.setState(s => ((s.siteForm.legal_information = Some(val)), s));
576   }
577
578   handleSiteApplicationQuestionChange(val: string) {
579     this.setState(s => ((s.siteForm.application_question = Some(val)), s));
580   }
581
582   handleSiteDescChange(i: SiteForm, event: any) {
583     i.state.siteForm.description = Some(event.target.value);
584     i.setState(i.state);
585   }
586
587   handleSiteEnableNsfwChange(i: SiteForm, event: any) {
588     i.state.siteForm.enable_nsfw = Some(event.target.checked);
589     i.setState(i.state);
590   }
591
592   handleSiteOpenRegistrationChange(i: SiteForm, event: any) {
593     i.state.siteForm.open_registration = Some(event.target.checked);
594     i.setState(i.state);
595   }
596
597   handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) {
598     i.state.siteForm.community_creation_admin_only = Some(event.target.checked);
599     i.setState(i.state);
600   }
601
602   handleSiteEnableDownvotesChange(i: SiteForm, event: any) {
603     i.state.siteForm.enable_downvotes = Some(event.target.checked);
604     i.setState(i.state);
605   }
606
607   handleSiteRequireApplication(i: SiteForm, event: any) {
608     i.state.siteForm.require_application = Some(event.target.checked);
609     i.setState(i.state);
610   }
611
612   handleSiteRequireEmailVerification(i: SiteForm, event: any) {
613     i.state.siteForm.require_email_verification = Some(event.target.checked);
614     i.setState(i.state);
615   }
616
617   handleSiteApplicationEmailAdmins(i: SiteForm, event: any) {
618     i.state.siteForm.application_email_admins = Some(event.target.checked);
619     i.setState(i.state);
620   }
621
622   handleSitePrivateInstance(i: SiteForm, event: any) {
623     i.state.siteForm.private_instance = Some(event.target.checked);
624     i.setState(i.state);
625   }
626
627   handleSiteHideModlogModNames(i: SiteForm, event: any) {
628     i.state.siteForm.hide_modlog_mod_names = Some(event.target.checked);
629     i.setState(i.state);
630   }
631
632   handleSiteDefaultTheme(i: SiteForm, event: any) {
633     i.state.siteForm.default_theme = Some(event.target.value);
634     i.setState(i.state);
635   }
636
637   handleCancel(i: SiteForm) {
638     i.props.onCancel();
639   }
640
641   handleIconUpload(url: string) {
642     this.setState(s => ((s.siteForm.icon = Some(url)), s));
643   }
644
645   handleIconRemove() {
646     this.setState(s => ((s.siteForm.icon = Some("")), s));
647   }
648
649   handleBannerUpload(url: string) {
650     this.setState(s => ((s.siteForm.banner = Some(url)), s));
651   }
652
653   handleBannerRemove() {
654     this.setState(s => ((s.siteForm.banner = Some("")), s));
655   }
656
657   handleDefaultPostListingTypeChange(val: ListingType) {
658     this.setState(
659       s => (
660         (s.siteForm.default_post_listing_type = Some(
661           ListingType[ListingType[val]]
662         )),
663         s
664       )
665     );
666   }
667 }