From b1a7a679f011e11bb521efadf9b33a80a0b5b4a7 Mon Sep 17 00:00:00 2001 From: abias <abias1122@gmail.com> Date: Sat, 20 May 2023 15:39:12 -0400 Subject: [PATCH 1/6] Refactor tabs into reuseable component --- src/shared/components/common/tabs.tsx | 54 ++++++++ src/shared/components/home/admin-settings.tsx | 124 +++++++----------- .../components/home/rate-limit-form.tsx | 11 ++ src/shared/components/person/settings.tsx | 61 ++++----- 4 files changed, 134 insertions(+), 116 deletions(-) create mode 100644 src/shared/components/common/tabs.tsx create mode 100644 src/shared/components/home/rate-limit-form.tsx diff --git a/src/shared/components/common/tabs.tsx b/src/shared/components/common/tabs.tsx new file mode 100644 index 00000000..36e1a015 --- /dev/null +++ b/src/shared/components/common/tabs.tsx @@ -0,0 +1,54 @@ +import { Component, InfernoNode, linkEvent } from "inferno"; + +interface TabItem { + key: string; + getNode: () => InfernoNode; + label: string; +} + +interface TabsProps { + tabs: TabItem[]; +} + +interface TabsState { + currentTab: string; +} + +function handleSwitchTab({ ctx, tab }: { ctx: Tabs; tab: string }) { + console.log(tab); + ctx.setState({ currentTab: tab }); +} + +export default class Tabs extends Component<TabsProps, TabsState> { + constructor(props: TabsProps, context) { + super(props, context); + + this.state = { + currentTab: props.tabs.length > 0 ? props.tabs[0].key : "", + }; + } + + render() { + return ( + <div> + <ul className="nav nav-tabs mb-2"> + {this.props.tabs.map(({ key, label }) => ( + <li key={key} className="nav-item"> + <button + className={`nav-link btn${ + this.state?.currentTab === key ? " active" : "" + }`} + onClick={linkEvent({ ctx: this, tab: key }, handleSwitchTab)} + > + {label} + </button> + </li> + ))} + </ul> + {this.props.tabs + .find(tab => tab.key === this.state?.currentTab) + ?.getNode()} + </div> + ); + } +} diff --git a/src/shared/components/home/admin-settings.tsx b/src/shared/components/home/admin-settings.tsx index ab897fe1..3a2238ba 100644 --- a/src/shared/components/home/admin-settings.tsx +++ b/src/shared/components/home/admin-settings.tsx @@ -28,6 +28,7 @@ import { } from "../../utils"; import { HtmlTags } from "../common/html-tags"; import { Spinner } from "../common/icon"; +import Tabs from "../common/tabs"; import { PersonListing } from "../person/person-listing"; import { EmojiForm } from "./emojis-form"; import { SiteForm } from "./site-form"; @@ -39,7 +40,6 @@ interface AdminSettingsState { banned: PersonView[]; loading: boolean; leaveAdminTeamLoading: boolean; - currentTab: string; } export class AdminSettings extends Component<any, AdminSettingsState> { @@ -51,7 +51,6 @@ export class AdminSettings extends Component<any, AdminSettingsState> { banned: [], loading: true, leaveAdminTeamLoading: false, - currentTab: "site", }; constructor(props: any, context: any) { @@ -119,83 +118,56 @@ export class AdminSettings extends Component<any, AdminSettingsState> { render() { return ( <div className="container-lg"> + <HtmlTags + title={this.documentTitle} + path={this.context.router.route.match.url} + /> {this.state.loading ? ( <h5> <Spinner large /> </h5> ) : ( - <div> - <HtmlTags - title={this.documentTitle} - path={this.context.router.route.match.url} - /> - <ul className="nav nav-tabs mb-2"> - <li className="nav-item"> - <button - className={`nav-link btn ${ - this.state.currentTab == "site" && "active" - }`} - onClick={linkEvent( - { ctx: this, tab: "site" }, - this.handleSwitchTab - )} - > - {i18n.t("site")} - </button> - </li> - <li className="nav-item"> - <button - className={`nav-link btn ${ - this.state.currentTab == "taglines" && "active" - }`} - onClick={linkEvent( - { ctx: this, tab: "taglines" }, - this.handleSwitchTab - )} - > - {i18n.t("taglines")} - </button> - </li> - <li className="nav-item"> - <button - className={`nav-link btn ${ - this.state.currentTab == "emojis" && "active" - }`} - onClick={linkEvent( - { ctx: this, tab: "emojis" }, - this.handleSwitchTab - )} - > - {i18n.t("emojis")} - </button> - </li> - </ul> - {this.state.currentTab == "site" && ( - <div className="row"> - <div className="col-12 col-md-6"> - <SiteForm - siteRes={this.state.siteRes} - instancesRes={this.state.instancesRes} - showLocal={showLocal(this.isoData)} - /> - </div> - <div className="col-12 col-md-6"> - {this.admins()} - {this.bannedUsers()} - </div> - </div> - )} - {this.state.currentTab == "taglines" && ( - <div className="row"> - <TaglineForm siteRes={this.state.siteRes}></TaglineForm> - </div> - )} - {this.state.currentTab == "emojis" && ( - <div className="row"> - <EmojiForm></EmojiForm> - </div> - )} - </div> + <Tabs + tabs={[ + { + key: "site", + label: i18n.t("site"), + getNode: () => ( + <div className="row"> + <div className="col-12 col-md-6"> + <SiteForm + siteRes={this.state.siteRes} + instancesRes={this.state.instancesRes} + showLocal={showLocal(this.isoData)} + /> + </div> + <div className="col-12 col-md-6"> + {this.admins()} + {this.bannedUsers()} + </div> + </div> + ), + }, + { + key: "taglines", + label: i18n.t("taglines"), + getNode: () => ( + <div className="row"> + <TaglineForm siteRes={this.state.siteRes} /> + </div> + ), + }, + { + key: "emojis", + label: i18n.t("emojis"), + getNode: () => ( + <div className="row"> + <EmojiForm /> + </div> + ), + }, + ]} + /> )} </div> ); @@ -247,10 +219,6 @@ export class AdminSettings extends Component<any, AdminSettingsState> { ); } - handleSwitchTab(i: { ctx: AdminSettings; tab: string }) { - i.ctx.setState({ currentTab: i.tab }); - } - handleLeaveAdminTeam(i: AdminSettings) { let auth = myAuth(); if (auth) { diff --git a/src/shared/components/home/rate-limit-form.tsx b/src/shared/components/home/rate-limit-form.tsx new file mode 100644 index 00000000..b99c2630 --- /dev/null +++ b/src/shared/components/home/rate-limit-form.tsx @@ -0,0 +1,11 @@ +import { Component } from "inferno"; + +export default class RateLimitForm extends Component { + constructor(props, context) { + super(props, context); + } + + render() { + return <></>; + } +} diff --git a/src/shared/components/person/settings.tsx b/src/shared/components/person/settings.tsx index 95b25901..f6278f7c 100644 --- a/src/shared/components/person/settings.tsx +++ b/src/shared/components/person/settings.tsx @@ -54,6 +54,7 @@ import { ListingTypeSelect } from "../common/listing-type-select"; import { MarkdownTextArea } from "../common/markdown-textarea"; import { SearchableSelect } from "../common/searchable-select"; import { SortSelect } from "../common/sort-select"; +import Tabs from "../common/tabs"; import { CommunityLink } from "../community/community-link"; import { PersonListing } from "./person-listing"; @@ -176,6 +177,8 @@ export class Settings extends Component<any, SettingsState> { this.handleBannerUpload = this.handleBannerUpload.bind(this); this.handleBannerRemove = this.handleBannerRemove.bind(this); + this.userSettings = this.userSettings.bind(this); + this.blockCards = this.blockCards.bind(this); this.parseMessage = this.parseMessage.bind(this); this.subscription = wsSubscribe(this.parseMessage); @@ -253,44 +256,26 @@ export class Settings extends Component<any, SettingsState> { render() { return ( <div className="container-lg"> - <> - <HtmlTags - title={this.documentTitle} - path={this.context.router.route.match.url} - description={this.documentTitle} - image={this.state.saveUserSettingsForm.avatar} - /> - <ul className="nav nav-tabs mb-2"> - <li className="nav-item"> - <button - className={`nav-link btn ${ - this.state.currentTab == "settings" && "active" - }`} - onClick={linkEvent( - { ctx: this, tab: "settings" }, - this.handleSwitchTab - )} - > - {i18n.t("settings")} - </button> - </li> - <li className="nav-item"> - <button - className={`nav-link btn ${ - this.state.currentTab == "blocks" && "active" - }`} - onClick={linkEvent( - { ctx: this, tab: "blocks" }, - this.handleSwitchTab - )} - > - {i18n.t("blocks")} - </button> - </li> - </ul> - {this.state.currentTab == "settings" && this.userSettings()} - {this.state.currentTab == "blocks" && this.blockCards()} - </> + <HtmlTags + title={this.documentTitle} + path={this.context.router.route.match.url} + description={this.documentTitle} + image={this.state.saveUserSettingsForm.avatar} + /> + <Tabs + tabs={[ + { + key: "settings", + label: i18n.t("settings"), + getNode: this.userSettings, + }, + { + key: "blocks", + label: i18n.t("blocks"), + getNode: this.blockCards, + }, + ]} + /> </div> ); } From 2e3c1a6cfa81ce0cb4e1ba81a80e8038e5800213 Mon Sep 17 00:00:00 2001 From: abias <abias1122@gmail.com> Date: Sun, 21 May 2023 09:07:22 -0400 Subject: [PATCH 2/6] Put rate limit options in its own tab --- src/shared/components/home/admin-settings.tsx | 12 + .../components/home/rate-limit-form.tsx | 158 +++++++- src/shared/components/home/site-form.tsx | 347 ------------------ 3 files changed, 166 insertions(+), 351 deletions(-) diff --git a/src/shared/components/home/admin-settings.tsx b/src/shared/components/home/admin-settings.tsx index 3a2238ba..8370bec9 100644 --- a/src/shared/components/home/admin-settings.tsx +++ b/src/shared/components/home/admin-settings.tsx @@ -31,6 +31,7 @@ import { Spinner } from "../common/icon"; import Tabs from "../common/tabs"; import { PersonListing } from "../person/person-listing"; import { EmojiForm } from "./emojis-form"; +import RateLimitForm from "./rate-limit-form"; import { SiteForm } from "./site-form"; import { TaglineForm } from "./tagline-form"; @@ -148,6 +149,17 @@ export class AdminSettings extends Component<any, AdminSettingsState> { </div> ), }, + { + key: "rate_limiting", + label: "Rate Limiting", + getNode: () => ( + <RateLimitForm + localSiteRateLimit={ + this.state.siteRes.site_view.local_site_rate_limit + } + /> + ), + }, { key: "taglines", label: i18n.t("taglines"), diff --git a/src/shared/components/home/rate-limit-form.tsx b/src/shared/components/home/rate-limit-form.tsx index b99c2630..95f7fee9 100644 --- a/src/shared/components/home/rate-limit-form.tsx +++ b/src/shared/components/home/rate-limit-form.tsx @@ -1,11 +1,161 @@ -import { Component } from "inferno"; +import { Component, FormEventHandler, linkEvent } from "inferno"; +import { LocalSiteRateLimit } from "lemmy-js-client"; +import { i18n } from "../../i18next"; +import Tabs from "../common/tabs"; -export default class RateLimitForm extends Component { - constructor(props, context) { +const rateLimitTypes = [ + "message", + "post", + "image", + "comment", + "search", + "register", +] as const; + +interface RateLimitsProps { + handleRateLimit: FormEventHandler<HTMLInputElement>; + handleRateLimitPerSecond: FormEventHandler<HTMLInputElement>; + rateLimitLabel: string; + rateLimitValue?: number; + rateLimitPerSecondValue?: number; +} + +interface RateLimitFormProps { + localSiteRateLimit: LocalSiteRateLimit; +} + +interface RateLimitFormState { + message?: number; + message_per_second?: number; + post?: number; + post_per_second?: number; + comment?: number; + comment_per_second?: number; + image?: number; + image_per_second?: number; + search?: number; + search_per_second?: number; + register?: number; + register_per_second?: number; +} + +function RateLimits({ + handleRateLimit, + handleRateLimitPerSecond, + rateLimitLabel, + rateLimitPerSecondValue, + rateLimitValue, +}: RateLimitsProps) { + return ( + <div className="form-group row"> + <label className="col-12 col-form-label" htmlFor="rate-limit"> + {rateLimitLabel} + </label> + <input + type="number" + id="rate-limit" + className="form-control col-12" + min={0} + value={rateLimitValue} + onInput={handleRateLimit} + /> + <label className="col-12 col-form-label" htmlFor="rate-limit-per-second"> + {i18n.t("per_second")} + </label> + <input + type="number" + id="rate-limit-per-second" + className="form-control col-12" + min={0} + value={rateLimitPerSecondValue} + onInput={handleRateLimitPerSecond} + /> + </div> + ); +} + +function handleRateLimitChange( + { rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm }, + event: any +) { + ctx.setState({ + [rateLimitType]: Number(event.target.value), + }); +} + +function handlePerSecondChange( + { rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm }, + event: any +) { + ctx.setState({ + [`${rateLimitType}_per_second`]: Number(event.target.value), + }); +} + +export default class RateLimitsForm extends Component< + RateLimitFormProps, + RateLimitFormState +> { + state: RateLimitFormState = {}; + constructor(props: RateLimitFormProps, context) { super(props, context); + + const { + comment, + comment_per_second, + image, + image_per_second, + message, + message_per_second, + post, + post_per_second, + register, + register_per_second, + search, + search_per_second, + } = props.localSiteRateLimit; + + this.state = { + comment, + comment_per_second, + image, + image_per_second, + message, + message_per_second, + post, + post_per_second, + register, + register_per_second, + search, + search_per_second, + }; } render() { - return <></>; + return ( + <Tabs + tabs={rateLimitTypes.map(rateLimitType => ({ + key: rateLimitType, + label: rateLimitType, + getNode: () => ( + <RateLimits + handleRateLimit={linkEvent( + { rateLimitType, ctx: this }, + handleRateLimitChange + )} + handleRateLimitPerSecond={linkEvent( + { rateLimitType, ctx: this }, + handlePerSecondChange + )} + rateLimitLabel={i18n.t(`rate_limit_${rateLimitType}`)} + rateLimitValue={this.state[rateLimitType]} + rateLimitPerSecondValue={ + this.state[`${rateLimitType}_per_second`] + } + /> + ), + }))} + /> + ); } } diff --git a/src/shared/components/home/site-form.tsx b/src/shared/components/home/site-form.tsx index ea4d25e8..d3d3679f 100644 --- a/src/shared/components/home/site-form.tsx +++ b/src/shared/components/home/site-form.tsx @@ -78,7 +78,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { let site = this.props.siteRes.site_view.site; let ls = this.props.siteRes.site_view.local_site; - let lsrl = this.props.siteRes.site_view.local_site_rate_limit; this.state = { ...this.state, siteForm: { @@ -103,18 +102,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { discussion_languages: this.props.siteRes.discussion_languages, slur_filter_regex: ls.slur_filter_regex, actor_name_max_length: ls.actor_name_max_length, - rate_limit_message: lsrl.message, - rate_limit_message_per_second: lsrl.message_per_second, - rate_limit_comment: lsrl.comment, - rate_limit_comment_per_second: lsrl.comment_per_second, - rate_limit_image: lsrl.image, - rate_limit_image_per_second: lsrl.image_per_second, - rate_limit_post: lsrl.post, - rate_limit_post_per_second: lsrl.post_per_second, - rate_limit_register: lsrl.register, - rate_limit_register_per_second: lsrl.register_per_second, - rate_limit_search: lsrl.search, - rate_limit_search_per_second: lsrl.search_per_second, federation_enabled: ls.federation_enabled, federation_debug: ls.federation_debug, federation_worker_count: ls.federation_worker_count, @@ -655,238 +642,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { </div> </div> )} - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-message" - > - {i18n.t("rate_limit_message")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-message" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_message} - onInput={linkEvent(this, this.handleSiteRateLimitMessage)} - /> - </div> - </div> - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-message-per-second" - > - {i18n.t("per_second")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-message-per-second" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_message_per_second} - onInput={linkEvent( - this, - this.handleSiteRateLimitMessagePerSecond - )} - /> - </div> - </div> - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-post" - > - {i18n.t("rate_limit_post")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-post" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_post} - onInput={linkEvent(this, this.handleSiteRateLimitPost)} - /> - </div> - </div> - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-post-per-second" - > - {i18n.t("per_second")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-post-per-second" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_post_per_second} - onInput={linkEvent(this, this.handleSiteRateLimitPostPerSecond)} - /> - </div> - </div> - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-register" - > - {i18n.t("rate_limit_register")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-register" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_register} - onInput={linkEvent(this, this.handleSiteRateLimitRegister)} - /> - </div> - </div> - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-register-per-second" - > - {i18n.t("per_second")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-register-per-second" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_register_per_second} - onInput={linkEvent( - this, - this.handleSiteRateLimitRegisterPerSecond - )} - /> - </div> - </div> - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-image" - > - {i18n.t("rate_limit_image")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-image" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_image} - onInput={linkEvent(this, this.handleSiteRateLimitImage)} - /> - </div> - </div> - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-image-per-second" - > - {i18n.t("per_second")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-image-per-second" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_image_per_second} - onInput={linkEvent( - this, - this.handleSiteRateLimitImagePerSecond - )} - /> - </div> - </div> - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-comment" - > - {i18n.t("rate_limit_comment")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-comment" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_comment} - onInput={linkEvent(this, this.handleSiteRateLimitComment)} - /> - </div> - </div> - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-comment-per-second" - > - {i18n.t("per_second")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-comment-per-second" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_comment_per_second} - onInput={linkEvent( - this, - this.handleSiteRateLimitCommentPerSecond - )} - /> - </div> - </div> - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-search" - > - {i18n.t("rate_limit_search")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-search" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_search} - onInput={linkEvent(this, this.handleSiteRateLimitSearch)} - /> - </div> - </div> - <div className="form-group row"> - <label - className="col-12 col-form-label" - htmlFor="create-site-rate-limit-search-per-second" - > - {i18n.t("per_second")} - </label> - <div className="col-12"> - <input - type="number" - id="create-site-rate-limit-search-per-second" - className="form-control" - min={0} - value={this.state.siteForm.rate_limit_search_per_second} - onInput={linkEvent( - this, - this.handleSiteRateLimitSearchPerSecond - )} - /> - </div> - </div> - <div className="form-group row"> <div className="col-12"> <button @@ -1018,18 +773,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { legal_information: sForm.legal_information, slur_filter_regex: sForm.slur_filter_regex, actor_name_max_length: sForm.actor_name_max_length, - rate_limit_message: sForm.rate_limit_message, - rate_limit_message_per_second: sForm.rate_limit_message_per_second, - rate_limit_comment: sForm.rate_limit_comment, - rate_limit_comment_per_second: sForm.rate_limit_comment_per_second, - rate_limit_image: sForm.rate_limit_image, - rate_limit_image_per_second: sForm.rate_limit_image_per_second, - rate_limit_post: sForm.rate_limit_post, - rate_limit_post_per_second: sForm.rate_limit_post_per_second, - rate_limit_register: sForm.rate_limit_register, - rate_limit_register_per_second: sForm.rate_limit_register_per_second, - rate_limit_search: sForm.rate_limit_search, - rate_limit_search_per_second: sForm.rate_limit_search_per_second, federation_enabled: sForm.federation_enabled, federation_debug: sForm.federation_debug, federation_worker_count: sForm.federation_worker_count, @@ -1218,96 +961,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> { ); } - handleSiteRateLimitMessage(i: SiteForm, event: any) { - i.setState( - s => ((s.siteForm.rate_limit_message = Number(event.target.value)), s) - ); - } - - handleSiteRateLimitMessagePerSecond(i: SiteForm, event: any) { - i.setState( - s => ( - (s.siteForm.rate_limit_message_per_second = Number(event.target.value)), - s - ) - ); - } - - handleSiteRateLimitPost(i: SiteForm, event: any) { - i.setState( - s => ((s.siteForm.rate_limit_post = Number(event.target.value)), s) - ); - } - - handleSiteRateLimitPostPerSecond(i: SiteForm, event: any) { - i.setState( - s => ( - (s.siteForm.rate_limit_post_per_second = Number(event.target.value)), s - ) - ); - } - - handleSiteRateLimitImage(i: SiteForm, event: any) { - i.setState( - s => ((s.siteForm.rate_limit_image = Number(event.target.value)), s) - ); - } - - handleSiteRateLimitImagePerSecond(i: SiteForm, event: any) { - i.setState( - s => ( - (s.siteForm.rate_limit_image_per_second = Number(event.target.value)), s - ) - ); - } - - handleSiteRateLimitComment(i: SiteForm, event: any) { - i.setState( - s => ((s.siteForm.rate_limit_comment = Number(event.target.value)), s) - ); - } - - handleSiteRateLimitCommentPerSecond(i: SiteForm, event: any) { - i.setState( - s => ( - (s.siteForm.rate_limit_comment_per_second = Number(event.target.value)), - s - ) - ); - } - - handleSiteRateLimitSearch(i: SiteForm, event: any) { - i.setState( - s => ((s.siteForm.rate_limit_search = Number(event.target.value)), s) - ); - } - - handleSiteRateLimitSearchPerSecond(i: SiteForm, event: any) { - i.setState( - s => ( - (s.siteForm.rate_limit_search_per_second = Number(event.target.value)), - s - ) - ); - } - - handleSiteRateLimitRegister(i: SiteForm, event: any) { - i.setState( - s => ((s.siteForm.rate_limit_register = Number(event.target.value)), s) - ); - } - - handleSiteRateLimitRegisterPerSecond(i: SiteForm, event: any) { - i.setState( - s => ( - (s.siteForm.rate_limit_register_per_second = Number( - event.target.value - )), - s - ) - ); - } - handleSiteFederationEnabled(i: SiteForm, event: any) { i.state.siteForm.federation_enabled = event.target.checked; i.setState(i.state); From cf58a0c4515f4c8ba772c00ca02a9f928fe749fc Mon Sep 17 00:00:00 2001 From: abias <abias1122@gmail.com> Date: Sun, 21 May 2023 12:40:22 -0400 Subject: [PATCH 3/6] Allow user to submit rate limit changes --- src/shared/components/common/tabs.tsx | 1 + src/shared/components/home/admin-settings.tsx | 4 + .../components/home/rate-limit-form.tsx | 173 ++++++++++++------ 3 files changed, 124 insertions(+), 54 deletions(-) diff --git a/src/shared/components/common/tabs.tsx b/src/shared/components/common/tabs.tsx index 36e1a015..61ed396e 100644 --- a/src/shared/components/common/tabs.tsx +++ b/src/shared/components/common/tabs.tsx @@ -35,6 +35,7 @@ export default class Tabs extends Component<TabsProps, TabsState> { {this.props.tabs.map(({ key, label }) => ( <li key={key} className="nav-item"> <button + type="button" className={`nav-link btn${ this.state?.currentTab === key ? " active" : "" }`} diff --git a/src/shared/components/home/admin-settings.tsx b/src/shared/components/home/admin-settings.tsx index 8370bec9..d266db58 100644 --- a/src/shared/components/home/admin-settings.tsx +++ b/src/shared/components/home/admin-settings.tsx @@ -157,6 +157,10 @@ export class AdminSettings extends Component<any, AdminSettingsState> { localSiteRateLimit={ this.state.siteRes.site_view.local_site_rate_limit } + applicationQuestion={ + this.state.siteRes.site_view.local_site + .application_question + } /> ), }, diff --git a/src/shared/components/home/rate-limit-form.tsx b/src/shared/components/home/rate-limit-form.tsx index 95f7fee9..db37090c 100644 --- a/src/shared/components/home/rate-limit-form.tsx +++ b/src/shared/components/home/rate-limit-form.tsx @@ -1,6 +1,9 @@ import { Component, FormEventHandler, linkEvent } from "inferno"; -import { LocalSiteRateLimit } from "lemmy-js-client"; +import { EditSite, LocalSiteRateLimit } from "lemmy-js-client"; import { i18n } from "../../i18next"; +import { WebSocketService } from "../../services"; +import { capitalizeFirstLetter, myAuth, wsClient } from "../../utils"; +import { Spinner } from "../common/icon"; import Tabs from "../common/tabs"; const rateLimitTypes = [ @@ -22,21 +25,25 @@ interface RateLimitsProps { interface RateLimitFormProps { localSiteRateLimit: LocalSiteRateLimit; + applicationQuestion?: string; } interface RateLimitFormState { - message?: number; - message_per_second?: number; - post?: number; - post_per_second?: number; - comment?: number; - comment_per_second?: number; - image?: number; - image_per_second?: number; - search?: number; - search_per_second?: number; - register?: number; - register_per_second?: number; + form: { + message?: number; + message_per_second?: number; + post?: number; + post_per_second?: number; + comment?: number; + comment_per_second?: number; + image?: number; + image_per_second?: number; + search?: number; + search_per_second?: number; + register?: number; + register_per_second?: number; + }; + loading: boolean; } function RateLimits({ @@ -78,25 +85,52 @@ function handleRateLimitChange( { rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm }, event: any ) { - ctx.setState({ - [rateLimitType]: Number(event.target.value), - }); + ctx.setState(prev => ({ + ...prev, + form: { + ...prev.form, + [rateLimitType]: Number(event.target.value), + }, + })); } function handlePerSecondChange( { rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm }, event: any ) { - ctx.setState({ - [`${rateLimitType}_per_second`]: Number(event.target.value), - }); + ctx.setState(prev => ({ + ...prev, + form: { + ...prev.form, + [`${rateLimitType}_per_second`]: Number(event.target.value), + }, + })); +} + +function submitRateLimitForm(i: RateLimitsForm, event: any) { + event.preventDefault(); + const auth = myAuth() ?? "TODO"; + const form: EditSite = Object.entries(i.state.form).reduce( + (acc, [key, val]) => { + acc[`rate_limit_${key}`] = val; + return acc; + }, + { auth, application_question: i.props.applicationQuestion } + ); + + i.setState({ loading: true }); + + WebSocketService.Instance.send(wsClient.editSite(form)); } export default class RateLimitsForm extends Component< RateLimitFormProps, RateLimitFormState > { - state: RateLimitFormState = {}; + state: RateLimitFormState = { + loading: false, + form: {}, + }; constructor(props: RateLimitFormProps, context) { super(props, context); @@ -116,46 +150,77 @@ export default class RateLimitsForm extends Component< } = props.localSiteRateLimit; this.state = { - comment, - comment_per_second, - image, - image_per_second, - message, - message_per_second, - post, - post_per_second, - register, - register_per_second, - search, - search_per_second, + ...this.state, + form: { + comment, + comment_per_second, + image, + image_per_second, + message, + message_per_second, + post, + post_per_second, + register, + register_per_second, + search, + search_per_second, + }, }; } render() { return ( - <Tabs - tabs={rateLimitTypes.map(rateLimitType => ({ - key: rateLimitType, - label: rateLimitType, - getNode: () => ( - <RateLimits - handleRateLimit={linkEvent( - { rateLimitType, ctx: this }, - handleRateLimitChange + <form onSubmit={linkEvent(this, submitRateLimitForm)}> + <Tabs + tabs={rateLimitTypes.map(rateLimitType => ({ + key: rateLimitType, + label: rateLimitType, + getNode: () => ( + <RateLimits + handleRateLimit={linkEvent( + { rateLimitType, ctx: this }, + handleRateLimitChange + )} + handleRateLimitPerSecond={linkEvent( + { rateLimitType, ctx: this }, + handlePerSecondChange + )} + rateLimitLabel={i18n.t(`rate_limit_${rateLimitType}`)} + rateLimitValue={this.state.form[rateLimitType]} + rateLimitPerSecondValue={ + this.state.form[`${rateLimitType}_per_second`] + } + /> + ), + }))} + /> + <div className="form-group row"> + <div className="col-12"> + <button + type="submit" + className="btn btn-secondary mr-2" + disabled={this.state.loading} + > + {this.state.loading ? ( + <Spinner /> + ) : ( + capitalizeFirstLetter(i18n.t("save")) )} - handleRateLimitPerSecond={linkEvent( - { rateLimitType, ctx: this }, - handlePerSecondChange - )} - rateLimitLabel={i18n.t(`rate_limit_${rateLimitType}`)} - rateLimitValue={this.state[rateLimitType]} - rateLimitPerSecondValue={ - this.state[`${rateLimitType}_per_second`] - } - /> - ), - }))} - /> + </button> + </div> + </div> + </form> ); } + + componentDidUpdate({ localSiteRateLimit }: RateLimitFormProps) { + if ( + this.state.loading && + Object.entries(localSiteRateLimit).some( + ([key, val]) => this.state.form[key] !== val + ) + ) { + this.setState({ loading: false }); + } + } } From 132f241e636e63833ec3b9368266780a9e4a947b Mon Sep 17 00:00:00 2001 From: abias <abias1122@gmail.com> Date: Sun, 21 May 2023 15:11:20 -0400 Subject: [PATCH 4/6] Remove console log --- src/shared/components/common/tabs.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shared/components/common/tabs.tsx b/src/shared/components/common/tabs.tsx index 61ed396e..17980a47 100644 --- a/src/shared/components/common/tabs.tsx +++ b/src/shared/components/common/tabs.tsx @@ -15,7 +15,6 @@ interface TabsState { } function handleSwitchTab({ ctx, tab }: { ctx: Tabs; tab: string }) { - console.log(tab); ctx.setState({ currentTab: tab }); } From 9998d64d754c05349549e2dcbb5b69ad026dd13b Mon Sep 17 00:00:00 2001 From: abias <abias1122@gmail.com> Date: Sun, 21 May 2023 15:31:42 -0400 Subject: [PATCH 5/6] Add heading tag to rate limit form --- src/shared/components/home/rate-limit-form.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/components/home/rate-limit-form.tsx b/src/shared/components/home/rate-limit-form.tsx index db37090c..d4b68beb 100644 --- a/src/shared/components/home/rate-limit-form.tsx +++ b/src/shared/components/home/rate-limit-form.tsx @@ -171,6 +171,7 @@ export default class RateLimitsForm extends Component< render() { return ( <form onSubmit={linkEvent(this, submitRateLimitForm)}> + <h5>Rate Limit Options</h5> <Tabs tabs={rateLimitTypes.map(rateLimitType => ({ key: rateLimitType, From 98167b50dc5c5d9e93c2eb29af8df822e0a0d38f Mon Sep 17 00:00:00 2001 From: abias <abias1122@gmail.com> Date: Tue, 23 May 2023 19:31:33 -0400 Subject: [PATCH 6/6] Add translations --- src/shared/components/home/rate-limit-form.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/shared/components/home/rate-limit-form.tsx b/src/shared/components/home/rate-limit-form.tsx index d4b68beb..8f2a1a81 100644 --- a/src/shared/components/home/rate-limit-form.tsx +++ b/src/shared/components/home/rate-limit-form.tsx @@ -18,7 +18,6 @@ const rateLimitTypes = [ interface RateLimitsProps { handleRateLimit: FormEventHandler<HTMLInputElement>; handleRateLimitPerSecond: FormEventHandler<HTMLInputElement>; - rateLimitLabel: string; rateLimitValue?: number; rateLimitPerSecondValue?: number; } @@ -49,14 +48,13 @@ interface RateLimitFormState { function RateLimits({ handleRateLimit, handleRateLimitPerSecond, - rateLimitLabel, rateLimitPerSecondValue, rateLimitValue, }: RateLimitsProps) { return ( <div className="form-group row"> <label className="col-12 col-form-label" htmlFor="rate-limit"> - {rateLimitLabel} + {i18n.t("rate_limit")} </label> <input type="number" @@ -171,11 +169,11 @@ export default class RateLimitsForm extends Component< render() { return ( <form onSubmit={linkEvent(this, submitRateLimitForm)}> - <h5>Rate Limit Options</h5> + <h5>{i18n.t("rate_limit_header")}</h5> <Tabs tabs={rateLimitTypes.map(rateLimitType => ({ key: rateLimitType, - label: rateLimitType, + label: i18n.t(`rate_limit_${rateLimitType}`), getNode: () => ( <RateLimits handleRateLimit={linkEvent( @@ -186,7 +184,6 @@ export default class RateLimitsForm extends Component< { rateLimitType, ctx: this }, handlePerSecondChange )} - rateLimitLabel={i18n.t(`rate_limit_${rateLimitType}`)} rateLimitValue={this.state.form[rateLimitType]} rateLimitPerSecondValue={ this.state.form[`${rateLimitType}_per_second`]