Merge pull request #204 from Snazzah/bots-banner-and-info

Add banner and info box to bots page
This commit is contained in:
Paul Makles 2021-09-09 20:49:54 +01:00 committed by GitHub
commit 932756d7c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 165 additions and 7 deletions

View file

@ -3,20 +3,27 @@ import { LockAlt } from "@styled-icons/boxicons-solid";
import type { AxiosError } from "axios"; import type { AxiosError } from "axios";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Bot } from "revolt-api/types/Bots"; import { Bot } from "revolt-api/types/Bots";
import { Profile as ProfileI } from "revolt-api/types/Users";
import { User } from "revolt.js/dist/maps/Users"; import { User } from "revolt.js/dist/maps/Users";
import styled from "styled-components"; import styled from "styled-components";
import styles from "./Panes.module.scss"; import styles from "./Panes.module.scss";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks"; import { useCallback, useEffect, useState } from "preact/hooks";
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
import { internalEmit } from "../../../lib/eventEmitter"; import { internalEmit } from "../../../lib/eventEmitter";
import { useTranslation } from "../../../lib/i18n";
import { stopPropagation } from "../../../lib/stopPropagation"; import { stopPropagation } from "../../../lib/stopPropagation";
import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { FileUploader } from "../../../context/revoltjs/FileUploads"; import { FileUploader } from "../../../context/revoltjs/FileUploads";
import { useClient } from "../../../context/revoltjs/RevoltClient"; import { useClient } from "../../../context/revoltjs/RevoltClient";
import AutoComplete, {
useAutoComplete,
} from "../../../components/common/AutoComplete";
import CollapsibleSection from "../../../components/common/CollapsibleSection";
import Tooltip from "../../../components/common/Tooltip"; import Tooltip from "../../../components/common/Tooltip";
import UserIcon from "../../../components/common/user/UserIcon"; import UserIcon from "../../../components/common/user/UserIcon";
import Button from "../../../components/ui/Button"; import Button from "../../../components/ui/Button";
@ -62,6 +69,7 @@ interface Props {
function BotCard({ bot, onDelete, onUpdate }: Props) { function BotCard({ bot, onDelete, onUpdate }: Props) {
const client = useClient(); const client = useClient();
const translate = useTranslation();
const [user, setUser] = useState<User>(client.users.get(bot._id)!); const [user, setUser] = useState<User>(client.users.get(bot._id)!);
const [data, setData] = useState<Data>({ const [data, setData] = useState<Data>({
_id: bot._id, _id: bot._id,
@ -79,6 +87,37 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
useState<HTMLInputElement | null>(null); useState<HTMLInputElement | null>(null);
const { writeClipboard, openScreen } = useIntermediate(); const { writeClipboard, openScreen } = useIntermediate();
const [profile, setProfile] = useState<undefined | ProfileI>(undefined);
const refreshProfile = useCallback(() => {
client
.request(
"GET",
`/users/${bot._id}/profile` as "/users/id/profile",
{
headers: { "x-bot-token": bot.token },
transformRequest: (data, headers) => {
// Remove user headers for this request
delete headers["x-user-id"];
delete headers["x-session-token"];
return data;
},
},
)
.then((profile) => setProfile(profile ?? {}));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user, setProfile]);
useEffect(() => {
if (profile === undefined && editMode) refreshProfile();
}, [profile, editMode, refreshProfile]);
const [changed, setChanged] = useState(false);
function setContent(content?: string) {
setProfile({ ...profile, content });
if (!changed) setChanged(true);
}
async function save() { async function save() {
const changes: Changes = {}; const changes: Changes = {};
if (data.username !== user!.username) changes.name = data.username; if (data.username !== user!.username) changes.name = data.username;
@ -90,7 +129,9 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
setError(""); setError("");
try { try {
await client.bots.edit(bot._id, changes); await client.bots.edit(bot._id, changes);
if (changed) await editBotContent(profile?.content);
onUpdate(changes); onUpdate(changes);
setChanged(false);
setEditMode(false); setEditMode(false);
} catch (e) { } catch (e) {
const err = e as AxiosError; const err = e as AxiosError;
@ -128,6 +169,63 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
setSaving(false); setSaving(false);
} }
async function editBotBackground(background?: string) {
setSaving(true);
setError("");
await client.request("PATCH", "/users/id", {
headers: { "x-bot-token": bot.token },
transformRequest: (data, headers) => {
// Remove user headers for this request
delete headers["x-user-id"];
delete headers["x-session-token"];
return data;
},
data: JSON.stringify(
background
? { profile: { background } }
: { remove: "ProfileBackground" },
),
});
if (!background) setProfile({ ...profile, background: undefined });
else refreshProfile();
setSaving(false);
}
async function editBotContent(content?: string) {
setSaving(true);
setError("");
await client.request("PATCH", "/users/id", {
headers: { "x-bot-token": bot.token },
transformRequest: (data, headers) => {
// Remove user headers for this request
delete headers["x-user-id"];
delete headers["x-session-token"];
return data;
},
data: JSON.stringify(
content
? { profile: { content } }
: { remove: "ProfileContent" },
),
});
if (!content) setProfile({ ...profile, content: undefined });
else refreshProfile();
setSaving(false);
}
const {
onChange,
onKeyUp,
onKeyDown,
onFocus,
onBlur,
...autoCompleteProps
} = useAutoComplete(setContent, {
users: { type: "all" },
});
return ( return (
<div key={bot._id} className={styles.botCard}> <div key={bot._id} className={styles.botCard}>
<div className={styles.infoheader}> <div className={styles.infoheader}>
@ -270,6 +368,60 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
)} )}
{editMode && ( {editMode && (
<div className={styles.botSection}> <div className={styles.botSection}>
<CollapsibleSection
defaultValue={false}
id={`bot_profile_${bot._id}`}
summary="Profile">
<h3>
<Text id="app.settings.pages.profile.custom_background" />
</h3>
<FileUploader
height={92}
style="banner"
behaviour="upload"
fileType="backgrounds"
maxFileSize={6_000_000}
onUpload={(background) =>
editBotBackground(background)
}
remove={() => editBotBackground()}
previewURL={
profile?.background
? client.generateFileURL(
profile.background,
{ width: 1000 },
true,
)
: undefined
}
/>
<h3>
<Text id="app.settings.pages.profile.info" />
</h3>
<AutoComplete detached {...autoCompleteProps} />
<TextAreaAutoSize
maxRows={10}
minHeight={200}
maxLength={2000}
value={profile?.content ?? ""}
disabled={typeof profile === "undefined"}
onChange={(ev) => {
onChange(ev);
setContent(ev.currentTarget.value);
}}
placeholder={translate(
`app.settings.pages.profile.${
typeof profile === "undefined"
? "fetching"
: "placeholder"
}`,
)}
onKeyUp={onKeyUp}
onKeyDown={onKeyDown}
onFocus={onFocus}
onBlur={onBlur}
/>
</CollapsibleSection>
<Checkbox <Checkbox
checked={data.public} checked={data.public}
disabled={saving} disabled={saving}

View file

@ -525,15 +525,11 @@
} }
.myBots { .myBots {
.botCard { .botCard {
background: var(--secondary-background); background: var(--secondary-background);
margin: 8px 0; margin: 8px 0;
padding: 12px; padding: 12px;
border-radius: var(--border-radius); border-radius: var(--border-radius);
h5 { margin-bottom: 1em }
h3 { margin-bottom: 0 }
} }
.botSection { .botSection {
@ -542,7 +538,17 @@
flex-direction: column; flex-direction: column;
gap: 5px; gap: 5px;
label { margin-top: 0 } > h5 {
margin-bottom: 1em;
}
> h3 {
margin-bottom: 0;
}
details,
label {
margin-top: 0;
}
} }
.infoheader { .infoheader {