Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Showing
with 731 additions and 1089 deletions
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
import { Channels, Servers, Users } from "revolt.js/dist/api/objects";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Message as MessageI } from "revolt.js/dist/maps/Messages";
import { Server } from "revolt.js/dist/maps/Servers";
import { User } from "revolt.js/dist/maps/Users";
import { ulid } from "ulid";
import styles from "./Prompt.module.scss";
......@@ -9,9 +12,6 @@ import { useContext, useEffect, useState } from "preact/hooks";
import { TextReact } from "../../../lib/i18n";
import { User } from "../../../mobx";
import { useData } from "../../../mobx/State";
import Message from "../../../components/common/messaging/Message";
import UserIcon from "../../../components/common/user/UserIcon";
import InputBox from "../../../components/ui/InputBox";
......@@ -21,7 +21,7 @@ import Radio from "../../../components/ui/Radio";
import { Children } from "../../../types/Preact";
import { AppContext } from "../../revoltjs/RevoltClient";
import { mapMessage, takeError } from "../../revoltjs/util";
import { takeError } from "../../revoltjs/util";
import { useIntermediate } from "../Intermediate";
interface Props {
......@@ -55,21 +55,21 @@ export function PromptModal({
}
type SpecialProps = { onClose: () => void } & (
| { type: "leave_group"; target: Channels.GroupChannel }
| { type: "close_dm"; target: Channels.DirectMessageChannel }
| { type: "leave_server"; target: Servers.Server }
| { type: "delete_server"; target: Servers.Server }
| { type: "delete_channel"; target: Channels.TextChannel }
| { type: "delete_message"; target: Channels.Message }
| { type: "leave_group"; target: Channel }
| { type: "close_dm"; target: Channel }
| { type: "leave_server"; target: Server }
| { type: "delete_server"; target: Server }
| { type: "delete_channel"; target: Channel }
| { type: "delete_message"; target: MessageI }
| {
type: "create_invite";
target: Channels.TextChannel | Channels.GroupChannel;
target: Channel;
}
| { type: "kick_member"; target: Servers.Server; user: User }
| { type: "ban_member"; target: Servers.Server; user: User }
| { type: "kick_member"; target: Server; user: User }
| { type: "ban_member"; target: Server; user: User }
| { type: "unfriend_user"; target: User }
| { type: "block_user"; target: User }
| { type: "create_channel"; target: Servers.Server }
| { type: "create_channel"; target: Server }
);
export const SpecialPromptModal = observer((props: SpecialProps) => {
......@@ -104,9 +104,7 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
name = props.target.username;
break;
case "close_dm":
name = client.users.get(
client.channels.getRecipient(props.target._id),
)?.username;
name = props.target.recipient?.username;
break;
default:
name = props.target.name;
......@@ -137,27 +135,19 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
try {
switch (props.type) {
case "unfriend_user":
await client.users.removeFriend(
props.target._id,
);
await props.target.removeFriend();
break;
case "block_user":
await client.users.blockUser(
props.target._id,
);
await props.target.blockUser();
break;
case "leave_group":
case "close_dm":
case "delete_channel":
await client.channels.delete(
props.target._id,
);
props.target.delete();
break;
case "leave_server":
case "delete_server":
await client.servers.delete(
props.target._id,
);
props.target.delete();
break;
}
......@@ -203,11 +193,7 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
setProcessing(true);
try {
await client.channels.deleteMessage(
props.target.channel,
props.target._id,
);
props.target.delete();
onClose();
} catch (err) {
setError(takeError(err));
......@@ -229,7 +215,7 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
id={`app.special.modals.prompt.confirm_delete_message_long`}
/>
<Message
message={mapMessage(props.target)}
message={props.target}
head={true}
contrast
/>
......@@ -247,12 +233,12 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
useEffect(() => {
setProcessing(true);
client.channels
.createInvite(props.target._id)
props.target
.createInvite()
.then((code) => setCode(code))
.catch((err) => setError(takeError(err)))
.finally(() => setProcessing(false));
}, []);
}, [props.target]);
return (
<PromptModal
......@@ -306,10 +292,13 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
setProcessing(true);
try {
await client.members.kickMember(
props.target._id,
props.user._id,
);
client.members
.getKey({
server: props.target._id,
user: props.user._id,
})
?.kick();
onClose();
} catch (err) {
setError(takeError(err));
......@@ -357,11 +346,9 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
setProcessing(true);
try {
await client.servers.banUser(
props.target._id,
props.user._id,
{ reason },
);
await props.target.banUser(props.user._id, {
reason,
});
onClose();
} catch (err) {
setError(takeError(err));
......@@ -420,14 +407,11 @@ export const SpecialPromptModal = observer((props: SpecialProps) => {
try {
const channel =
await client.servers.createChannel(
props.target._id,
{
type,
name,
nonce: ulid(),
},
);
await props.target.createChannel({
type,
name,
nonce: ulid(),
});
history.push(
`/server/${props.target._id}/channel/${channel._id}`,
......
import { X } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
import styles from "./ChannelInfo.module.scss";
import Modal from "../../../components/ui/Modal";
import Markdown from "../../../components/markdown/Markdown";
import { useChannel, useForceUpdate } from "../../revoltjs/hooks";
import { getChannelName } from "../../revoltjs/util";
interface Props {
channel_id: string;
channel: Channel;
onClose: () => void;
}
export function ChannelInfo({ channel_id, onClose }: Props) {
const ctx = useForceUpdate();
const channel = useChannel(channel_id, ctx);
if (!channel) return null;
export const ChannelInfo = observer(({ channel, onClose }: Props) => {
if (
channel.channel_type === "DirectMessage" ||
channel.channel_type === "SavedMessages"
......@@ -30,15 +27,15 @@ export function ChannelInfo({ channel_id, onClose }: Props) {
<Modal visible={true} onClose={onClose}>
<div className={styles.info}>
<div className={styles.header}>
<h1>{getChannelName(ctx.client, channel, true)}</h1>
<h1>{getChannelName(channel, true)}</h1>
<div onClick={onClose}>
<X size={36} />
</div>
</div>
<p>
<Markdown content={channel.description} />
<Markdown content={channel.description!} />
</p>
</div>
</Modal>
);
}
});
import {
Attachment,
AttachmentMetadata,
EmbedImage,
} from "revolt.js/dist/api/objects";
/* eslint-disable react-hooks/rules-of-hooks */
import { Attachment, AttachmentMetadata } from "revolt-api/types/Autumn";
import { EmbedImage } from "revolt-api/types/January";
import styles from "./ImageViewer.module.scss";
import { useContext, useEffect } from "preact/hooks";
import AttachmentActions from "../../../components/common/messaging/attachments/AttachmentActions";
import EmbedMediaActions from "../../../components/common/messaging/embed/EmbedMediaActions";
......
......@@ -85,11 +85,13 @@ export function ModifyAccountModal({ onClose, field }: Props) {
]}>
{/* Preact / React typing incompatabilities */}
<form
onSubmit={
onSubmit={(e) => {
e.preventDefault();
handleSubmit(
onSubmit,
) as JSX.GenericEventHandler<HTMLFormElement>
}>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
)(e as any);
}}>
{field === "email" && (
<FormField
type="email"
......
import { observer } from "mobx-react-lite";
import { User } from "revolt.js/dist/maps/Users";
import styles from "./UserPicker.module.scss";
import { Text } from "preact-i18n";
import { User } from "../../../mobx";
import Modal from "../../../components/ui/Modal";
import { Friend } from "../../../pages/friends/Friend";
......
......@@ -4,7 +4,6 @@
max-height: 360px;
overflow-y: scroll;
// ! FIXME: very temporary code
> label {
> span {
align-items: flex-start !important;
......@@ -18,4 +17,4 @@
}
}
}
}
\ No newline at end of file
}
import { Users } from "revolt.js/dist/api/objects";
import { RelationshipStatus } from "revolt-api/types/Users";
import styles from "./UserPicker.module.scss";
import { Text } from "preact-i18n";
import { useState } from "preact/hooks";
import { useData } from "../../../mobx/State";
import UserCheckbox from "../../../components/common/user/UserCheckbox";
import Modal from "../../../components/ui/Modal";
import { useClient } from "../../revoltjs/RevoltClient";
interface Props {
omit?: string[];
onClose: () => void;
......@@ -19,7 +19,7 @@ export function UserPicker(props: Props) {
const [selected, setSelected] = useState<string[]>([]);
const omit = [...(props.omit || []), "00000000000000000000000000"];
const store = useData();
const client = useClient();
return (
<Modal
......@@ -33,15 +33,16 @@ export function UserPicker(props: Props) {
},
]}>
<div className={styles.list}>
{[...store.users.values()]
{[...client.users.values()]
.filter(
(x) =>
x &&
x.relationship === Users.Relationship.Friend &&
x.relationship === RelationshipStatus.Friend &&
!omit.includes(x._id),
)
.map((x) => (
<UserCheckbox
key={x._id}
user={x}
checked={selected.includes(x._id)}
onChange={(v) => {
......
......@@ -57,13 +57,13 @@
gap: 8px;
display: flex;
padding: 0 1.5em;
font-size: .875rem;
font-size: 0.875rem;
> div {
padding: 8px;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: border-bottom .3s;
transition: border-bottom 0.3s;
&[data-active="true"] {
border-bottom: 2px solid var(--foreground);
......@@ -81,7 +81,10 @@
height: 100%;
display: flex;
padding: 1em 1.5em;
max-width: 560px;
max-height: 240px;
overflow-y: auto;
flex-direction: column;
background: var(--primary-background);
......@@ -141,7 +144,7 @@
display: flex;
cursor: pointer;
align-items: center;
transition: background-color .1s;
transition: background-color 0.1s;
color: var(--secondary-foreground);
border-radius: var(--border-radius);
background-color: var(--secondary-background);
......
import { Plus, X, XCircle } from "@styled-icons/boxicons-regular";
import { Plus } from "@styled-icons/boxicons-regular";
import { Pencil } from "@styled-icons/boxicons-solid";
import Axios, { AxiosRequestConfig } from "axios";
......@@ -147,6 +147,7 @@ export function FileUploader(props: Props) {
}
if (props.behaviour === "multi" && props.append) {
// eslint-disable-next-line
useEffect(() => {
// File pasting.
function paste(e: ClipboardEvent) {
......@@ -210,7 +211,7 @@ export function FileUploader(props: Props) {
document.removeEventListener("dragover", dragover);
document.removeEventListener("drop", drop);
};
}, [props.append]);
}, [openScreen, props, props.append]);
}
if (props.style === "icon" || props.style === "banner") {
......
This diff is collapsed.
import { openDB } from "idb";
import { useHistory } from "react-router-dom";
/* eslint-disable react-hooks/rules-of-hooks */
import { Client } from "revolt.js";
import { Route } from "revolt.js/dist/api/routes";
......@@ -14,7 +13,6 @@ import { AuthState } from "../../redux/reducers/auth";
import Preloader from "../../components/ui/Preloader";
import { useData } from "../../mobx/State";
import { Children } from "../../types/Preact";
import { useIntermediate } from "../intermediate/Intermediate";
import { registerEvents, setReconnectDisallowed } from "./events";
......@@ -36,8 +34,6 @@ export interface ClientOperations {
logout: (shouldRequest?: boolean) => Promise<void>;
loggedIn: () => boolean;
ready: () => boolean;
openDM: (user_id: string) => Promise<string>;
}
// By the time they are used, they should all be initialized.
......@@ -53,7 +49,6 @@ type Props = {
};
function Context({ auth, children }: Props) {
const history = useHistory();
const { openScreen } = useIntermediate();
const [status, setStatus] = useState(ClientStatus.INIT);
const [client, setClient] = useState<Client>(
......@@ -62,34 +57,10 @@ function Context({ auth, children }: Props) {
useEffect(() => {
(async () => {
let db;
try {
// Match sw.ts#L23
db = await openDB("state", 3, {
upgrade(db) {
for (const store of [
"channels",
"servers",
"users",
"members",
]) {
db.createObjectStore(store, {
keyPath: "_id",
});
}
},
});
} catch (err) {
console.error(
"Failed to open IndexedDB store, continuing without.",
);
}
const client = new Client({
autoReconnect: false,
apiURL: import.meta.env.VITE_API_URL,
debug: import.meta.env.DEV,
db,
});
setClient(client);
......@@ -150,26 +121,16 @@ function Context({ auth, children }: Props) {
loggedIn: () => typeof auth.active !== "undefined",
ready: () =>
operations.loggedIn() && typeof client.user !== "undefined",
openDM: async (user_id: string) => {
const channel = await client.users.openDM(user_id);
history.push(`/channel/${channel!._id}`);
return channel!._id;
},
};
}, [client, auth.active]);
}, [client, auth.active, openScreen]);
const store = useData();
useEffect(
() => registerEvents({ operations }, setStatus, client, store),
[client, store],
() => registerEvents({ operations }, setStatus, client),
[client, operations],
);
useEffect(() => {
(async () => {
if (client.db) {
await client.restore();
}
if (auth.active) {
dispatch({ type: "QUEUE_FAIL_ALL" });
......@@ -218,6 +179,7 @@ function Context({ auth, children }: Props) {
setStatus(ClientStatus.READY);
}
})();
// eslint-disable-next-line
}, []);
if (status === ClientStatus.LOADING) {
......
/**
* This file monitors the message cache to delete any queued messages that have already sent.
*/
import { Message } from "revolt.js";
import { Message } from "revolt.js/dist/maps/Messages";
import { useContext, useEffect } from "preact/hooks";
import { dispatch } from "../../redux";
import { connectState } from "../../redux/connector";
import { QueuedMessage } from "../../redux/reducers/queue";
import { Typing } from "../../redux/reducers/typing";
import { AppContext } from "./RevoltClient";
type Props = {
messages: QueuedMessage[];
typing: Typing;
};
function StateMonitor(props: Props) {
......@@ -39,31 +37,7 @@ function StateMonitor(props: Props) {
client.addListener("message", add);
return () => client.removeListener("message", add);
}, [props.messages]);
useEffect(() => {
function removeOld() {
if (!props.typing) return;
for (const channel of Object.keys(props.typing)) {
const users = props.typing[channel];
for (const user of users) {
if (+new Date() > user.started + 5000) {
dispatch({
type: "TYPING_STOP",
channel,
user: user.id,
});
}
}
}
}
removeOld();
const interval = setInterval(removeOld, 1000);
return () => clearInterval(interval);
}, [props.typing]);
}, [client, props.messages]);
return null;
}
......@@ -71,6 +45,5 @@ function StateMonitor(props: Props) {
export default connectState(StateMonitor, (state) => {
return {
messages: [...state.queue],
typing: state.typing,
};
});
......@@ -2,10 +2,10 @@
* This file monitors changes to settings and syncs them to the server.
*/
import isEqual from "lodash.isequal";
import { Sync } from "revolt.js/dist/api/objects";
import { UserSettings } from "revolt-api/types/Sync";
import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";
import { useContext, useEffect } from "preact/hooks";
import { useCallback, useContext, useEffect, useMemo } from "preact/hooks";
import { dispatch } from "../../redux";
import { connectState } from "../../redux/connector";
......@@ -28,10 +28,10 @@ type Props = {
notifications: Notifications;
};
const lastValues: { [key in SyncKeys]?: any } = {};
const lastValues: { [key in SyncKeys]?: unknown } = {};
export function mapSync(
packet: Sync.UserSettings,
packet: UserSettings,
revision?: Record<string, number>,
) {
const update: { [key in SyncKeys]?: [number, SyncData[key]] } = {};
......@@ -78,31 +78,38 @@ function SyncManager(props: Props) {
.syncFetchUnreads()
.then((unreads) => dispatch({ type: "UNREADS_SET", unreads }));
}
}, [status]);
function syncChange(key: SyncKeys, data: any) {
const timestamp = +new Date();
dispatch({
type: "SYNC_SET_REVISION",
key,
timestamp,
});
client.syncSetSettings(
{
[key]: data,
},
timestamp,
);
}
const disabled = props.sync.disabled ?? [];
}, [client, props.sync?.disabled, status]);
const syncChange = useCallback(
(key: SyncKeys, data: unknown) => {
const timestamp = +new Date();
dispatch({
type: "SYNC_SET_REVISION",
key,
timestamp,
});
client.syncSetSettings(
{
[key]: data as string,
},
timestamp,
);
},
[client],
);
const disabled = useMemo(
() => props.sync.disabled ?? [],
[props.sync.disabled],
);
for (const [key, object] of [
["appearance", props.settings.appearance],
["theme", props.settings.theme],
["locale", props.locale],
["notifications", props.notifications],
] as [SyncKeys, any][]) {
] as [SyncKeys, unknown][]) {
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (disabled.indexOf(key) === -1) {
if (typeof lastValues[key] !== "undefined") {
......@@ -113,7 +120,7 @@ function SyncManager(props: Props) {
}
lastValues[key] = object;
}, [disabled, object]);
}, [key, syncChange, disabled, object]);
}
useEffect(() => {
......@@ -131,7 +138,7 @@ function SyncManager(props: Props) {
client.addListener("packet", onPacket);
return () => client.removeListener("packet", onPacket);
}, [disabled, props.sync]);
}, [client, disabled, props.sync]);
return null;
}
......
This diff is collapsed.
This diff is collapsed.
import { Client } from "revolt.js";
import { Channel, Message, User } from "revolt.js/dist/api/objects";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Text } from "preact-i18n";
import { Children } from "../../types/Preact";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function takeError(error: any): string {
const type = error?.response?.data?.type;
const id = type;
......@@ -23,7 +23,6 @@ export function takeError(error: any): string {
}
export function getChannelName(
client: Client,
channel: Channel,
prefixType?: boolean,
): Children {
......@@ -31,11 +30,10 @@ export function getChannelName(
return <Text id="app.navigation.tabs.saved" />;
if (channel.channel_type === "DirectMessage") {
const uid = client.channels.getRecipient(channel._id);
return (
<>
{prefixType && "@"}
{client.users.get(uid)?.username}
{channel.recipient!.username}
</>
);
}
......@@ -46,12 +44,3 @@ export function getChannelName(
return <>{channel.name}</>;
}
export type MessageObject = Omit<Message, "edited"> & { edited?: string };
export function mapMessage(message: Partial<Message>) {
const { edited, ...msg } = message;
return {
...msg,
edited: edited?.$date,
} as MessageObject;
}
This diff is collapsed.
This diff is collapsed.
/* eslint-disable react-hooks/rules-of-hooks */
import { useState } from "preact/hooks";
const counts: { [key: string]: number } = {};
......