forked from abner/for-legacy-web
merge: branch 'quark/permissions'
This commit is contained in:
@@ -3,7 +3,7 @@ import { Ghost } from "@styled-icons/boxicons-solid";
|
||||
import { reaction } from "mobx";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Redirect, useParams } from "react-router-dom";
|
||||
import { Channel as ChannelI } from "revolt.js/dist/maps/Channels";
|
||||
import { Channel as ChannelI } from "revolt.js";
|
||||
import styled from "styled-components/macro";
|
||||
|
||||
import { Text } from "preact-i18n";
|
||||
@@ -97,29 +97,35 @@ const PlaceholderBase = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export function Channel({ id, server_id }: { id: string; server_id: string }) {
|
||||
const client = useClient();
|
||||
const channel = client.channels.get(id);
|
||||
export const Channel = observer(
|
||||
({ id, server_id }: { id: string; server_id: string }) => {
|
||||
const client = useClient();
|
||||
|
||||
if (server_id && !channel) {
|
||||
const server = client.servers.get(server_id);
|
||||
if (server && server.channel_ids.length > 0) {
|
||||
return (
|
||||
<Redirect
|
||||
to={`/server/${server_id}/channel/${server.channel_ids[0]}`}
|
||||
/>
|
||||
);
|
||||
if (!client.channels.exists(id)) {
|
||||
if (server_id) {
|
||||
const server = client.servers.get(server_id);
|
||||
if (server && server.channel_ids.length > 0) {
|
||||
return (
|
||||
<Redirect
|
||||
to={`/server/${server_id}/channel/${server.channel_ids[0]}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return <Redirect to="/" />;
|
||||
}
|
||||
|
||||
return <ChannelPlaceholder />;
|
||||
}
|
||||
}
|
||||
|
||||
if (!channel) return <ChannelPlaceholder />;
|
||||
const channel = client.channels.get(id)!;
|
||||
if (channel.channel_type === "VoiceChannel") {
|
||||
return <VoiceChannel channel={channel} />;
|
||||
}
|
||||
|
||||
if (channel.channel_type === "VoiceChannel") {
|
||||
return <VoiceChannel channel={channel} />;
|
||||
}
|
||||
|
||||
return <TextChannel channel={channel} />;
|
||||
}
|
||||
return <TextChannel channel={channel} />;
|
||||
},
|
||||
);
|
||||
|
||||
const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
|
||||
const layout = useApplicationState().layout;
|
||||
@@ -143,9 +149,9 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
|
||||
// Mark channel as read.
|
||||
useEffect(() => {
|
||||
setLastId(
|
||||
channel.unread
|
||||
(channel.unread
|
||||
? channel.client.unreads?.getUnread(channel._id)?.last_id
|
||||
: undefined ?? undefined,
|
||||
: undefined) ?? undefined,
|
||||
);
|
||||
|
||||
const checkUnread = () =>
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
} from "@styled-icons/boxicons-regular";
|
||||
import { Notepad, Group } from "@styled-icons/boxicons-solid";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||
import { User } from "revolt.js/dist/maps/Users";
|
||||
import { Channel } from "revolt.js";
|
||||
import { User } from "revolt.js";
|
||||
import styled, { css } from "styled-components/macro";
|
||||
|
||||
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||
import { Channel } from "revolt.js";
|
||||
import styled from "styled-components/macro";
|
||||
|
||||
import { Text } from "preact-i18n";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { runInAction } from "mobx";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import { animateScroll } from "react-scroll";
|
||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||
import { Channel } from "revolt.js";
|
||||
import styled from "styled-components/macro";
|
||||
import useResizeObserver from "use-resize-observer";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Message } from "revolt.js/dist/maps/Messages";
|
||||
import { Message } from "revolt.js";
|
||||
import styled from "styled-components/macro";
|
||||
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
import { X } from "@styled-icons/boxicons-regular";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Masquerade } from "revolt-api/types/Channels";
|
||||
import { RelationshipStatus } from "revolt-api/types/Users";
|
||||
import { Message as MessageI } from "revolt.js/dist/maps/Messages";
|
||||
import { Nullable } from "revolt.js/dist/util/null";
|
||||
import { API } from "revolt.js";
|
||||
import { Message as MessageI } from "revolt.js";
|
||||
import { Nullable } from "revolt.js";
|
||||
import styled from "styled-components/macro";
|
||||
import { decodeTime } from "ulid";
|
||||
|
||||
@@ -99,10 +98,10 @@ export default observer(({ last_id, renderer, highlight }: Props) => {
|
||||
function compare(
|
||||
current: string,
|
||||
curAuthor: string,
|
||||
currentMasq: Nullable<Masquerade>,
|
||||
currentMasq: Nullable<API.Masquerade>,
|
||||
previous: string,
|
||||
prevAuthor: string,
|
||||
previousMasq: Nullable<Masquerade>,
|
||||
previousMasq: Nullable<API.Masquerade>,
|
||||
) {
|
||||
head = false;
|
||||
const atime = decodeTime(current),
|
||||
@@ -172,9 +171,7 @@ export default observer(({ last_id, renderer, highlight }: Props) => {
|
||||
highlight={highlight === message._id}
|
||||
/>,
|
||||
);
|
||||
} else if (
|
||||
message.author?.relationship === RelationshipStatus.Blocked
|
||||
) {
|
||||
} else if (message.author?.relationship === "Blocked") {
|
||||
blocked++;
|
||||
} else {
|
||||
if (blocked > 0) pushBlocked();
|
||||
|
||||
@@ -2,8 +2,7 @@ import { X, Plus } from "@styled-icons/boxicons-regular";
|
||||
import { PhoneCall, Envelope, UserX } from "@styled-icons/boxicons-solid";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { RelationshipStatus } from "revolt-api/types/Users";
|
||||
import { User } from "revolt.js/dist/maps/Users";
|
||||
import { User } from "revolt.js";
|
||||
|
||||
import styles from "./Friend.module.scss";
|
||||
import classNames from "classnames";
|
||||
@@ -33,7 +32,7 @@ export const Friend = observer(({ user }: Props) => {
|
||||
const actions: Children[] = [];
|
||||
let subtext: Children = null;
|
||||
|
||||
if (user.relationship === RelationshipStatus.Friend) {
|
||||
if (user.relationship === "Friend") {
|
||||
subtext = <UserStatus user={user} />;
|
||||
actions.push(
|
||||
<>
|
||||
@@ -70,7 +69,7 @@ export const Friend = observer(({ user }: Props) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (user.relationship === RelationshipStatus.Incoming) {
|
||||
if (user.relationship === "Incoming") {
|
||||
actions.push(
|
||||
<IconButton
|
||||
type="circle"
|
||||
@@ -83,14 +82,14 @@ export const Friend = observer(({ user }: Props) => {
|
||||
subtext = <Text id="app.special.friends.incoming" />;
|
||||
}
|
||||
|
||||
if (user.relationship === RelationshipStatus.Outgoing) {
|
||||
if (user.relationship === "Outgoing") {
|
||||
subtext = <Text id="app.special.friends.outgoing" />;
|
||||
}
|
||||
|
||||
if (
|
||||
user.relationship === RelationshipStatus.Friend ||
|
||||
user.relationship === RelationshipStatus.Outgoing ||
|
||||
user.relationship === RelationshipStatus.Incoming
|
||||
user.relationship === "Friend" ||
|
||||
user.relationship === "Outgoing" ||
|
||||
user.relationship === "Incoming"
|
||||
) {
|
||||
actions.push(
|
||||
<IconButton
|
||||
@@ -103,7 +102,7 @@ export const Friend = observer(({ user }: Props) => {
|
||||
onClick={(ev) =>
|
||||
stopPropagation(
|
||||
ev,
|
||||
user.relationship === RelationshipStatus.Friend
|
||||
user.relationship === "Friend"
|
||||
? openScreen({
|
||||
id: "special_prompt",
|
||||
type: "unfriend_user",
|
||||
@@ -117,7 +116,7 @@ export const Friend = observer(({ user }: Props) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (user.relationship === RelationshipStatus.Blocked) {
|
||||
if (user.relationship === "Blocked") {
|
||||
actions.push(
|
||||
<IconButton
|
||||
type="circle"
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { ChevronRight } from "@styled-icons/boxicons-regular";
|
||||
import { UserDetail, MessageAdd, UserPlus } from "@styled-icons/boxicons-solid";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { RelationshipStatus, Presence } from "revolt-api/types/Users";
|
||||
import { User } from "revolt.js/dist/maps/Users";
|
||||
import styled, { css } from "styled-components/macro";
|
||||
import { User } from "revolt.js";
|
||||
|
||||
import styles from "./Friend.module.scss";
|
||||
import classNames from "classnames";
|
||||
@@ -31,36 +29,32 @@ export default observer(() => {
|
||||
const users = [...client.users.values()];
|
||||
users.sort((a, b) => a.username.localeCompare(b.username));
|
||||
|
||||
const friends = users.filter(
|
||||
(x) => x.relationship === RelationshipStatus.Friend,
|
||||
);
|
||||
const friends = users.filter((x) => x.relationship === "Friend");
|
||||
|
||||
const lists = [
|
||||
[
|
||||
"",
|
||||
users.filter((x) => x.relationship === RelationshipStatus.Incoming),
|
||||
],
|
||||
["", users.filter((x) => x.relationship === "Incoming")],
|
||||
[
|
||||
"app.special.friends.sent",
|
||||
users.filter((x) => x.relationship === RelationshipStatus.Outgoing),
|
||||
users.filter((x) => x.relationship === "Outgoing"),
|
||||
"outgoing",
|
||||
],
|
||||
[
|
||||
"app.status.online",
|
||||
friends.filter(
|
||||
(x) => x.online && x.status?.presence !== Presence.Invisible,
|
||||
(x) => x.online && x.status?.presence !== "Invisible",
|
||||
),
|
||||
"online",
|
||||
],
|
||||
[
|
||||
"app.status.offline",
|
||||
friends.filter(
|
||||
(x) => !x.online || x.status?.presence === Presence.Invisible,
|
||||
(x) => !x.online || x.status?.presence === "Invisible",
|
||||
),
|
||||
"offline",
|
||||
],
|
||||
[
|
||||
"app.special.friends.blocked",
|
||||
users.filter((x) => x.relationship === RelationshipStatus.Blocked),
|
||||
users.filter((x) => x.relationship === "Blocked"),
|
||||
"blocked",
|
||||
],
|
||||
] as [string, User[], string][];
|
||||
|
||||
@@ -21,9 +21,9 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
||||
|
||||
import { useApplicationState } from "../../mobx/State";
|
||||
|
||||
import { useIntermediate } from "../../context/intermediate/Intermediate";
|
||||
import { AppContext } from "../../context/revoltjs/RevoltClient";
|
||||
|
||||
import Tooltip from "../../components/common/Tooltip";
|
||||
import { PageHeader } from "../../components/ui/Header";
|
||||
import CategoryButton from "../../components/ui/fluent/CategoryButton";
|
||||
import wideSVG from "/assets/wide.svg";
|
||||
@@ -42,6 +42,7 @@ const Overlay = styled.div`
|
||||
`;
|
||||
|
||||
export default observer(() => {
|
||||
const { openScreen } = useIntermediate();
|
||||
const client = useContext(AppContext);
|
||||
const state = useApplicationState();
|
||||
|
||||
@@ -93,7 +94,13 @@ export default observer(() => {
|
||||
<img src={wideSVG} />
|
||||
</h3>
|
||||
<div className={styles.actions}>
|
||||
<Link to="/settings">
|
||||
<a
|
||||
onClick={() =>
|
||||
openScreen({
|
||||
id: "special_input",
|
||||
type: "create_group",
|
||||
})
|
||||
}>
|
||||
<CategoryButton
|
||||
action="chevron"
|
||||
icon={<PlusCircle size={32} />}
|
||||
@@ -102,7 +109,7 @@ export default observer(() => {
|
||||
}>
|
||||
<Text id="app.home.group" />
|
||||
</CategoryButton>
|
||||
</Link>
|
||||
</a>
|
||||
<Link to="/discover">
|
||||
<a>
|
||||
<CategoryButton
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ArrowBack } from "@styled-icons/boxicons-regular";
|
||||
import { autorun } from "mobx";
|
||||
import { Redirect, useHistory, useParams } from "react-router-dom";
|
||||
import { RetrievedInvite } from "revolt-api/types/Invites";
|
||||
import { API } from "revolt.js";
|
||||
|
||||
import styles from "./Invite.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
@@ -36,7 +36,7 @@ export default function Invite() {
|
||||
const { code } = useParams<{ code: string }>();
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
const [invite, setInvite] = useState<RetrievedInvite | undefined>(
|
||||
const [invite, setInvite] = useState<API.InviteResponse | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
@@ -92,6 +92,8 @@ export default function Invite() {
|
||||
);
|
||||
}
|
||||
|
||||
if (invite.type === "Group") return <h1>unimplemented</h1>;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.invite}
|
||||
@@ -156,42 +158,17 @@ export default function Invite() {
|
||||
return history.push("/");
|
||||
}
|
||||
|
||||
setProcessing(true);
|
||||
|
||||
try {
|
||||
setProcessing(true);
|
||||
await client.joinInvite(invite);
|
||||
|
||||
if (invite.type === "Server") {
|
||||
if (
|
||||
client.servers.get(invite.server_id)
|
||||
) {
|
||||
history.push(
|
||||
`/server/${invite.server_id}/channel/${invite.channel_id}`,
|
||||
);
|
||||
}
|
||||
|
||||
const dispose = autorun(() => {
|
||||
const server = client.servers.get(
|
||||
invite.server_id,
|
||||
);
|
||||
|
||||
defer(() => {
|
||||
if (server) {
|
||||
client.unreads!.markMultipleRead(
|
||||
server.channel_ids,
|
||||
);
|
||||
|
||||
history.push(
|
||||
`/server/${server._id}/channel/${invite.channel_id}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
dispose();
|
||||
});
|
||||
}
|
||||
|
||||
await client.joinInvite(code);
|
||||
history.push(
|
||||
`/server/${invite.server_id}/channel/${invite.channel_id}`,
|
||||
);
|
||||
} catch (err) {
|
||||
setError(takeError(err));
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
}}>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { ServerPermission } from "revolt.js";
|
||||
import { Route } from "revolt.js/dist/api/routes";
|
||||
import { API, Permission } from "revolt.js";
|
||||
import styled from "styled-components/macro";
|
||||
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
@@ -37,8 +36,7 @@ const Option = styled.div`
|
||||
export default function InviteBot() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const client = useClient();
|
||||
const [data, setData] =
|
||||
useState<Route<"GET", "/bots/id/invite">["response"]>();
|
||||
const [data, setData] = useState<API.PublicBot>();
|
||||
|
||||
useEffect(() => {
|
||||
client.bots.fetchPublic(id).then(setData);
|
||||
@@ -72,11 +70,7 @@ export default function InviteBot() {
|
||||
onChange={(e) => setServer(e.currentTarget.value)}>
|
||||
<option value="none">Select a server</option>
|
||||
{[...client.servers.values()]
|
||||
.filter(
|
||||
(x) =>
|
||||
x.permission &
|
||||
ServerPermission.ManageServer,
|
||||
)
|
||||
.filter((x) => x.havePermission("ManageServer"))
|
||||
.map((server) => (
|
||||
<option value={server._id} key={server._id}>
|
||||
{server.name}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { detect } from "detect-browser";
|
||||
import { Session } from "revolt-api/types/Auth";
|
||||
import { Client } from "revolt.js";
|
||||
import { API } from "revolt.js";
|
||||
|
||||
import { useApplicationState } from "../../../mobx/State";
|
||||
|
||||
@@ -44,25 +43,26 @@ export function FormLogin() {
|
||||
// This should be replaced in the future.
|
||||
const client = state.config.createClient();
|
||||
await client.fetchConfiguration();
|
||||
const session = (await client.req(
|
||||
"POST",
|
||||
"/auth/session/login",
|
||||
{ ...data, friendly_name },
|
||||
)) as unknown as Session;
|
||||
const session = await client.api.post("/auth/session/login", {
|
||||
...data,
|
||||
friendly_name,
|
||||
});
|
||||
|
||||
client.session = session;
|
||||
(client as any).Axios.defaults.headers = {
|
||||
"x-session-token": session?.token,
|
||||
};
|
||||
|
||||
async function login() {
|
||||
state.auth.setSession(session);
|
||||
if (session.result !== "Success") {
|
||||
alert("unsupported!");
|
||||
return;
|
||||
}
|
||||
|
||||
const { onboarding } = await client.req(
|
||||
"GET",
|
||||
"/onboard/hello",
|
||||
);
|
||||
const s = session;
|
||||
|
||||
client.session = session;
|
||||
(client as any).$updateHeaders();
|
||||
|
||||
async function login() {
|
||||
state.auth.setSession(s);
|
||||
}
|
||||
|
||||
const { onboarding } = await client.api.get("/onboard/hello");
|
||||
|
||||
if (onboarding) {
|
||||
openScreen({
|
||||
|
||||
@@ -16,7 +16,7 @@ export function FormSendReset() {
|
||||
<Form
|
||||
page="send_reset"
|
||||
callback={async (data) => {
|
||||
await client.req("POST", "/auth/account/reset_password", data);
|
||||
await client.api.post("/auth/account/reset_password", data);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -32,7 +32,7 @@ export function FormReset() {
|
||||
<Form
|
||||
page="reset"
|
||||
callback={async (data) => {
|
||||
await client.req("PATCH", "/auth/account/reset_password", {
|
||||
await client.api.patch("/auth/account/reset_password", {
|
||||
token,
|
||||
...data,
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ export function FormResend() {
|
||||
<Form
|
||||
page="resend"
|
||||
callback={async (data) => {
|
||||
await client.req("POST", "/auth/account/reverify", data);
|
||||
await client.api.post("/auth/account/reverify", data);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -34,11 +34,8 @@ export function FormVerify() {
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
client
|
||||
.req(
|
||||
"POST",
|
||||
`/auth/account/verify/${token}` as "/auth/account/verify/:code",
|
||||
)
|
||||
client.api
|
||||
.post(`/auth/account/verify/${token as ""}`)
|
||||
.then(() => history.push("/login"))
|
||||
.catch((err) => setError(takeError(err)));
|
||||
// eslint-disable-next-line
|
||||
|
||||
@@ -347,7 +347,9 @@ export default observer(() => {
|
||||
<Trash
|
||||
size={24}
|
||||
onClick={() =>
|
||||
client.users.edit({ remove: "StatusText" })
|
||||
client.users.edit({
|
||||
remove: ["StatusText"],
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||
import { Channel } from "revolt.js";
|
||||
import styled from "styled-components/macro";
|
||||
|
||||
import { Text } from "preact-i18n";
|
||||
@@ -69,7 +69,7 @@ export default observer(({ channel }: Props) => {
|
||||
{ max_side: 256 },
|
||||
true,
|
||||
)}
|
||||
remove={() => channel.edit({ remove: "Icon" })}
|
||||
remove={() => channel.edit({ remove: ["Icon"] })}
|
||||
defaultPreview={
|
||||
channel.channel_type === "Group"
|
||||
? "/assets/group.png"
|
||||
|
||||
@@ -1,102 +1,119 @@
|
||||
import isEqual from "lodash.isequal";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import {
|
||||
ChannelPermission,
|
||||
DEFAULT_PERMISSION_DM,
|
||||
} from "revolt.js/dist/api/permissions";
|
||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||
import { Channel, API } from "revolt.js";
|
||||
import { DEFAULT_PERMISSION_DIRECT_MESSAGE } from "revolt.js";
|
||||
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { Text } from "preact-i18n";
|
||||
import { useState } from "preact/hooks";
|
||||
|
||||
import Button from "../../../components/ui/Button";
|
||||
import Checkbox from "../../../components/ui/Checkbox";
|
||||
import Tip from "../../../components/ui/Tip";
|
||||
import { TextReact } from "../../../lib/i18n";
|
||||
|
||||
import { PermissionsLayout, Button, SpaceBetween, H1 } from "@revoltchat/ui";
|
||||
|
||||
import { PermissionList } from "../../../components/settings/roles/PermissionList";
|
||||
import { RoleOrDefault } from "../../../components/settings/roles/RoleSelection";
|
||||
import { useRoles } from "../server/Roles";
|
||||
|
||||
interface Props {
|
||||
channel: Channel;
|
||||
}
|
||||
|
||||
// ! FIXME: bad code :)
|
||||
export default observer(({ channel }: Props) => {
|
||||
const [selected, setSelected] = useState("default");
|
||||
|
||||
type R = { name: string; permissions: number };
|
||||
const roles: { [key: string]: R } = {};
|
||||
if (channel.channel_type !== "Group") {
|
||||
const server = channel.server;
|
||||
const a = server?.roles ?? {};
|
||||
for (const b of Object.keys(a)) {
|
||||
roles[b] = {
|
||||
name: a[b].name,
|
||||
permissions:
|
||||
channel.role_permissions?.[b] ?? a[b].permissions[1],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const keys = ["default", ...Object.keys(roles)];
|
||||
|
||||
const defaultRole = {
|
||||
name: "Default",
|
||||
permissions:
|
||||
(channel.channel_type === "Group"
|
||||
? channel.permissions
|
||||
: channel.default_permissions) ?? DEFAULT_PERMISSION_DM,
|
||||
};
|
||||
const selectedRole = selected === "default" ? defaultRole : roles[selected];
|
||||
|
||||
if (!selectedRole) {
|
||||
useEffect(() => setSelected("default"), []);
|
||||
return null;
|
||||
}
|
||||
|
||||
const [p, setPerm] = useState(selectedRole.permissions >>> 0);
|
||||
|
||||
useEffect(() => {
|
||||
setPerm(selectedRole.permissions >>> 0);
|
||||
}, [selected, selectedRole.permissions]);
|
||||
// Consolidate all permissions that we can change right now.
|
||||
const currentRoles =
|
||||
channel.channel_type === "Group"
|
||||
? ([
|
||||
{
|
||||
id: "default",
|
||||
name: "Default",
|
||||
permissions:
|
||||
channel.permissions ??
|
||||
DEFAULT_PERMISSION_DIRECT_MESSAGE,
|
||||
},
|
||||
] as RoleOrDefault[])
|
||||
: (useRoles(channel.server! as any).map((role) => {
|
||||
return {
|
||||
...role,
|
||||
permissions: (role.id === "default"
|
||||
? channel.default_permissions
|
||||
: channel.role_permissions?.[role.id]) ?? {
|
||||
a: 0,
|
||||
d: 0,
|
||||
},
|
||||
};
|
||||
}) as RoleOrDefault[]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Tip warning>This section is under construction.</Tip>
|
||||
<h2>select role</h2>
|
||||
{selected}
|
||||
{keys.map((id) => {
|
||||
const role: R = id === "default" ? defaultRole : roles[id];
|
||||
<PermissionsLayout
|
||||
channel={channel}
|
||||
editor={({ selected }) => {
|
||||
const currentRole = currentRoles.find(
|
||||
(x) => x.id === selected,
|
||||
)!;
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
key={id}
|
||||
checked={selected === id}
|
||||
onChange={(selected) => selected && setSelected(id)}>
|
||||
{role.name}
|
||||
</Checkbox>
|
||||
);
|
||||
})}
|
||||
<h2>channel permissions</h2>
|
||||
{Object.keys(ChannelPermission).map((perm) => {
|
||||
if (perm === "View") return null;
|
||||
if (!currentRole) return null;
|
||||
|
||||
const value =
|
||||
ChannelPermission[perm as keyof typeof ChannelPermission];
|
||||
if (value & DEFAULT_PERMISSION_DM) {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={(p & value) > 0}
|
||||
onChange={(c) =>
|
||||
setPerm(c ? p | value : p ^ value)
|
||||
}>
|
||||
{perm}
|
||||
</Checkbox>
|
||||
// Keep track of whatever role we're editing right now.
|
||||
const [value, setValue] = useState<
|
||||
API.OverrideField | number | undefined
|
||||
>(undefined);
|
||||
const currentPermission = currentRoles.find(
|
||||
(x) => x.id === selected,
|
||||
)!.permissions;
|
||||
const currentValue = value ?? currentPermission;
|
||||
|
||||
// Upload new role information to server.
|
||||
function save() {
|
||||
channel.setPermissions(
|
||||
selected,
|
||||
typeof currentValue === "number"
|
||||
? currentValue
|
||||
: ({
|
||||
allow: currentValue.a,
|
||||
deny: currentValue.d,
|
||||
} as any),
|
||||
);
|
||||
}
|
||||
})}
|
||||
<Button
|
||||
contrast
|
||||
onClick={() => {
|
||||
channel.setPermissions(selected, p);
|
||||
}}>
|
||||
click here to save permissions for role
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SpaceBetween>
|
||||
<H1>
|
||||
<TextReact
|
||||
id="app.settings.permissions.title"
|
||||
fields={{ role: currentRole.name }}
|
||||
/>
|
||||
</H1>
|
||||
<Button
|
||||
palette="secondary"
|
||||
disabled={isEqual(
|
||||
currentPermission,
|
||||
currentValue,
|
||||
)}
|
||||
onClick={save}>
|
||||
<Text id="app.special.modals.actions.save" />
|
||||
</Button>
|
||||
</SpaceBetween>
|
||||
<PermissionList
|
||||
value={currentValue}
|
||||
onChange={setValue}
|
||||
filter={[
|
||||
...(channel.channel_type === "Group"
|
||||
? []
|
||||
: ["ViewChannel" as "ViewChannel"]),
|
||||
"ReadMessageHistory",
|
||||
"SendMessage",
|
||||
"ManageMessages",
|
||||
"ManageWebhooks",
|
||||
"InviteOthers",
|
||||
"SendEmbeds",
|
||||
"UploadFiles",
|
||||
"Masquerade",
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { At, Key, Block, ListOl } from "@styled-icons/boxicons-regular";
|
||||
import { At, Key, Block } from "@styled-icons/boxicons-regular";
|
||||
import {
|
||||
Envelope,
|
||||
HelpCircle,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@styled-icons/boxicons-solid";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Profile } from "revolt-api/types/Users";
|
||||
import { API } from "revolt.js";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
@@ -37,7 +37,9 @@ export const Account = observer(() => {
|
||||
|
||||
const [email, setEmail] = useState("...");
|
||||
const [revealEmail, setRevealEmail] = useState(false);
|
||||
const [profile, setProfile] = useState<undefined | Profile>(undefined);
|
||||
const [profile, setProfile] = useState<undefined | API.UserProfile>(
|
||||
undefined,
|
||||
);
|
||||
const history = useHistory();
|
||||
|
||||
function switchPage(to: string) {
|
||||
@@ -46,8 +48,8 @@ export const Account = observer(() => {
|
||||
|
||||
useEffect(() => {
|
||||
if (email === "..." && status === ClientStatus.ONLINE) {
|
||||
client
|
||||
.req("GET", "/auth/account")
|
||||
client.api
|
||||
.get("/auth/account/")
|
||||
.then((account) => setEmail(account.email));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// ! FIXME: this code is garbage, need to replace
|
||||
import { Key, Clipboard, Globe, Plus } from "@styled-icons/boxicons-regular";
|
||||
import { LockAlt, HelpCircle } from "@styled-icons/boxicons-solid";
|
||||
import type { AxiosError } from "axios";
|
||||
import { observer } from "mobx-react-lite";
|
||||
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 { API } from "revolt.js";
|
||||
import { User } from "revolt.js";
|
||||
import styled from "styled-components/macro";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
@@ -43,7 +43,7 @@ interface Changes {
|
||||
name?: string;
|
||||
public?: boolean;
|
||||
interactions_url?: string;
|
||||
remove?: "InteractionsURL";
|
||||
remove?: "InteractionsURL"[];
|
||||
}
|
||||
|
||||
const BotBadge = styled.div`
|
||||
@@ -62,7 +62,7 @@ const BotBadge = styled.div`
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
bot: Bot;
|
||||
bot: API.Bot;
|
||||
onDelete(): void;
|
||||
onUpdate(changes: Changes): void;
|
||||
}
|
||||
@@ -75,7 +75,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||
_id: bot._id,
|
||||
username: user.username,
|
||||
public: bot.public,
|
||||
interactions_url: bot.interactions_url,
|
||||
interactions_url: bot.interactions_url as any,
|
||||
});
|
||||
const [error, setError] = useState<string | JSX.Element>("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -87,23 +87,21 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||
useState<HTMLInputElement | null>(null);
|
||||
const { writeClipboard, openScreen } = useIntermediate();
|
||||
|
||||
const [profile, setProfile] = useState<undefined | ProfileI>(undefined);
|
||||
const [profile, setProfile] = useState<undefined | API.UserProfile>(
|
||||
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;
|
||||
},
|
||||
client.api
|
||||
.get(`/users/${bot._id as ""}/profile`, undefined, {
|
||||
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]);
|
||||
@@ -122,14 +120,14 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||
const changes: Changes = {};
|
||||
if (data.username !== user!.username) changes.name = data.username;
|
||||
if (data.public !== bot.public) changes.public = data.public;
|
||||
if (data.interactions_url === "") changes.remove = "InteractionsURL";
|
||||
if (data.interactions_url === "") changes.remove = ["InteractionsURL"];
|
||||
else if (data.interactions_url !== bot.interactions_url)
|
||||
changes.interactions_url = data.interactions_url;
|
||||
setSaving(true);
|
||||
setError("");
|
||||
try {
|
||||
await client.bots.edit(bot._id, changes);
|
||||
if (changed) await editBotContent(profile?.content);
|
||||
if (changed) await editBotContent(profile?.content ?? undefined);
|
||||
onUpdate(changes);
|
||||
setChanged(false);
|
||||
setEditMode(false);
|
||||
@@ -152,19 +150,22 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||
async function editBotAvatar(avatar?: 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;
|
||||
await client.api.patch(
|
||||
"/users/@me",
|
||||
avatar ? { avatar } : { remove: ["Avatar"] },
|
||||
{
|
||||
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(avatar ? { avatar } : { remove: "Avatar" }),
|
||||
});
|
||||
);
|
||||
|
||||
const res = await client.bots.fetch(bot._id);
|
||||
if (!avatar) res.user.update({}, "Avatar");
|
||||
if (!avatar) res.user.update({}, ["Avatar"]);
|
||||
setUser(res.user);
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -172,20 +173,21 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||
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;
|
||||
await client.api.patch(
|
||||
"/users/@me",
|
||||
background
|
||||
? { profile: { background } }
|
||||
: { remove: ["ProfileBackground"] },
|
||||
{
|
||||
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();
|
||||
@@ -195,20 +197,19 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||
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;
|
||||
await client.api.patch(
|
||||
"/users/@me",
|
||||
content ? { profile: { content } } : { remove: ["ProfileContent"] },
|
||||
{
|
||||
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();
|
||||
@@ -333,7 +334,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||
_id: bot._id,
|
||||
username: user!.username,
|
||||
public: bot.public,
|
||||
interactions_url: bot.interactions_url,
|
||||
interactions_url: bot.interactions_url as any,
|
||||
});
|
||||
usernameRef!.value = user!.username;
|
||||
interactionsRef!.value = bot.interactions_url || "";
|
||||
@@ -521,7 +522,7 @@ function BotCard({ bot, onDelete, onUpdate }: Props) {
|
||||
|
||||
export const MyBots = observer(() => {
|
||||
const client = useClient();
|
||||
const [bots, setBots] = useState<Bot[] | undefined>(undefined);
|
||||
const [bots, setBots] = useState<API.Bot[] | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
client.bots.fetchOwned().then(({ bots }) => setBots(bots));
|
||||
@@ -582,7 +583,7 @@ export const MyBots = observer(() => {
|
||||
changes.interactions_url;
|
||||
if (
|
||||
changes.remove ===
|
||||
"InteractionsURL"
|
||||
["InteractionsURL"]
|
||||
)
|
||||
x.interactions_url = undefined;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export const Notifications = observer(() => {
|
||||
// tell the server we just subscribed
|
||||
const json = sub.toJSON();
|
||||
if (json.keys) {
|
||||
client.req("POST", "/push/subscribe", {
|
||||
client.api.post("/push/subscribe", {
|
||||
endpoint: sub.endpoint,
|
||||
...(json.keys as {
|
||||
p256dh: string;
|
||||
@@ -96,7 +96,7 @@ export const Notifications = observer(() => {
|
||||
sub?.unsubscribe();
|
||||
setPushEnabled(false);
|
||||
|
||||
client.req("POST", "/push/unsubscribe");
|
||||
client.api.post("/push/unsubscribe");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Markdown } from "@styled-icons/boxicons-logos";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Profile as ProfileI } from "revolt-api/types/Users";
|
||||
import { API } from "revolt.js";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
@@ -30,7 +30,9 @@ export const Profile = observer(() => {
|
||||
const client = useClient();
|
||||
const history = useHistory();
|
||||
|
||||
const [profile, setProfile] = useState<undefined | ProfileI>(undefined);
|
||||
const [profile, setProfile] = useState<undefined | API.UserProfile>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
// ! FIXME: temporary solution
|
||||
// ! we should just announce profile changes through WS
|
||||
@@ -103,7 +105,7 @@ export const Profile = observer(() => {
|
||||
behaviour="upload"
|
||||
maxFileSize={4_000_000}
|
||||
onUpload={(avatar) => client.users.edit({ avatar })}
|
||||
remove={() => client.users.edit({ remove: "Avatar" })}
|
||||
remove={() => client.users.edit({ remove: ["Avatar"] })}
|
||||
defaultPreview={client.user!.generateAvatarURL(
|
||||
{ max_side: 256 },
|
||||
true,
|
||||
@@ -132,7 +134,7 @@ export const Profile = observer(() => {
|
||||
}}
|
||||
remove={async () => {
|
||||
await client.users.edit({
|
||||
remove: "ProfileBackground",
|
||||
remove: ["ProfileBackground"],
|
||||
});
|
||||
setProfile({ ...profile, background: undefined });
|
||||
}}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "@styled-icons/simple-icons";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { SessionInfo } from "revolt-api/types/Auth";
|
||||
import { API } from "revolt.js";
|
||||
import { decodeTime } from "ulid";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
@@ -33,7 +33,7 @@ export function Sessions() {
|
||||
const deviceId =
|
||||
typeof client.session === "object" ? client.session._id : undefined;
|
||||
|
||||
const [sessions, setSessions] = useState<SessionInfo[] | undefined>(
|
||||
const [sessions, setSessions] = useState<API.SessionInfo[] | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [attemptingDelete, setDelete] = useState<string[]>([]);
|
||||
@@ -44,7 +44,7 @@ export function Sessions() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
client.req("GET", "/auth/session/all").then((data) => {
|
||||
client.api.get("/auth/session/all").then((data) => {
|
||||
data.sort(
|
||||
(a, b) =>
|
||||
(b._id === deviceId ? 1 : 0) - (a._id === deviceId ? 1 : 0),
|
||||
@@ -61,7 +61,7 @@ export function Sessions() {
|
||||
);
|
||||
}
|
||||
|
||||
function getIcon(session: SessionInfo) {
|
||||
function getIcon(session: API.SessionInfo) {
|
||||
const name = session.name;
|
||||
switch (true) {
|
||||
case /firefox/i.test(name):
|
||||
@@ -83,7 +83,7 @@ export function Sessions() {
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemIcon(session: SessionInfo) {
|
||||
function getSystemIcon(session: API.SessionInfo) {
|
||||
const name = session.name;
|
||||
switch (true) {
|
||||
case /linux/i.test(name):
|
||||
@@ -187,9 +187,10 @@ export function Sessions() {
|
||||
...attemptingDelete,
|
||||
session._id,
|
||||
]);
|
||||
await client.req(
|
||||
"DELETE",
|
||||
`/auth/session/${session._id}` as "/auth/session/id",
|
||||
await client.api.delete(
|
||||
`/auth/session/${
|
||||
session._id as ""
|
||||
}`,
|
||||
);
|
||||
setSessions(
|
||||
sessions?.filter(
|
||||
@@ -222,10 +223,7 @@ export function Sessions() {
|
||||
setDelete(del);
|
||||
|
||||
for (const id of del) {
|
||||
await client.req(
|
||||
"DELETE",
|
||||
`/auth/session/${id}` as "/auth/session/id",
|
||||
);
|
||||
await client.api.delete(`/auth/session/${id as ""}`);
|
||||
}
|
||||
|
||||
setSessions(sessions.filter((x) => x._id === deviceId));
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { XCircle } from "@styled-icons/boxicons-regular";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { Ban } from "revolt-api/types/Servers";
|
||||
import { User } from "revolt-api/types/Users";
|
||||
import { Route } from "revolt.js/dist/api/routes";
|
||||
import { Server } from "revolt.js/dist/maps/Servers";
|
||||
import { API } from "revolt.js";
|
||||
import { Server } from "revolt.js";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
@@ -15,8 +13,8 @@ import IconButton from "../../../components/ui/IconButton";
|
||||
import Preloader from "../../../components/ui/Preloader";
|
||||
|
||||
interface InnerProps {
|
||||
ban: Ban;
|
||||
users: Pick<User, "username" | "avatar" | "_id">[];
|
||||
ban: API.ServerBan;
|
||||
users: Pick<API.User, "username" | "avatar" | "_id">[];
|
||||
server: Server;
|
||||
removeSelf: () => void;
|
||||
}
|
||||
@@ -53,9 +51,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const Bans = observer(({ server }: Props) => {
|
||||
const [data, setData] = useState<
|
||||
Route<"GET", "/servers/id/bans">["response"] | undefined
|
||||
>(undefined);
|
||||
const [data, setData] = useState<API.BanListResult | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
server.fetchBans().then(setData);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Plus, X } from "@styled-icons/boxicons-regular";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { DragDropContext } from "react-beautiful-dnd";
|
||||
import { TextChannel, VoiceChannel } from "revolt-api/types/Channels";
|
||||
import { Category } from "revolt-api/types/Servers";
|
||||
import { Server } from "revolt.js/dist/maps/Servers";
|
||||
import { Channel, Server, API } from "revolt.js";
|
||||
import styled, { css } from "styled-components/macro";
|
||||
import { ulid } from "ulid";
|
||||
|
||||
@@ -135,7 +133,7 @@ interface Props {
|
||||
|
||||
export const Categories = observer(({ server }: Props) => {
|
||||
const [status, setStatus] = useState<EditStatus>("saved");
|
||||
const [categories, setCategories] = useState<Category[]>(
|
||||
const [categories, setCategories] = useState<API.Category[]>(
|
||||
server.categories ?? [],
|
||||
);
|
||||
|
||||
@@ -327,12 +325,14 @@ function ListElement({
|
||||
addChannel,
|
||||
draggable,
|
||||
}: {
|
||||
category: Category;
|
||||
category: API.Category;
|
||||
server: Server;
|
||||
index: number;
|
||||
setTitle?: (title: string) => void;
|
||||
deleteSelf?: () => void;
|
||||
addChannel: (channel: TextChannel | VoiceChannel) => void;
|
||||
addChannel: (
|
||||
channel: Channel & { channel_type: "TextChannel" | "VoiceChannel" },
|
||||
) => void;
|
||||
draggable?: boolean;
|
||||
}) {
|
||||
const { openScreen } = useIntermediate();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { XCircle } from "@styled-icons/boxicons-regular";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { Invite, ServerInvite } from "revolt-api/types/Invites";
|
||||
import { Server } from "revolt.js/dist/maps/Servers";
|
||||
import { API, Server } from "revolt.js";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
@@ -16,7 +15,7 @@ import IconButton from "../../../components/ui/IconButton";
|
||||
import Preloader from "../../../components/ui/Preloader";
|
||||
|
||||
interface InnerProps {
|
||||
invite: Invite;
|
||||
invite: API.Invite;
|
||||
server: Server;
|
||||
removeSelf: () => void;
|
||||
}
|
||||
@@ -52,12 +51,10 @@ interface Props {
|
||||
}
|
||||
|
||||
export const Invites = ({ server }: Props) => {
|
||||
const [invites, setInvites] = useState<ServerInvite[] | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [invites, setInvites] = useState<API.Invite[] | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
server.fetchInvites().then(setInvites);
|
||||
server.fetchInvites().then((v) => setInvites(v));
|
||||
}, [server, setInvites]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ChevronDown } from "@styled-icons/boxicons-regular";
|
||||
import { isEqual } from "lodash";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { Member } from "revolt.js/dist/maps/Members";
|
||||
import { Server } from "revolt.js/dist/maps/Servers";
|
||||
import { Member } from "revolt.js";
|
||||
import { Server } from "revolt.js";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Markdown } from "@styled-icons/boxicons-logos";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Server } from "revolt.js/dist/maps/Servers";
|
||||
import { Server } from "revolt.js";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
||||
import { noop } from "../../../lib/js";
|
||||
|
||||
import { FileUploader } from "../../../context/revoltjs/FileUploads";
|
||||
import { getChannelName } from "../../../context/revoltjs/util";
|
||||
@@ -60,9 +61,9 @@ export const Overview = observer(({ server }: Props) => {
|
||||
fileType="icons"
|
||||
behaviour="upload"
|
||||
maxFileSize={2_500_000}
|
||||
onUpload={(icon) => server.edit({ icon })}
|
||||
onUpload={(icon) => server.edit({ icon }).then(noop)}
|
||||
previewURL={server.generateIconURL({ max_side: 256 }, true)}
|
||||
remove={() => server.edit({ remove: "Icon" })}
|
||||
remove={() => server.edit({ remove: ["Icon"] }).then(noop)}
|
||||
/>
|
||||
<div className={styles.name}>
|
||||
<h3>
|
||||
@@ -117,9 +118,9 @@ export const Overview = observer(({ server }: Props) => {
|
||||
fileType="banners"
|
||||
behaviour="upload"
|
||||
maxFileSize={6_000_000}
|
||||
onUpload={(banner) => server.edit({ banner })}
|
||||
onUpload={(banner) => server.edit({ banner }).then(noop)}
|
||||
previewURL={server.generateBannerURL({ width: 1000 }, true)}
|
||||
remove={() => server.edit({ remove: "Banner" })}
|
||||
remove={() => server.edit({ remove: ["Banner"] }).then(noop)}
|
||||
/>
|
||||
<hr />
|
||||
<h3>
|
||||
|
||||
@@ -116,48 +116,3 @@
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.roles {
|
||||
gap: 12px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
||||
.list {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.permissions {
|
||||
flex-grow: 1;
|
||||
padding: 0 8px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.title {
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
margin-bottom: 1em;
|
||||
align-items: center;
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
svg {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
padding: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,289 +1,237 @@
|
||||
import { Plus } from "@styled-icons/boxicons-regular";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ChannelPermission, ServerPermission } from "revolt.js";
|
||||
import { Server } from "revolt.js/dist/maps/Servers";
|
||||
import { Server } from "revolt.js";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "preact/hooks";
|
||||
import { useMemo, useState } from "preact/hooks";
|
||||
|
||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
import Button from "../../../components/ui/Button";
|
||||
import Checkbox from "../../../components/ui/Checkbox";
|
||||
import ColourSwatches from "../../../components/ui/ColourSwatches";
|
||||
import InputBox from "../../../components/ui/InputBox";
|
||||
import Overline from "../../../components/ui/Overline";
|
||||
import { Button, PermissionsLayout, SpaceBetween, H1 } from "@revoltchat/ui";
|
||||
|
||||
import ButtonItem from "../../../components/navigation/items/ButtonItem";
|
||||
import { PermissionList } from "../../../components/settings/roles/PermissionList";
|
||||
import { RoleOrDefault } from "../../../components/settings/roles/RoleSelection";
|
||||
|
||||
interface Props {
|
||||
server: Server;
|
||||
}
|
||||
|
||||
const I32ToU32 = (arr: number[]) => arr.map((x) => x >>> 0);
|
||||
/**
|
||||
* Hook to memo-ize role information.
|
||||
* @param server Target server
|
||||
* @returns Role array
|
||||
*/
|
||||
export function useRoles(server: Server) {
|
||||
return useMemo(
|
||||
() =>
|
||||
[
|
||||
// Pull in known server roles.
|
||||
...server.orderedRoles,
|
||||
// Include the default server permissions.
|
||||
{
|
||||
id: "default",
|
||||
name: "Default",
|
||||
permissions: server.default_permissions,
|
||||
},
|
||||
] as RoleOrDefault[],
|
||||
[server.roles, server.default_permissions],
|
||||
);
|
||||
}
|
||||
|
||||
// ! FIXME: bad code :)
|
||||
/**
|
||||
* Roles settings menu
|
||||
*/
|
||||
export const Roles = observer(({ server }: Props) => {
|
||||
const client = useContext(AppContext);
|
||||
const [role, setRole] = useState("default");
|
||||
// Consolidate all permissions that we can change right now.
|
||||
const currentRoles = useRoles(server);
|
||||
|
||||
// Pull in modal context.
|
||||
const { openScreen } = useIntermediate();
|
||||
const roles = server.roles || {};
|
||||
|
||||
if (role !== "default" && typeof roles[role] === "undefined") {
|
||||
useEffect(() => setRole("default"), [role]);
|
||||
return null;
|
||||
}
|
||||
|
||||
const clientPermissions = client.servers.get(server._id)!.permission;
|
||||
|
||||
const {
|
||||
name: roleName,
|
||||
colour: roleColour,
|
||||
hoist: roleHoist,
|
||||
rank: roleRank,
|
||||
permissions,
|
||||
} = roles[role] ?? {};
|
||||
|
||||
const getPermissions = useCallback(
|
||||
(id: string) => {
|
||||
return I32ToU32(
|
||||
id === "default"
|
||||
? server.default_permissions
|
||||
: roles[id].permissions,
|
||||
);
|
||||
},
|
||||
[roles, server],
|
||||
);
|
||||
|
||||
const [perm, setPerm] = useState(getPermissions(role));
|
||||
const [name, setName] = useState(roleName);
|
||||
const [hoist, setHoist] = useState(roleHoist);
|
||||
const [rank, setRank] = useState(roleRank);
|
||||
const [colour, setColour] = useState(roleColour);
|
||||
|
||||
useEffect(
|
||||
() => setPerm(getPermissions(role)),
|
||||
[getPermissions, role, permissions],
|
||||
);
|
||||
|
||||
useEffect(() => setName(roleName), [role, roleName]);
|
||||
useEffect(() => setHoist(roleHoist), [role, roleHoist]);
|
||||
useEffect(() => setRank(roleRank), [role, roleRank]);
|
||||
useEffect(() => setColour(roleColour), [role, roleColour]);
|
||||
|
||||
const modified =
|
||||
!isEqual(perm, getPermissions(role)) ||
|
||||
!isEqual(name, roleName) ||
|
||||
!isEqual(colour, roleColour) ||
|
||||
!isEqual(hoist, roleHoist) ||
|
||||
!isEqual(rank, roleRank);
|
||||
|
||||
const save = () => {
|
||||
if (!isEqual(perm, getPermissions(role))) {
|
||||
server.setPermissions(role, {
|
||||
server: perm[0],
|
||||
channel: perm[1],
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!isEqual(name, roleName) ||
|
||||
!isEqual(colour, roleColour) ||
|
||||
!isEqual(hoist, roleHoist) ||
|
||||
!isEqual(rank, roleRank)
|
||||
) {
|
||||
server.editRole(role, { name, colour, hoist, rank });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteRole = () => {
|
||||
setRole("default");
|
||||
server.deleteRole(role);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.roles}>
|
||||
<div className={styles.list}>
|
||||
<div className={styles.title}>
|
||||
<h1>
|
||||
<Text id="app.settings.server_pages.roles.title" />
|
||||
</h1>
|
||||
<Plus
|
||||
size={22}
|
||||
onClick={() =>
|
||||
openScreen({
|
||||
id: "special_input",
|
||||
type: "create_role",
|
||||
server,
|
||||
callback: (id) => setRole(id),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{["default", ...Object.keys(roles)].map((id) =>
|
||||
id === "default" ? (
|
||||
<ButtonItem
|
||||
active={role === "default"}
|
||||
onClick={() => setRole("default")}>
|
||||
<Text id="app.settings.permissions.default_role" />
|
||||
</ButtonItem>
|
||||
) : (
|
||||
<ButtonItem
|
||||
key={id}
|
||||
active={role === id}
|
||||
onClick={() => setRole(id)}
|
||||
style={{
|
||||
color: roles[id].colour,
|
||||
}}>
|
||||
{roles[id].name}
|
||||
</ButtonItem>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.permissions}>
|
||||
<div className={styles.title}>
|
||||
<h2>
|
||||
{role === "default" ? (
|
||||
<Text id="app.settings.permissions.default_role" />
|
||||
) : (
|
||||
roles[role].name
|
||||
<PermissionsLayout
|
||||
server={server}
|
||||
onCreateRole={(callback) =>
|
||||
openScreen({
|
||||
id: "special_input",
|
||||
type: "create_role",
|
||||
server: server as any,
|
||||
callback,
|
||||
})
|
||||
}
|
||||
editor={({ selected }) => {
|
||||
const currentRole = currentRoles.find(
|
||||
(x) => x.id === selected,
|
||||
)!;
|
||||
|
||||
if (!currentRole) return null;
|
||||
|
||||
// Keep track of whatever role we're editing right now.
|
||||
const [value, setValue] = useState<Partial<RoleOrDefault>>({});
|
||||
|
||||
const currentRoleValue = { ...currentRole, ...value };
|
||||
|
||||
// Calculate permissions we have access to on this server.
|
||||
const current = server.permission;
|
||||
|
||||
// Upload new role information to server.
|
||||
function save() {
|
||||
const { permissions: permsCurrent, ...current } =
|
||||
currentRole;
|
||||
const { permissions: permsValue, ...value } =
|
||||
currentRoleValue;
|
||||
|
||||
if (!isEqual(permsCurrent, permsValue)) {
|
||||
server.setPermissions(
|
||||
selected,
|
||||
typeof permsValue === "number"
|
||||
? permsValue
|
||||
: {
|
||||
allow: permsValue.a,
|
||||
deny: permsValue.d,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (!isEqual(current, value)) {
|
||||
server.editRole(selected, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the role from this server.
|
||||
function deleteRole() {
|
||||
server.deleteRole(selected);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SpaceBetween>
|
||||
<H1>
|
||||
<Text
|
||||
id="app.settings.actions.edit"
|
||||
fields={{ name: currentRole.name }}
|
||||
/>
|
||||
</H1>
|
||||
<Button
|
||||
palette="secondary"
|
||||
disabled={isEqual(
|
||||
currentRole,
|
||||
currentRoleValue,
|
||||
)}
|
||||
onClick={save}>
|
||||
<Text id="app.special.modals.actions.save" />
|
||||
</Button>
|
||||
</SpaceBetween>
|
||||
<hr />
|
||||
{selected !== "default" && (
|
||||
<>
|
||||
<section>
|
||||
<Overline type="subtle">
|
||||
<Text id="app.settings.permissions.role_name" />
|
||||
</Overline>
|
||||
<p>
|
||||
<InputBox
|
||||
value={currentRoleValue.name}
|
||||
onChange={(e) =>
|
||||
setValue({
|
||||
...value,
|
||||
name: e.currentTarget.value,
|
||||
})
|
||||
}
|
||||
contrast
|
||||
/>
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<Overline type="subtle">
|
||||
<Text id="app.settings.permissions.role_colour" />
|
||||
</Overline>
|
||||
<p>
|
||||
<ColourSwatches
|
||||
value={
|
||||
currentRoleValue.colour ??
|
||||
"gray"
|
||||
}
|
||||
onChange={(colour) =>
|
||||
setValue({ ...value, colour })
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<Overline type="subtle">
|
||||
<Text id="app.settings.permissions.role_options" />
|
||||
</Overline>
|
||||
<p>
|
||||
<Checkbox
|
||||
checked={
|
||||
currentRoleValue.hoist ?? false
|
||||
}
|
||||
onChange={(hoist) =>
|
||||
setValue({ ...value, hoist })
|
||||
}
|
||||
description={
|
||||
<Text id="app.settings.permissions.hoist_desc" />
|
||||
}>
|
||||
<Text id="app.settings.permissions.hoist_role" />
|
||||
</Checkbox>
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<Overline type="subtle">
|
||||
<Text id="app.settings.permissions.role_ranking" />
|
||||
</Overline>
|
||||
<p>
|
||||
<InputBox
|
||||
type="number"
|
||||
value={currentRoleValue.rank ?? 0}
|
||||
onChange={(e) =>
|
||||
setValue({
|
||||
...value,
|
||||
rank: parseInt(
|
||||
e.currentTarget.value,
|
||||
),
|
||||
})
|
||||
}
|
||||
contrast
|
||||
/>
|
||||
</p>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</h2>
|
||||
<Button contrast disabled={!modified} onClick={save}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
{role !== "default" && (
|
||||
<>
|
||||
<section>
|
||||
<Overline type="subtle">Role Name</Overline>
|
||||
<p>
|
||||
<InputBox
|
||||
value={name}
|
||||
onChange={(e) =>
|
||||
setName(e.currentTarget.value)
|
||||
}
|
||||
contrast
|
||||
/>
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<Overline type="subtle">Role Colour</Overline>
|
||||
<p>
|
||||
<ColourSwatches
|
||||
value={colour ?? "gray"}
|
||||
onChange={(value) => setColour(value)}
|
||||
/>
|
||||
</p>
|
||||
</section>
|
||||
<section>
|
||||
<Overline type="subtle">Role Options</Overline>
|
||||
<p>
|
||||
<Checkbox
|
||||
checked={hoist ?? false}
|
||||
onChange={(v) => setHoist(v)}
|
||||
description="Display this role above others.">
|
||||
Hoist Role
|
||||
</Checkbox>
|
||||
</p>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
<section>
|
||||
<Overline type="subtle">
|
||||
<Text id="app.settings.permissions.server" />
|
||||
</Overline>
|
||||
{Object.keys(ServerPermission).map((key) => {
|
||||
if (key === "View") return;
|
||||
const value =
|
||||
ServerPermission[
|
||||
key as keyof typeof ServerPermission
|
||||
];
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
key={key}
|
||||
checked={(perm[0] & value) > 0}
|
||||
onChange={() =>
|
||||
setPerm([perm[0] ^ value, perm[1]])
|
||||
}
|
||||
disabled={!(clientPermissions & value)}
|
||||
description={
|
||||
<Text id={`permissions.server.${key}.d`} />
|
||||
}>
|
||||
<Text id={`permissions.server.${key}.t`} />
|
||||
</Checkbox>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
<section>
|
||||
<Overline type="subtle">
|
||||
<Text id="app.settings.permissions.channel" />
|
||||
</Overline>
|
||||
{Object.keys(ChannelPermission).map((key) => {
|
||||
if (key === "ManageChannel") return;
|
||||
const value =
|
||||
ChannelPermission[
|
||||
key as keyof typeof ChannelPermission
|
||||
];
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
key={key}
|
||||
checked={((perm[1] >>> 0) & value) > 0}
|
||||
onChange={() =>
|
||||
setPerm([perm[0], perm[1] ^ value])
|
||||
}
|
||||
disabled={
|
||||
key === "View" ||
|
||||
!(clientPermissions & value)
|
||||
}
|
||||
description={
|
||||
<Text id={`permissions.channel.${key}.d`} />
|
||||
}>
|
||||
<Text id={`permissions.channel.${key}.t`} />
|
||||
</Checkbox>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
<div className={styles.actions}>
|
||||
<Button contrast disabled={!modified} onClick={save}>
|
||||
Save
|
||||
</Button>
|
||||
{role !== "default" && (
|
||||
<Button contrast error onClick={deleteRole}>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{role !== "default" && (
|
||||
<>
|
||||
<section>
|
||||
<Overline type="subtle">
|
||||
Experimental Role Ranking
|
||||
</Overline>
|
||||
<p>
|
||||
<InputBox
|
||||
value={rank ?? 0}
|
||||
onChange={(e) =>
|
||||
setRank(parseInt(e.currentTarget.value))
|
||||
}
|
||||
contrast
|
||||
/>
|
||||
</p>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<h1>
|
||||
<Text id="app.settings.permissions.edit_title" />
|
||||
</h1>
|
||||
<PermissionList
|
||||
value={currentRoleValue.permissions}
|
||||
onChange={(permissions) =>
|
||||
setValue({
|
||||
...value,
|
||||
permissions,
|
||||
} as RoleOrDefault)
|
||||
}
|
||||
/>
|
||||
{selected !== "default" && (
|
||||
<>
|
||||
<hr />
|
||||
<h1>
|
||||
<Text id="app.settings.categories.danger_zone" />
|
||||
</h1>
|
||||
<Button
|
||||
palette="error"
|
||||
compact
|
||||
onClick={deleteRole}>
|
||||
<Text id="app.settings.permissions.delete_role" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user