Add bot editing

This commit is contained in:
Snazzah 2021-08-30 19:10:55 +00:00 committed by GitHub
parent 86027f7b3d
commit 3678a32d95
4 changed files with 252 additions and 150 deletions

View file

@ -1,4 +1,5 @@
{ {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true,
} "compile-hero.disable-compile-files-on-did-save-code": true
}

View file

@ -51,7 +51,7 @@ const CheckboxDescription = styled.span`
color: var(--secondary-foreground); color: var(--secondary-foreground);
`; `;
const Checkmark = styled.div<{ checked: boolean }>` const Checkmark = styled.div<{ checked: boolean; contrast?: boolean }>`
margin: 4px; margin: 4px;
width: 24px; width: 24px;
height: 24px; height: 24px;
@ -66,6 +66,16 @@ const Checkmark = styled.div<{ checked: boolean }>`
color: var(--secondary-background); color: var(--secondary-background);
} }
${(props) =>
props.contrast &&
css`
background: var(--primary-background);
svg {
color: var(--primary-background);
}
`}
${(props) => ${(props) =>
props.checked && props.checked &&
css` css`
@ -76,6 +86,7 @@ const Checkmark = styled.div<{ checked: boolean }>`
export interface CheckboxProps { export interface CheckboxProps {
checked: boolean; checked: boolean;
disabled?: boolean; disabled?: boolean;
contrast?: boolean;
className?: string; className?: string;
children: Children; children: Children;
description?: Children; description?: Children;
@ -100,7 +111,7 @@ export default function Checkbox(props: CheckboxProps) {
!props.disabled && props.onChange(!props.checked) !props.disabled && props.onChange(!props.checked)
} }
/> />
<Checkmark checked={props.checked} className="check"> <Checkmark checked={props.checked} contrast={props.contrast} className="check">
<Check size={20} /> <Check size={20} />
</Checkmark> </Checkmark>
</CheckboxBase> </CheckboxBase>

View file

@ -21,6 +21,7 @@ import InputBox from "../../../components/ui/InputBox";
import Overline from "../../../components/ui/Overline"; import Overline from "../../../components/ui/Overline";
import Tip from "../../../components/ui/Tip"; import Tip from "../../../components/ui/Tip";
import CategoryButton from "../../../components/ui/fluent/CategoryButton"; import CategoryButton from "../../../components/ui/fluent/CategoryButton";
import { User } from "revolt.js/dist/maps/Users";
interface Data { interface Data {
_id: string; _id: string;
@ -29,6 +30,13 @@ interface Data {
interactions_url?: string; interactions_url?: string;
} }
interface Changes {
name?: string;
public?: boolean;
interactions_url?: string;
remove?: "InteractionsURL";
}
const BotBadge = styled.div` const BotBadge = styled.div`
display: inline-block; display: inline-block;
@ -44,50 +52,212 @@ const BotBadge = styled.div`
border-radius: calc(var(--border-radius) / 2); border-radius: calc(var(--border-radius) / 2);
`; `;
function BotEditor({ bot }: { bot: Data }) { interface Props {
const client = useClient(); bot: Bot;
const [data, setData] = useState<Data>(bot); user: User;
onDelete(): Promise<void>;
onUpdate(changes: Changes): Promise<void>;
}
function save() { function BotCard({ bot, user, onDelete, onUpdate }: Props) {
const changes: Record<string, string | boolean | undefined> = {}; const [data, setData] = useState<Data>({
if (data.username !== bot.username) changes.name = data.username; _id: bot._id,
username: user!.username,
public: bot.public,
interactions_url: bot.interactions_url,
});
const [saving, setSaving] = useState(false);
const [editMode, setEditMode] = useState(false);
const [usernameRef, setUsernameRef] = useState<HTMLInputElement | null>(
null,
);
const [interactionsRef, setInteractionsRef] =
useState<HTMLInputElement | null>(null);
const { writeClipboard, openScreen } = useIntermediate();
async function save() {
const changes: Changes = {};
if (data.username !== user!.username) changes.name = data.username;
if (data.public !== bot.public) changes.public = data.public; if (data.public !== bot.public) changes.public = data.public;
if (data.interactions_url !== bot.interactions_url) if (data.interactions_url === '')
changes.remove = 'InteractionsURL';
else if (data.interactions_url !== bot.interactions_url)
changes.interactions_url = data.interactions_url; changes.interactions_url = data.interactions_url;
setSaving(true);
client.bots.edit(bot._id, changes); try {
await onUpdate(changes);
setEditMode(false);
} catch (e) {
// TODO error handling
}
setSaving(false);
} }
return ( return (
<div> <div
<p> key={bot._id}
<InputBox style={{
value={data.username} background: "var(--secondary-background)",
onChange={(e) => margin: "8px 0",
setData({ ...data, username: e.currentTarget.value }) padding: "12px",
}}>
<div className={styles.infoheader}>
<div className={styles.container}>
<UserIcon
className={styles.avatar}
target={user!}
size={48}
onClick={() =>
openScreen({
id: "profile",
user_id: user!._id,
})
}
/>
{!editMode ? (
<div className={styles.userDetail}>
<div className={styles.userName}>
{user!.username}{" "}
<BotBadge>
<Text id="app.main.channel.bot" />
</BotBadge>
</div>
<div className={styles.userid}>
<Tooltip
content={<Text id="app.special.copy" />}>
<a
onClick={() =>
writeClipboard(user!._id)
}>
{user!._id}
</a>
</Tooltip>
</div>
</div>
) : (
<InputBox
ref={setUsernameRef}
value={data.username}
disabled={saving}
onChange={(e) =>
setData({
...data,
username: e.currentTarget.value,
})
}
/>
)}
</div>
{!editMode && (
<Tooltip
content={
bot.public
? "Bot is public. Anyone can invite it."
: "Bot is private. Only you can invite it."
}>
{bot.public ? (
<Globe size={24} />
) : (
<LockAlt size={24} />
)}
</Tooltip>
)}
<Button
onClick={() => {
if (editMode) {
setData({
_id: bot._id,
username: user!.username,
public: bot.public,
interactions_url: bot.interactions_url,
});
usernameRef!.value = user!.username;
interactionsRef!.value = bot.interactions_url || "";
setEditMode(false);
} else setEditMode(true);
}}
contrast>
{editMode ? "Cancel" : "Edit"}
</Button>
</div>
{!editMode && (
<CategoryButton
account
icon={<Key size={24} />}
onClick={() => writeClipboard(bot.token)}
description={
<>
{"••••••••••••••••••••••••••••••••••••"}{" "}
<a
onClick={(ev) =>
stopPropagation(
ev,
openScreen({
id: "token_reveal",
token: bot.token,
username: user!.username,
}),
)
}>
<Text id="app.special.modals.actions.reveal" />
</a>
</>
} }
/> action={<Clipboard size={18} />}>
</p> Token
<p> </CategoryButton>
<Checkbox )}
checked={data.public} {editMode && (
onChange={(v) => setData({ ...data, public: v })}> <>
is public <Checkbox
</Checkbox> checked={data.public}
</p> disabled={saving}
<p>interactions url: (reserved for the future)</p> contrast
<p> description="Whether to allow other users to invite this bot."
<InputBox onChange={(v) => setData({ ...data, public: v })}>
value={data.interactions_url} Public Bot
onChange={(e) => </Checkbox>
setData({ <h3>Interactions URL</h3>
...data, <h5>This field is reserved for the future.</h5>
interactions_url: e.currentTarget.value, <InputBox
}) ref={setInteractionsRef}
} value={data.interactions_url}
/> disabled={saving}
</p> onChange={(e) =>
<Button onClick={save}>save</Button> setData({
...data,
interactions_url:
e.currentTarget.value,
})
}
/>
</>
)}
<div className={styles.buttonRow}>
{editMode && (
<>
<Button accent onClick={save}>
Save
</Button>
<Button
error
onClick={onDelete}>
Delete
</Button>
</>
)}
{!editMode && (
<Button
onClick={() =>
writeClipboard(`${window.origin}/bot/${bot._id}`)
}>
Copy Invite Link
</Button>
)}
</div>
</div> </div>
); );
} }
@ -102,8 +272,6 @@ export const MyBots = observer(() => {
}, []); }, []);
const [name, setName] = useState(""); const [name, setName] = useState("");
const [editMode, setEditMode] = useState(false);
const { writeClipboard, openScreen } = useIntermediate();
return ( return (
<div className={styles.myBots}> <div className={styles.myBots}>
@ -132,116 +300,31 @@ export const MyBots = observer(() => {
</p> </p>
<Overline>my bots</Overline> <Overline>my bots</Overline>
{bots?.map((bot) => { {bots?.map((bot) => {
const user = client.users.get(bot._id); const user = client.users.get(bot._id)!;
return ( return (
<div <BotCard
key={bot._id} key={bot._id}
style={{ bot={bot}
background: "var(--secondary-background)", user={user}
margin: "8px 0", onDelete={() =>
padding: "12px", client.bots
}}>
<div className={styles.infoheader}>
<div className={styles.container}>
<UserIcon
className={styles.avatar}
target={user!}
size={48}
onClick={() =>
openScreen({
id: "profile",
user_id: user!._id,
})
}
/>
<div className={styles.userDetail}>
<div className={styles.userName}>
{user!.username}{" "}
<BotBadge>
<Text id="app.main.channel.bot" />
</BotBadge>
</div>
<div className={styles.userid}>
<Tooltip
content={
<Text id="app.special.copy" />
}>
<a
onClick={() =>
writeClipboard(
client.user!._id,
)
}>
{client.user!._id}
</a>
</Tooltip>
</div>
</div>
</div>
<Tooltip content={bot.public ? "Bot is public. Anyone can invite it." : "Bot is private. Only you can invite it."}>
{bot.public ? <Globe size={24} /> : <LockAlt size={24} />}
</Tooltip>
{/* <Button onClick={() => switchPage("profile")} contrast>
<Text id="app.settings.pages.profile.edit_profile" />
</Button> */}
</div>
<CategoryButton
account
icon={<Key size={24} />}
onClick={() => writeClipboard(bot.token)}
description={
<>
{"••••••••••••••••••••••••••••••••••••"}{" "}
<a
onClick={(ev) =>
stopPropagation(
ev,
openScreen({
id: "token_reveal",
token: bot.token,
username: user!.username,
}),
)
}>
<Text id="app.special.modals.actions.reveal" />
</a>
</>
}
action={<Clipboard size={18} />}>
Token
</CategoryButton>
<BotEditor
bot={{
...bot,
username: user!.username,
}}
/>
<Button
error
onClick={() =>
client.bots
.delete(bot._id) .delete(bot._id)
.then(() => .then(() => setBots(bots.filter((x) => x._id !== bot._id)))
setBots(
bots.filter( }
(x) => x._id !== bot._id, onUpdate={(changes: Changes) =>
), client.bots.edit(bot._id, changes).then(() => setBots(
), bots.map((x) => {
) if (x._id === bot._id) {
}> if ('public' in changes && typeof changes.public === 'boolean') x.public = changes.public;
delete if ('interactions_url' in changes) x.interactions_url = changes.interactions_url;
</Button> if (changes.remove === 'InteractionsURL') x.interactions_url = undefined;
<Button }
onClick={() => return x;
writeClipboard( }),
`${window.origin}/bot/${bot._id}`, ))
) }
}> />
copy invite link
</Button>
</div>
); );
})} })}
</div> </div>

View file

@ -584,6 +584,13 @@
} }
} }
} }
.buttonRow {
margin-top: 2em;
display: flex;
flex-direction: row;
gap: 10px;
}
} }
section { section {