]> Untitled Git - lemmy-ui.git/blob - src/shared/components/person/settings.tsx
Trying to catch promise and json parse errors. #489 (#490)
[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-sm-5 col-form-label" htmlFor="user-password">
231               {i18n.t("new_password")}
232             </label>
233             <div class="col-sm-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-sm-5 col-form-label"
248               htmlFor="user-verify-password"
249             >
250               {i18n.t("verify_password")}
251             </label>
252             <div class="col-sm-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-sm-5 col-form-label" htmlFor="user-old-password">
266               {i18n.t("old_password")}
267             </label>
268             <div class="col-sm-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-sm-5 col-form-label" htmlFor="display-name">
421               {i18n.t("display_name")}
422             </label>
423             <div class="col-sm-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-sm-3 col-form-label" htmlFor="user-bio">
438               {i18n.t("bio")}
439             </label>
440             <div class="col-sm-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-sm-3 col-form-label" htmlFor="user-email">
451               {i18n.t("email")}
452             </label>
453             <div class="col-sm-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-sm-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-sm-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 row">
484             <label class="col-sm-3">{i18n.t("avatar")}</label>
485             <div class="col-sm-9">
486               <ImageUploadForm
487                 uploadTitle={i18n.t("upload_avatar")}
488                 imageSrc={this.state.saveUserSettingsForm.avatar}
489                 onUpload={this.handleAvatarUpload}
490                 onRemove={this.handleAvatarRemove}
491                 rounded
492               />
493             </div>
494           </div>
495           <div class="form-group row">
496             <label class="col-sm-3">{i18n.t("banner")}</label>
497             <div class="col-sm-9">
498               <ImageUploadForm
499                 uploadTitle={i18n.t("upload_banner")}
500                 imageSrc={this.state.saveUserSettingsForm.banner}
501                 onUpload={this.handleBannerUpload}
502                 onRemove={this.handleBannerRemove}
503               />
504             </div>
505           </div>
506           <div class="form-group row">
507             <label class="col-sm-3" htmlFor="user-language">
508               {i18n.t("language")}
509             </label>
510             <div class="col-sm-9">
511               <select
512                 id="user-language"
513                 value={this.state.saveUserSettingsForm.lang}
514                 onChange={linkEvent(this, this.handleLangChange)}
515                 class="custom-select w-auto"
516               >
517                 <option disabled aria-hidden="true">
518                   {i18n.t("language")}
519                 </option>
520                 <option value="browser">{i18n.t("browser_default")}</option>
521                 <option disabled aria-hidden="true">
522                   â”€â”€
523                 </option>
524                 {languages.sort().map(lang => (
525                   <option value={lang.code}>
526                     {ISO6391.getNativeName(lang.code) || lang.code}
527                   </option>
528                 ))}
529               </select>
530             </div>
531           </div>
532           <div class="form-group row">
533             <label class="col-sm-3" htmlFor="user-theme">
534               {i18n.t("theme")}
535             </label>
536             <div class="col-sm-9">
537               <select
538                 id="user-theme"
539                 value={this.state.saveUserSettingsForm.theme}
540                 onChange={linkEvent(this, this.handleThemeChange)}
541                 class="custom-select w-auto"
542               >
543                 <option disabled aria-hidden="true">
544                   {i18n.t("theme")}
545                 </option>
546                 <option value="browser">{i18n.t("browser_default")}</option>
547                 {themes.map(theme => (
548                   <option value={theme}>{theme}</option>
549                 ))}
550               </select>
551             </div>
552           </div>
553           <form className="form-group row">
554             <label class="col-sm-3">{i18n.t("type")}</label>
555             <div class="col-sm-9">
556               <ListingTypeSelect
557                 type_={
558                   Object.values(ListingType)[
559                     this.state.saveUserSettingsForm.default_listing_type
560                   ]
561                 }
562                 showLocal={showLocal(this.isoData)}
563                 onChange={this.handleListingTypeChange}
564               />
565             </div>
566           </form>
567           <form className="form-group row">
568             <label class="col-sm-3">{i18n.t("sort_type")}</label>
569             <div class="col-sm-9">
570               <SortSelect
571                 sort={
572                   Object.values(SortType)[
573                     this.state.saveUserSettingsForm.default_sort_type
574                   ]
575                 }
576                 onChange={this.handleSortTypeChange}
577               />
578             </div>
579           </form>
580           {this.state.siteRes.site_view.site.enable_nsfw && (
581             <div class="form-group">
582               <div class="form-check">
583                 <input
584                   class="form-check-input"
585                   id="user-show-nsfw"
586                   type="checkbox"
587                   checked={this.state.saveUserSettingsForm.show_nsfw}
588                   onChange={linkEvent(this, this.handleShowNsfwChange)}
589                 />
590                 <label class="form-check-label" htmlFor="user-show-nsfw">
591                   {i18n.t("show_nsfw")}
592                 </label>
593               </div>
594             </div>
595           )}
596           <div class="form-group">
597             <div class="form-check">
598               <input
599                 class="form-check-input"
600                 id="user-show-scores"
601                 type="checkbox"
602                 checked={this.state.saveUserSettingsForm.show_scores}
603                 onChange={linkEvent(this, this.handleShowScoresChange)}
604               />
605               <label class="form-check-label" htmlFor="user-show-scores">
606                 {i18n.t("show_scores")}
607               </label>
608             </div>
609           </div>
610           <div class="form-group">
611             <div class="form-check">
612               <input
613                 class="form-check-input"
614                 id="user-show-avatars"
615                 type="checkbox"
616                 checked={this.state.saveUserSettingsForm.show_avatars}
617                 onChange={linkEvent(this, this.handleShowAvatarsChange)}
618               />
619               <label class="form-check-label" htmlFor="user-show-avatars">
620                 {i18n.t("show_avatars")}
621               </label>
622             </div>
623           </div>
624           <div class="form-group">
625             <div class="form-check">
626               <input
627                 class="form-check-input"
628                 id="user-bot-account"
629                 type="checkbox"
630                 checked={this.state.saveUserSettingsForm.bot_account}
631                 onChange={linkEvent(this, this.handleBotAccount)}
632               />
633               <label class="form-check-label" htmlFor="user-bot-account">
634                 {i18n.t("bot_account")}
635               </label>
636             </div>
637           </div>
638           <div class="form-group">
639             <div class="form-check">
640               <input
641                 class="form-check-input"
642                 id="user-show-bot-accounts"
643                 type="checkbox"
644                 checked={this.state.saveUserSettingsForm.show_bot_accounts}
645                 onChange={linkEvent(this, this.handleShowBotAccounts)}
646               />
647               <label class="form-check-label" htmlFor="user-show-bot-accounts">
648                 {i18n.t("show_bot_accounts")}
649               </label>
650             </div>
651           </div>
652           <div class="form-group">
653             <div class="form-check">
654               <input
655                 class="form-check-input"
656                 id="user-show-read-posts"
657                 type="checkbox"
658                 checked={this.state.saveUserSettingsForm.show_read_posts}
659                 onChange={linkEvent(this, this.handleReadPosts)}
660               />
661               <label class="form-check-label" htmlFor="user-show-read-posts">
662                 {i18n.t("show_read_posts")}
663               </label>
664             </div>
665           </div>
666           <div class="form-group">
667             <div class="form-check">
668               <input
669                 class="form-check-input"
670                 id="user-show-new-post-notifs"
671                 type="checkbox"
672                 checked={this.state.saveUserSettingsForm.show_new_post_notifs}
673                 onChange={linkEvent(this, this.handleShowNewPostNotifs)}
674               />
675               <label
676                 class="form-check-label"
677                 htmlFor="user-show-new-post-notifs"
678               >
679                 {i18n.t("show_new_post_notifs")}
680               </label>
681             </div>
682           </div>
683           <div class="form-group">
684             <div class="form-check">
685               <input
686                 class="form-check-input"
687                 id="user-send-notifications-to-email"
688                 type="checkbox"
689                 disabled={!this.state.saveUserSettingsForm.email}
690                 checked={
691                   this.state.saveUserSettingsForm.send_notifications_to_email
692                 }
693                 onChange={linkEvent(
694                   this,
695                   this.handleSendNotificationsToEmailChange
696                 )}
697               />
698               <label
699                 class="form-check-label"
700                 htmlFor="user-send-notifications-to-email"
701               >
702                 {i18n.t("send_notifications_to_email")}
703               </label>
704             </div>
705           </div>
706           <div class="form-group">
707             <button type="submit" class="btn btn-block btn-secondary mr-4">
708               {this.state.saveUserSettingsLoading ? (
709                 <Spinner />
710               ) : (
711                 capitalizeFirstLetter(i18n.t("save"))
712               )}
713             </button>
714           </div>
715           <hr />
716           <div class="form-group">
717             <button
718               class="btn btn-block btn-danger"
719               onClick={linkEvent(
720                 this,
721                 this.handleDeleteAccountShowConfirmToggle
722               )}
723             >
724               {i18n.t("delete_account")}
725             </button>
726             {this.state.deleteAccountShowConfirm && (
727               <>
728                 <div class="my-2 alert alert-danger" role="alert">
729                   {i18n.t("delete_account_confirm")}
730                 </div>
731                 <input
732                   type="password"
733                   value={this.state.deleteAccountForm.password}
734                   autoComplete="new-password"
735                   maxLength={60}
736                   onInput={linkEvent(
737                     this,
738                     this.handleDeleteAccountPasswordChange
739                   )}
740                   class="form-control my-2"
741                 />
742                 <button
743                   class="btn btn-danger mr-4"
744                   disabled={!this.state.deleteAccountForm.password}
745                   onClick={linkEvent(this, this.handleDeleteAccount)}
746                 >
747                   {this.state.deleteAccountLoading ? (
748                     <Spinner />
749                   ) : (
750                     capitalizeFirstLetter(i18n.t("delete"))
751                   )}
752                 </button>
753                 <button
754                   class="btn btn-secondary"
755                   onClick={linkEvent(
756                     this,
757                     this.handleDeleteAccountShowConfirmToggle
758                   )}
759                 >
760                   {i18n.t("cancel")}
761                 </button>
762               </>
763             )}
764           </div>
765         </form>
766       </>
767     );
768   }
769
770   setupBlockPersonChoices() {
771     if (isBrowser()) {
772       let selectId: any = document.getElementById("block-person-filter");
773       if (selectId) {
774         this.blockPersonChoices = new Choices(selectId, choicesConfig);
775         this.blockPersonChoices.passedElement.element.addEventListener(
776           "choice",
777           (e: any) => {
778             this.handleBlockPerson(Number(e.detail.choice.value));
779           },
780           false
781         );
782         this.blockPersonChoices.passedElement.element.addEventListener(
783           "search",
784           debounce(async (e: any) => {
785             try {
786               let persons = (await fetchUsers(e.detail.value)).users;
787               let choices = persons.map(pvs => personToChoice(pvs));
788               this.blockPersonChoices.setChoices(
789                 choices,
790                 "value",
791                 "label",
792                 true
793               );
794             } catch (err) {
795               console.log(err);
796             }
797           }, 400),
798           false
799         );
800       }
801     }
802   }
803
804   setupBlockCommunityChoices() {
805     if (isBrowser()) {
806       let selectId: any = document.getElementById("block-community-filter");
807       if (selectId) {
808         this.blockCommunityChoices = new Choices(selectId, choicesConfig);
809         this.blockCommunityChoices.passedElement.element.addEventListener(
810           "choice",
811           (e: any) => {
812             this.handleBlockCommunity(Number(e.detail.choice.value));
813           },
814           false
815         );
816         this.blockCommunityChoices.passedElement.element.addEventListener(
817           "search",
818           debounce(async (e: any) => {
819             try {
820               let communities = (await fetchCommunities(e.detail.value))
821                 .communities;
822               let choices = communities.map(cv => communityToChoice(cv));
823               this.blockCommunityChoices.setChoices(
824                 choices,
825                 "value",
826                 "label",
827                 true
828               );
829             } catch (err) {
830               console.log(err);
831             }
832           }, 400),
833           false
834         );
835       }
836     }
837   }
838
839   handleBlockPerson(personId: number) {
840     if (personId != 0) {
841       let blockUserForm: BlockPerson = {
842         person_id: personId,
843         block: true,
844         auth: authField(),
845       };
846       WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
847     }
848   }
849
850   handleUnblockPerson(i: { ctx: Settings; recipientId: number }) {
851     let blockUserForm: BlockPerson = {
852       person_id: i.recipientId,
853       block: false,
854       auth: authField(),
855     };
856     WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
857   }
858
859   handleBlockCommunity(community_id: number) {
860     if (community_id != 0) {
861       let blockCommunityForm: BlockCommunity = {
862         community_id,
863         block: true,
864         auth: authField(),
865       };
866       WebSocketService.Instance.send(
867         wsClient.blockCommunity(blockCommunityForm)
868       );
869     }
870   }
871
872   handleUnblockCommunity(i: { ctx: Settings; communityId: number }) {
873     let blockCommunityForm: BlockCommunity = {
874       community_id: i.communityId,
875       block: false,
876       auth: authField(),
877     };
878     WebSocketService.Instance.send(wsClient.blockCommunity(blockCommunityForm));
879   }
880
881   handleShowNsfwChange(i: Settings, event: any) {
882     i.state.saveUserSettingsForm.show_nsfw = event.target.checked;
883     i.setState(i.state);
884   }
885
886   handleShowAvatarsChange(i: Settings, event: any) {
887     i.state.saveUserSettingsForm.show_avatars = event.target.checked;
888     UserService.Instance.myUserInfo.local_user_view.local_user.show_avatars =
889       event.target.checked; // Just for instant updates
890     i.setState(i.state);
891   }
892
893   handleBotAccount(i: Settings, event: any) {
894     i.state.saveUserSettingsForm.bot_account = event.target.checked;
895     i.setState(i.state);
896   }
897
898   handleShowBotAccounts(i: Settings, event: any) {
899     i.state.saveUserSettingsForm.show_bot_accounts = event.target.checked;
900     i.setState(i.state);
901   }
902
903   handleReadPosts(i: Settings, event: any) {
904     i.state.saveUserSettingsForm.show_read_posts = event.target.checked;
905     i.setState(i.state);
906   }
907
908   handleShowNewPostNotifs(i: Settings, event: any) {
909     i.state.saveUserSettingsForm.show_new_post_notifs = event.target.checked;
910     i.setState(i.state);
911   }
912
913   handleShowScoresChange(i: Settings, event: any) {
914     i.state.saveUserSettingsForm.show_scores = event.target.checked;
915     UserService.Instance.myUserInfo.local_user_view.local_user.show_scores =
916       event.target.checked; // Just for instant updates
917     i.setState(i.state);
918   }
919
920   handleSendNotificationsToEmailChange(i: Settings, event: any) {
921     i.state.saveUserSettingsForm.send_notifications_to_email =
922       event.target.checked;
923     i.setState(i.state);
924   }
925
926   handleThemeChange(i: Settings, event: any) {
927     i.state.saveUserSettingsForm.theme = event.target.value;
928     setTheme(event.target.value, true);
929     i.setState(i.state);
930   }
931
932   handleLangChange(i: Settings, event: any) {
933     i.state.saveUserSettingsForm.lang = event.target.value;
934     i18n.changeLanguage(getLanguage(i.state.saveUserSettingsForm.lang));
935     i.setState(i.state);
936   }
937
938   handleSortTypeChange(val: SortType) {
939     this.state.saveUserSettingsForm.default_sort_type =
940       Object.keys(SortType).indexOf(val);
941     this.setState(this.state);
942   }
943
944   handleListingTypeChange(val: ListingType) {
945     this.state.saveUserSettingsForm.default_listing_type =
946       Object.keys(ListingType).indexOf(val);
947     this.setState(this.state);
948   }
949
950   handleEmailChange(i: Settings, event: any) {
951     i.state.saveUserSettingsForm.email = event.target.value;
952     i.setState(i.state);
953   }
954
955   handleBioChange(val: string) {
956     this.state.saveUserSettingsForm.bio = val;
957     this.setState(this.state);
958   }
959
960   handleAvatarUpload(url: string) {
961     this.state.saveUserSettingsForm.avatar = url;
962     this.setState(this.state);
963   }
964
965   handleAvatarRemove() {
966     this.state.saveUserSettingsForm.avatar = "";
967     this.setState(this.state);
968   }
969
970   handleBannerUpload(url: string) {
971     this.state.saveUserSettingsForm.banner = url;
972     this.setState(this.state);
973   }
974
975   handleBannerRemove() {
976     this.state.saveUserSettingsForm.banner = "";
977     this.setState(this.state);
978   }
979
980   handleDisplayNameChange(i: Settings, event: any) {
981     i.state.saveUserSettingsForm.display_name = event.target.value;
982     i.setState(i.state);
983   }
984
985   handleMatrixUserIdChange(i: Settings, event: any) {
986     i.state.saveUserSettingsForm.matrix_user_id = event.target.value;
987     if (
988       i.state.saveUserSettingsForm.matrix_user_id == "" &&
989       !UserService.Instance.myUserInfo.local_user_view.person.matrix_user_id
990     ) {
991       i.state.saveUserSettingsForm.matrix_user_id = undefined;
992     }
993     i.setState(i.state);
994   }
995
996   handleNewPasswordChange(i: Settings, event: any) {
997     i.state.changePasswordForm.new_password = event.target.value;
998     if (i.state.changePasswordForm.new_password == "") {
999       i.state.changePasswordForm.new_password = undefined;
1000     }
1001     i.setState(i.state);
1002   }
1003
1004   handleNewPasswordVerifyChange(i: Settings, event: any) {
1005     i.state.changePasswordForm.new_password_verify = event.target.value;
1006     if (i.state.changePasswordForm.new_password_verify == "") {
1007       i.state.changePasswordForm.new_password_verify = undefined;
1008     }
1009     i.setState(i.state);
1010   }
1011
1012   handleOldPasswordChange(i: Settings, event: any) {
1013     i.state.changePasswordForm.old_password = event.target.value;
1014     if (i.state.changePasswordForm.old_password == "") {
1015       i.state.changePasswordForm.old_password = undefined;
1016     }
1017     i.setState(i.state);
1018   }
1019
1020   handleSaveSettingsSubmit(i: Settings, event: any) {
1021     event.preventDefault();
1022     i.state.saveUserSettingsLoading = true;
1023     i.setState(i.state);
1024
1025     WebSocketService.Instance.send(
1026       wsClient.saveUserSettings(i.state.saveUserSettingsForm)
1027     );
1028   }
1029
1030   handleChangePasswordSubmit(i: Settings, event: any) {
1031     event.preventDefault();
1032     i.state.changePasswordLoading = true;
1033     i.setState(i.state);
1034
1035     WebSocketService.Instance.send(
1036       wsClient.changePassword(i.state.changePasswordForm)
1037     );
1038   }
1039
1040   handleDeleteAccountShowConfirmToggle(i: Settings, event: any) {
1041     event.preventDefault();
1042     i.state.deleteAccountShowConfirm = !i.state.deleteAccountShowConfirm;
1043     i.setState(i.state);
1044   }
1045
1046   handleDeleteAccountPasswordChange(i: Settings, event: any) {
1047     i.state.deleteAccountForm.password = event.target.value;
1048     i.setState(i.state);
1049   }
1050
1051   handleDeleteAccount(i: Settings, event: any) {
1052     event.preventDefault();
1053     i.state.deleteAccountLoading = true;
1054     i.setState(i.state);
1055
1056     WebSocketService.Instance.send(
1057       wsClient.deleteAccount(i.state.deleteAccountForm)
1058     );
1059   }
1060
1061   handleSwitchTab(i: { ctx: Settings; tab: string }) {
1062     i.ctx.setState({ currentTab: i.tab });
1063
1064     if (i.ctx.state.currentTab == "blocks") {
1065       i.ctx.setupBlockPersonChoices();
1066       i.ctx.setupBlockCommunityChoices();
1067     }
1068   }
1069
1070   setUserInfo() {
1071     let luv = UserService.Instance.myUserInfo.local_user_view;
1072     this.state.saveUserSettingsForm.show_nsfw = luv.local_user.show_nsfw;
1073     this.state.saveUserSettingsForm.theme = luv.local_user.theme
1074       ? luv.local_user.theme
1075       : "browser";
1076     this.state.saveUserSettingsForm.default_sort_type =
1077       luv.local_user.default_sort_type;
1078     this.state.saveUserSettingsForm.default_listing_type =
1079       luv.local_user.default_listing_type;
1080     this.state.saveUserSettingsForm.lang = luv.local_user.lang;
1081     this.state.saveUserSettingsForm.avatar = luv.person.avatar;
1082     this.state.saveUserSettingsForm.banner = luv.person.banner;
1083     this.state.saveUserSettingsForm.display_name = luv.person.display_name;
1084     this.state.saveUserSettingsForm.show_avatars = luv.local_user.show_avatars;
1085     this.state.saveUserSettingsForm.bot_account = luv.person.bot_account;
1086     this.state.saveUserSettingsForm.show_bot_accounts =
1087       luv.local_user.show_bot_accounts;
1088     this.state.saveUserSettingsForm.show_scores = luv.local_user.show_scores;
1089     this.state.saveUserSettingsForm.show_read_posts =
1090       luv.local_user.show_read_posts;
1091     this.state.saveUserSettingsForm.show_new_post_notifs =
1092       luv.local_user.show_new_post_notifs;
1093     this.state.saveUserSettingsForm.email = luv.local_user.email;
1094     this.state.saveUserSettingsForm.bio = luv.person.bio;
1095     this.state.saveUserSettingsForm.send_notifications_to_email =
1096       luv.local_user.send_notifications_to_email;
1097     this.state.saveUserSettingsForm.matrix_user_id = luv.person.matrix_user_id;
1098     this.state.personBlocks = UserService.Instance.myUserInfo.person_blocks;
1099     this.state.communityBlocks =
1100       UserService.Instance.myUserInfo.community_blocks;
1101   }
1102
1103   parseMessage(msg: any) {
1104     let op = wsUserOp(msg);
1105     console.log(msg);
1106     if (msg.error) {
1107       toast(i18n.t(msg.error), "danger");
1108       return;
1109     } else if (op == UserOperation.SaveUserSettings) {
1110       let data = wsJsonToRes<LoginResponse>(msg).data;
1111       UserService.Instance.login(data);
1112       this.state.saveUserSettingsLoading = false;
1113       this.setState(this.state);
1114
1115       window.scrollTo(0, 0);
1116     } else if (op == UserOperation.ChangePassword) {
1117       let data = wsJsonToRes<LoginResponse>(msg).data;
1118       UserService.Instance.login(data);
1119       this.state.changePasswordLoading = false;
1120       this.setState(this.state);
1121       window.scrollTo(0, 0);
1122       toast(i18n.t("password_changed"));
1123     } else if (op == UserOperation.DeleteAccount) {
1124       this.setState({
1125         deleteAccountLoading: false,
1126         deleteAccountShowConfirm: false,
1127       });
1128       UserService.Instance.logout();
1129       window.location.href = "/";
1130       location.reload();
1131     } else if (op == UserOperation.BlockPerson) {
1132       let data = wsJsonToRes<BlockPersonResponse>(msg).data;
1133       this.setState({ personBlocks: updatePersonBlock(data) });
1134     } else if (op == UserOperation.BlockCommunity) {
1135       let data = wsJsonToRes<BlockCommunityResponse>(msg).data;
1136       this.setState({ communityBlocks: updateCommunityBlock(data) });
1137     }
1138   }
1139 }