[chore] Settings refactor 2: the re-refactoring-ing (#2866)

* [chore] Bit more refactoring of settings panel

* fix up some remaining things

* groovy baby yeah!

* remove unused Suspense
This commit is contained in:
tobi
2024-04-25 18:24:24 +02:00
committed by GitHub
parent 7a1e639483
commit aecf74951c
41 changed files with 1360 additions and 958 deletions

View File

@ -0,0 +1,103 @@
/*
GoToSocial
Copyright (C) GoToSocial Authors admin@gotosocial.org
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { Redirect, useParams } from "wouter";
import { useBaseUrl } from "../../../lib/navigation/util";
import { useValue, useTextInput } from "../../../lib/form";
import useFormSubmit from "../../../lib/form/submit";
import { TextArea } from "../../../components/form/inputs";
import MutationButton from "../../../components/form/mutation-button";
import BackButton from "../../../components/back-button";
import Loading from "../../../components/loading";
import { useDeleteInstanceRuleMutation, useInstanceRulesQuery, useUpdateInstanceRuleMutation } from "../../../lib/query/admin";
import { Error } from "../../../components/error";
export default function InstanceRuleDetail() {
const baseUrl = useBaseUrl();
const params: { ruleId: string } = useParams();
const { data: rules, isLoading, isError, error } = useInstanceRulesQuery();
if (isLoading) {
return <Loading />;
} else if (isError) {
return <Error error={error} />;
}
if (rules === undefined) {
throw "undefined rules";
}
return (
<>
<BackButton to={`~${baseUrl}/rules`} />
<EditInstanceRuleForm rule={rules[params.ruleId]} />
</>
);
}
function EditInstanceRuleForm({ rule }) {
const baseUrl = useBaseUrl();
const form = {
id: useValue("id", rule.id),
rule: useTextInput("text", { defaultValue: rule.text })
};
const [submitForm, result] = useFormSubmit(form, useUpdateInstanceRuleMutation());
const [deleteRule, deleteResult] = useDeleteInstanceRuleMutation({ fixedCacheKey: rule.id });
if (result.isSuccess || deleteResult.isSuccess) {
return (
<Redirect to={`~${baseUrl}/rules`} />
);
}
return (
<div className="rule-detail">
<form onSubmit={submitForm}>
<TextArea
field={form.rule}
/>
<div className="action-buttons row">
<MutationButton
label="Save"
showError={false}
result={result}
disabled={!form.rule.hasChanged()}
/>
<MutationButton
disabled={false}
type="button"
onClick={() => deleteRule(rule.id)}
label="Delete"
className="button danger"
showError={false}
result={deleteResult}
/>
</div>
{result.error && <Error error={result.error} />}
{deleteResult.error && <Error error={deleteResult.error} />}
</form>
</div>
);
}

View File

@ -0,0 +1,75 @@
/*
GoToSocial
Copyright (C) GoToSocial Authors admin@gotosocial.org
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { Link } from "wouter";
import { useInstanceRulesQuery, useAddInstanceRuleMutation } from "../../../lib/query/admin";
import { useBaseUrl } from "../../../lib/navigation/util";
import { useTextInput } from "../../../lib/form";
import useFormSubmit from "../../../lib/form/submit";
import { TextArea } from "../../../components/form/inputs";
import MutationButton from "../../../components/form/mutation-button";
import { InstanceRule, MappedRules } from "../../../lib/types/rules";
import FormWithData from "../../../lib/form/form-with-data";
export default function InstanceRules() {
return (
<>
<h1>Instance Rules</h1>
<FormWithData
dataQuery={useInstanceRulesQuery}
DataForm={InstanceRulesForm}
/>
</>
);
}
function InstanceRulesForm({ data: rules }: { data: MappedRules }) {
const baseUrl = useBaseUrl();
const newRule = useTextInput("text");
const [submitForm, result] = useFormSubmit({ newRule }, useAddInstanceRuleMutation(), {
changedOnly: true,
onFinish: () => newRule.reset()
});
return (
<form onSubmit={submitForm} className="new-rule">
<ol className="instance-rules">
{Object.values(rules).map((rule: InstanceRule) => (
<Link key={"link-"+rule.id} className="rule" to={`~${baseUrl}/rules/${rule.id}`}>
<li key={rule.id}>
<h2>{rule.text} <i className="fa fa-pencil edit-icon" /></h2>
</li>
<span>{new Date(rule.created_at).toLocaleString()}</span>
</Link>
))}
</ol>
<TextArea
field={newRule}
label="New instance rule"
/>
<MutationButton
disabled={newRule.value === undefined || newRule.value.length === 0}
label="Add rule"
result={result}
/>
</form>
);
}

View File

@ -0,0 +1,186 @@
/*
GoToSocial
Copyright (C) GoToSocial Authors admin@gotosocial.org
SPDX-License-Identifier: AGPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { useTextInput, useFileInput } from "../../../lib/form";
import { TextInput, TextArea, FileInput } from "../../../components/form/inputs";
import MutationButton from "../../../components/form/mutation-button";
import { useInstanceV1Query } from "../../../lib/query/gts-api";
import { useUpdateInstanceMutation } from "../../../lib/query/admin";
import { InstanceV1 } from "../../../lib/types/instance";
import FormWithData from "../../../lib/form/form-with-data";
import useFormSubmit from "../../../lib/form/submit";
export default function InstanceSettings() {
return (
<FormWithData
dataQuery={useInstanceV1Query}
DataForm={InstanceSettingsForm}
/>
);
}
interface InstanceSettingsFormProps{
data: InstanceV1;
}
function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
const titleLimit = 40;
const shortDescLimit = 500;
const descLimit = 5000;
const termsLimit = 5000;
const form = {
title: useTextInput("title", {
source: instance,
validator: (val: string) => val.length <= titleLimit ? "" : `Instance title is ${val.length} characters; must be ${titleLimit} characters or less`
}),
thumbnail: useFileInput("thumbnail", { withPreview: true }),
thumbnailDesc: useTextInput("thumbnail_description", { source: instance }),
shortDesc: useTextInput("short_description", {
source: instance,
// Select "raw" text version of parsed field for editing.
valueSelector: (s: InstanceV1) => s.short_description_text,
validator: (val: string) => val.length <= shortDescLimit ? "" : `Instance short description is ${val.length} characters; must be ${shortDescLimit} characters or less`
}),
description: useTextInput("description", {
source: instance,
// Select "raw" text version of parsed field for editing.
valueSelector: (s: InstanceV1) => s.description_text,
validator: (val: string) => val.length <= descLimit ? "" : `Instance description is ${val.length} characters; must be ${descLimit} characters or less`
}),
terms: useTextInput("terms", {
source: instance,
// Select "raw" text version of parsed field for editing.
valueSelector: (s: InstanceV1) => s.terms_text,
validator: (val: string) => val.length <= termsLimit ? "" : `Instance terms and conditions is ${val.length} characters; must be ${termsLimit} characters or less`
}),
contactUser: useTextInput("contact_username", { source: instance, valueSelector: (s) => s.contact_account?.username }),
contactEmail: useTextInput("contact_email", { source: instance, valueSelector: (s) => s.email })
};
const [submitForm, result] = useFormSubmit(form, useUpdateInstanceMutation());
return (
<form onSubmit={submitForm}>
<h1>Instance Settings</h1>
<div className="form-section-docs">
<h3>Appearance</h3>
<a
href="https://docs.gotosocial.org/en/latest/admin/settings/#instance-appearance"
target="_blank"
className="docslink"
rel="noreferrer"
>
Learn more about these settings (opens in a new tab)
</a>
</div>
<TextInput
field={form.title}
label={`Instance title (max ${titleLimit} characters)`}
placeholder="My GoToSocial instance"
/>
<div className="file-upload" aria-labelledby="avatar">
<strong id="avatar">Instance avatar (1:1 images look best)</strong>
<div className="file-upload-with-preview">
<img
className="preview avatar"
src={form.thumbnail.previewValue ?? instance?.thumbnail}
alt={form.thumbnailDesc.value ?? (instance?.thumbnail ? `Thumbnail image for the instance` : "No instance thumbnail image set")}
/>
<div className="file-input-with-image-description">
<FileInput
field={form.thumbnail}
accept="image/png, image/jpeg, image/webp, image/gif"
/>
<TextInput
field={form.thumbnailDesc}
label="Avatar image description"
placeholder="A cute drawing of a smiling sloth."
/>
</div>
</div>
</div>
<div className="form-section-docs">
<h3>Descriptors</h3>
<a
href="https://docs.gotosocial.org/en/latest/admin/settings/#instance-descriptors"
target="_blank"
className="docslink"
rel="noreferrer"
>
Learn more about these settings (opens in a new tab)
</a>
</div>
<TextArea
field={form.shortDesc}
label={`Short description (markdown accepted, max ${shortDescLimit} characters)`}
placeholder="A small testing instance for the GoToSocial alpha software."
rows={6}
/>
<TextArea
field={form.description}
label={`Full description (markdown accepted, max ${descLimit} characters)`}
placeholder="A small testing instance for the GoToSocial alpha software. Just trying it out, my main instance is https://example.com"
rows={6}
/>
<TextArea
field={form.terms}
label={`Terms & Conditions (markdown accepted, max ${termsLimit} characters)`}
placeholder="Terms and conditions of using this instance, data policy, imprint, GDPR stuff, yadda yadda."
rows={6}
/>
<div className="form-section-docs">
<h3>Contact info</h3>
<a
href="https://docs.gotosocial.org/en/latest/admin/settings/#instance-contact-info"
target="_blank"
className="docslink"
rel="noreferrer"
>
Learn more about these settings (opens in a new tab)
</a>
</div>
<TextInput
field={form.contactUser}
label="Contact user (local account username)"
placeholder="admin"
/>
<TextInput
field={form.contactEmail}
label="Contact email"
placeholder="admin@example.com"
/>
<MutationButton label="Save" result={result} disabled={false} />
</form>
);
}