Bring back ability to edit roles on members.

Improve ban list design.
This commit is contained in:
Paul 2021-07-24 18:46:33 +01:00
parent dbaf246c27
commit 71020e6e8c
6 changed files with 149 additions and 38 deletions

View file

@ -94,7 +94,7 @@
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scroll": "^1.8.2", "react-scroll": "^1.8.2",
"redux": "^4.1.0", "redux": "^4.1.0",
"revolt.js": "4.3.3-alpha.16", "revolt.js": "4.3.3-alpha.17",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sass": "^1.35.1", "sass": "^1.35.1",
"shade-blend-color": "^1.0.0", "shade-blend-color": "^1.0.0",

View file

@ -1,5 +1,6 @@
import { XCircle } from "@styled-icons/boxicons-regular"; import { XCircle } from "@styled-icons/boxicons-regular";
import { Servers, Users } from "revolt.js/dist/api/objects"; import { Servers, Users } from "revolt.js/dist/api/objects";
import { Route } from "revolt.js/dist/api/routes";
import styles from "./Panes.module.scss"; import styles from "./Panes.module.scss";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
@ -19,11 +20,7 @@ export function Bans({ server }: Props) {
const client = useContext(AppContext); const client = useContext(AppContext);
const [deleting, setDelete] = useState<string[]>([]); const [deleting, setDelete] = useState<string[]>([]);
const [data, setData] = useState< const [data, setData] = useState<
| { Route<"GET", "/servers/id/bans">["response"] | undefined
users: Pick<Users.User, "_id" | "username" | "avatar">[];
bans: Servers.Ban[];
}
| undefined
>(undefined); >(undefined);
useEffect(() => { useEffect(() => {

View file

@ -64,12 +64,14 @@ export function Invites({ server }: Props) {
<code>{invite._id}</code> <code>{invite._id}</code>
<span> <span>
<UserIcon target={creator} size={24} />{" "} <UserIcon target={creator} size={24} />{" "}
{creator?.username ?? "unknown"} {creator?.username ?? (
<Text id="app.main.channel.unknown_user" />
)}
</span> </span>
<span> <span>
{channel && creator {channel && creator
? getChannelName(ctx.client, channel, true) ? getChannelName(ctx.client, channel, true)
: "#unknown"} : "#??"}
</span> </span>
<IconButton <IconButton
onClick={async () => { onClick={async () => {

View file

@ -1,21 +1,30 @@
import { ChevronDown } from "@styled-icons/boxicons-regular";
import { isEqual } from "lodash";
import { Servers } from "revolt.js/dist/api/objects"; import { Servers } from "revolt.js/dist/api/objects";
import styles from "./Panes.module.scss"; import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { useForceUpdate, useUsers } from "../../../context/revoltjs/hooks"; import { useForceUpdate, useUsers } from "../../../context/revoltjs/hooks";
import UserIcon from "../../../components/common/user/UserIcon";
import Button from "../../../components/ui/Button";
import Checkbox from "../../../components/ui/Checkbox";
import IconButton from "../../../components/ui/IconButton";
import Overline from "../../../components/ui/Overline";
interface Props { interface Props {
server: Servers.Server; server: Servers.Server;
} }
// ! FIXME: bad code :)
export function Members({ server }: Props) { export function Members({ server }: Props) {
const [members, setMembers] = useState<Servers.Member[] | undefined>( const [members, setMembers] = useState<Servers.Member[] | undefined>(
undefined, undefined,
); );
const ctx = useForceUpdate(); const ctx = useForceUpdate();
const [selected, setSelected] = useState<undefined | string>();
const users = useUsers(members?.map((x) => x._id.user) ?? [], ctx); const users = useUsers(members?.map((x) => x._id.user) ?? [], ctx);
useEffect(() => { useEffect(() => {
@ -24,21 +33,125 @@ export function Members({ server }: Props) {
.then((members) => setMembers(members)); .then((members) => setMembers(members));
}, []); }, []);
const [roles, setRoles] = useState<string[]>([]);
useEffect(() => {
if (selected) {
setRoles(
members!.find((x) => x._id.user === selected)?.roles ?? [],
);
}
}, [selected]);
return ( return (
<div className={styles.members}> <div className={styles.userList}>
<div className={styles.subtitle}> <div className={styles.subtitle}>
{members?.length ?? 0} Members {members?.length ?? 0} Members
</div> </div>
{members && {members &&
members.length > 0 && members.length > 0 &&
users?.map( members
(x) => .map((x) => {
x && ( return {
<div className={styles.member}> member: x,
<div>@{x.username}</div> user: users.find((y) => y?._id === x._id.user),
</div> };
), })
.map(({ member, user }) => (
<>
<div
key={member._id.user}
className={styles.member}
data-open={selected === member._id.user}
onClick={() =>
setSelected(
selected === member._id.user
? undefined
: member._id.user,
)
}>
<span>
<UserIcon target={user} size={24} />{" "}
{user?.username ?? (
<Text id="app.main.channel.unknown_user" />
)} )}
</span>
<IconButton className={styles.chevron}>
<ChevronDown size={24} />
</IconButton>
</div>
{selected === member._id.user && (
<div
key={"drop_" + member._id.user}
className={styles.memberView}>
<Overline type="subtle">Roles</Overline>
{Object.keys(server.roles ?? {}).map(
(key) => {
let role = server.roles![key];
return (
<Checkbox
checked={
roles.includes(key) ??
false
}
onChange={(v) => {
if (v) {
setRoles([
...roles,
key,
]);
} else {
setRoles(
roles.filter(
(x) =>
x !==
key,
),
);
}
}}>
<span
style={{
color: role.colour,
}}>
{role.name}
</span>
</Checkbox>
);
},
)}
<Button
compact
disabled={isEqual(
member.roles ?? [],
roles,
)}
onClick={async () => {
await ctx.client.servers.members.editMember(
server._id,
member._id.user,
{
roles,
},
);
setMembers(
members.map((x) =>
x._id.user ===
member._id.user
? {
...x,
roles,
}
: x,
),
);
}}>
<Text id="app.special.modals.actions.save" />
</Button>
</div>
)}
</>
))}
</div> </div>
); );
} }

View file

@ -41,7 +41,8 @@
} }
.invite, .invite,
.ban { .ban,
.member {
gap: 8px; gap: 8px;
padding: 10px; padding: 10px;
display: flex; display: flex;
@ -69,25 +70,23 @@
opacity: 0.5; opacity: 0.5;
} }
} }
}
.members {
.subtitle {
display: flex;
justify-content: space-between;
font-size: 13px;
text-transform: uppercase;
color: var(--secondary-foreground);
font-weight: 700;
}
.member { .member {
gap: 8px; cursor: pointer;
.chevron {
transition: 0.2s ease all;
}
&:not([data-open="true"]) .chevron {
transform: rotateZ(90deg);
}
}
.memberView {
padding: 10px; padding: 10px;
display: flex; margin: 0 10px;
align-items: center; background: var(--background);
flex-direction: row;
background: var(--secondary-background);
} }
} }

View file

@ -3563,10 +3563,10 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
revolt.js@4.3.3-alpha.16: revolt.js@4.3.3-alpha.17:
version "4.3.3-alpha.16" version "4.3.3-alpha.17"
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.16.tgz#ed595d34cdefc1d8756694787abda01e28d373e8" resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.3-alpha.17.tgz#0745d251c695840b87e98098bcc4d67c7cc15de5"
integrity sha512-UejqRKYoO98Uj2eki6dZMbEbX8msYz/JgY3EYxhbe1qMnBvLD8JxjcemHTLBf2Iytom8fFQ1EV0ee4Z89Jkcjw== integrity sha512-MjxVnkkeX5md5NxZNRS9fl06jsjcDciAxKnbZ2rkBYJofQ94tvr1CYBWvFhS/u/tAR80HAPIEjJVC9HKJDK9Fg==
dependencies: dependencies:
"@insertish/mutable" "1.1.0" "@insertish/mutable" "1.1.0"
axios "^0.19.2" axios "^0.19.2"