diff --git a/src/shared/components/common/tabs.tsx b/src/shared/components/common/tabs.tsx new file mode 100644 index 00000000..17980a47 --- /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 }) { + ctx.setState({ currentTab: tab }); +} + +export default class Tabs extends Component { + constructor(props: TabsProps, context) { + super(props, context); + + this.state = { + currentTab: props.tabs.length > 0 ? props.tabs[0].key : "", + }; + } + + render() { + return ( +
+ + {this.props.tabs + .find(tab => tab.key === this.state?.currentTab) + ?.getNode()} +
+ ); + } +} diff --git a/src/shared/components/home/admin-settings.tsx b/src/shared/components/home/admin-settings.tsx index ab897fe1..d266db58 100644 --- a/src/shared/components/home/admin-settings.tsx +++ b/src/shared/components/home/admin-settings.tsx @@ -28,8 +28,10 @@ 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 RateLimitForm from "./rate-limit-form"; import { SiteForm } from "./site-form"; import { TaglineForm } from "./tagline-form"; @@ -39,7 +41,6 @@ interface AdminSettingsState { banned: PersonView[]; loading: boolean; leaveAdminTeamLoading: boolean; - currentTab: string; } export class AdminSettings extends Component { @@ -51,7 +52,6 @@ export class AdminSettings extends Component { banned: [], loading: true, leaveAdminTeamLoading: false, - currentTab: "site", }; constructor(props: any, context: any) { @@ -119,83 +119,71 @@ export class AdminSettings extends Component { render() { return (
+ {this.state.loading ? (
) : ( -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- {this.state.currentTab == "site" && ( -
-
- ( +
+
+ +
+
+ {this.admins()} + {this.bannedUsers()} +
+
+ ), + }, + { + key: "rate_limiting", + label: "Rate Limiting", + getNode: () => ( + -
-
- {this.admins()} - {this.bannedUsers()} -
-
- )} - {this.state.currentTab == "taglines" && ( -
- -
- )} - {this.state.currentTab == "emojis" && ( -
- -
- )} -
+ ), + }, + { + key: "taglines", + label: i18n.t("taglines"), + getNode: () => ( +
+ +
+ ), + }, + { + key: "emojis", + label: i18n.t("emojis"), + getNode: () => ( +
+ +
+ ), + }, + ]} + /> )}
); @@ -247,10 +235,6 @@ export class AdminSettings extends Component { ); } - 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..8f2a1a81 --- /dev/null +++ b/src/shared/components/home/rate-limit-form.tsx @@ -0,0 +1,224 @@ +import { Component, FormEventHandler, linkEvent } from "inferno"; +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 = [ + "message", + "post", + "image", + "comment", + "search", + "register", +] as const; + +interface RateLimitsProps { + handleRateLimit: FormEventHandler; + handleRateLimitPerSecond: FormEventHandler; + rateLimitValue?: number; + rateLimitPerSecondValue?: number; +} + +interface RateLimitFormProps { + localSiteRateLimit: LocalSiteRateLimit; + applicationQuestion?: string; +} + +interface RateLimitFormState { + 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({ + handleRateLimit, + handleRateLimitPerSecond, + rateLimitPerSecondValue, + rateLimitValue, +}: RateLimitsProps) { + return ( +
+ + + + +
+ ); +} + +function handleRateLimitChange( + { rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm }, + event: any +) { + ctx.setState(prev => ({ + ...prev, + form: { + ...prev.form, + [rateLimitType]: Number(event.target.value), + }, + })); +} + +function handlePerSecondChange( + { rateLimitType, ctx }: { rateLimitType: string; ctx: RateLimitsForm }, + event: any +) { + 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 = { + loading: false, + form: {}, + }; + 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 = { + ...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 ( +
+
{i18n.t("rate_limit_header")}
+ ({ + key: rateLimitType, + label: i18n.t(`rate_limit_${rateLimitType}`), + getNode: () => ( + + ), + }))} + /> +
+
+ +
+
+ + ); + } + + componentDidUpdate({ localSiteRateLimit }: RateLimitFormProps) { + if ( + this.state.loading && + Object.entries(localSiteRateLimit).some( + ([key, val]) => this.state.form[key] !== val + ) + ) { + this.setState({ loading: false }); + } + } +} 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 { 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 { 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 { )} -
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
  • - -
  • - - {this.state.currentTab == "settings" && this.userSettings()} - {this.state.currentTab == "blocks" && this.blockCards()} - + +
    ); }