-import autosize from "autosize";
import { Component, linkEvent } from "inferno";
import {
BannedPersonsResponse,
- GetBannedPersons,
+ CreateCustomEmoji,
+ DeleteCustomEmoji,
+ EditCustomEmoji,
+ EditSite,
+ GetFederatedInstancesResponse,
GetSiteResponse,
- PersonViewSafe,
- SiteResponse,
- UserOperation,
- wsJsonToRes,
- wsUserOp,
+ PersonView,
} from "lemmy-js-client";
-import { Subscription } from "rxjs";
import { i18n } from "../../i18next";
import { InitialFetchRequest } from "../../interfaces";
-import { WebSocketService } from "../../services";
+import { FirstLoadService } from "../../services/FirstLoadService";
+import { HttpService, RequestState } from "../../services/HttpService";
import {
+ RouteDataResponse,
capitalizeFirstLetter,
- isBrowser,
- myAuth,
- randomStr,
+ fetchThemeList,
+ myAuthRequired,
+ removeFromEmojiDataModel,
setIsoData,
showLocal,
toast,
- wsClient,
- wsSubscribe,
+ updateEmojiDataModel,
} 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";
+
+type AdminSettingsData = RouteDataResponse<{
+ bannedRes: BannedPersonsResponse;
+ instancesRes: GetFederatedInstancesResponse;
+}>;
interface AdminSettingsState {
siteRes: GetSiteResponse;
- banned: PersonViewSafe[];
- loading: boolean;
- leaveAdminTeamLoading: boolean;
+ banned: PersonView[];
currentTab: string;
+ instancesRes: RequestState<GetFederatedInstancesResponse>;
+ bannedRes: RequestState<BannedPersonsResponse>;
+ leaveAdminTeamRes: RequestState<GetSiteResponse>;
+ emojiLoading: boolean;
+ loading: boolean;
+ themeList: string[];
+ isIsomorphic: boolean;
}
export class AdminSettings extends Component<any, AdminSettingsState> {
- private siteConfigTextAreaId = `site-config-${randomStr()}`;
- private isoData = setIsoData(this.context);
- private subscription?: Subscription;
+ private isoData = setIsoData<AdminSettingsData>(this.context);
state: AdminSettingsState = {
siteRes: this.isoData.site_res,
banned: [],
- loading: true,
- leaveAdminTeamLoading: false,
currentTab: "site",
+ bannedRes: { state: "empty" },
+ instancesRes: { state: "empty" },
+ leaveAdminTeamRes: { state: "empty" },
+ emojiLoading: false,
+ loading: false,
+ themeList: [],
+ isIsomorphic: false,
};
constructor(props: any, context: any) {
super(props, context);
- this.parseMessage = this.parseMessage.bind(this);
- this.subscription = wsSubscribe(this.parseMessage);
+ this.handleEditSite = this.handleEditSite.bind(this);
+ this.handleEditEmoji = this.handleEditEmoji.bind(this);
+ this.handleDeleteEmoji = this.handleDeleteEmoji.bind(this);
+ this.handleCreateEmoji = this.handleCreateEmoji.bind(this);
// Only fetch the data if coming from another route
- if (this.isoData.path == this.context.router.route.match.url) {
+ if (FirstLoadService.isFirstLoad) {
+ const { bannedRes, instancesRes } = this.isoData.routeData;
+
this.state = {
...this.state,
- banned: (this.isoData.routeData[0] as BannedPersonsResponse).banned,
- loading: false,
+ bannedRes,
+ instancesRes,
+ isIsomorphic: true,
};
- } else {
- let cAuth = myAuth();
- if (cAuth) {
- WebSocketService.Instance.send(
- wsClient.getBannedPersons({
- auth: cAuth,
- })
- );
- }
}
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- let promises: Promise<any>[] = [];
-
- let auth = req.auth;
- if (auth) {
- let bannedPersonsForm: GetBannedPersons = { auth };
- promises.push(req.client.getBannedPersons(bannedPersonsForm));
- }
-
- return promises;
+ static async fetchInitialData({
+ auth,
+ client,
+ }: InitialFetchRequest): Promise<AdminSettingsData> {
+ return {
+ bannedRes: await client.getBannedPersons({
+ auth: auth as string,
+ }),
+ instancesRes: await client.getFederatedInstances({
+ auth: auth as string,
+ }),
+ };
}
- componentDidMount() {
- if (isBrowser()) {
- var textarea: any = document.getElementById(this.siteConfigTextAreaId);
- autosize(textarea);
- }
- }
-
- componentWillUnmount() {
- if (isBrowser()) {
- this.subscription?.unsubscribe();
+ async componentDidMount() {
+ if (!this.state.isIsomorphic) {
+ await this.fetchData();
}
}
}
render() {
+ const federationData =
+ this.state.instancesRes.state === "success"
+ ? this.state.instancesRes.data.federated_instances
+ : undefined;
+
return (
<div className="container-lg">
- {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 == "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}
- showLocal={showLocal(this.isoData)}
+ <HtmlTags
+ title={this.documentTitle}
+ path={this.context.router.route.match.url}
+ />
+ <Tabs
+ tabs={[
+ {
+ key: "site",
+ label: i18n.t("site"),
+ getNode: () => (
+ <div className="row">
+ <div className="col-12 col-md-6">
+ <SiteForm
+ showLocal={showLocal(this.isoData)}
+ allowedInstances={federationData?.allowed}
+ blockedInstances={federationData?.blocked}
+ onSaveSite={this.handleEditSite}
+ siteRes={this.state.siteRes}
+ themeList={this.state.themeList}
+ loading={this.state.loading}
+ />
+ </div>
+ <div className="col-12 col-md-6">
+ {this.admins()}
+ {this.bannedUsers()}
+ </div>
+ </div>
+ ),
+ },
+ {
+ key: "rate_limiting",
+ label: "Rate Limiting",
+ getNode: () => (
+ <RateLimitForm
+ rateLimits={
+ this.state.siteRes.site_view.local_site_rate_limit
+ }
+ onSaveSite={this.handleEditSite}
+ loading={this.state.loading}
+ />
+ ),
+ },
+ {
+ key: "taglines",
+ label: i18n.t("taglines"),
+ getNode: () => (
+ <div className="row">
+ <TaglineForm
+ taglines={this.state.siteRes.taglines}
+ onSaveSite={this.handleEditSite}
+ loading={this.state.loading}
/>
</div>
- <div className="col-12 col-md-6">
- {this.admins()}
- {this.bannedUsers()}
+ ),
+ },
+ {
+ key: "emojis",
+ label: i18n.t("emojis"),
+ getNode: () => (
+ <div className="row">
+ <EmojiForm
+ onCreate={this.handleCreateEmoji}
+ onDelete={this.handleDeleteEmoji}
+ onEdit={this.handleEditEmoji}
+ loading={this.state.emojiLoading}
+ />
</div>
- </div>
- )}
- {this.state.currentTab == "emojis" && (
- <div className="row">
- <EmojiForm></EmojiForm>
- </div>
- )}
- </div>
- )}
+ ),
+ },
+ ]}
+ />
</div>
);
}
+ async fetchData() {
+ this.setState({
+ bannedRes: { state: "loading" },
+ instancesRes: { state: "loading" },
+ themeList: [],
+ });
+
+ const auth = myAuthRequired();
+
+ const [bannedRes, instancesRes, themeList] = await Promise.all([
+ HttpService.client.getBannedPersons({ auth }),
+ HttpService.client.getFederatedInstances({ auth }),
+ fetchThemeList(),
+ ]);
+
+ this.setState({
+ bannedRes,
+ instancesRes,
+ themeList,
+ });
+ }
+
admins() {
return (
<>
onClick={linkEvent(this, this.handleLeaveAdminTeam)}
className="btn btn-danger mb-2"
>
- {this.state.leaveAdminTeamLoading ? (
+ {this.state.leaveAdminTeamRes.state == "loading" ? (
<Spinner />
) : (
i18n.t("leave_admin_team")
}
bannedUsers() {
- return (
- <>
- <h5>{i18n.t("banned_users")}</h5>
- <ul className="list-unstyled">
- {this.state.banned.map(banned => (
- <li key={banned.person.id} className="list-inline-item">
- <PersonListing person={banned.person} />
- </li>
- ))}
- </ul>
- </>
- );
+ switch (this.state.bannedRes.state) {
+ case "loading":
+ return (
+ <h5>
+ <Spinner large />
+ </h5>
+ );
+ case "success": {
+ const bans = this.state.bannedRes.data.banned;
+ return (
+ <>
+ <h5>{i18n.t("banned_users")}</h5>
+ <ul className="list-unstyled">
+ {bans.map(banned => (
+ <li key={banned.person.id} className="list-inline-item">
+ <PersonListing person={banned.person} />
+ </li>
+ ))}
+ </ul>
+ </>
+ );
+ }
+ }
+ }
+
+ async handleEditSite(form: EditSite) {
+ this.setState({ loading: true });
+
+ const editRes = await HttpService.client.editSite(form);
+
+ if (editRes.state === "success") {
+ this.setState(s => {
+ s.siteRes.site_view = editRes.data.site_view;
+ // TODO: Where to get taglines from?
+ s.siteRes.taglines = editRes.data.taglines;
+ return s;
+ });
+ toast(i18n.t("site_saved"));
+ }
+
+ this.setState({ loading: false });
+
+ return editRes;
}
handleSwitchTab(i: { ctx: AdminSettings; tab: string }) {
i.ctx.setState({ currentTab: i.tab });
}
- handleLeaveAdminTeam(i: AdminSettings) {
- let auth = myAuth();
- if (auth) {
- i.setState({ leaveAdminTeamLoading: true });
- WebSocketService.Instance.send(wsClient.leaveAdmin({ auth }));
+ async handleLeaveAdminTeam(i: AdminSettings) {
+ i.setState({ leaveAdminTeamRes: { state: "loading" } });
+ this.setState({
+ leaveAdminTeamRes: await HttpService.client.leaveAdmin({
+ auth: myAuthRequired(),
+ }),
+ });
+
+ if (this.state.leaveAdminTeamRes.state === "success") {
+ toast(i18n.t("left_admin_team"));
+ this.context.router.history.replace("/");
}
}
- parseMessage(msg: any) {
- let op = wsUserOp(msg);
- console.log(msg);
- if (msg.error) {
- toast(i18n.t(msg.error), "danger");
- this.context.router.history.push("/");
- this.setState({ loading: false });
- return;
- } else if (op == UserOperation.EditSite) {
- let data = wsJsonToRes<SiteResponse>(msg);
- this.setState(s => ((s.siteRes.site_view = data.site_view), s));
- toast(i18n.t("site_saved"));
- } else if (op == UserOperation.GetBannedPersons) {
- let data = wsJsonToRes<BannedPersonsResponse>(msg);
- this.setState({ banned: data.banned, loading: false });
- } else if (op == UserOperation.LeaveAdmin) {
- let data = wsJsonToRes<GetSiteResponse>(msg);
- this.setState(s => ((s.siteRes.site_view = data.site_view), s));
- this.setState({ leaveAdminTeamLoading: false });
+ async handleEditEmoji(form: EditCustomEmoji) {
+ this.setState({ emojiLoading: true });
- toast(i18n.t("left_admin_team"));
- this.context.router.history.push("/");
+ const res = await HttpService.client.editCustomEmoji(form);
+ if (res.state === "success") {
+ updateEmojiDataModel(res.data.custom_emoji);
+ }
+
+ this.setState({ emojiLoading: false });
+ }
+
+ async handleDeleteEmoji(form: DeleteCustomEmoji) {
+ this.setState({ emojiLoading: true });
+
+ const res = await HttpService.client.deleteCustomEmoji(form);
+ if (res.state === "success") {
+ removeFromEmojiDataModel(res.data.id);
+ }
+
+ this.setState({ emojiLoading: false });
+ }
+
+ async handleCreateEmoji(form: CreateCustomEmoji) {
+ this.setState({ emojiLoading: true });
+
+ const res = await HttpService.client.createCustomEmoji(form);
+ if (res.state === "success") {
+ updateEmojiDataModel(res.data.custom_emoji);
}
+
+ this.setState({ emojiLoading: false });
}
}