mirror of
https://github.com/stoatchat/for-legacy-web.git
synced 2026-03-06 08:38:37 +00:00
Make the linter happy.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
|
||||
import { Text } from "preact-i18n";
|
||||
@@ -44,7 +45,7 @@ export default function Open() {
|
||||
return;
|
||||
}
|
||||
|
||||
let user = client.users.get(id);
|
||||
const user = client.users.get(id);
|
||||
if (user) {
|
||||
const channel: string | undefined = [
|
||||
...client.channels.values(),
|
||||
@@ -68,7 +69,7 @@ export default function Open() {
|
||||
}
|
||||
|
||||
history.push("/");
|
||||
}, []);
|
||||
});
|
||||
|
||||
return (
|
||||
<Header placement="primary">
|
||||
|
||||
@@ -16,7 +16,7 @@ export function App() {
|
||||
<Context>
|
||||
<Masks />
|
||||
{/*
|
||||
// @ts-expect-error */}
|
||||
// @ts-expect-error typings mis-match between preact... and preact? */}
|
||||
<Suspense fallback={<Preloader type="spinner" />}>
|
||||
<Switch>
|
||||
<Route path="/login">
|
||||
|
||||
@@ -60,11 +60,11 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
|
||||
type="channel"
|
||||
channel={channel}
|
||||
gated={
|
||||
(channel.channel_type === "TextChannel" ||
|
||||
channel.channel_type === "Group") &&
|
||||
channel.name?.includes("nsfw")
|
||||
? true
|
||||
: false
|
||||
!!(
|
||||
(channel.channel_type === "TextChannel" ||
|
||||
channel.channel_type === "Group") &&
|
||||
channel.name?.includes("nsfw")
|
||||
)
|
||||
}>
|
||||
<ChannelHeader
|
||||
channel={channel}
|
||||
@@ -110,7 +110,7 @@ function VoiceChannel({ channel }: { channel: ChannelI }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function () {
|
||||
export default function ChannelComponent() {
|
||||
const { channel } = useParams<{ channel: string }>();
|
||||
return <Channel id={channel} key={channel} />;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import {
|
||||
UserPlus,
|
||||
Cog,
|
||||
@@ -9,15 +10,12 @@ import { useHistory } from "react-router-dom";
|
||||
|
||||
import { useContext } from "preact/hooks";
|
||||
|
||||
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
|
||||
|
||||
import {
|
||||
VoiceContext,
|
||||
VoiceOperationsContext,
|
||||
VoiceStatus,
|
||||
} from "../../../context/Voice";
|
||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
import UpdateIndicator from "../../../components/common/UpdateIndicator";
|
||||
import IconButton from "../../../components/ui/IconButton";
|
||||
|
||||
@@ -5,6 +5,7 @@ import useResizeObserver from "use-resize-observer";
|
||||
|
||||
import { createContext } from "preact";
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
@@ -74,7 +75,7 @@ export function MessageArea({ id }: Props) {
|
||||
// ? useRef to avoid re-renders
|
||||
const scrollState = useRef<ScrollState>({ type: "Free" });
|
||||
|
||||
const setScrollState = (v: ScrollState) => {
|
||||
const setScrollState = useCallback((v: ScrollState) => {
|
||||
if (v.type === "StayAtBottom") {
|
||||
if (scrollState.current.type === "Bottom" || atBottom()) {
|
||||
scrollState.current = {
|
||||
@@ -131,7 +132,7 @@ export function MessageArea({ id }: Props) {
|
||||
setScrollState({ type: "Free" });
|
||||
}
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ? Determine if we are at the bottom of the scroll container.
|
||||
// -> https://stackoverflow.com/a/44893438
|
||||
@@ -151,7 +152,7 @@ export function MessageArea({ id }: Props) {
|
||||
return internalSubscribe("MessageArea", "jump_to_bottom", () =>
|
||||
setScrollState({ type: "ScrollToBottom" }),
|
||||
);
|
||||
}, []);
|
||||
}, [setScrollState]);
|
||||
|
||||
// ? Handle events from renderer.
|
||||
useEffect(() => {
|
||||
@@ -163,12 +164,13 @@ export function MessageArea({ id }: Props) {
|
||||
SingletonMessageRenderer.addListener("scroll", setScrollState);
|
||||
return () =>
|
||||
SingletonMessageRenderer.removeListener("scroll", setScrollState);
|
||||
}, [scrollState]);
|
||||
}, [scrollState, setScrollState]);
|
||||
|
||||
// ? Load channel initially.
|
||||
useEffect(() => {
|
||||
if (message) return;
|
||||
SingletonMessageRenderer.init(id);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id]);
|
||||
|
||||
// ? If message present or changes, load it as well.
|
||||
@@ -184,6 +186,7 @@ export function MessageArea({ id }: Props) {
|
||||
history.push(`/channel/${id}`);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [message]);
|
||||
|
||||
// ? If we are waiting for network, try again.
|
||||
@@ -203,11 +206,14 @@ export function MessageArea({ id }: Props) {
|
||||
SingletonMessageRenderer.markStale();
|
||||
break;
|
||||
}
|
||||
}, [status, state]);
|
||||
}, [id, status, state]);
|
||||
|
||||
// ? When the container is scrolled.
|
||||
// ? Also handle StayAtBottom
|
||||
useEffect(() => {
|
||||
const current = ref.current;
|
||||
if (!current) return;
|
||||
|
||||
async function onScroll() {
|
||||
if (scrollState.current.type === "Free" && atBottom()) {
|
||||
setScrollState({ type: "Bottom" });
|
||||
@@ -221,12 +227,15 @@ export function MessageArea({ id }: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
ref.current?.addEventListener("scroll", onScroll);
|
||||
return () => ref.current?.removeEventListener("scroll", onScroll);
|
||||
}, [ref, scrollState]);
|
||||
current.addEventListener("scroll", onScroll);
|
||||
return () => current.removeEventListener("scroll", onScroll);
|
||||
}, [ref, scrollState, setScrollState]);
|
||||
|
||||
// ? Top and bottom loaders.
|
||||
useEffect(() => {
|
||||
const current = ref.current;
|
||||
if (!current) return;
|
||||
|
||||
async function onScroll() {
|
||||
if (atTop(100)) {
|
||||
SingletonMessageRenderer.loadTop(ref.current!);
|
||||
@@ -237,12 +246,12 @@ export function MessageArea({ id }: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
ref.current?.addEventListener("scroll", onScroll);
|
||||
return () => ref.current?.removeEventListener("scroll", onScroll);
|
||||
current.addEventListener("scroll", onScroll);
|
||||
return () => current.removeEventListener("scroll", onScroll);
|
||||
}, [ref]);
|
||||
|
||||
// ? Scroll down whenever the message area resizes.
|
||||
function stbOnResize() {
|
||||
const stbOnResize = useCallback(() => {
|
||||
if (!atBottom() && scrollState.current.type === "Bottom") {
|
||||
animateScroll.scrollToBottom({
|
||||
container: ref.current,
|
||||
@@ -251,18 +260,18 @@ export function MessageArea({ id }: Props) {
|
||||
|
||||
setScrollState({ type: "Bottom" });
|
||||
}
|
||||
}
|
||||
}, [setScrollState]);
|
||||
|
||||
// ? Scroll down when container resized.
|
||||
useLayoutEffect(() => {
|
||||
stbOnResize();
|
||||
}, [height]);
|
||||
}, [stbOnResize, height]);
|
||||
|
||||
// ? Scroll down whenever the window resizes.
|
||||
useLayoutEffect(() => {
|
||||
document.addEventListener("resize", stbOnResize);
|
||||
return () => document.removeEventListener("resize", stbOnResize);
|
||||
}, [ref, scrollState]);
|
||||
}, [ref, scrollState, stbOnResize]);
|
||||
|
||||
// ? Scroll to bottom when pressing 'Escape'.
|
||||
useEffect(() => {
|
||||
@@ -275,7 +284,7 @@ export function MessageArea({ id }: Props) {
|
||||
|
||||
document.body.addEventListener("keyup", keyUp);
|
||||
return () => document.body.removeEventListener("keyup", keyUp);
|
||||
}, [ref, focusTaken]);
|
||||
}, [id, ref, focusTaken]);
|
||||
|
||||
return (
|
||||
<MessageAreaWidthContext.Provider
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
IntermediateContext,
|
||||
useIntermediate,
|
||||
} from "../../../context/intermediate/Intermediate";
|
||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
import AutoComplete, {
|
||||
useAutoComplete,
|
||||
@@ -79,7 +78,7 @@ export default function MessageEditor({ message, finish }: Props) {
|
||||
|
||||
document.body.addEventListener("keyup", keyUp);
|
||||
return () => document.body.removeEventListener("keyup", keyUp);
|
||||
}, [focusTaken]);
|
||||
}, [focusTaken, finish]);
|
||||
|
||||
const {
|
||||
onChange,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { X } from "@styled-icons/boxicons-regular";
|
||||
import { RelationshipStatus } from "revolt-api/types/Users";
|
||||
import { SYSTEM_USER_ID } from "revolt.js";
|
||||
import { Message as MessageObject } from "revolt.js/dist/maps/Messages";
|
||||
import { Message as MessageI } from "revolt.js/dist/maps/Messages";
|
||||
import styled from "styled-components";
|
||||
import { decodeTime } from "ulid";
|
||||
|
||||
import { Text } from "preact-i18n";
|
||||
import { memo } from "preact/compat";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { internalSubscribe, internalEmit } from "../../../lib/eventEmitter";
|
||||
import { RenderState } from "../../../lib/renderer/types";
|
||||
@@ -17,7 +17,7 @@ import { connectState } from "../../../redux/connector";
|
||||
import { QueuedMessage } from "../../../redux/reducers/queue";
|
||||
|
||||
import RequiresOnline from "../../../context/revoltjs/RequiresOnline";
|
||||
import { AppContext, useClient } from "../../../context/revoltjs/RevoltClient";
|
||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
import Message from "../../../components/common/messaging/Message";
|
||||
import { SystemMessage } from "../../../components/common/messaging/SystemMessage";
|
||||
@@ -76,10 +76,10 @@ function MessageRenderer({ id, state, queue, highlight }: Props) {
|
||||
];
|
||||
|
||||
return () => subs.forEach((unsub) => unsub());
|
||||
}, [state.messages]);
|
||||
}, [state.messages, state.type, userId]);
|
||||
|
||||
let render: Children[] = [],
|
||||
previous: MessageObject | undefined;
|
||||
const render: Children[] = [];
|
||||
let previous: MessageI | undefined;
|
||||
|
||||
if (state.atTop) {
|
||||
render.push(<ConversationStart id={id} />);
|
||||
@@ -148,30 +148,30 @@ function MessageRenderer({ id, state, queue, highlight }: Props) {
|
||||
highlight={highlight === message._id}
|
||||
/>,
|
||||
);
|
||||
} else if (
|
||||
message.author?.relationship === RelationshipStatus.Blocked
|
||||
) {
|
||||
blocked++;
|
||||
} else {
|
||||
if (message.author?.relationship === RelationshipStatus.Blocked) {
|
||||
blocked++;
|
||||
} else {
|
||||
if (blocked > 0) pushBlocked();
|
||||
if (blocked > 0) pushBlocked();
|
||||
|
||||
render.push(
|
||||
<Message
|
||||
message={message}
|
||||
key={message._id}
|
||||
head={head}
|
||||
content={
|
||||
editing === message._id ? (
|
||||
<MessageEditor
|
||||
message={message}
|
||||
finish={stopEditing}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
attachContext
|
||||
highlight={highlight === message._id}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
render.push(
|
||||
<Message
|
||||
message={message}
|
||||
key={message._id}
|
||||
head={head}
|
||||
content={
|
||||
editing === message._id ? (
|
||||
<MessageEditor
|
||||
message={message}
|
||||
finish={stopEditing}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
attachContext
|
||||
highlight={highlight === message._id}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
previous = message;
|
||||
@@ -191,7 +191,7 @@ function MessageRenderer({ id, state, queue, highlight }: Props) {
|
||||
previous = {
|
||||
_id: msg.id,
|
||||
author_id: userId!,
|
||||
} as any;
|
||||
} as MessageI;
|
||||
}
|
||||
|
||||
render.push(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BarChart } from "@styled-icons/boxicons-regular";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import styled from "styled-components";
|
||||
|
||||
|
||||
@@ -15,10 +15,6 @@ import { stopPropagation } from "../../lib/stopPropagation";
|
||||
|
||||
import { VoiceOperationsContext } from "../../context/Voice";
|
||||
import { useIntermediate } from "../../context/intermediate/Intermediate";
|
||||
import {
|
||||
AppContext,
|
||||
OperationsContext,
|
||||
} from "../../context/revoltjs/RevoltClient";
|
||||
|
||||
import UserIcon from "../../components/common/user/UserIcon";
|
||||
import UserStatus from "../../components/common/user/UserStatus";
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
ListPlus,
|
||||
} from "@styled-icons/boxicons-regular";
|
||||
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";
|
||||
@@ -68,7 +64,9 @@ export default observer(() => {
|
||||
] as [string, User[], string][];
|
||||
|
||||
const incoming = lists[0][1];
|
||||
const userlist: Children[] = incoming.map((x) => <b>{x.username}</b>);
|
||||
const userlist: Children[] = incoming.map((x) => (
|
||||
<b key={x._id}>{x.username}</b>
|
||||
));
|
||||
for (let i = incoming.length - 1; i > 0; i--) userlist.splice(i, 0, ", ");
|
||||
|
||||
const isEmpty = lists.reduce((p: number, n) => p + n.length, 0) === 0;
|
||||
@@ -195,6 +193,7 @@ export default observer(() => {
|
||||
|
||||
return (
|
||||
<CollapsibleSection
|
||||
key={section_id}
|
||||
id={`friends_${section_id}`}
|
||||
defaultValue={true}
|
||||
sticky
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Coffee } from "@styled-icons/boxicons-regular";
|
||||
import { Home as HomeIcon } from "@styled-icons/boxicons-solid";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function Invite() {
|
||||
.then((data) => setInvite(data))
|
||||
.catch((err) => setError(takeError(err)));
|
||||
}
|
||||
}, [status]);
|
||||
}, [client, code, invite, status]);
|
||||
|
||||
if (typeof invite === "undefined") {
|
||||
return (
|
||||
@@ -128,7 +128,7 @@ export default function Invite() {
|
||||
}
|
||||
|
||||
const dispose = autorun(() => {
|
||||
let server = client.servers.get(
|
||||
const server = client.servers.get(
|
||||
invite.server_id,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { UseFormMethods } from "react-hook-form";
|
||||
|
||||
import { Text, Localizer } from "preact-i18n";
|
||||
|
||||
import InputBox from "../../components/ui/InputBox";
|
||||
@@ -6,7 +8,7 @@ import Overline from "../../components/ui/Overline";
|
||||
interface Props {
|
||||
type: "email" | "username" | "password" | "invite" | "current_password";
|
||||
showOverline?: boolean;
|
||||
register: Function;
|
||||
register: UseFormMethods["register"];
|
||||
error?: string;
|
||||
name?: string;
|
||||
}
|
||||
@@ -27,9 +29,11 @@ export default function FormField({
|
||||
)}
|
||||
<Localizer>
|
||||
<InputBox
|
||||
// Styled uses React typing while we use Preact
|
||||
// this leads to inconsistances where things need to be typed oddly
|
||||
placeholder={(<Text id={`login.enter.${type}`} />) as any}
|
||||
placeholder={
|
||||
(
|
||||
<Text id={`login.enter.${type}`} />
|
||||
) as unknown as string
|
||||
}
|
||||
name={
|
||||
type === "current_password" ? "password" : name ?? type
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function CaptchaBlock(props: CaptchaProps) {
|
||||
if (!client.configuration?.features.captcha.enabled) {
|
||||
props.onSuccess();
|
||||
}
|
||||
}, []);
|
||||
}, [client.configuration?.features.captcha.enabled, props]);
|
||||
|
||||
if (!client.configuration?.features.captcha.enabled)
|
||||
return <Preloader type="spinner" />;
|
||||
|
||||
@@ -63,7 +63,7 @@ export function Form({ page, callback }: Props) {
|
||||
setGlobalError(undefined);
|
||||
setLoading(true);
|
||||
|
||||
function onError(err: any) {
|
||||
function onError(err: unknown) {
|
||||
setLoading(false);
|
||||
|
||||
const error = takeError(err);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ListCheck, ListUl } from "@styled-icons/boxicons-regular";
|
||||
import { Route, useHistory, useParams } from "react-router-dom";
|
||||
import { Route, Switch, useHistory, useParams } from "react-router-dom";
|
||||
|
||||
import { Text } from "preact-i18n";
|
||||
|
||||
@@ -16,7 +16,9 @@ export default function ChannelSettings() {
|
||||
const { channel: cid } = useParams<{ channel: string }>();
|
||||
|
||||
const client = useClient();
|
||||
const history = useHistory();
|
||||
const channel = client.channels.get(cid);
|
||||
|
||||
if (!channel) return null;
|
||||
if (
|
||||
channel.channel_type === "SavedMessages" ||
|
||||
@@ -24,7 +26,6 @@ export default function ChannelSettings() {
|
||||
)
|
||||
return null;
|
||||
|
||||
const history = useHistory();
|
||||
function switchPage(to?: string) {
|
||||
let base_url;
|
||||
switch (channel?.channel_type) {
|
||||
@@ -67,18 +68,20 @@ export default function ChannelSettings() {
|
||||
),
|
||||
},
|
||||
]}
|
||||
children={[
|
||||
<Route path="/server/:server/channel/:channel/settings/permissions">
|
||||
<Permissions channel={channel} />
|
||||
</Route>,
|
||||
<Route path="/channel/:channel/settings/permissions">
|
||||
<Permissions channel={channel} />
|
||||
</Route>,
|
||||
children={
|
||||
<Switch>
|
||||
<Route path="/server/:server/channel/:channel/settings/permissions">
|
||||
<Permissions channel={channel} />
|
||||
</Route>
|
||||
<Route path="/channel/:channel/settings/permissions">
|
||||
<Permissions channel={channel} />
|
||||
</Route>
|
||||
|
||||
<Route path="/">
|
||||
<Overview channel={channel} />
|
||||
</Route>,
|
||||
]}
|
||||
<Route>
|
||||
<Overview channel={channel} />
|
||||
</Route>
|
||||
</Switch>
|
||||
}
|
||||
category="channel_pages"
|
||||
switchPage={switchPage}
|
||||
defaultPage="overview"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ArrowBack, X } from "@styled-icons/boxicons-regular";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { Switch, useHistory, useParams } from "react-router-dom";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
|
||||
import styles from "./Settings.module.scss";
|
||||
import classNames from "classnames";
|
||||
import { Text } from "preact-i18n";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
||||
|
||||
@@ -51,7 +51,7 @@ export function GenericSettings({
|
||||
const { page } = useParams<{ page: string }>();
|
||||
|
||||
const [closing, setClosing] = useState(false);
|
||||
function exitSettings() {
|
||||
const exitSettings = useCallback(() => {
|
||||
if (history.length > 1) {
|
||||
setClosing(true);
|
||||
|
||||
@@ -61,7 +61,7 @@ export function GenericSettings({
|
||||
} else {
|
||||
history.push("/");
|
||||
}
|
||||
}
|
||||
}, [history]);
|
||||
|
||||
useEffect(() => {
|
||||
function keyDown(e: KeyboardEvent) {
|
||||
@@ -72,7 +72,7 @@ export function GenericSettings({
|
||||
|
||||
document.body.addEventListener("keydown", keyDown);
|
||||
return () => document.body.removeEventListener("keydown", keyDown);
|
||||
}, []);
|
||||
}, [exitSettings]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -158,7 +158,9 @@ export function GenericSettings({
|
||||
<div className={styles.scrollbox}>
|
||||
<div className={styles.contentcontainer}>
|
||||
{!isTouchscreenDevice &&
|
||||
!pages.find((x) => x.id === page && x.hideTitle) && (
|
||||
!pages.find(
|
||||
(x) => x.id === page && x.hideTitle,
|
||||
) && (
|
||||
<h1>
|
||||
<Text
|
||||
id={`app.settings.${category}.${
|
||||
@@ -167,19 +169,20 @@ export function GenericSettings({
|
||||
/>
|
||||
</h1>
|
||||
)}
|
||||
<Switch>{children}</Switch>
|
||||
{children}
|
||||
</div>
|
||||
{!isTouchscreenDevice && (
|
||||
<div className={styles.action}>
|
||||
<div onClick={exitSettings} className={styles.closeButton}>
|
||||
<X size={28} />
|
||||
<div className={styles.action}>
|
||||
<div
|
||||
onClick={exitSettings}
|
||||
className={styles.closeButton}>
|
||||
<X size={28} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ListUl, ListCheck, ListMinus } from "@styled-icons/boxicons-regular";
|
||||
import { XSquare, Share, Group } from "@styled-icons/boxicons-solid";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Route, useHistory, useParams } from "react-router-dom";
|
||||
import { Route, Switch, useHistory, useParams } from "react-router-dom";
|
||||
|
||||
import { Text } from "preact-i18n";
|
||||
|
||||
@@ -77,34 +77,36 @@ export default observer(() => {
|
||||
hideTitle: true,
|
||||
},
|
||||
]}
|
||||
children={[
|
||||
<Route path="/server/:server/settings/categories">
|
||||
<Categories server={server} />
|
||||
</Route>,
|
||||
<Route path="/server/:server/settings/members">
|
||||
<RequiresOnline>
|
||||
<Members server={server} />
|
||||
</RequiresOnline>
|
||||
</Route>,
|
||||
<Route path="/server/:server/settings/invites">
|
||||
<RequiresOnline>
|
||||
<Invites server={server} />
|
||||
</RequiresOnline>
|
||||
</Route>,
|
||||
<Route path="/server/:server/settings/bans">
|
||||
<RequiresOnline>
|
||||
<Bans server={server} />
|
||||
</RequiresOnline>
|
||||
</Route>,
|
||||
<Route path="/server/:server/settings/roles">
|
||||
<RequiresOnline>
|
||||
<Roles server={server} />
|
||||
</RequiresOnline>
|
||||
</Route>,
|
||||
<Route path="/">
|
||||
<Overview server={server} />
|
||||
</Route>,
|
||||
]}
|
||||
children={
|
||||
<Switch>
|
||||
<Route path="/server/:server/settings/categories">
|
||||
<Categories server={server} />
|
||||
</Route>
|
||||
<Route path="/server/:server/settings/members">
|
||||
<RequiresOnline>
|
||||
<Members server={server} />
|
||||
</RequiresOnline>
|
||||
</Route>
|
||||
<Route path="/server/:server/settings/invites">
|
||||
<RequiresOnline>
|
||||
<Invites server={server} />
|
||||
</RequiresOnline>
|
||||
</Route>
|
||||
<Route path="/server/:server/settings/bans">
|
||||
<RequiresOnline>
|
||||
<Bans server={server} />
|
||||
</RequiresOnline>
|
||||
</Route>
|
||||
<Route path="/server/:server/settings/roles">
|
||||
<RequiresOnline>
|
||||
<Roles server={server} />
|
||||
</RequiresOnline>
|
||||
</Route>
|
||||
<Route>
|
||||
<Overview server={server} />
|
||||
</Route>
|
||||
</Switch>
|
||||
}
|
||||
category="server_pages"
|
||||
switchPage={switchPage}
|
||||
defaultPage="overview"
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
User,
|
||||
Megaphone,
|
||||
} from "@styled-icons/boxicons-solid";
|
||||
import { Route, useHistory } from "react-router-dom";
|
||||
import { Route, Switch, useHistory } from "react-router-dom";
|
||||
import { LIBRARY_VERSION } from "revolt.js";
|
||||
|
||||
import styles from "./Settings.module.scss";
|
||||
@@ -120,103 +120,107 @@ export default function Settings() {
|
||||
title: <Text id="app.settings.pages.feedback.title" />,
|
||||
},
|
||||
]}
|
||||
children={[
|
||||
<Route path="/settings/profile">
|
||||
<Profile />
|
||||
</Route>,
|
||||
<Route path="/settings/sessions">
|
||||
<RequiresOnline>
|
||||
<Sessions />
|
||||
</RequiresOnline>
|
||||
</Route>,
|
||||
<Route path="/settings/appearance">
|
||||
<Appearance />
|
||||
</Route>,
|
||||
<Route path="/settings/notifications">
|
||||
<Notifications />
|
||||
</Route>,
|
||||
<Route path="/settings/language">
|
||||
<Languages />
|
||||
</Route>,
|
||||
<Route path="/settings/sync">
|
||||
<Sync />
|
||||
</Route>,
|
||||
<Route path="/settings/native">
|
||||
<Native />
|
||||
</Route>,
|
||||
<Route path="/settings/experiments">
|
||||
<ExperimentsPage />
|
||||
</Route>,
|
||||
<Route path="/settings/feedback">
|
||||
<Feedback />
|
||||
</Route>,
|
||||
<Route path="/">
|
||||
<Account />
|
||||
</Route>,
|
||||
]}
|
||||
children={
|
||||
<Switch>
|
||||
<Route path="/settings/profile">
|
||||
<Profile />
|
||||
</Route>
|
||||
<Route path="/settings/sessions">
|
||||
<RequiresOnline>
|
||||
<Sessions />
|
||||
</RequiresOnline>
|
||||
</Route>
|
||||
<Route path="/settings/appearance">
|
||||
<Appearance />
|
||||
</Route>
|
||||
<Route path="/settings/notifications">
|
||||
<Notifications />
|
||||
</Route>
|
||||
<Route path="/settings/language">
|
||||
<Languages />
|
||||
</Route>
|
||||
<Route path="/settings/sync">
|
||||
<Sync />
|
||||
</Route>
|
||||
<Route path="/settings/native">
|
||||
<Native />
|
||||
</Route>
|
||||
<Route path="/settings/experiments">
|
||||
<ExperimentsPage />
|
||||
</Route>
|
||||
<Route path="/settings/feedback">
|
||||
<Feedback />
|
||||
</Route>
|
||||
<Route path="/" exact>
|
||||
<Account />
|
||||
</Route>
|
||||
</Switch>
|
||||
}
|
||||
defaultPage="account"
|
||||
switchPage={switchPage}
|
||||
category="pages"
|
||||
custom={[
|
||||
<a
|
||||
href="https://gitlab.insrt.uk/revolt"
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
<ButtonItem compact>
|
||||
<Gitlab size={20} />
|
||||
<Text id="app.settings.pages.source_code" />
|
||||
custom={
|
||||
<>
|
||||
<a
|
||||
href="https://gitlab.insrt.uk/revolt"
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
<ButtonItem compact>
|
||||
<Gitlab size={20} />
|
||||
<Text id="app.settings.pages.source_code" />
|
||||
</ButtonItem>
|
||||
</a>
|
||||
<a
|
||||
href="https://insrt.uk/donate"
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
<ButtonItem className={styles.donate} compact>
|
||||
<Coffee size={20} />
|
||||
<Text id="app.settings.pages.donate.title" />
|
||||
</ButtonItem>
|
||||
</a>
|
||||
<LineDivider />
|
||||
<ButtonItem
|
||||
onClick={() => operations.logout()}
|
||||
className={styles.logOut}
|
||||
compact>
|
||||
<LogOut size={20} />
|
||||
<Text id="app.settings.pages.logOut" />
|
||||
</ButtonItem>
|
||||
</a>,
|
||||
<a
|
||||
href="https://insrt.uk/donate"
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
<ButtonItem className={styles.donate} compact>
|
||||
<Coffee size={20} />
|
||||
<Text id="app.settings.pages.donate.title" />
|
||||
</ButtonItem>
|
||||
</a>,
|
||||
<LineDivider />,
|
||||
<ButtonItem
|
||||
onClick={() => operations.logout()}
|
||||
className={styles.logOut}
|
||||
compact>
|
||||
<LogOut size={20} />
|
||||
<Text id="app.settings.pages.logOut" />
|
||||
</ButtonItem>,
|
||||
<div className={styles.version}>
|
||||
<span className={styles.revision}>
|
||||
<a
|
||||
href={`${REPO_URL}/${GIT_REVISION}`}
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
{GIT_REVISION.substr(0, 7)}
|
||||
</a>
|
||||
{` `}
|
||||
<a
|
||||
href={
|
||||
GIT_BRANCH !== "DETACHED"
|
||||
? `https://gitlab.insrt.uk/revolt/client/-/tree/${GIT_BRANCH}`
|
||||
: undefined
|
||||
}
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
({GIT_BRANCH})
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
{GIT_BRANCH === "production" ? "Stable" : "Nightly"}{" "}
|
||||
{APP_VERSION}
|
||||
</span>
|
||||
{window.isNative && (
|
||||
<span>Native: {window.nativeVersion}</span>
|
||||
)}
|
||||
<span>
|
||||
API: {client.configuration?.revolt ?? "N/A"}
|
||||
</span>
|
||||
<span>revolt.js: {LIBRARY_VERSION}</span>
|
||||
</div>,
|
||||
]}
|
||||
<div className={styles.version}>
|
||||
<span className={styles.revision}>
|
||||
<a
|
||||
href={`${REPO_URL}/${GIT_REVISION}`}
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
{GIT_REVISION.substr(0, 7)}
|
||||
</a>
|
||||
{` `}
|
||||
<a
|
||||
href={
|
||||
GIT_BRANCH !== "DETACHED"
|
||||
? `https://gitlab.insrt.uk/revolt/client/-/tree/${GIT_BRANCH}`
|
||||
: undefined
|
||||
}
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
({GIT_BRANCH})
|
||||
</a>
|
||||
</span>
|
||||
<span>
|
||||
{GIT_BRANCH === "production" ? "Stable" : "Nightly"}{" "}
|
||||
{APP_VERSION}
|
||||
</span>
|
||||
{window.isNative && (
|
||||
<span>Native: {window.nativeVersion}</span>
|
||||
)}
|
||||
<span>
|
||||
API: {client.configuration?.revolt ?? "N/A"}
|
||||
</span>
|
||||
<span>revolt.js: {LIBRARY_VERSION}</span>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||
import styled, { css } from "styled-components";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { Text } from "preact-i18n";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
||||
|
||||
import { FileUploader } from "../../../context/revoltjs/FileUploads";
|
||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
import Button from "../../../components/ui/Button";
|
||||
import InputBox from "../../../components/ui/InputBox";
|
||||
@@ -31,8 +30,6 @@ const Row = styled.div`
|
||||
`;
|
||||
|
||||
export default observer(({ channel }: Props) => {
|
||||
const client = useContext(AppContext);
|
||||
|
||||
const [name, setName] = useState(channel.name ?? undefined);
|
||||
const [description, setDescription] = useState(channel.description ?? "");
|
||||
|
||||
@@ -44,7 +41,7 @@ export default observer(({ channel }: Props) => {
|
||||
|
||||
const [changed, setChanged] = useState(false);
|
||||
function save() {
|
||||
const changes: any = {};
|
||||
const changes: Record<string, string | undefined> = {};
|
||||
if (name !== channel.name) changes.name = name;
|
||||
if (description !== channel.description)
|
||||
changes.description = description;
|
||||
|
||||
@@ -64,6 +64,7 @@ export default observer(({ channel }: Props) => {
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
key={id}
|
||||
checked={selected === id}
|
||||
onChange={(selected) => selected && setSelected(id)}>
|
||||
{role.name}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Trash,
|
||||
} from "@styled-icons/boxicons-solid";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Link, useHistory } from "react-router-dom";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { Profile } from "revolt-api/types/Users";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
@@ -55,7 +55,7 @@ export const Account = observer(() => {
|
||||
.user!.fetchProfile()
|
||||
.then((profile) => setProfile(profile ?? {}));
|
||||
}
|
||||
}, [status]);
|
||||
}, [client, email, profile, status]);
|
||||
|
||||
return (
|
||||
<div className={styles.user}>
|
||||
@@ -95,12 +95,17 @@ export const Account = observer(() => {
|
||||
<div>
|
||||
{(
|
||||
[
|
||||
["username", client.user!.username, <At size={24} />],
|
||||
["email", email, <Envelope size={24} />],
|
||||
["password", "•••••••••", <Key size={24} />],
|
||||
[
|
||||
"username",
|
||||
client.user!.username,
|
||||
<At key="at" size={24} />,
|
||||
],
|
||||
["email", email, <Envelope key="envelope" size={24} />],
|
||||
["password", "•••••••••", <Key key="key" size={24} />],
|
||||
] as const
|
||||
).map(([field, value, icon]) => (
|
||||
<CategoryButton
|
||||
key={field}
|
||||
icon={icon}
|
||||
description={
|
||||
field === "email" ? (
|
||||
@@ -152,13 +157,15 @@ export const Account = observer(() => {
|
||||
</h3>
|
||||
<h5>
|
||||
{/*<Text id="app.settings.pages.account.2fa.description" />*/}
|
||||
Two-factor authentication is currently work-in-progress, see {` `}
|
||||
Two-factor authentication is currently work-in-progress, see{" "}
|
||||
{` `}
|
||||
<a
|
||||
href="https://gitlab.insrt.uk/insert/rauth/-/issues/2"
|
||||
target="_blank"
|
||||
rel="noreferrer">
|
||||
tracking issue here
|
||||
</a>.
|
||||
</a>
|
||||
.
|
||||
</h5>
|
||||
<CategoryButton
|
||||
icon={<Lock size={24} color="var(--error)" />}
|
||||
@@ -188,7 +195,7 @@ export const Account = observer(() => {
|
||||
description={
|
||||
"Delete your account, including all of your data."
|
||||
}
|
||||
onClick={() => {}}
|
||||
hover
|
||||
action="external">
|
||||
<Text id="app.settings.pages.account.manage.delete" />
|
||||
</CategoryButton>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Reset, Import } from "@styled-icons/boxicons-regular";
|
||||
import { Pencil } from "@styled-icons/boxicons-solid";
|
||||
// @ts-ignore
|
||||
// @ts-expect-error shade-blend-color does not have typings.
|
||||
import pSBC from "shade-blend-color";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
@@ -17,8 +17,10 @@ import { EmojiPacks, Settings } from "../../../redux/reducers/settings";
|
||||
import {
|
||||
DEFAULT_FONT,
|
||||
DEFAULT_MONO_FONT,
|
||||
Fonts,
|
||||
FONTS,
|
||||
FONT_KEYS,
|
||||
MonospaceFonts,
|
||||
MONOSPACE_FONTS,
|
||||
MONOSPACE_FONT_KEYS,
|
||||
Theme,
|
||||
@@ -30,6 +32,7 @@ import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||
import CollapsibleSection from "../../../components/common/CollapsibleSection";
|
||||
import Tooltip from "../../../components/common/Tooltip";
|
||||
import Button from "../../../components/ui/Button";
|
||||
import Checkbox from "../../../components/ui/Checkbox";
|
||||
import ColourSwatches from "../../../components/ui/ColourSwatches";
|
||||
import ComboBox from "../../../components/ui/ComboBox";
|
||||
import InputBox from "../../../components/ui/InputBox";
|
||||
@@ -56,12 +59,12 @@ export function Component(props: Props) {
|
||||
});
|
||||
}
|
||||
|
||||
function pushOverride(custom: Partial<Theme>) {
|
||||
const pushOverride = useCallback((custom: Partial<Theme>) => {
|
||||
dispatch({
|
||||
type: "SETTINGS_SET_THEME_OVERRIDE",
|
||||
custom,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
function setAccent(accent: string) {
|
||||
setOverride({
|
||||
@@ -80,12 +83,14 @@ export function Component(props: Props) {
|
||||
});
|
||||
}
|
||||
|
||||
const setOverride = useCallback(debounce(pushOverride, 200), []) as (
|
||||
custom: Partial<Theme>,
|
||||
) => void;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const setOverride = useCallback(
|
||||
debounce(pushOverride as (...args: unknown[]) => void, 200),
|
||||
[pushOverride],
|
||||
) as (custom: Partial<Theme>) => void;
|
||||
const [css, setCSS] = useState(props.settings.theme?.custom?.css ?? "");
|
||||
|
||||
useEffect(() => setOverride({ css }), [css]);
|
||||
useEffect(() => setOverride({ css }), [setOverride, css]);
|
||||
|
||||
const selected = props.settings.theme?.preset ?? "dark";
|
||||
return (
|
||||
@@ -169,15 +174,15 @@ export function Component(props: Props) {
|
||||
<ComboBox
|
||||
value={theme.font ?? DEFAULT_FONT}
|
||||
onChange={(e) =>
|
||||
pushOverride({ font: e.currentTarget.value as any })
|
||||
pushOverride({ font: e.currentTarget.value as Fonts })
|
||||
}>
|
||||
{FONT_KEYS.map((key) => (
|
||||
<option value={key}>
|
||||
<option value={key} key={key}>
|
||||
{FONTS[key as keyof typeof FONTS].name}
|
||||
</option>
|
||||
))}
|
||||
</ComboBox>
|
||||
{/* TOFIX: Only show when a font with ligature support is selected, i.e.: Inter.
|
||||
{/* TOFIX: Only show when a font with ligature support is selected, i.e.: Inter.*/}
|
||||
<p>
|
||||
<Checkbox
|
||||
checked={props.settings.theme?.ligatures === true}
|
||||
@@ -191,7 +196,7 @@ export function Component(props: Props) {
|
||||
}>
|
||||
<Text id="app.settings.pages.appearance.ligatures" />
|
||||
</Checkbox>
|
||||
</p>*/}
|
||||
</p>
|
||||
|
||||
<h3>
|
||||
<Text id="app.settings.pages.appearance.emoji_pack" />
|
||||
@@ -405,11 +410,12 @@ export function Component(props: Props) {
|
||||
value={theme.monospaceFont ?? DEFAULT_MONO_FONT}
|
||||
onChange={(e) =>
|
||||
pushOverride({
|
||||
monospaceFont: e.currentTarget.value as any,
|
||||
monospaceFont: e.currentTarget
|
||||
.value as MonospaceFonts,
|
||||
})
|
||||
}>
|
||||
{MONOSPACE_FONT_KEYS.map((key) => (
|
||||
<option value={key}>
|
||||
<option value={key} key={key}>
|
||||
{
|
||||
MONOSPACE_FONTS[
|
||||
key as keyof typeof MONOSPACE_FONTS
|
||||
|
||||
@@ -23,6 +23,7 @@ export function Component(props: Props) {
|
||||
</h3>
|
||||
{AVAILABLE_EXPERIMENTS.map((key) => (
|
||||
<Checkbox
|
||||
key={key}
|
||||
checked={(props.options?.enabled ?? []).indexOf(key) > -1}
|
||||
onChange={(enabled) =>
|
||||
dispatch({
|
||||
|
||||
@@ -70,7 +70,7 @@ export function Feedback() {
|
||||
placeholder={
|
||||
(
|
||||
<Text id="app.settings.pages.feedback.other" />
|
||||
) as any
|
||||
) as unknown as string
|
||||
}
|
||||
/>
|
||||
</Localizer>
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { SyncOptions } from "../../../redux/reducers/sync";
|
||||
|
||||
import Button from "../../../components/ui/Button";
|
||||
import Checkbox from "../../../components/ui/Checkbox";
|
||||
|
||||
interface Props {
|
||||
options?: SyncOptions;
|
||||
}
|
||||
|
||||
export function Native(props: Props) {
|
||||
export function Native() {
|
||||
const [config, setConfig] = useState(window.native.getConfig());
|
||||
const [autoStart, setAutoStart] = useState<boolean | undefined>();
|
||||
const fetchValue = () => window.native.getAutoStart().then(setAutoStart);
|
||||
@@ -41,8 +35,7 @@ export function Native(props: Props) {
|
||||
description="Launch Revolt when you log into your computer.">
|
||||
Start with computer
|
||||
</Checkbox>
|
||||
|
||||
|
||||
|
||||
<Checkbox
|
||||
checked={config.discordRPC}
|
||||
onChange={(discordRPC) => {
|
||||
@@ -181,4 +174,4 @@ export function Native(props: Props) {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ export function Component({ options }: Props) {
|
||||
</h3>
|
||||
{SOUNDS_ARRAY.map((key) => (
|
||||
<Checkbox
|
||||
key={key}
|
||||
checked={!!enabledSounds[key]}
|
||||
onChange={(enabled) =>
|
||||
dispatch({
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Profile as ProfileI } from "revolt-api/types/Users";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { IntlContext, Text, translate } from "preact-i18n";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { Text } from "preact-i18n";
|
||||
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
||||
|
||||
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
||||
import { useTranslation } from "../../../lib/i18n";
|
||||
|
||||
import { UserProfile } from "../../../context/intermediate/popovers/UserProfile";
|
||||
import { FileUploader } from "../../../context/revoltjs/FileUploads";
|
||||
@@ -16,31 +19,27 @@ import AutoComplete, {
|
||||
useAutoComplete,
|
||||
} from "../../../components/common/AutoComplete";
|
||||
import Button from "../../../components/ui/Button";
|
||||
import { Profile } from "revolt-api/types/Users";
|
||||
|
||||
export function Profile() {
|
||||
const { intl } = useContext(IntlContext);
|
||||
const status = useContext(StatusContext);
|
||||
|
||||
const translate = useTranslation();
|
||||
const client = useClient();
|
||||
|
||||
const [profile, setProfile] = useState<undefined | Profile>(
|
||||
undefined,
|
||||
);
|
||||
const [profile, setProfile] = useState<undefined | ProfileI>(undefined);
|
||||
|
||||
// ! FIXME: temporary solution
|
||||
// ! we should just announce profile changes through WS
|
||||
function refreshProfile() {
|
||||
const refreshProfile = useCallback(() => {
|
||||
client
|
||||
.user!.fetchProfile()
|
||||
.then((profile) => setProfile(profile ?? {}));
|
||||
}
|
||||
}, [client.user, setProfile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (profile === undefined && status === ClientStatus.ONLINE) {
|
||||
refreshProfile();
|
||||
}
|
||||
}, [status]);
|
||||
}, [profile, status, refreshProfile]);
|
||||
|
||||
const [changed, setChanged] = useState(false);
|
||||
function setContent(content?: string) {
|
||||
@@ -69,7 +68,6 @@ export function Profile() {
|
||||
user_id={client.user!._id}
|
||||
dummy={true}
|
||||
dummyProfile={profile}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
@@ -85,9 +83,7 @@ export function Profile() {
|
||||
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,
|
||||
@@ -152,8 +148,6 @@ export function Profile() {
|
||||
? "fetching"
|
||||
: "placeholder"
|
||||
}`,
|
||||
"",
|
||||
(intl as any).dictionary as Record<string, unknown>,
|
||||
)}
|
||||
onKeyUp={onKeyUp}
|
||||
onKeyDown={onKeyDown}
|
||||
|
||||
@@ -50,7 +50,7 @@ export function Sessions() {
|
||||
);
|
||||
setSessions(data);
|
||||
});
|
||||
}, []);
|
||||
}, [client, setSessions, deviceId]);
|
||||
|
||||
if (typeof sessions === "undefined") {
|
||||
return (
|
||||
@@ -123,6 +123,7 @@ export function Sessions() {
|
||||
const systemIcon = getSystemIcon(session);
|
||||
return (
|
||||
<div
|
||||
key={session.id}
|
||||
className={styles.entry}
|
||||
data-active={session.id === deviceId}
|
||||
data-deleting={
|
||||
|
||||
@@ -26,6 +26,7 @@ export function Component(props: Props) {
|
||||
] as [SyncKeys, string][]
|
||||
).map(([key, title]) => (
|
||||
<Checkbox
|
||||
key={key}
|
||||
checked={
|
||||
(props.options?.disabled ?? []).indexOf(key) === -1
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ import { Server } from "revolt.js/dist/maps/Servers";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import UserIcon from "../../../components/common/user/UserIcon";
|
||||
import IconButton from "../../../components/ui/IconButton";
|
||||
@@ -18,15 +16,14 @@ interface Props {
|
||||
}
|
||||
|
||||
export const Bans = observer(({ server }: Props) => {
|
||||
const client = useContext(AppContext);
|
||||
const [deleting, setDelete] = useState<string[]>([]);
|
||||
const [data, setData] = useState<
|
||||
Route<"GET", "/servers/id/bans">["response"] | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
server.fetchBans().then(setData as any);
|
||||
}, []);
|
||||
server.fetchBans().then(setData);
|
||||
}, [server, setData]);
|
||||
|
||||
return (
|
||||
<div className={styles.userList}>
|
||||
@@ -43,10 +40,11 @@ export const Bans = observer(({ server }: Props) => {
|
||||
</div>
|
||||
{typeof data === "undefined" && <Preloader type="ring" />}
|
||||
{data?.bans.map((x) => {
|
||||
let user = data.users.find((y) => y._id === x._id.user);
|
||||
const user = data.users.find((y) => y._id === x._id.user);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={x._id.user}
|
||||
className={styles.ban}
|
||||
data-deleting={deleting.indexOf(x._id.user) > -1}>
|
||||
<span>
|
||||
|
||||
@@ -4,17 +4,12 @@ import { Category } from "revolt-api/types/Servers";
|
||||
import { Server } from "revolt.js/dist/maps/Servers";
|
||||
import { ulid } from "ulid";
|
||||
|
||||
import { useContext, useState } from "preact/hooks";
|
||||
|
||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||
import { useState } from "preact/hooks";
|
||||
|
||||
import ChannelIcon from "../../../components/common/ChannelIcon";
|
||||
import UserIcon from "../../../components/common/user/UserIcon";
|
||||
import Button from "../../../components/ui/Button";
|
||||
import ComboBox from "../../../components/ui/ComboBox";
|
||||
import IconButton from "../../../components/ui/IconButton";
|
||||
import InputBox from "../../../components/ui/InputBox";
|
||||
import Preloader from "../../../components/ui/Preloader";
|
||||
import Tip from "../../../components/ui/Tip";
|
||||
|
||||
interface Props {
|
||||
@@ -23,7 +18,6 @@ interface Props {
|
||||
|
||||
// ! FIXME: really bad code
|
||||
export const Categories = observer(({ server }: Props) => {
|
||||
const client = useContext(AppContext);
|
||||
const channels = server.channels.filter((x) => typeof x !== "undefined");
|
||||
|
||||
const [cats, setCats] = useState<Category[]>(server.categories ?? []);
|
||||
@@ -95,6 +89,7 @@ export const Categories = observer(({ server }: Props) => {
|
||||
{channels.map((channel) => {
|
||||
return (
|
||||
<div
|
||||
key={channel!._id}
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "12px",
|
||||
@@ -131,7 +126,9 @@ export const Categories = observer(({ server }: Props) => {
|
||||
}>
|
||||
<option value="none">Uncategorised</option>
|
||||
{cats.map((x) => (
|
||||
<option value={x.id}>{x.title}</option>
|
||||
<option key={x.id} value={x.id}>
|
||||
{x.title}
|
||||
</option>
|
||||
))}
|
||||
</ComboBox>
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,7 @@ export const Invites = observer(({ server }: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
server.fetchInvites().then(setInvites);
|
||||
}, []);
|
||||
}, [server, setInvites]);
|
||||
|
||||
return (
|
||||
<div className={styles.userList}>
|
||||
@@ -57,6 +57,7 @@ export const Invites = observer(({ server }: Props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
key={invite._id}
|
||||
className={styles.invite}
|
||||
data-deleting={deleting.indexOf(invite._id) > -1}>
|
||||
<code>{invite._id}</code>
|
||||
|
||||
@@ -9,8 +9,6 @@ import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
import UserIcon from "../../../components/common/user/UserIcon";
|
||||
import Button from "../../../components/ui/Button";
|
||||
import Checkbox from "../../../components/ui/Checkbox";
|
||||
@@ -29,7 +27,7 @@ export const Members = observer(({ server }: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
server.fetchMembers().then(setData);
|
||||
}, []);
|
||||
}, [server, setData]);
|
||||
|
||||
const [roles, setRoles] = useState<string[]>([]);
|
||||
useEffect(() => {
|
||||
@@ -38,7 +36,7 @@ export const Members = observer(({ server }: Props) => {
|
||||
data!.members.find((x) => x._id.user === selected)?.roles ?? [],
|
||||
);
|
||||
}
|
||||
}, [selected]);
|
||||
}, [setRoles, selected, data]);
|
||||
|
||||
return (
|
||||
<div className={styles.userList}>
|
||||
@@ -57,9 +55,10 @@ export const Members = observer(({ server }: Props) => {
|
||||
};
|
||||
})
|
||||
.map(({ member, user }) => (
|
||||
<>
|
||||
// @ts-expect-error brokey
|
||||
// eslint-disable-next-line react/jsx-no-undef
|
||||
<Fragment key={member._id.user}>
|
||||
<div
|
||||
key={member._id.user}
|
||||
className={styles.member}
|
||||
data-open={selected === member._id.user}
|
||||
onClick={() =>
|
||||
@@ -81,14 +80,15 @@ export const Members = observer(({ server }: Props) => {
|
||||
</div>
|
||||
{selected === member._id.user && (
|
||||
<div
|
||||
key={"drop_" + member._id.user}
|
||||
key={`drop_${member._id.user}`}
|
||||
className={styles.memberView}>
|
||||
<Overline type="subtle">Roles</Overline>
|
||||
{Object.keys(server.roles ?? {}).map(
|
||||
(key) => {
|
||||
let role = server.roles![key];
|
||||
const role = server.roles![key];
|
||||
return (
|
||||
<Checkbox
|
||||
key={key}
|
||||
checked={
|
||||
roles.includes(key) ??
|
||||
false
|
||||
@@ -134,7 +134,7 @@ export const Members = observer(({ server }: Props) => {
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,12 +4,11 @@ import { Server } from "revolt.js/dist/maps/Servers";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
||||
|
||||
import { FileUploader } from "../../../context/revoltjs/FileUploads";
|
||||
import { AppContext, useClient } from "../../../context/revoltjs/RevoltClient";
|
||||
import { getChannelName } from "../../../context/revoltjs/util";
|
||||
|
||||
import Button from "../../../components/ui/Button";
|
||||
@@ -21,8 +20,6 @@ interface Props {
|
||||
}
|
||||
|
||||
export const Overview = observer(({ server }: Props) => {
|
||||
const client = useClient();
|
||||
|
||||
const [name, setName] = useState(server.name);
|
||||
const [description, setDescription] = useState(server.description ?? "");
|
||||
const [systemMessages, setSystemMessages] = useState(
|
||||
@@ -41,7 +38,7 @@ export const Overview = observer(({ server }: Props) => {
|
||||
|
||||
const [changed, setChanged] = useState(false);
|
||||
function save() {
|
||||
const changes: Record<string, any> = {};
|
||||
const changes: Record<string, unknown> = {};
|
||||
if (name !== server.name) changes.name = name;
|
||||
if (description !== server.description)
|
||||
changes.description = description;
|
||||
@@ -122,6 +119,7 @@ export const Overview = observer(({ server }: Props) => {
|
||||
].map(([i18n, key]) => (
|
||||
// ! FIXME: temporary code just so we can expose the options
|
||||
<p
|
||||
key={key}
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
@@ -156,7 +154,7 @@ export const Overview = observer(({ server }: Props) => {
|
||||
{server.channels
|
||||
.filter((x) => typeof x !== "undefined")
|
||||
.map((channel) => (
|
||||
<option value={channel!._id}>
|
||||
<option key={channel!._id} value={channel!._id}>
|
||||
{getChannelName(channel!, true)}
|
||||
</option>
|
||||
))}
|
||||
|
||||
@@ -6,18 +6,15 @@ import { Server } from "revolt.js/dist/maps/Servers";
|
||||
|
||||
import styles from "./Panes.module.scss";
|
||||
import { Text } from "preact-i18n";
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { useCallback, useEffect, 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 IconButton from "../../../components/ui/IconButton";
|
||||
import InputBox from "../../../components/ui/InputBox";
|
||||
import Overline from "../../../components/ui/Overline";
|
||||
import Tip from "../../../components/ui/Tip";
|
||||
|
||||
import ButtonItem from "../../../components/navigation/items/ButtonItem";
|
||||
|
||||
@@ -31,23 +28,29 @@ const I32ToU32 = (arr: number[]) => arr.map((x) => x >>> 0);
|
||||
export const Roles = observer(({ server }: Props) => {
|
||||
const [role, setRole] = useState("default");
|
||||
const { openScreen } = useIntermediate();
|
||||
const client = useContext(AppContext);
|
||||
const roles = server.roles ?? {};
|
||||
const roles = useMemo(() => server.roles ?? {}, [server]);
|
||||
|
||||
if (role !== "default" && typeof roles[role] === "undefined") {
|
||||
useEffect(() => setRole("default"));
|
||||
useEffect(() => setRole("default"), [role]);
|
||||
return null;
|
||||
}
|
||||
|
||||
function getPermissions(id: string) {
|
||||
return I32ToU32(
|
||||
id === "default"
|
||||
? server.default_permissions
|
||||
: roles[id].permissions,
|
||||
);
|
||||
}
|
||||
const {
|
||||
name: roleName,
|
||||
colour: roleColour,
|
||||
permissions,
|
||||
} = roles[role] ?? {};
|
||||
|
||||
const { name: roleName, colour: roleColour } = 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);
|
||||
@@ -55,7 +58,7 @@ export const Roles = observer(({ server }: Props) => {
|
||||
|
||||
useEffect(
|
||||
() => setPerm(getPermissions(role)),
|
||||
[role, roles[role]?.permissions],
|
||||
[getPermissions, role, permissions],
|
||||
);
|
||||
|
||||
useEffect(() => setName(roleName), [role, roleName]);
|
||||
@@ -115,6 +118,7 @@ export const Roles = observer(({ server }: Props) => {
|
||||
}
|
||||
return (
|
||||
<ButtonItem
|
||||
key={id}
|
||||
active={role === id}
|
||||
onClick={() => setRole(id)}
|
||||
style={{ color: roles[id].colour }}>
|
||||
@@ -174,6 +178,7 @@ export const Roles = observer(({ server }: Props) => {
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
key={key}
|
||||
checked={(perm[0] & value) > 0}
|
||||
onChange={() =>
|
||||
setPerm([perm[0] ^ value, perm[1]])
|
||||
@@ -199,6 +204,7 @@ export const Roles = observer(({ server }: Props) => {
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
key={key}
|
||||
checked={((perm[1] >>> 0) & value) > 0}
|
||||
onChange={() =>
|
||||
setPerm([perm[0], perm[1] ^ value])
|
||||
|
||||
Reference in New Issue
Block a user