]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/settings.tsx
Feature/user community block (#362)
[lemmy-ui.git] / src / shared / components / person / settings.tsx
1 import { Component, linkEvent } from "inferno";
2 import ISO6391 from "iso-639-1";
3 import {
4   BlockCommunity,
5   BlockCommunityResponse,
6   BlockPerson,
7   BlockPersonResponse,
8   ChangePassword,
9   CommunityBlockView,
10   CommunityView,
11   DeleteAccount,
12   GetSiteResponse,
13   ListingType,
14   LoginResponse,
15   PersonBlockView,
16   PersonViewSafe,
17   SaveUserSettings,
18   SortType,
19   UserOperation,
20 } from "lemmy-js-client";
21 import { Subscription } from "rxjs";
22 import { i18n } from "../../i18next";
23 import { UserService, WebSocketService } from "../../services";
24 import {
25   authField,
26   capitalizeFirstLetter,
27   choicesConfig,
28   communitySelectName,
29   communityToChoice,
30   debounce,
31   elementUrl,
32   fetchCommunities,
33   fetchUsers,
34   getLanguage,
35   isBrowser,
36   languages,
37   personSelectName,
38   personToChoice,
39   setIsoData,
40   setTheme,
41   setupTippy,
42   showLocal,
43   themes,
44   toast,
45   updateCommunityBlock,
46   updatePersonBlock,
47   wsClient,
48   wsJsonToRes,
49   wsSubscribe,
50   wsUserOp,
51 } from "../../utils";
52 import { HtmlTags } from "../common/html-tags";
53 import { Icon, Spinner } from "../common/icon";
54 import { ImageUploadForm } from "../common/image-upload-form";
55 import { ListingTypeSelect } from "../common/listing-type-select";
56 import { MarkdownTextArea } from "../common/markdown-textarea";
57 import { SortSelect } from "../common/sort-select";
58 import { CommunityLink } from "../community/community-link";
59 import { PersonListing } from "./person-listing";
60
61 var Choices: any;
62 if (isBrowser()) {
63   Choices = require("choices.js");
64 }
65
66 interface SettingsState {
67   saveUserSettingsForm: SaveUserSettings;
68   changePasswordForm: ChangePassword;
69   saveUserSettingsLoading: boolean;
70   changePasswordLoading: boolean;
71   deleteAccountLoading: boolean;
72   deleteAccountShowConfirm: boolean;
73   deleteAccountForm: DeleteAccount;
74   personBlocks: PersonBlockView[];
75   blockPersonId: number;
76   blockPerson?: PersonViewSafe;
77   communityBlocks: CommunityBlockView[];
78   blockCommunityId: number;
79   blockCommunity?: CommunityView;
80   currentTab: string;
81   siteRes: GetSiteResponse;
82 }
83
84 export class Settings extends Component<any, SettingsState> {
85   private isoData = setIsoData(this.context);
86   private blockPersonChoices: any;
87   private blockCommunityChoices: any;
88   private subscription: Subscription;
89   private emptyState: SettingsState = {
90     saveUserSettingsForm: {
91       auth: authField(false),
92     },
93     changePasswordForm: {
94       new_password: null,
95       new_password_verify: null,
96       old_password: null,
97       auth: authField(false),
98     },
99     saveUserSettingsLoading: null,
100     changePasswordLoading: false,
101     deleteAccountLoading: null,
102     deleteAccountShowConfirm: false,
103     deleteAccountForm: {
104       password: null,
105       auth: authField(false),
106     },
107     personBlocks: [],
108     blockPersonId: 0,
109     communityBlocks: [],
110     blockCommunityId: 0,
111     currentTab: "settings",
112     siteRes: this.isoData.site_res,
113   };
114
115   constructor(props: any, context: any) {
116     super(props, context);
117
118     this.state = this.emptyState;
119     this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
120     this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
121     this.handleBioChange = this.handleBioChange.bind(this);
122
123     this.handleAvatarUpload = this.handleAvatarUpload.bind(this);
124     this.handleAvatarRemove = this.handleAvatarRemove.bind(this);
125
126     this.handleBannerUpload = this.handleBannerUpload.bind(this);
127     this.handleBannerRemove = this.handleBannerRemove.bind(this);
128
129     this.parseMessage = this.parseMessage.bind(this);
130     this.subscription = wsSubscribe(this.parseMessage);
131
132     this.setUserInfo();
133
134     setupTippy();
135   }
136
137   componentWillUnmount() {
138     this.subscription.unsubscribe();
139   }
140
141   get documentTitle(): string {
142     return i18n.t("settings");
143   }
144
145   render() {
146     return (
147       <div class="container">
148         <>
149           <HtmlTags
150             title={this.documentTitle}
151             path={this.context.router.route.match.url}
152             description={this.documentTitle}
153             image={this.state.saveUserSettingsForm.avatar}
154           />
155           <ul class="nav nav-tabs mb-2">
156             <li class="nav-item">
157               <button
158                 class={`nav-link btn ${
159                   this.state.currentTab == "settings" && "active"
160                 }`}
161                 onClick={linkEvent(
162                   { ctx: this, tab: "settings" },
163                   this.handleSwitchTab
164                 )}
165               >
166                 {i18n.t("settings")}
167               </button>
168             </li>
169             <li class="nav-item">
170               <button
171                 class={`nav-link btn ${
172                   this.state.currentTab == "blocks" && "active"
173                 }`}
174                 onClick={linkEvent(
175                   { ctx: this, tab: "blocks" },
176                   this.handleSwitchTab
177                 )}
178               >
179                 {i18n.t("blocks")}
180               </button>
181             </li>
182           </ul>
183           {this.state.currentTab == "settings" && this.userSettings()}
184           {this.state.currentTab == "blocks" && this.blockCards()}
185         </>
186       </div>
187     );
188   }
189
190   userSettings() {
191     return (
192       <div class="row">
193         <div class="col-12 col-md-6">
194           <div class="card border-secondary mb-3">
195             <div class="card-body">{this.saveUserSettingsHtmlForm()}</div>
196           </div>
197         </div>
198         <div class="col-12 col-md-6">
199           <div class="card border-secondary mb-3">
200             <div class="card-body">{this.changePasswordHtmlForm()}</div>
201           </div>
202         </div>
203       </div>
204     );
205   }
206
207   blockCards() {
208     return (
209       <div class="row">
210         <div class="col-12 col-md-6">
211           <div class="card border-secondary mb-3">
212             <div class="card-body">{this.blockUserCard()}</div>
213           </div>
214         </div>
215         <div class="col-12 col-md-6">
216           <div class="card border-secondary mb-3">
217             <div class="card-body">{this.blockCommunityCard()}</div>
218           </div>
219         </div>
220       </div>
221     );
222   }
223
224   changePasswordHtmlForm() {
225     return (
226       <>
227         <h5>{i18n.t("change_password")}</h5>
228         <form onSubmit={linkEvent(this, this.handleChangePasswordSubmit)}>
229           <div class="form-group row">
230             <label class="col-lg-5 col-form-label" htmlFor="user-password">
231               {i18n.t("new_password")}
232             </label>
233             <div class="col-lg-7">
234               <input
235                 type="password"
236                 id="user-password"
237                 class="form-control"
238                 value={this.state.changePasswordForm.new_password}
239                 autoComplete="new-password"
240                 maxLength={60}
241                 onInput={linkEvent(this, this.handleNewPasswordChange)}
242               />
243             </div>
244           </div>
245           <div class="form-group row">
246             <label
247               class="col-lg-5 col-form-label"
248               htmlFor="user-verify-password"
249             >
250               {i18n.t("verify_password")}
251             </label>
252             <div class="col-lg-7">
253               <input
254                 type="password"
255                 id="user-verify-password"
256                 class="form-control"
257                 value={this.state.changePasswordForm.new_password_verify}
258                 autoComplete="new-password"
259                 maxLength={60}
260                 onInput={linkEvent(this, this.handleNewPasswordVerifyChange)}
261               />
262             </div>
263           </div>
264           <div class="form-group row">
265             <label class="col-lg-5 col-form-label" htmlFor="user-old-password">
266               {i18n.t("old_password")}
267             </label>
268             <div class="col-lg-7">
269               <input
270                 type="password"
271                 id="user-old-password"
272                 class="form-control"
273                 value={this.state.changePasswordForm.old_password}
274                 autoComplete="new-password"
275                 maxLength={60}
276                 onInput={linkEvent(this, this.handleOldPasswordChange)}
277               />
278             </div>
279           </div>
280           <div class="form-group">
281             <button type="submit" class="btn btn-block btn-secondary mr-4">
282               {this.state.changePasswordLoading ? (
283                 <Spinner />
284               ) : (
285                 capitalizeFirstLetter(i18n.t("save"))
286               )}
287             </button>
288           </div>
289         </form>
290       </>
291     );
292   }
293
294   blockUserCard() {
295     return (
296       <div>
297         {this.blockUserForm()}
298         {this.blockedUsersList()}
299       </div>
300     );
301   }
302
303   blockedUsersList() {
304     return (
305       <>
306         <h5>{i18n.t("blocked_users")}</h5>
307         <ul class="list-unstyled mb-0">
308           {this.state.personBlocks.map(pb => (
309             <li>
310               <span>
311                 <PersonListing person={pb.target} />
312                 <button
313                   className="btn btn-sm"
314                   onClick={linkEvent(
315                     { ctx: this, recipientId: pb.target.id },
316                     this.handleUnblockPerson
317                   )}
318                   data-tippy-content={i18n.t("unblock_user")}
319                 >
320                   <Icon icon="x" classes="icon-inline" />
321                 </button>
322               </span>
323             </li>
324           ))}
325         </ul>
326       </>
327     );
328   }
329
330   blockUserForm() {
331     return (
332       <div class="form-group row">
333         <label class="col-md-4 col-form-label" htmlFor="block-person-filter">
334           {i18n.t("block_user")}
335         </label>
336         <div class="col-md-8">
337           <select
338             class="form-control"
339             id="block-person-filter"
340             value={this.state.blockPersonId}
341           >
342             <option value="0">—</option>
343             {this.state.blockPerson && (
344               <option value={this.state.blockPerson.person.id}>
345                 {personSelectName(this.state.blockPerson)}
346               </option>
347             )}
348           </select>
349         </div>
350       </div>
351     );
352   }
353
354   blockCommunityCard() {
355     return (
356       <div>
357         {this.blockCommunityForm()}
358         {this.blockedCommunitiesList()}
359       </div>
360     );
361   }
362
363   blockedCommunitiesList() {
364     return (
365       <>
366         <h5>{i18n.t("blocked_communities")}</h5>
367         <ul class="list-unstyled mb-0">
368           {this.state.communityBlocks.map(cb => (
369             <li>
370               <span>
371                 <CommunityLink community={cb.community} />
372                 <button
373                   className="btn btn-sm"
374                   onClick={linkEvent(
375                     { ctx: this, communityId: cb.community.id },
376                     this.handleUnblockCommunity
377                   )}
378                   data-tippy-content={i18n.t("unblock_community")}
379                 >
380                   <Icon icon="x" classes="icon-inline" />
381                 </button>
382               </span>
383             </li>
384           ))}
385         </ul>
386       </>
387     );
388   }
389
390   blockCommunityForm() {
391     return (
392       <div class="form-group row">
393         <label class="col-md-4 col-form-label" htmlFor="block-community-filter">
394           {i18n.t("block_community")}
395         </label>
396         <div class="col-md-8">
397           <select
398             class="form-control"
399             id="block-community-filter"
400             value={this.state.blockCommunityId}
401           >
402             <option value="0">—</option>
403             {this.state.blockCommunity && (
404               <option value={this.state.blockCommunity.community.id}>
405                 {communitySelectName(this.state.blockCommunity)}
406               </option>
407             )}
408           </select>
409         </div>
410       </div>
411     );
412   }
413
414   saveUserSettingsHtmlForm() {
415     return (
416       <>
417         <h5>{i18n.t("settings")}</h5>
418         <form onSubmit={linkEvent(this, this.handleSaveSettingsSubmit)}>
419           <div class="form-group row">
420             <label class="col-lg-5 col-form-label" htmlFor="display-name">
421               {i18n.t("display_name")}
422             </label>
423             <div class="col-lg-7">
424               <input
425                 id="display-name"
426                 type="text"
427                 class="form-control"
428                 placeholder={i18n.t("optional")}
429                 value={this.state.saveUserSettingsForm.display_name}
430                 onInput={linkEvent(this, this.handleDisplayNameChange)}
431                 pattern="^(?!@)(.+)$"
432                 minLength={3}
433               />
434             </div>
435           </div>
436           <div class="form-group row">
437             <label class="col-lg-3 col-form-label" htmlFor="user-bio">
438               {i18n.t("bio")}
439             </label>
440             <div class="col-lg-9">
441               <MarkdownTextArea
442                 initialContent={this.state.saveUserSettingsForm.bio}
443                 onContentChange={this.handleBioChange}
444                 maxLength={300}
445                 hideNavigationWarnings
446               />
447             </div>
448           </div>
449           <div class="form-group row">
450             <label class="col-lg-3 col-form-label" htmlFor="user-email">
451               {i18n.t("email")}
452             </label>
453             <div class="col-lg-9">
454               <input
455                 type="email"
456                 id="user-email"
457                 class="form-control"
458                 placeholder={i18n.t("optional")}
459                 value={this.state.saveUserSettingsForm.email}
460                 onInput={linkEvent(this, this.handleEmailChange)}
461                 minLength={3}
462               />
463             </div>
464           </div>
465           <div class="form-group row">
466             <label class="col-lg-5 col-form-label" htmlFor="matrix-user-id">
467               <a href={elementUrl} rel="noopener">
468                 {i18n.t("matrix_user_id")}
469               </a>
470             </label>
471             <div class="col-lg-7">
472               <input
473                 id="matrix-user-id"
474                 type="text"
475                 class="form-control"
476                 placeholder="@user:example.com"
477                 value={this.state.saveUserSettingsForm.matrix_user_id}
478                 onInput={linkEvent(this, this.handleMatrixUserIdChange)}
479                 pattern="^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
480               />
481             </div>
482           </div>
483           <div class="form-group">
484             <label>{i18n.t("avatar")}</label>
485             <ImageUploadForm
486               uploadTitle={i18n.t("upload_avatar")}
487               imageSrc={this.state.saveUserSettingsForm.avatar}
488               onUpload={this.handleAvatarUpload}
489               onRemove={this.handleAvatarRemove}
490               rounded
491             />
492           </div>
493           <div class="form-group">
494             <label>{i18n.t("banner")}</label>
495             <ImageUploadForm
496               uploadTitle={i18n.t("upload_banner")}
497               imageSrc={this.state.saveUserSettingsForm.banner}
498               onUpload={this.handleBannerUpload}
499               onRemove={this.handleBannerRemove}
500             />
501           </div>
502           <div class="form-group">
503             <label htmlFor="user-language">{i18n.t("language")}</label>
504             <select
505               id="user-language"
506               value={this.state.saveUserSettingsForm.lang}
507               onChange={linkEvent(this, this.handleLangChange)}
508               class="ml-2 custom-select w-auto"
509             >
510               <option disabled aria-hidden="true">
511                 {i18n.t("language")}
512               </option>
513               <option value="browser">{i18n.t("browser_default")}</option>
514               <option disabled aria-hidden="true">
515                 â”€â”€
516               </option>
517               {languages.sort().map(lang => (
518                 <option value={lang.code}>
519                   {ISO6391.getNativeName(lang.code) || lang.code}
520                 </option>
521               ))}
522             </select>
523           </div>
524           <div class="form-group">
525             <label htmlFor="user-theme">{i18n.t("theme")}</label>
526             <select
527               id="user-theme"
528               value={this.state.saveUserSettingsForm.theme}
529               onChange={linkEvent(this, this.handleThemeChange)}
530               class="ml-2 custom-select w-auto"
531             >
532               <option disabled aria-hidden="true">
533                 {i18n.t("theme")}
534               </option>
535               <option value="browser">{i18n.t("browser_default")}</option>
536               {themes.map(theme => (
537                 <option value={theme}>{theme}</option>
538               ))}
539             </select>
540           </div>
541           <form className="form-group">
542             <label>
543               <div class="mr-2">{i18n.t("type")}</div>
544             </label>
545             <ListingTypeSelect
546               type_={
547                 Object.values(ListingType)[
548                   this.state.saveUserSettingsForm.default_listing_type
549                 ]
550               }
551               showLocal={showLocal(this.isoData)}
552               onChange={this.handleListingTypeChange}
553             />
554           </form>
555           <form className="form-group">
556             <label>
557               <div class="mr-2">{i18n.t("sort_type")}</div>
558             </label>
559             <SortSelect
560               sort={
561                 Object.values(SortType)[
562                   this.state.saveUserSettingsForm.default_sort_type
563                 ]
564               }
565               onChange={this.handleSortTypeChange}
566             />
567           </form>
568           {this.state.siteRes.site_view.site.enable_nsfw && (
569             <div class="form-group">
570               <div class="form-check">
571                 <input
572                   class="form-check-input"
573                   id="user-show-nsfw"
574                   type="checkbox"
575                   checked={this.state.saveUserSettingsForm.show_nsfw}
576                   onChange={linkEvent(this, this.handleShowNsfwChange)}
577                 />
578                 <label class="form-check-label" htmlFor="user-show-nsfw">
579                   {i18n.t("show_nsfw")}
580                 </label>
581               </div>
582             </div>
583           )}
584           <div class="form-group">
585             <div class="form-check">
586               <input
587                 class="form-check-input"
588                 id="user-show-scores"
589                 type="checkbox"
590                 checked={this.state.saveUserSettingsForm.show_scores}
591                 onChange={linkEvent(this, this.handleShowScoresChange)}
592               />
593               <label class="form-check-label" htmlFor="user-show-scores">
594                 {i18n.t("show_scores")}
595               </label>
596             </div>
597           </div>
598           <div class="form-group">
599             <div class="form-check">
600               <input
601                 class="form-check-input"
602                 id="user-show-avatars"
603                 type="checkbox"
604                 checked={this.state.saveUserSettingsForm.show_avatars}
605                 onChange={linkEvent(this, this.handleShowAvatarsChange)}
606               />
607               <label class="form-check-label" htmlFor="user-show-avatars">
608                 {i18n.t("show_avatars")}
609               </label>
610             </div>
611           </div>
612           <div class="form-group">
613             <div class="form-check">
614               <input
615                 class="form-check-input"
616                 id="user-bot-account"
617                 type="checkbox"
618                 checked={this.state.saveUserSettingsForm.bot_account}
619                 onChange={linkEvent(this, this.handleBotAccount)}
620               />
621               <label class="form-check-label" htmlFor="user-bot-account">
622                 {i18n.t("bot_account")}
623               </label>
624             </div>
625           </div>
626           <div class="form-group">
627             <div class="form-check">
628               <input
629                 class="form-check-input"
630                 id="user-show-bot-accounts"
631                 type="checkbox"
632                 checked={this.state.saveUserSettingsForm.show_bot_accounts}
633                 onChange={linkEvent(this, this.handleShowBotAccounts)}
634               />
635               <label class="form-check-label" htmlFor="user-show-bot-accounts">
636                 {i18n.t("show_bot_accounts")}
637               </label>
638             </div>
639           </div>
640           <div class="form-group">
641             <div class="form-check">
642               <input
643                 class="form-check-input"
644                 id="user-show-read-posts"
645                 type="checkbox"
646                 checked={this.state.saveUserSettingsForm.show_read_posts}
647                 onChange={linkEvent(this, this.handleReadPosts)}
648               />
649               <label class="form-check-label" htmlFor="user-show-read-posts">
650                 {i18n.t("show_read_posts")}
651               </label>
652             </div>
653           </div>
654           <div class="form-group">
655             <div class="form-check">
656               <input
657                 class="form-check-input"
658                 id="user-show-new-post-notifs"
659                 type="checkbox"
660                 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
661                 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
662               />
663               <label
664                 class="form-check-label"
665                 htmlFor="user-show-new-post-notifs"
666               >
667                 {i18n.t("show_new_post_notifs")}
668               </label>
669             </div>
670           </div>
671           <div class="form-group">
672             <div class="form-check">
673               <input
674                 class="form-check-input"
675                 id="user-send-notifications-to-email"
676                 type="checkbox"
677                 disabled={!this.state.saveUserSettingsForm.email}
678                 checked={
679                   this.state.saveUserSettingsForm.send_notifications_to_email
680                 }
681                 onChange={linkEvent(
682                   this,
683                   this.handleSendNotificationsToEmailChange
684                 )}
685               />
686               <label
687                 class="form-check-label"
688                 htmlFor="user-send-notifications-to-email"
689               >
690                 {i18n.t("send_notifications_to_email")}
691               </label>
692             </div>
693           </div>
694           <div class="form-group">
695             <button type="submit" class="btn btn-block btn-secondary mr-4">
696               {this.state.saveUserSettingsLoading ? (
697                 <Spinner />
698               ) : (
699                 capitalizeFirstLetter(i18n.t("save"))
700               )}
701             </button>
702           </div>
703           <hr />
704           <div class="form-group">
705             <button
706               class="btn btn-block btn-danger"
707               onClick={linkEvent(
708                 this,
709                 this.handleDeleteAccountShowConfirmToggle
710               )}
711             >
712               {i18n.t("delete_account")}
713             </button>
714             {this.state.deleteAccountShowConfirm && (
715               <>
716                 <div class="my-2 alert alert-danger" role="alert">
717                   {i18n.t("delete_account_confirm")}
718                 </div>
719                 <input
720                   type="password"
721                   value={this.state.deleteAccountForm.password}
722                   autoComplete="new-password"
723                   maxLength={60}
724                   onInput={linkEvent(
725                     this,
726                     this.handleDeleteAccountPasswordChange
727                   )}
728                   class="form-control my-2"
729                 />
730                 <button
731                   class="btn btn-danger mr-4"
732                   disabled={!this.state.deleteAccountForm.password}
733                   onClick={linkEvent(this, this.handleDeleteAccount)}
734                 >
735                   {this.state.deleteAccountLoading ? (
736                     <Spinner />
737                   ) : (
738                     capitalizeFirstLetter(i18n.t("delete"))
739                   )}
740                 </button>
741                 <button
742                   class="btn btn-secondary"
743                   onClick={linkEvent(
744                     this,
745                     this.handleDeleteAccountShowConfirmToggle
746                   )}
747                 >
748                   {i18n.t("cancel")}
749                 </button>
750               </>
751             )}
752           </div>
753         </form>
754       </>
755     );
756   }
757
758   setupBlockPersonChoices() {
759     if (isBrowser()) {
760       let selectId: any = document.getElementById("block-person-filter");
761       if (selectId) {
762         this.blockPersonChoices = new Choices(selectId, choicesConfig);
763         this.blockPersonChoices.passedElement.element.addEventListener(
764           "choice",
765           (e: any) => {
766             this.handleBlockPerson(Number(e.detail.choice.value));
767           },
768           false
769         );
770         this.blockPersonChoices.passedElement.element.addEventListener(
771           "search",
772           debounce(async (e: any) => {
773             let persons = (await fetchUsers(e.detail.value)).users;
774             let choices = persons.map(pvs => personToChoice(pvs));
775             this.blockPersonChoices.setChoices(choices, "value", "label", true);
776           }, 400),
777           false
778         );
779       }
780     }
781   }
782
783   setupBlockCommunityChoices() {
784     if (isBrowser()) {
785       let selectId: any = document.getElementById("block-community-filter");
786       if (selectId) {
787         this.blockCommunityChoices = new Choices(selectId, choicesConfig);
788         this.blockCommunityChoices.passedElement.element.addEventListener(
789           "choice",
790           (e: any) => {
791             this.handleBlockCommunity(Number(e.detail.choice.value));
792           },
793           false
794         );
795         this.blockCommunityChoices.passedElement.element.addEventListener(
796           "search",
797           debounce(async (e: any) => {
798             let communities = (await fetchCommunities(e.detail.value))
799               .communities;
800             let choices = communities.map(cv => communityToChoice(cv));
801             this.blockCommunityChoices.setChoices(
802               choices,
803               "value",
804               "label",
805               true
806             );
807           }, 400),
808           false
809         );
810       }
811     }
812   }
813
814   handleBlockPerson(personId: number) {
815     if (personId != 0) {
816       let blockUserForm: BlockPerson = {
817         person_id: personId,
818         block: true,
819         auth: authField(),
820       };
821       WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
822     }
823   }
824
825   handleUnblockPerson(i: { ctx: Settings; recipientId: number }) {
826     let blockUserForm: BlockPerson = {
827       person_id: i.recipientId,
828       block: false,
829       auth: authField(),
830     };
831     WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
832   }
833
834   handleBlockCommunity(community_id: number) {
835     if (community_id != 0) {
836       let blockCommunityForm: BlockCommunity = {
837         community_id,
838         block: true,
839         auth: authField(),
840       };
841       WebSocketService.Instance.send(
842         wsClient.blockCommunity(blockCommunityForm)
843       );
844     }
845   }
846
847   handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
848     let blockCommunityForm: BlockCommunity = {
849       community_id: i.communityId,
850       block: false,
851       auth: authField(),
852     };
853     WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));
854   }
855
856   handleShowNsfwChange(i: Settings, event: any) {
857     i.state.saveUserSettingsForm.show_nsfw = event.target.checked;
858     i.setState(i.state);
859   }
860
861   handleShowAvatarsChange(i: Settings, event: any) {
862     i.state.saveUserSettingsForm.show_avatars = event.target.checked;
863     UserService.Instance.myUserInfo.local_user_view.local_user.show_avatars =
864       event.target.checked; // Just for instant updates
865     i.setState(i.state);
866   }
867
868   handleBotAccount(i: Settings, event: any) {
869     i.state.saveUserSettingsForm.bot_account = event.target.checked;
870     i.setState(i.state);
871   }
872
873   handleShowBotAccounts(i: Settings, event: any) {
874     i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked;
875     i.setState(i.state);
876   }
877
878   handleReadPosts(i: Settings, event: any) {
879     i.state.saveUserSettingsForm.show_read_posts = event.target.checked;
880     i.setState(i.state);
881   }
882
883   handleShowNewPostNotifs(i: Settings, event: any) {
884     i.state.saveUserSettingsForm.show_new_post_notifs = event.target.checked;
885     i.setState(i.state);
886   }
887
888   handleShowScoresChange(i: Settings, event: any) {
889     i.state.saveUserSettingsForm.show_scores = event.target.checked;
890     UserService.Instance.myUserInfo.local_user_view.local_user.show_scores =
891       event.target.checked; // Just for instant updates
892     i.setState(i.state);
893   }
894
895   handleSendNotificationsToEmailChange(i: Settings, event: any) {
896     i.state.saveUserSettingsForm.send_notifications_to_email =
897       event.target.checked;
898     i.setState(i.state);
899   }
900
901   handleThemeChange(i: Settings, event: any) {
902     i.state.saveUserSettingsForm.theme = event.target.value;
903     setTheme(event.target.value, true);
904     i.setState(i.state);
905   }
906
907   handleLangChange(i: Settings, event: any) {
908     i.state.saveUserSettingsForm.lang = event.target.value;
909     i18n.changeLanguage(getLanguage(i.state.saveUserSettingsForm.lang));
910     i.setState(i.state);
911   }
912
913   handleSortTypeChange(val: SortType) {
914     this.state.saveUserSettingsForm.default_sort_type =
915       Object.keys(SortType).indexOf(val);
916     this.setState(this.state);
917   }
918
919   handleListingTypeChange(val: ListingType) {
920     this.state.saveUserSettingsForm.default_listing_type =
921       Object.keys(ListingType).indexOf(val);
922     this.setState(this.state);
923   }
924
925   handleEmailChange(i: Settings, event: any) {
926     i.state.saveUserSettingsForm.email = event.target.value;
927     i.setState(i.state);
928   }
929
930   handleBioChange(val: string) {
931     this.state.saveUserSettingsForm.bio = val;
932     this.setState(this.state);
933   }
934
935   handleAvatarUpload(url: string) {
936     this.state.saveUserSettingsForm.avatar = url;
937     this.setState(this.state);
938   }
939
940   handleAvatarRemove() {
941     this.state.saveUserSettingsForm.avatar = "";
942     this.setState(this.state);
943   }
944
945   handleBannerUpload(url: string) {
946     this.state.saveUserSettingsForm.banner = url;
947     this.setState(this.state);
948   }
949
950   handleBannerRemove() {
951     this.state.saveUserSettingsForm.banner = "";
952     this.setState(this.state);
953   }
954
955   handleDisplayNameChange(i: Settings, event: any) {
956     i.state.saveUserSettingsForm.display_name = event.target.value;
957     i.setState(i.state);
958   }
959
960   handleMatrixUserIdChange(i: Settings, event: any) {
961     i.state.saveUserSettingsForm.matrix_user_id = event.target.value;
962     if (
963       i.state.saveUserSettingsForm.matrix_user_id == "" &&
964       !UserService.Instance.myUserInfo.local_user_view.person.matrix_user_id
965     ) {
966       i.state.saveUserSettingsForm.matrix_user_id = undefined;
967     }
968     i.setState(i.state);
969   }
970
971   handleNewPasswordChange(i: Settings, event: any) {
972     i.state.changePasswordForm.new_password = event.target.value;
973     if (i.state.changePasswordForm.new_password == "") {
974       i.state.changePasswordForm.new_password = undefined;
975     }
976     i.setState(i.state);
977   }
978
979   handleNewPasswordVerifyChange(i: Settings, event: any) {
980     i.state.changePasswordForm.new_password_verify = event.target.value;
981     if (i.state.changePasswordForm.new_password_verify == "") {
982       i.state.changePasswordForm.new_password_verify = undefined;
983     }
984     i.setState(i.state);
985   }
986
987   handleOldPasswordChange(i: Settings, event: any) {
988     i.state.changePasswordForm.old_password = event.target.value;
989     if (i.state.changePasswordForm.old_password == "") {
990       i.state.changePasswordForm.old_password = undefined;
991     }
992     i.setState(i.state);
993   }
994
995   handleSaveSettingsSubmit(i: Settings, event: any) {
996     event.preventDefault();
997     i.state.saveUserSettingsLoading = true;
998     i.setState(i.state);
999
1000     WebSocketService.Instance.send(
1001       wsClient.saveUserSettings(i.state.saveUserSettingsForm)
1002     );
1003   }
1004
1005   handleChangePasswordSubmit(i: Settings, event: any) {
1006     event.preventDefault();
1007     i.state.changePasswordLoading = true;
1008     i.setState(i.state);
1009
1010     WebSocketService.Instance.send(
1011       wsClient.changePassword(i.state.changePasswordForm)
1012     );
1013   }
1014
1015   handleDeleteAccountShowConfirmToggle(i: Settings, event: any) {
1016     event.preventDefault();
1017     i.state.deleteAccountShowConfirm = !i.state.deleteAccountShowConfirm;
1018     i.setState(i.state);
1019   }
1020
1021   handleDeleteAccountPasswordChange(i: Settings, event: any) {
1022     i.state.deleteAccountForm.password = event.target.value;
1023     i.setState(i.state);
1024   }
1025
1026   handleLogoutClick(i: Settings) {
1027     UserService.Instance.logout();
1028     i.context.router.history.push("/");
1029   }
1030
1031   handleDeleteAccount(i: Settings, event: any) {
1032     event.preventDefault();
1033     i.state.deleteAccountLoading = true;
1034     i.setState(i.state);
1035
1036     WebSocketService.Instance.send(
1037       wsClient.deleteAccount(i.state.deleteAccountForm)
1038     );
1039   }
1040
1041   handleSwitchTab(i: { ctx: Settings; tab: string }) {
1042     i.ctx.setState({ currentTab: i.tab });
1043
1044     if (i.ctx.state.currentTab == "blocks") {
1045       i.ctx.setupBlockPersonChoices();
1046       i.ctx.setupBlockCommunityChoices();
1047     }
1048   }
1049
1050   setUserInfo() {
1051     let luv = UserService.Instance.myUserInfo.local_user_view;
1052     this.state.saveUserSettingsForm.show_nsfw = luv.local_user.show_nsfw;
1053     this.state.saveUserSettingsForm.theme = luv.local_user.theme
1054       ? luv.local_user.theme
1055       : "browser";
1056     this.state.saveUserSettingsForm.default_sort_type =
1057       luv.local_user.default_sort_type;
1058     this.state.saveUserSettingsForm.default_listing_type =
1059       luv.local_user.default_listing_type;
1060     this.state.saveUserSettingsForm.lang = luv.local_user.lang;
1061     this.state.saveUserSettingsForm.avatar = luv.person.avatar;
1062     this.state.saveUserSettingsForm.banner = luv.person.banner;
1063     this.state.saveUserSettingsForm.display_name = luv.person.display_name;
1064     this.state.saveUserSettingsForm.show_avatars = luv.local_user.show_avatars;
1065     this.state.saveUserSettingsForm.bot_account = luv.person.bot_account;
1066     this.state.saveUserSettingsForm.show_bot_accounts =
1067       luv.local_user.show_bot_accounts;
1068     this.state.saveUserSettingsForm.show_scores = luv.local_user.show_scores;
1069     this.state.saveUserSettingsForm.show_read_posts =
1070       luv.local_user.show_read_posts;
1071     this.state.saveUserSettingsForm.show_new_post_notifs =
1072       luv.local_user.show_new_post_notifs;
1073     this.state.saveUserSettingsForm.email = luv.local_user.email;
1074     this.state.saveUserSettingsForm.bio = luv.person.bio;
1075     this.state.saveUserSettingsForm.send_notifications_to_email =
1076       luv.local_user.send_notifications_to_email;
1077     this.state.saveUserSettingsForm.matrix_user_id = luv.person.matrix_user_id;
1078     this.state.personBlocks = UserService.Instance.myUserInfo.person_blocks;
1079     this.state.communityBlocks =
1080       UserService.Instance.myUserInfo.community_blocks;
1081   }
1082
1083   parseMessage(msg: any) {
1084     let op = wsUserOp(msg);
1085     console.log(msg);
1086     if (msg.error) {
1087       toast(i18n.t(msg.error), "danger");
1088       return;
1089     } else if (op == UserOperation.SaveUserSettings) {
1090       let data = wsJsonToRes<LoginResponse>(msg).data;
1091       UserService.Instance.login(data);
1092       this.state.saveUserSettingsLoading = false;
1093       this.setState(this.state);
1094
1095       window.scrollTo(0, 0);
1096     } else if (op == UserOperation.ChangePassword) {
1097       let data = wsJsonToRes<LoginResponse>(msg).data;
1098       UserService.Instance.login(data);
1099       this.state.changePasswordLoading = false;
1100       this.setState(this.state);
1101       window.scrollTo(0, 0);
1102       toast(i18n.t("password_changed"));
1103     } else if (op == UserOperation.DeleteAccount) {
1104       this.setState({
1105         deleteAccountLoading: false,
1106         deleteAccountShowConfirm: false,
1107       });
1108       UserService.Instance.logout();
1109       window.location.href = "/";
1110     } else if (op == UserOperation.BlockPerson) {
1111       let data = wsJsonToRes<BlockPersonResponse>(msg).data;
1112       this.setState({ personBlocks: updatePersonBlock(data) });
1113     } else if (op == UserOperation.BlockCommunity) {
1114       let data = wsJsonToRes<BlockCommunityResponse>(msg).data;
1115       this.setState({ communityBlocks: updateCommunityBlock(data) });
1116     }
1117   }
1118 }