-import autosize from "autosize";
import { Component, linkEvent } from "inferno";
import {
BannedPersonsResponse,
- GetBannedPersons,
- GetSiteConfig,
- GetSiteConfigResponse,
+ CreateCustomEmoji,
+ DeleteCustomEmoji,
+ EditCustomEmoji,
+ EditSite,
+ GetFederatedInstancesResponse,
GetSiteResponse,
- PersonViewSafe,
- SaveSiteConfig,
- SiteResponse,
- UserOperation,
+ 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 {
- authField,
+ RouteDataResponse,
capitalizeFirstLetter,
- isBrowser,
- randomStr,
+ fetchThemeList,
+ myAuthRequired,
+ removeFromEmojiDataModel,
setIsoData,
+ showLocal,
toast,
- wsClient,
- wsJsonToRes,
- wsSubscribe,
- wsUserOp,
+ 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;
- siteConfigRes: GetSiteConfigResponse;
- siteConfigForm: SaveSiteConfig;
+ banned: PersonView[];
+ currentTab: string;
+ instancesRes: RequestState<GetFederatedInstancesResponse>;
+ bannedRes: RequestState<BannedPersonsResponse>;
+ leaveAdminTeamRes: RequestState<GetSiteResponse>;
+ emojiLoading: boolean;
loading: boolean;
- banned: PersonViewSafe[];
- siteConfigLoading: boolean;
- leaveAdminTeamLoading: 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 emptyState: AdminSettingsState = {
+ private isoData = setIsoData<AdminSettingsData>(this.context);
+ state: AdminSettingsState = {
siteRes: this.isoData.site_res,
- siteConfigForm: {
- config_hjson: null,
- auth: null,
- },
- siteConfigRes: {
- config_hjson: null,
- },
banned: [],
- loading: true,
- siteConfigLoading: null,
- leaveAdminTeamLoading: null,
+ 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.state = this.emptyState;
-
- 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) {
- this.state.siteConfigRes = this.isoData.routeData[0];
- this.state.siteConfigForm.config_hjson =
- this.state.siteConfigRes.config_hjson;
- this.state.banned = this.isoData.routeData[1].banned;
- this.state.siteConfigLoading = false;
- this.state.loading = false;
- } else {
- this.state.siteConfigForm.auth = authField();
- WebSocketService.Instance.send(
- wsClient.getSiteConfig({
- auth: authField(),
- })
- );
- WebSocketService.Instance.send(
- wsClient.getBannedPersons({
- auth: authField(),
- })
- );
+ if (FirstLoadService.isFirstLoad) {
+ const { bannedRes, instancesRes } = this.isoData.routeData;
+
+ this.state = {
+ ...this.state,
+ bannedRes,
+ instancesRes,
+ isIsomorphic: true,
+ };
}
}
- static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
- let promises: Promise<any>[] = [];
-
- let siteConfigForm: GetSiteConfig = { auth: req.auth };
- promises.push(req.client.getSiteConfig(siteConfigForm));
-
- let bannedPersonsForm: GetBannedPersons = { auth: req.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 class="container">
- {this.state.loading ? (
- <h5>
- <Spinner large />
- </h5>
- ) : (
- <div class="row">
- <div class="col-12 col-md-6">
- <HtmlTags
- title={this.documentTitle}
- path={this.context.router.route.match.url}
- />
- {this.state.siteRes.site_view.site.id && (
- <SiteForm site={this.state.siteRes.site_view.site} />
- )}
- {this.admins()}
- {this.bannedUsers()}
- </div>
- <div class="col-12 col-md-6">{this.adminSettings()}</div>
- </div>
- )}
+ <div className="container-lg">
+ <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>
+ ),
+ },
+ {
+ 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>
);
}
+ 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 (
<>
<h5>{capitalizeFirstLetter(i18n.t("admins"))}</h5>
- <ul class="list-unstyled">
+ <ul className="list-unstyled">
{this.state.siteRes.admins.map(admin => (
- <li class="list-inline-item">
+ <li key={admin.person.id} className="list-inline-item">
<PersonListing person={admin.person} />
</li>
))}
return (
<button
onClick={linkEvent(this, this.handleLeaveAdminTeam)}
- class="btn btn-danger mb-2"
+ 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 class="list-unstyled">
- {this.state.banned.map(banned => (
- <li class="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>
+ </>
+ );
+ }
+ }
}
- adminSettings() {
- return (
- <div>
- <h5>{i18n.t("admin_settings")}</h5>
- <form onSubmit={linkEvent(this, this.handleSiteConfigSubmit)}>
- <div class="form-group row">
- <label
- class="col-12 col-form-label"
- htmlFor={this.siteConfigTextAreaId}
- >
- {i18n.t("site_config")}
- </label>
- <div class="col-12">
- <textarea
- id={this.siteConfigTextAreaId}
- value={this.state.siteConfigForm.config_hjson}
- onInput={linkEvent(this, this.handleSiteConfigHjsonChange)}
- class="form-control text-monospace"
- rows={3}
- />
- </div>
- </div>
- <div class="form-group row">
- <div class="col-12">
- <button type="submit" class="btn btn-secondary mr-2">
- {this.state.siteConfigLoading ? (
- <Spinner />
- ) : (
- capitalizeFirstLetter(i18n.t("save"))
- )}
- </button>
- </div>
- </div>
- </form>
- </div>
- );
+ 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;
}
- handleSiteConfigSubmit(i: AdminSettings, event: any) {
- event.preventDefault();
- i.state.siteConfigLoading = true;
- WebSocketService.Instance.send(
- wsClient.saveSiteConfig(i.state.siteConfigForm)
- );
- i.setState(i.state);
+ handleSwitchTab(i: { ctx: AdminSettings; tab: string }) {
+ i.ctx.setState({ currentTab: i.tab });
+ }
+
+ 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("/");
+ }
}
- handleSiteConfigHjsonChange(i: AdminSettings, event: any) {
- i.state.siteConfigForm.config_hjson = event.target.value;
- i.setState(i.state);
+ async handleEditEmoji(form: EditCustomEmoji) {
+ this.setState({ emojiLoading: true });
+
+ const res = await HttpService.client.editCustomEmoji(form);
+ if (res.state === "success") {
+ updateEmojiDataModel(res.data.custom_emoji);
+ }
+
+ this.setState({ emojiLoading: false });
}
- handleLeaveAdminTeam(i: AdminSettings) {
- i.state.leaveAdminTeamLoading = true;
- WebSocketService.Instance.send(wsClient.leaveAdmin({ auth: authField() }));
- i.setState(i.state);
+ 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 });
}
- 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.state.loading = false;
- this.setState(this.state);
- return;
- } else if (op == UserOperation.EditSite) {
- let data = wsJsonToRes<SiteResponse>(msg).data;
- this.state.siteRes.site_view = data.site_view;
- this.setState(this.state);
- toast(i18n.t("site_saved"));
- } else if (op == UserOperation.GetBannedPersons) {
- let data = wsJsonToRes<BannedPersonsResponse>(msg).data;
- this.state.banned = data.banned;
- this.setState(this.state);
- } else if (op == UserOperation.GetSiteConfig) {
- let data = wsJsonToRes<GetSiteConfigResponse>(msg).data;
- this.state.siteConfigRes = data;
- this.state.loading = false;
- this.state.siteConfigForm.config_hjson =
- this.state.siteConfigRes.config_hjson;
- this.setState(this.state);
- var textarea: any = document.getElementById(this.siteConfigTextAreaId);
- autosize(textarea);
- } else if (op == UserOperation.LeaveAdmin) {
- let data = wsJsonToRes<GetSiteResponse>(msg).data;
- this.state.siteRes.site_view = data.site_view;
- this.setState(this.state);
- this.state.leaveAdminTeamLoading = false;
- toast(i18n.t("left_admin_team"));
- this.setState(this.state);
- this.context.router.history.push("/");
- } else if (op == UserOperation.SaveSiteConfig) {
- let data = wsJsonToRes<GetSiteConfigResponse>(msg).data;
- this.state.siteConfigRes = data;
- this.state.siteConfigForm.config_hjson =
- this.state.siteConfigRes.config_hjson;
- this.state.siteConfigLoading = false;
- toast(i18n.t("site_saved"));
- this.setState(this.state);
+ 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 });
}
}