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