Start migration to revolt.js@5.0.0.

200 error milestone
This commit is contained in:
Paul 2021-07-30 20:24:53 +01:00
parent 564c1d8494
commit 3184269ba4
32 changed files with 215 additions and 834 deletions

View file

@ -22,7 +22,8 @@
} }
}, },
"dependencies": { "dependencies": {
"preact": "^10.5.13" "preact": "^10.5.13",
"revolt-api": "0.5.1-alpha.10-patch.0"
}, },
"devDependencies": { "devDependencies": {
"@fontsource/atkinson-hyperlegible": "^4.4.5", "@fontsource/atkinson-hyperlegible": "^4.4.5",
@ -96,7 +97,7 @@
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scroll": "^1.8.2", "react-scroll": "^1.8.2",
"redux": "^4.1.0", "redux": "^4.1.0",
"revolt.js": "4.4.0-alpha.0", "revolt.js": "5.0.0-alpha.5",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sass": "^1.35.1", "sass": "^1.35.1",
"shade-blend-color": "^1.0.0", "shade-blend-color": "^1.0.0",

View file

@ -1,11 +1,11 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { Channel } from "revolt.js/dist/maps/Channels";
import styled from "styled-components"; import styled from "styled-components";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { Channel } from "../../mobx";
import { dispatch, getState } from "../../redux"; import { dispatch, getState } from "../../redux";
import Button from "../ui/Button"; import Button from "../ui/Button";

View file

@ -1,13 +1,11 @@
import { useStore } from "react-redux"; import { useStore } from "react-redux";
import { SYSTEM_USER_ID } from "revolt.js"; import { SYSTEM_USER_ID } from "revolt.js";
import { Channels } from "revolt.js/dist/api/objects"; import { Channel } from "revolt.js/dist/maps/Channels";
import { User } from "revolt.js/dist/maps/Users";
import styled, { css } from "styled-components"; import styled, { css } from "styled-components";
import { StateUpdater, useState } from "preact/hooks"; import { StateUpdater, useState } from "preact/hooks";
import { Channel, User } from "../../mobx";
import { useData } from "../../mobx/State";
import { useClient } from "../../context/revoltjs/RevoltClient"; import { useClient } from "../../context/revoltjs/RevoltClient";
import { emojiDictionary } from "../../assets/emojis"; import { emojiDictionary } from "../../assets/emojis";
@ -57,7 +55,6 @@ export function useAutoComplete(
const [state, setState] = useState<AutoCompleteState>({ type: "none" }); const [state, setState] = useState<AutoCompleteState>({ type: "none" });
const [focused, setFocused] = useState(false); const [focused, setFocused] = useState(false);
const client = useClient(); const client = useClient();
const store = useData();
function findSearchString( function findSearchString(
el: HTMLTextAreaElement, el: HTMLTextAreaElement,
@ -132,7 +129,7 @@ export function useAutoComplete(
let users: User[] = []; let users: User[] = [];
switch (searchClues.users.type) { switch (searchClues.users.type) {
case "all": case "all":
users = [...store.users.values()]; users = [...client.users.values()];
break; break;
case "channel": { case "channel": {
const channel = client.channels.get( const channel = client.channels.get(
@ -141,22 +138,15 @@ export function useAutoComplete(
switch (channel?.channel_type) { switch (channel?.channel_type) {
case "Group": case "Group":
case "DirectMessage": case "DirectMessage":
users = channel.recipients users = channel.recipients!.filter(
.map((x) => store.users.get(x)) (x) => typeof x !== "undefined",
.filter( ) as User[];
(x) => typeof x !== "undefined",
) as User[];
break; break;
case "TextChannel": case "TextChannel":
const server = channel.server; const server = channel.server_id;
users = client.members users = [...client.members.keys()]
.toArray() .filter((x) => x.server === server)
.filter( .map((x) => client.users.get(x.user))
(x) => x._id.substr(0, 26) === server,
)
.map((x) =>
store.users.get(x._id.substr(26)),
)
.filter( .filter(
(x) => typeof x !== "undefined", (x) => typeof x !== "undefined",
) as User[]; ) as User[];
@ -197,7 +187,7 @@ export function useAutoComplete(
if (type === "channel" && searchClues?.channels) { if (type === "channel" && searchClues?.channels) {
const channels = client.servers const channels = client.servers
.get(searchClues.channels.server) .get(searchClues.channels.server)
?.channels.map((x) => store.channels.get(x)) ?.channels.map((x) => client.channels.get(x))
.filter((x) => typeof x !== "undefined") as Channel[]; .filter((x) => typeof x !== "undefined") as Channel[];
const matches = ( const matches = (

View file

@ -1,15 +1,14 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Message as MessageObject } from "revolt.js/dist/maps/Messages";
import { attachContextMenu } from "preact-context-menu"; import { attachContextMenu } from "preact-context-menu";
import { memo } from "preact/compat"; import { memo } from "preact/compat";
import { useContext, useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useData } from "../../../mobx/State";
import { QueuedMessage } from "../../../redux/reducers/queue"; import { QueuedMessage } from "../../../redux/reducers/queue";
import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { useClient } from "../../../context/revoltjs/RevoltClient";
import { MessageObject } from "../../../context/revoltjs/util";
import Overline from "../../ui/Overline"; import Overline from "../../ui/Overline";
@ -46,15 +45,14 @@ const Message = observer(
head: preferHead, head: preferHead,
queued, queued,
}: Props) => { }: Props) => {
const store = useData(); const client = useClient();
const user = store.users.get(message.author); const user = message.author;
const client = useContext(AppContext);
const { openScreen } = useIntermediate(); const { openScreen } = useIntermediate();
const content = message.content as string; const content = message.content as string;
const head = const head =
preferHead || (message.replies && message.replies.length > 0); preferHead || (message.reply_ids && message.reply_ids.length > 0);
// ! FIXME: tell fatal to make this type generic // ! FIXME: tell fatal to make this type generic
// bree: Fatal please... // bree: Fatal please...
@ -66,28 +64,33 @@ const Message = observer(
: undefined; : undefined;
const openProfile = () => const openProfile = () =>
openScreen({ id: "profile", user_id: message.author }); openScreen({ id: "profile", user_id: message.author_id });
// ! FIXME: animate on hover // ! FIXME: animate on hover
const [animate, setAnimate] = useState(false); const [animate, setAnimate] = useState(false);
return ( return (
<div id={message._id}> <div id={message._id}>
{message.replies?.map((message_id, index) => ( {message.reply_ids?.map((message_id, index) => (
<MessageReply <MessageReply
index={index} index={index}
id={message_id} id={message_id}
channel={message.channel} channel={message.channel!}
/> />
))} ))}
<MessageBase <MessageBase
highlight={highlight} highlight={highlight}
head={ head={
head && !(message.replies && message.replies.length > 0) (head &&
!(
message.reply_ids &&
message.reply_ids.length > 0
)) ??
false
} }
contrast={contrast} contrast={contrast}
sending={typeof queued !== "undefined"} sending={typeof queued !== "undefined"}
mention={message.mentions?.includes(client.user!._id)} mention={message.mention_ids?.includes(client.user!._id)}
failed={typeof queued?.error !== "undefined"} failed={typeof queued?.error !== "undefined"}
onContextMenu={ onContextMenu={
attachContext attachContext

View file

@ -2,8 +2,10 @@ import { Reply } from "@styled-icons/boxicons-regular";
import { File } from "@styled-icons/boxicons-solid"; import { File } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { RelationshipStatus } from "revolt-api/types/Users";
import { SYSTEM_USER_ID } from "revolt.js"; import { SYSTEM_USER_ID } from "revolt.js";
import { Users } from "revolt.js/dist/api/objects"; import { Channel } from "revolt.js/dist/maps/Channels";
import { Message } from "revolt.js/dist/maps/Messages";
import styled, { css } from "styled-components"; import styled, { css } from "styled-components";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
@ -11,17 +13,14 @@ import { useLayoutEffect, useState } from "preact/hooks";
import { useRenderState } from "../../../../lib/renderer/Singleton"; import { useRenderState } from "../../../../lib/renderer/Singleton";
import { useData } from "../../../../mobx/State";
import { useClient } from "../../../../context/revoltjs/RevoltClient"; import { useClient } from "../../../../context/revoltjs/RevoltClient";
import { mapMessage, MessageObject } from "../../../../context/revoltjs/util";
import Markdown from "../../../markdown/Markdown"; import Markdown from "../../../markdown/Markdown";
import UserShort from "../../user/UserShort"; import UserShort from "../../user/UserShort";
import { SystemMessage } from "../SystemMessage"; import { SystemMessage } from "../SystemMessage";
interface Props { interface Props {
channel: string; channel: Channel;
index: number; index: number;
id: string; id: string;
} }
@ -124,13 +123,11 @@ export const ReplyBase = styled.div<{
`; `;
export const MessageReply = observer(({ index, channel, id }: Props) => { export const MessageReply = observer(({ index, channel, id }: Props) => {
const client = useClient(); const view = useRenderState(channel._id);
const view = useRenderState(channel);
if (view?.type !== "RENDER") return null; if (view?.type !== "RENDER") return null;
const [message, setMessage] = useState<MessageObject | undefined>( const [message, setMessage] = useState<Message | undefined>(undefined);
undefined,
);
useLayoutEffect(() => { useLayoutEffect(() => {
// ! FIXME: We should do this through the message renderer, so it can fetch it from cache if applicable. // ! FIXME: We should do this through the message renderer, so it can fetch it from cache if applicable.
const m = view.messages.find((x) => x._id === id); const m = view.messages.find((x) => x._id === id);
@ -138,9 +135,7 @@ export const MessageReply = observer(({ index, channel, id }: Props) => {
if (m) { if (m) {
setMessage(m); setMessage(m);
} else { } else {
client.channels channel.fetchMessage(id).then(setMessage);
.fetchMessage(channel, id)
.then((m) => setMessage(mapMessage(m)));
} }
}, [view.messages]); }, [view.messages]);
@ -155,33 +150,32 @@ export const MessageReply = observer(({ index, channel, id }: Props) => {
); );
} }
const store = useData();
const user = store.users.get(message.author);
const history = useHistory(); const history = useHistory();
return ( return (
<ReplyBase head={index === 0}> <ReplyBase head={index === 0}>
<Reply size={16} /> <Reply size={16} />
{user?.relationship === Users.Relationship.Blocked ? ( {message.author?.relationship === RelationshipStatus.Blocked ? (
<> <>
<Text id="app.main.channel.misc.blocked_user" /> <Text id="app.main.channel.misc.blocked_user" />
</> </>
) : ( ) : (
<> <>
{message.author === SYSTEM_USER_ID ? ( {message.author_id === SYSTEM_USER_ID ? (
<SystemMessage message={message} hideInfo /> <SystemMessage message={message} hideInfo />
) : ( ) : (
<> <>
<div className="user"> <div className="user">
<UserShort user={user} size={16} /> <UserShort user={message.author} size={16} />
</div> </div>
<div <div
className="content" className="content"
onClick={() => { onClick={() => {
const obj = client.channels.get(channel); if (
if (obj?.channel_type === "TextChannel") { channel.channel_type === "TextChannel"
) {
history.push( history.push(
`/server/${obj.server}/channel/${obj._id}/${message._id}`, `/server/${channel.server}/channel/${channel._id}/${message._id}`,
); );
} else { } else {
history.push( history.push(

View file

@ -1,20 +1,17 @@
import { Prompt } from "react-router"; import { Prompt } from "react-router";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { import type { Attachment } from "revolt-api/types/Autumn";
Attachment, import type { EmbedImage } from "revolt-api/types/January";
Channels, import { Channel } from "revolt.js/dist/maps/Channels";
EmbedImage, import { Message } from "revolt.js/dist/maps/Messages";
Servers, import { Server } from "revolt.js/dist/maps/Servers";
Users, import { User } from "revolt.js/dist/maps/Users";
} from "revolt.js/dist/api/objects";
import { createContext } from "preact"; import { createContext } from "preact";
import { useContext, useEffect, useMemo, useState } from "preact/hooks"; import { useContext, useEffect, useMemo, useState } from "preact/hooks";
import { internalSubscribe } from "../../lib/eventEmitter"; import { internalSubscribe } from "../../lib/eventEmitter";
import { Channel, Server, User } from "../../mobx";
import { Action } from "../../components/ui/Modal"; import { Action } from "../../components/ui/Modal";
import { Children } from "../../types/Preact"; import { Children } from "../../types/Preact";
@ -39,7 +36,7 @@ export type Screen =
| { type: "leave_server"; target: Server } | { type: "leave_server"; target: Server }
| { type: "delete_server"; target: Server } | { type: "delete_server"; target: Server }
| { type: "delete_channel"; target: Channel } | { type: "delete_channel"; target: Channel }
| { type: "delete_message"; target: Channels.Message } | { type: "delete_message"; target: Message }
| { | {
type: "create_invite"; type: "create_invite";
target: Channel; target: Channel;

View file

@ -1,10 +1,8 @@
import { Client } from "revolt.js"; import { Client } from "revolt.js";
import { Message } from "revolt.js/dist/api/objects"; import { Channel } from "revolt.js/dist/maps/Channels";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { Channel } from "../../mobx";
import { Children } from "../../types/Preact"; import { Children } from "../../types/Preact";
export function takeError(error: any): string { export function takeError(error: any): string {
@ -25,7 +23,6 @@ export function takeError(error: any): string {
} }
export function getChannelName( export function getChannelName(
client: Client,
channel: Channel, channel: Channel,
prefixType?: boolean, prefixType?: boolean,
): Children { ): Children {
@ -33,11 +30,10 @@ export function getChannelName(
return <Text id="app.navigation.tabs.saved" />; return <Text id="app.navigation.tabs.saved" />;
if (channel.channel_type === "DirectMessage") { if (channel.channel_type === "DirectMessage") {
const uid = client.channels.getRecipient(channel._id);
return ( return (
<> <>
{prefixType && "@"} {prefixType && "@"}
{client.users.get(uid)?.username} {channel.recipient!.username}
</> </>
); );
} }
@ -48,12 +44,3 @@ export function getChannelName(
return <>{channel.name}</>; 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;
}

View file

@ -1,39 +0,0 @@
import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";
import { createContext } from "preact";
import { useContext, useEffect, useState } from "preact/hooks";
import { useClient } from "../context/revoltjs/RevoltClient";
import { DataStore } from ".";
import { Children } from "../types/Preact";
interface Props {
children: Children;
}
export const DataContext = createContext<DataStore>(null!);
// ! later we can do seamless account switching, by hooking this into Redux
// ! and monitoring changes to active account and hence swapping stores.
// although this may need more work since we need a Client per account too.
export default function StateLoader(props: Props) {
const client = useClient();
const [store] = useState(new DataStore(client));
useEffect(() => {
const packet = (packet: ClientboundNotification) =>
store.packet(packet);
client.addListener("packet", packet);
return () => client.removeListener("packet", packet);
}, [client]);
return (
<DataContext.Provider value={store}>
{props.children}
</DataContext.Provider>
);
}
export const useData = () => useContext(DataContext);

View file

@ -1,443 +0,0 @@
import isEqual from "lodash.isequal";
import {
makeAutoObservable,
observable,
autorun,
runInAction,
reaction,
makeObservable,
action,
extendObservable,
} from "mobx";
import { Client } from "revolt.js";
import {
Attachment,
Channels,
Servers,
Users,
} from "revolt.js/dist/api/objects";
import {
RemoveChannelField,
RemoveMemberField,
RemoveServerField,
RemoveUserField,
} from "revolt.js/dist/api/routes";
import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";
type Nullable<T> = T | null;
function toNullable<T>(data?: T) {
return typeof data === "undefined" ? null : data;
}
export class User {
_id: string;
username: string;
avatar: Nullable<Attachment>;
badges: Nullable<number>;
status: Nullable<Users.Status>;
relationship: Nullable<Users.Relationship>;
online: Nullable<boolean>;
constructor(data: Users.User) {
this._id = data._id;
this.username = data.username;
this.avatar = toNullable(data.avatar);
this.badges = toNullable(data.badges);
this.status = toNullable(data.status);
this.relationship = toNullable(data.relationship);
this.online = toNullable(data.online);
makeAutoObservable(this);
}
@action update(data: Partial<Users.User>, clear?: RemoveUserField) {
const apply = (key: string) => {
// This code has been tested.
// @ts-expect-error
if (data[key] && !isEqual(this[key], data[key])) {
// @ts-expect-error
this[key] = data[key];
}
};
switch (clear) {
case "Avatar":
this.avatar = null;
break;
case "StatusText": {
if (this.status) {
this.status.text = undefined;
}
}
}
apply("username");
apply("avatar");
apply("badges");
apply("status");
apply("relationship");
apply("online");
}
@action setRelationship(relationship: Users.Relationship) {
this.relationship = relationship;
}
}
export class Channel {
_id: string;
channel_type: Channels.Channel["channel_type"];
// Direct Message
active: Nullable<boolean> = null;
// Group
owner: Nullable<string> = null;
// Server
server: Nullable<string> = null;
// Permissions
permissions: Nullable<number> = null;
default_permissions: Nullable<number> = null;
role_permissions: Nullable<{ [key: string]: number }> = null;
// Common
name: Nullable<string> = null;
icon: Nullable<Attachment> = null;
description: Nullable<string> = null;
recipients: Nullable<string[]> = null;
last_message: Nullable<string | Channels.LastMessage> = null;
constructor(data: Channels.Channel) {
this._id = data._id;
this.channel_type = data.channel_type;
switch (data.channel_type) {
case "DirectMessage": {
this.active = toNullable(data.active);
this.recipients = toNullable(data.recipients);
this.last_message = toNullable(data.last_message);
break;
}
case "Group": {
this.recipients = toNullable(data.recipients);
this.name = toNullable(data.name);
this.owner = toNullable(data.owner);
this.description = toNullable(data.description);
this.last_message = toNullable(data.last_message);
this.icon = toNullable(data.icon);
this.permissions = toNullable(data.permissions);
break;
}
case "TextChannel":
case "VoiceChannel": {
this.server = toNullable(data.server);
this.name = toNullable(data.name);
this.description = toNullable(data.description);
this.icon = toNullable(data.icon);
this.default_permissions = toNullable(data.default_permissions);
this.role_permissions = toNullable(data.role_permissions);
if (data.channel_type === "TextChannel") {
this.last_message = toNullable(data.last_message);
}
break;
}
}
makeAutoObservable(this);
}
@action update(
data: Partial<Channels.Channel>,
clear?: RemoveChannelField,
) {
const apply = (key: string) => {
// This code has been tested.
// @ts-expect-error
if (data[key] && !isEqual(this[key], data[key])) {
// @ts-expect-error
this[key] = data[key];
}
};
switch (clear) {
case "Description":
this.description = null;
break;
case "Icon":
this.icon = null;
break;
}
apply("active");
apply("owner");
apply("permissions");
apply("default_permissions");
apply("role_permissions");
apply("name");
apply("icon");
apply("description");
apply("recipients");
apply("last_message");
}
@action groupJoin(user: string) {
this.recipients?.push(user);
}
@action groupLeave(user: string) {
this.recipients = toNullable(
this.recipients?.filter((x) => x !== user),
);
}
}
export class Server {
_id: string;
owner: string;
name: string;
description: Nullable<string> = null;
channels: string[] = [];
categories: Nullable<Servers.Category[]> = null;
system_messages: Nullable<Servers.SystemMessageChannels> = null;
roles: Nullable<{ [key: string]: Servers.Role }> = null;
default_permissions: Servers.PermissionTuple;
icon: Nullable<Attachment> = null;
banner: Nullable<Attachment> = null;
constructor(data: Servers.Server) {
this._id = data._id;
this.owner = data.owner;
this.name = data.name;
this.description = toNullable(data.description);
this.channels = data.channels;
this.categories = toNullable(data.categories);
this.system_messages = toNullable(data.system_messages);
this.roles = toNullable(data.roles);
this.default_permissions = data.default_permissions;
this.icon = toNullable(data.icon);
this.banner = toNullable(data.banner);
makeAutoObservable(this);
}
@action update(data: Partial<Servers.Server>, clear?: RemoveServerField) {
const apply = (key: string) => {
// This code has been tested.
// @ts-expect-error
if (data[key] && !isEqual(this[key], data[key])) {
// @ts-expect-error
this[key] = data[key];
}
};
switch (clear) {
case "Banner":
this.banner = null;
break;
case "Description":
this.description = null;
break;
case "Icon":
this.icon = null;
break;
}
apply("owner");
apply("name");
apply("description");
apply("channels");
apply("categories");
apply("system_messages");
apply("roles");
apply("default_permissions");
apply("icon");
apply("banner");
}
}
export class Member {
_id: Servers.MemberCompositeKey;
nickname: Nullable<string> = null;
avatar: Nullable<Attachment> = null;
roles: Nullable<string[]> = null;
constructor(data: Servers.Member) {
this._id = data._id;
this.nickname = toNullable(data.nickname);
this.avatar = toNullable(data.avatar);
this.roles = toNullable(data.roles);
makeAutoObservable(this);
}
@action update(data: Partial<Servers.Member>, clear?: RemoveMemberField) {
const apply = (key: string) => {
// This code has been tested.
// @ts-expect-error
if (data[key] && !isEqual(this[key], data[key])) {
// @ts-expect-error
this[key] = data[key];
}
};
switch (clear) {
case "Nickname":
this.nickname = null;
break;
case "Avatar":
this.avatar = null;
break;
}
apply("nickname");
apply("avatar");
apply("roles");
}
}
export class DataStore {
client: Client;
@observable users = new Map<string, User>();
@observable channels = new Map<string, Channel>();
@observable servers = new Map<string, Server>();
@observable members = new Map<Servers.MemberCompositeKey, Member>();
constructor(client: Client) {
makeAutoObservable(this, undefined, { proxy: false });
this.client = client;
}
@action
async packet(packet: ClientboundNotification) {
switch (packet.type) {
case "Ready": {
for (let user of packet.users) {
this.users.set(user._id, new User(user));
}
for (let channel of packet.channels) {
this.channels.set(channel._id, new Channel(channel));
}
for (let server of packet.servers) {
this.servers.set(server._id, new Server(server));
}
break;
}
case "ChannelCreate": {
this.channels.set(packet._id, new Channel(packet));
break;
}
case "ChannelUpdate": {
this.channels.get(packet.id)?.update(packet.data, packet.clear);
break;
}
case "ChannelDelete": {
this.channels.delete(packet.id);
break;
}
case "ChannelGroupJoin": {
this.channels.get(packet.id)?.groupJoin(packet.user);
if (!this.users.has(packet.user)) {
let user = await this.client.users.fetch(packet.user);
this.users.set(packet.user, new User(user));
}
break;
}
case "ChannelGroupLeave": {
this.channels.get(packet.id)?.groupJoin(packet.user);
break;
}
case "UserUpdate": {
this.users.get(packet.id)?.update(packet.data, packet.clear);
break;
}
case "UserRelationship": {
if (!this.users.has(packet.user._id)) {
this.users.set(packet.user._id, new User(packet.user));
}
this.users.get(packet.user._id)?.setRelationship(packet.status);
break;
}
case "ServerUpdate": {
this.servers.get(packet.id)?.update(packet.data, packet.clear);
break;
}
case "ServerDelete": {
let server = this.servers.get(packet.id);
if (server) {
for (let channel of server.channels) {
this.channels.delete(channel);
}
}
this.servers.delete(packet.id);
break;
}
case "ServerMemberUpdate": {
this.members.get(packet.id)?.update(packet.data, packet.clear);
break;
}
case "ServerMemberJoin": {
const _id = { server: packet.id, user: packet.user };
this.members.set(_id, new Member({ _id }));
if (!this.servers.has(packet.id)) {
let server = await this.client.servers.fetch(packet.id);
this.servers.set(packet.id, new Server(server));
for (let id of server.channels) {
let channel = this.client.channels.get(id);
if (channel) {
this.channels.set(id, new Channel(channel));
}
}
}
if (!this.users.has(packet.user)) {
let user = await this.client.users.fetch(packet.user);
this.users.set(packet.user, new User(user));
}
break;
}
case "ServerMemberLeave": {
this.members.delete({ server: packet.id, user: packet.user });
if (packet.user === this.client.user!._id) {
await this.packet({ type: "ServerDelete", id: packet.id });
}
break;
}
}
}
async fetchMembers(server: string) {
let res = await this.client.members.fetchMembers(server);
for (let user of res.users) {
if (!this.users.has(user._id)) {
this.users.set(user._id, new User(user));
}
}
return res.members;
}
}

View file

@ -1,7 +1,8 @@
import { X, Plus } from "@styled-icons/boxicons-regular"; import { X, Plus } from "@styled-icons/boxicons-regular";
import { PhoneCall, Envelope, UserX } from "@styled-icons/boxicons-solid"; import { PhoneCall, Envelope, UserX } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Users } from "revolt.js/dist/api/objects"; import { RelationshipStatus } from "revolt-api/types/Users";
import { User } from "revolt.js/dist/maps/Users";
import styles from "./Friend.module.scss"; import styles from "./Friend.module.scss";
import classNames from "classnames"; import classNames from "classnames";
@ -11,8 +12,6 @@ import { useContext } from "preact/hooks";
import { stopPropagation } from "../../lib/stopPropagation"; import { stopPropagation } from "../../lib/stopPropagation";
import { User } from "../../mobx";
import { VoiceOperationsContext } from "../../context/Voice"; import { VoiceOperationsContext } from "../../context/Voice";
import { useIntermediate } from "../../context/intermediate/Intermediate"; import { useIntermediate } from "../../context/intermediate/Intermediate";
import { import {
@ -39,7 +38,7 @@ export const Friend = observer(({ user }: Props) => {
const actions: Children[] = []; const actions: Children[] = [];
let subtext: Children = null; let subtext: Children = null;
if (user.relationship === Users.Relationship.Friend) { if (user.relationship === RelationshipStatus.Friend) {
subtext = <UserStatus user={user} />; subtext = <UserStatus user={user} />;
actions.push( actions.push(
<> <>
@ -61,14 +60,12 @@ export const Friend = observer(({ user }: Props) => {
); );
} }
if (user.relationship === Users.Relationship.Incoming) { if (user.relationship === RelationshipStatus.Incoming) {
actions.push( actions.push(
<IconButton <IconButton
type="circle" type="circle"
className={styles.button} className={styles.button}
onClick={(ev) => onClick={(ev) => stopPropagation(ev, user.addFriend())}>
stopPropagation(ev, client.users.addFriend(user.username))
}>
<Plus size={24} /> <Plus size={24} />
</IconButton>, </IconButton>,
); );
@ -76,14 +73,14 @@ export const Friend = observer(({ user }: Props) => {
subtext = <Text id="app.special.friends.incoming" />; subtext = <Text id="app.special.friends.incoming" />;
} }
if (user.relationship === Users.Relationship.Outgoing) { if (user.relationship === RelationshipStatus.Outgoing) {
subtext = <Text id="app.special.friends.outgoing" />; subtext = <Text id="app.special.friends.outgoing" />;
} }
if ( if (
user.relationship === Users.Relationship.Friend || user.relationship === RelationshipStatus.Friend ||
user.relationship === Users.Relationship.Outgoing || user.relationship === RelationshipStatus.Outgoing ||
user.relationship === Users.Relationship.Incoming user.relationship === RelationshipStatus.Incoming
) { ) {
actions.push( actions.push(
<IconButton <IconButton
@ -96,13 +93,13 @@ export const Friend = observer(({ user }: Props) => {
onClick={(ev) => onClick={(ev) =>
stopPropagation( stopPropagation(
ev, ev,
user.relationship === Users.Relationship.Friend user.relationship === RelationshipStatus.Friend
? openScreen({ ? openScreen({
id: "special_prompt", id: "special_prompt",
type: "unfriend_user", type: "unfriend_user",
target: user, target: user,
}) })
: client.users.removeFriend(user._id), : user.removeFriend(),
) )
}> }>
<X size={24} /> <X size={24} />
@ -110,14 +107,12 @@ export const Friend = observer(({ user }: Props) => {
); );
} }
if (user.relationship === Users.Relationship.Blocked) { if (user.relationship === RelationshipStatus.Blocked) {
actions.push( actions.push(
<IconButton <IconButton
type="circle" type="circle"
className={classNames(styles.button, styles.error)} className={classNames(styles.button, styles.error)}
onClick={(ev) => onClick={(ev) => stopPropagation(ev, user.unblockUser())}>
stopPropagation(ev, client.users.unblockUser(user._id))
}>
<UserX size={24} /> <UserX size={24} />
</IconButton>, </IconButton>,
); );

View file

@ -5,7 +5,8 @@ import {
} from "@styled-icons/boxicons-regular"; } from "@styled-icons/boxicons-regular";
import { UserDetail, MessageAdd, UserPlus } from "@styled-icons/boxicons-solid"; import { UserDetail, MessageAdd, UserPlus } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Users } from "revolt.js/dist/api/objects"; import { RelationshipStatus, Presence } from "revolt-api/types/Users";
import { User } from "revolt.js/dist/maps/Users";
import styles from "./Friend.module.scss"; import styles from "./Friend.module.scss";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
@ -13,10 +14,8 @@ import { Text } from "preact-i18n";
import { TextReact } from "../../lib/i18n"; import { TextReact } from "../../lib/i18n";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
import { User } from "../../mobx";
import { useData } from "../../mobx/State";
import { useIntermediate } from "../../context/intermediate/Intermediate"; import { useIntermediate } from "../../context/intermediate/Intermediate";
import { useClient } from "../../context/revoltjs/RevoltClient";
import CollapsibleSection from "../../components/common/CollapsibleSection"; import CollapsibleSection from "../../components/common/CollapsibleSection";
import Tooltip from "../../components/common/Tooltip"; import Tooltip from "../../components/common/Tooltip";
@ -30,43 +29,40 @@ import { Friend } from "./Friend";
export default observer(() => { export default observer(() => {
const { openScreen } = useIntermediate(); const { openScreen } = useIntermediate();
const store = useData(); const client = useClient();
const users = [...store.users.values()]; const users = [...client.users.values()];
users.sort((a, b) => a.username.localeCompare(b.username)); users.sort((a, b) => a.username.localeCompare(b.username));
const friends = users.filter( const friends = users.filter(
(x) => x.relationship === Users.Relationship.Friend, (x) => x.relationship === RelationshipStatus.Friend,
); );
const lists = [ const lists = [
[ [
"", "",
users.filter((x) => x.relationship === Users.Relationship.Incoming), users.filter((x) => x.relationship === RelationshipStatus.Incoming),
], ],
[ [
"app.special.friends.sent", "app.special.friends.sent",
users.filter((x) => x.relationship === Users.Relationship.Outgoing), users.filter((x) => x.relationship === RelationshipStatus.Outgoing),
"outgoing", "outgoing",
], ],
[ [
"app.status.online", "app.status.online",
friends.filter( friends.filter(
(x) => (x) => x.online && x.status?.presence !== Presence.Invisible,
x.online && x.status?.presence !== Users.Presence.Invisible,
), ),
"online", "online",
], ],
[ [
"app.status.offline", "app.status.offline",
friends.filter( friends.filter(
(x) => (x) => !x.online || x.status?.presence === Presence.Invisible,
!x.online ||
x.status?.presence === Users.Presence.Invisible,
), ),
"offline", "offline",
], ],
[ [
"app.special.friends.blocked", "app.special.friends.blocked",
users.filter((x) => x.relationship === Users.Relationship.Blocked), users.filter((x) => x.relationship === RelationshipStatus.Blocked),
"blocked", "blocked",
], ],
] as [string, User[], string][]; ] as [string, User[], string][];

View file

@ -1,16 +1,13 @@
import { ArrowBack } from "@styled-icons/boxicons-regular"; import { ArrowBack } from "@styled-icons/boxicons-regular";
import { autorun } from "mobx"; import { autorun } from "mobx";
import { useStore } from "react-redux";
import { useHistory, useParams } from "react-router-dom"; import { useHistory, useParams } from "react-router-dom";
import { Invites, Servers } from "revolt.js/dist/api/objects"; import { RetrievedInvite } from "revolt-api/types/Invites";
import styles from "./Invite.module.scss"; import styles from "./Invite.module.scss";
import { useContext, useEffect, useState } from "preact/hooks"; import { useContext, useEffect, useState } from "preact/hooks";
import { defer } from "../../lib/defer"; import { defer } from "../../lib/defer";
import { useData } from "../../mobx/State";
import RequiresOnline from "../../context/revoltjs/RequiresOnline"; import RequiresOnline from "../../context/revoltjs/RequiresOnline";
import { import {
AppContext, AppContext,
@ -26,14 +23,13 @@ import Overline from "../../components/ui/Overline";
import Preloader from "../../components/ui/Preloader"; import Preloader from "../../components/ui/Preloader";
export default function Invite() { export default function Invite() {
const store = useData();
const history = useHistory(); const history = useHistory();
const client = useContext(AppContext); const client = useContext(AppContext);
const status = useContext(StatusContext); const status = useContext(StatusContext);
const { code } = useParams<{ code: string }>(); const { code } = useParams<{ code: string }>();
const [processing, setProcessing] = useState(false); const [processing, setProcessing] = useState(false);
const [error, setError] = useState<string | undefined>(undefined); const [error, setError] = useState<string | undefined>(undefined);
const [invite, setInvite] = useState<Invites.RetrievedInvite | undefined>( const [invite, setInvite] = useState<RetrievedInvite | undefined>(
undefined, undefined,
); );
@ -122,7 +118,7 @@ export default function Invite() {
} }
const dispose = autorun(() => { const dispose = autorun(() => {
let server = store.servers.get( let server = client.servers.get(
invite.server_id, invite.server_id,
); );

View file

@ -3,8 +3,6 @@ import { Route, useHistory, useParams } from "react-router-dom";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { useData } from "../../mobx/State";
import { useClient } from "../../context/revoltjs/RevoltClient"; import { useClient } from "../../context/revoltjs/RevoltClient";
import { getChannelName } from "../../context/revoltjs/util"; import { getChannelName } from "../../context/revoltjs/util";
@ -17,9 +15,8 @@ import Permissions from "./channel/Permissions";
export default function ChannelSettings() { export default function ChannelSettings() {
const { channel: cid } = useParams<{ channel: string }>(); const { channel: cid } = useParams<{ channel: string }>();
const store = useData();
const client = useClient(); const client = useClient();
const channel = store.channels.get(cid); const channel = client.channels.get(cid);
if (!channel) return null; if (!channel) return null;
if ( if (
channel.channel_type === "SavedMessages" || channel.channel_type === "SavedMessages" ||
@ -53,7 +50,7 @@ export default function ChannelSettings() {
category: ( category: (
<Category <Category
variant="uniform" variant="uniform"
text={getChannelName(client, channel, true)} text={getChannelName(channel, true)}
/> />
), ),
id: "overview", id: "overview",

View file

@ -1,10 +1,12 @@
import { ListUl, ListCheck, ListMinus } from "@styled-icons/boxicons-regular"; import { ListUl, ListCheck, ListMinus } from "@styled-icons/boxicons-regular";
import { XSquare, Share, Group } from "@styled-icons/boxicons-solid"; 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, useHistory, useParams } from "react-router-dom";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import RequiresOnline from "../../context/revoltjs/RequiresOnline"; import RequiresOnline from "../../context/revoltjs/RequiresOnline";
import { useClient } from "../../context/revoltjs/RevoltClient";
import Category from "../../components/ui/Category"; import Category from "../../components/ui/Category";
@ -15,12 +17,11 @@ import { Invites } from "./server/Invites";
import { Members } from "./server/Members"; import { Members } from "./server/Members";
import { Overview } from "./server/Overview"; import { Overview } from "./server/Overview";
import { Roles } from "./server/Roles"; import { Roles } from "./server/Roles";
import { useData } from "../../mobx/State";
export default function ServerSettings() { export default observer(() => {
const { server: sid } = useParams<{ server: string }>(); const { server: sid } = useParams<{ server: string }>();
const store = useData(); const client = useClient();
const server = store.servers.get(sid); const server = client.servers.get(sid);
if (!server) return null; if (!server) return null;
const history = useHistory(); const history = useHistory();
@ -36,7 +37,7 @@ export default function ServerSettings() {
<GenericSettings <GenericSettings
pages={[ pages={[
{ {
category: <Category variant="uniform" text={server.name} />, //TOFIX: Just add the server.name as a string, otherwise it makes a duplicate category category: <Category variant="uniform" text={server.name} />,
id: "overview", id: "overview",
icon: <ListUl size={20} />, icon: <ListUl size={20} />,
title: ( title: (
@ -110,4 +111,4 @@ export default function ServerSettings() {
showExitButton showExitButton
/> />
); );
} });

View file

@ -1,5 +1,5 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Channels } from "revolt.js/dist/api/objects"; import { Channel } from "revolt.js/dist/maps/Channels";
import styled, { css } from "styled-components"; import styled, { css } from "styled-components";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
@ -7,8 +7,6 @@ import { useContext, useEffect, useState } from "preact/hooks";
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
import { Channel } from "../../../mobx";
import { FileUploader } from "../../../context/revoltjs/FileUploads"; import { FileUploader } from "../../../context/revoltjs/FileUploads";
import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { AppContext } from "../../../context/revoltjs/RevoltClient";
@ -51,7 +49,7 @@ export default observer(({ channel }: Props) => {
if (description !== channel.description) if (description !== channel.description)
changes.description = description; changes.description = description;
client.channels.edit(channel._id, changes); channel.edit(changes);
setChanged(false); setChanged(false);
} }
@ -65,17 +63,12 @@ export default observer(({ channel }: Props) => {
fileType="icons" fileType="icons"
behaviour="upload" behaviour="upload"
maxFileSize={2_500_000} maxFileSize={2_500_000}
onUpload={(icon) => onUpload={(icon) => channel.edit({ icon })}
client.channels.edit(channel._id, { icon }) previewURL={channel.generateIconURL(
}
previewURL={client.channels.getIconURL(
channel._id,
{ max_side: 256 }, { max_side: 256 },
true, true,
)} )}
remove={() => remove={() => channel.edit({ remove: "Icon" })}
client.channels.edit(channel._id, { remove: "Icon" })
}
defaultPreview={ defaultPreview={
channel.channel_type === "Group" channel.channel_type === "Group"
? "/assets/group.png" ? "/assets/group.png"

View file

@ -1,13 +1,10 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Channels } from "revolt.js/dist/api/objects";
import { ChannelPermission } from "revolt.js/dist/api/permissions"; import { ChannelPermission } from "revolt.js/dist/api/permissions";
import { Channel } from "revolt.js/dist/maps/Channels";
import { useContext, useEffect, useState } from "preact/hooks"; import { useContext, useEffect, useState } from "preact/hooks";
import { Channel } from "../../../mobx"; import { AppContext, useClient } from "../../../context/revoltjs/RevoltClient";
import { useData } from "../../../mobx/State";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import Button from "../../../components/ui/Button"; import Button from "../../../components/ui/Button";
import Checkbox from "../../../components/ui/Checkbox"; import Checkbox from "../../../components/ui/Checkbox";
@ -30,13 +27,12 @@ interface Props {
// ! FIXME: bad code :) // ! FIXME: bad code :)
export default observer(({ channel }: Props) => { export default observer(({ channel }: Props) => {
const [selected, setSelected] = useState("default"); const [selected, setSelected] = useState("default");
const client = useContext(AppContext); const client = useClient();
const store = useData();
type R = { name: string; permissions: number }; type R = { name: string; permissions: number };
const roles: { [key: string]: R } = {}; const roles: { [key: string]: R } = {};
if (channel.channel_type !== "Group") { if (channel.channel_type !== "Group") {
const server = store.servers.get(channel.server!); const server = channel.server;
const a = server?.roles ?? {}; const a = server?.roles ?? {};
for (const b of Object.keys(a)) { for (const b of Object.keys(a)) {
roles[b] = { roles[b] = {
@ -105,7 +101,7 @@ export default observer(({ channel }: Props) => {
<Button <Button
contrast contrast
onClick={() => { onClick={() => {
client.channels.setPermissions(channel._id, selected, p); channel.setPermissions(selected, p);
}}> }}>
click here to save permissions for role click here to save permissions for role
</Button> </Button>

View file

@ -2,14 +2,12 @@ import { At } from "@styled-icons/boxicons-regular";
import { Envelope, Key, HelpCircle } from "@styled-icons/boxicons-solid"; import { Envelope, Key, HelpCircle } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Link, useHistory } from "react-router-dom"; import { Link, useHistory } from "react-router-dom";
import { Users } from "revolt.js/dist/api/objects"; import { Profile } from "revolt-api/types/Users";
import styles from "./Panes.module.scss"; import styles from "./Panes.module.scss";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { useContext, useEffect, useState } from "preact/hooks"; import { useContext, useEffect, useState } from "preact/hooks";
import { useData } from "../../../mobx/State";
import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { import {
ClientStatus, ClientStatus,
@ -28,14 +26,10 @@ export const Account = observer(() => {
const status = useContext(StatusContext); const status = useContext(StatusContext);
const client = useClient(); const client = useClient();
const store = useData();
const user = store.users.get(client.user!._id)!;
const [email, setEmail] = useState("..."); const [email, setEmail] = useState("...");
const [revealEmail, setRevealEmail] = useState(false); const [revealEmail, setRevealEmail] = useState(false);
const [profile, setProfile] = useState<undefined | Users.Profile>( const [profile, setProfile] = useState<undefined | Profile>(undefined);
undefined,
);
const history = useHistory(); const history = useHistory();
function switchPage(to: string) { function switchPage(to: string) {
@ -50,8 +44,8 @@ export const Account = observer(() => {
} }
if (profile === undefined && status === ClientStatus.ONLINE) { if (profile === undefined && status === ClientStatus.ONLINE) {
client.users client
.fetchProfile(user._id) .user!.fetchProfile()
.then((profile) => setProfile(profile ?? {})); .then((profile) => setProfile(profile ?? {}));
} }
}, [status]); }, [status]);
@ -61,12 +55,14 @@ export const Account = observer(() => {
<div className={styles.banner}> <div className={styles.banner}>
<UserIcon <UserIcon
className={styles.avatar} className={styles.avatar}
target={user} target={client.user!}
size={72} size={72}
onClick={() => switchPage("profile")} onClick={() => switchPage("profile")}
/> />
<div className={styles.userDetail}> <div className={styles.userDetail}>
<div className={styles.username}>@{user.username}</div> <div className={styles.username}>
@{client.user!.username}
</div>
<div className={styles.userid}> <div className={styles.userid}>
<Tooltip <Tooltip
content={ content={
@ -75,8 +71,8 @@ export const Account = observer(() => {
<HelpCircle size={16} /> <HelpCircle size={16} />
</Tooltip> </Tooltip>
<Tooltip content={<Text id="app.special.copy" />}> <Tooltip content={<Text id="app.special.copy" />}>
<a onClick={() => writeClipboard(user._id)}> <a onClick={() => writeClipboard(client.user!._id)}>
{user._id} {client.user!._id}
</a> </a>
</Tooltip> </Tooltip>
</div> </div>
@ -85,7 +81,7 @@ export const Account = observer(() => {
<div className={styles.details}> <div className={styles.details}>
{( {(
[ [
["username", user.username, <At size={24} />], ["username", client.user!.username, <At size={24} />],
["email", email, <Envelope size={24} />], ["email", email, <Envelope size={24} />],
["password", "***********", <Key size={24} />], ["password", "***********", <Key size={24} />],
] as const ] as const

View file

@ -1,5 +1,3 @@
import { Users } from "revolt.js/dist/api/objects";
import styles from "./Panes.module.scss"; import styles from "./Panes.module.scss";
import { IntlContext, Text, translate } from "preact-i18n"; import { IntlContext, Text, translate } from "preact-i18n";
import { useContext, useEffect, useState } from "preact/hooks"; import { useContext, useEffect, useState } from "preact/hooks";
@ -18,6 +16,7 @@ import AutoComplete, {
useAutoComplete, useAutoComplete,
} from "../../../components/common/AutoComplete"; } from "../../../components/common/AutoComplete";
import Button from "../../../components/ui/Button"; import Button from "../../../components/ui/Button";
import { Profile } from "revolt-api/types/Users";
export function Profile() { export function Profile() {
const { intl } = useContext(IntlContext); const { intl } = useContext(IntlContext);
@ -25,15 +24,15 @@ export function Profile() {
const client = useClient(); const client = useClient();
const [profile, setProfile] = useState<undefined | Users.Profile>( const [profile, setProfile] = useState<undefined | Profile>(
undefined, undefined,
); );
// ! FIXME: temporary solution // ! FIXME: temporary solution
// ! we should just announce profile changes through WS // ! we should just announce profile changes through WS
function refreshProfile() { function refreshProfile() {
client.users client
.fetchProfile(client.user!._id) .user!.fetchProfile()
.then((profile) => setProfile(profile ?? {})); .then((profile) => setProfile(profile ?? {}));
} }
@ -85,20 +84,17 @@ export function Profile() {
fileType="avatars" fileType="avatars"
behaviour="upload" behaviour="upload"
maxFileSize={4_000_000} maxFileSize={4_000_000}
onUpload={(avatar) => client.users.editUser({ avatar })} onUpload={(avatar) => client.users.edit({ avatar })}
remove={() => remove={() =>
client.users.editUser({ remove: "Avatar" }) client.users.edit({ remove: "Avatar" })
} }
defaultPreview={client.users.getAvatarURL( defaultPreview={client.user!.generateAvatarURL(
client.user!._id,
{ max_side: 256 }, { max_side: 256 },
true, true,
)} )}
previewURL={client.users.getAvatarURL( previewURL={client.user!.generateAvatarURL(
client.user!._id,
{ max_side: 256 }, { max_side: 256 },
true, true,
true,
)} )}
/> />
</div> </div>
@ -113,21 +109,21 @@ export function Profile() {
fileType="backgrounds" fileType="backgrounds"
maxFileSize={6_000_000} maxFileSize={6_000_000}
onUpload={async (background) => { onUpload={async (background) => {
await client.users.editUser({ await client.users.edit({
profile: { background }, profile: { background },
}); });
refreshProfile(); refreshProfile();
}} }}
remove={async () => { remove={async () => {
await client.users.editUser({ await client.users.edit({
remove: "ProfileBackground", remove: "ProfileBackground",
}); });
setProfile({ ...profile, background: undefined }); setProfile({ ...profile, background: undefined });
}} }}
previewURL={ previewURL={
profile?.background profile?.background
? client.users.getBackgroundURL( ? client.generateFileURL(
profile, profile.background,
{ width: 1000 }, { width: 1000 },
true, true,
) )
@ -169,7 +165,7 @@ export function Profile() {
contrast contrast
onClick={() => { onClick={() => {
setChanged(false); setChanged(false);
client.users.editUser({ client.users.edit({
profile: { content: profile?.content }, profile: { content: profile?.content },
}); });
}} }}

View file

@ -1,14 +1,12 @@
import { XCircle } from "@styled-icons/boxicons-regular"; import { XCircle } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Servers, Users } from "revolt.js/dist/api/objects";
import { Route } from "revolt.js/dist/api/routes"; import { Route } from "revolt.js/dist/api/routes";
import { Server } from "revolt.js/dist/maps/Servers";
import styles from "./Panes.module.scss"; import styles from "./Panes.module.scss";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { useContext, useEffect, useState } from "preact/hooks"; import { useContext, useEffect, useState } from "preact/hooks";
import { Server } from "../../../mobx";
import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { AppContext } from "../../../context/revoltjs/RevoltClient";
import UserIcon from "../../../components/common/user/UserIcon"; import UserIcon from "../../../components/common/user/UserIcon";
@ -27,7 +25,7 @@ export const Bans = observer(({ server }: Props) => {
>(undefined); >(undefined);
useEffect(() => { useEffect(() => {
client.servers.fetchBans(server._id).then(setData as any); server.fetchBans().then(setData as any);
}, []); }, []);
return ( return (
@ -64,10 +62,7 @@ export const Bans = observer(({ server }: Props) => {
onClick={async () => { onClick={async () => {
setDelete([...deleting, x._id.user]); setDelete([...deleting, x._id.user]);
await client.servers.unbanUser( await server.unbanUser(x._id.user);
server._id,
x._id.user,
);
setData({ setData({
...data, ...data,

View file

@ -1,16 +1,10 @@
import { XCircle } from "@styled-icons/boxicons-regular";
import isEqual from "lodash.isequal"; import isEqual from "lodash.isequal";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Channels, Servers, Users } from "revolt.js/dist/api/objects"; import { Category } from "revolt-api/types/Servers";
import { Route } from "revolt.js/dist/api/routes"; import { Server } from "revolt.js/dist/maps/Servers";
import { ulid } from "ulid"; import { ulid } from "ulid";
import styles from "./Panes.module.scss"; import { useContext, useState } from "preact/hooks";
import { Text } from "preact-i18n";
import { useContext, useEffect, useState } from "preact/hooks";
import { Server } from "../../../mobx";
import { useData } from "../../../mobx/State";
import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { AppContext } from "../../../context/revoltjs/RevoltClient";
@ -30,15 +24,9 @@ interface Props {
// ! FIXME: really bad code // ! FIXME: really bad code
export const Categories = observer(({ server }: Props) => { export const Categories = observer(({ server }: Props) => {
const client = useContext(AppContext); const client = useContext(AppContext);
const store = useData(); const channels = server.channels.filter((x) => typeof x !== "undefined");
const channels = server.channels
.map((id) => store.channels.get(id)!)
.filter((x) => typeof x !== "undefined");
const [cats, setCats] = useState<Servers.Category[]>(
server.categories ?? [],
);
const [cats, setCats] = useState<Category[]>(server.categories ?? []);
const [name, setName] = useState(""); const [name, setName] = useState("");
return ( return (
@ -48,9 +36,7 @@ export const Categories = observer(({ server }: Props) => {
<Button <Button
contrast contrast
disabled={isEqual(server.categories ?? [], cats)} disabled={isEqual(server.categories ?? [], cats)}
onClick={() => onClick={() => server.edit({ categories: cats })}>
client.servers.edit(server._id, { categories: cats })
}>
save categories save categories
</Button> </Button>
</p> </p>
@ -116,13 +102,13 @@ export const Categories = observer(({ server }: Props) => {
}}> }}>
<div style={{ flexShrink: 0 }}> <div style={{ flexShrink: 0 }}>
<ChannelIcon target={channel} size={24} />{" "} <ChannelIcon target={channel} size={24} />{" "}
<span>{channel.name}</span> <span>{channel!.name}</span>
</div> </div>
<ComboBox <ComboBox
style={{ flexGrow: 1 }} style={{ flexGrow: 1 }}
value={ value={
cats.find((x) => cats.find((x) =>
x.channels.includes(channel._id), x.channels.includes(channel!._id),
)?.id ?? "none" )?.id ?? "none"
} }
onChange={(e) => onChange={(e) =>
@ -132,11 +118,11 @@ export const Categories = observer(({ server }: Props) => {
...x, ...x,
channels: [ channels: [
...x.channels.filter( ...x.channels.filter(
(y) => y !== channel._id, (y) => y !== channel!._id,
), ),
...(e.currentTarget.value === ...(e.currentTarget.value ===
x.id x.id
? [channel._id] ? [channel!._id]
: []), : []),
], ],
}; };

View file

@ -1,14 +1,12 @@
import { XCircle } from "@styled-icons/boxicons-regular"; import { XCircle } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Invites as InvitesNS, Servers } from "revolt.js/dist/api/objects"; import { ServerInvite } from "revolt-api/types/Invites";
import { Server } from "revolt.js/dist/maps/Servers";
import styles from "./Panes.module.scss"; import styles from "./Panes.module.scss";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { Server } from "../../../mobx";
import { useData } from "../../../mobx/State";
import { useClient } from "../../../context/revoltjs/RevoltClient"; import { useClient } from "../../../context/revoltjs/RevoltClient";
import { getChannelName } from "../../../context/revoltjs/util"; import { getChannelName } from "../../../context/revoltjs/util";
@ -22,21 +20,18 @@ interface Props {
export const Invites = observer(({ server }: Props) => { export const Invites = observer(({ server }: Props) => {
const [deleting, setDelete] = useState<string[]>([]); const [deleting, setDelete] = useState<string[]>([]);
const [invites, setInvites] = useState< const [invites, setInvites] = useState<ServerInvite[] | undefined>(
InvitesNS.ServerInvite[] | undefined undefined,
>(undefined); );
const store = useData();
const client = useClient(); const client = useClient();
const users = invites?.map((invite) => store.users.get(invite.creator)); const users = invites?.map((invite) => client.users.get(invite.creator));
const channels = invites?.map((invite) => const channels = invites?.map((invite) =>
store.channels.get(invite.channel), client.channels.get(invite.channel),
); );
useEffect(() => { useEffect(() => {
client.servers server.fetchInvites().then(setInvites);
.fetchInvites(server._id)
.then((invites) => setInvites(invites));
}, []); }, []);
return ( return (
@ -73,7 +68,7 @@ export const Invites = observer(({ server }: Props) => {
</span> </span>
<span> <span>
{channel && creator {channel && creator
? getChannelName(client, channel, true) ? getChannelName(channel, true)
: "#??"} : "#??"}
</span> </span>
<IconButton <IconButton

View file

@ -1,15 +1,14 @@
import { ChevronDown } from "@styled-icons/boxicons-regular"; import { ChevronDown } from "@styled-icons/boxicons-regular";
import { isEqual } from "lodash"; import { isEqual } from "lodash";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Servers } from "revolt.js/dist/api/objects"; import { Member } from "revolt.js/dist/maps/Members";
import { Server } from "revolt.js/dist/maps/Servers";
import { User } from "revolt.js/dist/maps/Users";
import styles from "./Panes.module.scss"; import styles from "./Panes.module.scss";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { Server } from "../../../mobx";
import { useData } from "../../../mobx/State";
import { useClient } from "../../../context/revoltjs/RevoltClient"; import { useClient } from "../../../context/revoltjs/RevoltClient";
import UserIcon from "../../../components/common/user/UserIcon"; import UserIcon from "../../../components/common/user/UserIcon";
@ -24,25 +23,21 @@ interface Props {
export const Members = observer(({ server }: Props) => { export const Members = observer(({ server }: Props) => {
const [selected, setSelected] = useState<undefined | string>(); const [selected, setSelected] = useState<undefined | string>();
const [members, setMembers] = useState<Servers.Member[] | undefined>( const [data, setData] = useState<
undefined, { members: Member[]; users: User[] } | undefined
); >(undefined);
const store = useData();
const client = useClient(); const client = useClient();
const users = members?.map((member) => store.users.get(member._id.user));
useEffect(() => { useEffect(() => {
client.members server.fetchMembers().then(setData);
.fetchMembers(server._id)
.then((members) => setMembers(members));
}, []); }, []);
const [roles, setRoles] = useState<string[]>([]); const [roles, setRoles] = useState<string[]>([]);
useEffect(() => { useEffect(() => {
if (selected) { if (selected) {
setRoles( setRoles(
members!.find((x) => x._id.user === selected)?.roles ?? [], data!.members.find((x) => x._id.user === selected)?.roles ?? [],
); );
} }
}, [selected]); }, [selected]);
@ -50,15 +45,15 @@ export const Members = observer(({ server }: Props) => {
return ( return (
<div className={styles.userList}> <div className={styles.userList}>
<div className={styles.subtitle}> <div className={styles.subtitle}>
{members?.length ?? 0} Members {data?.members.length ?? 0} Members
</div> </div>
{members && {data &&
members.length > 0 && data.members.length > 0 &&
members data.members
.map((member, index) => { .map((member, index) => {
return { return {
member, member,
user: users![index], user: data.users[index],
}; };
}) })
.map(({ member, user }) => ( .map(({ member, user }) => (
@ -130,27 +125,11 @@ export const Members = observer(({ server }: Props) => {
member.roles ?? [], member.roles ?? [],
roles, roles,
)} )}
onClick={async () => { onClick={() =>
await client.members.editMember( member.edit({
server._id, roles,
member._id.user, })
{ }>
roles,
},
);
setMembers(
members.map((x) =>
x._id.user ===
member._id.user
? {
...x,
roles,
}
: x,
),
);
}}>
<Text id="app.special.modals.actions.save" /> <Text id="app.special.modals.actions.save" />
</Button> </Button>
</div> </div>

View file

@ -1,6 +1,6 @@
import isEqual from "lodash.isequal"; import isEqual from "lodash.isequal";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Servers } from "revolt.js/dist/api/objects"; import { Server } from "revolt.js/dist/maps/Servers";
import styles from "./Panes.module.scss"; import styles from "./Panes.module.scss";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
@ -8,11 +8,8 @@ import { useContext, useEffect, useState } from "preact/hooks";
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
import { Server } from "../../../mobx";
import { useData } from "../../../mobx/State";
import { FileUploader } from "../../../context/revoltjs/FileUploads"; import { FileUploader } from "../../../context/revoltjs/FileUploads";
import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { AppContext, useClient } from "../../../context/revoltjs/RevoltClient";
import { getChannelName } from "../../../context/revoltjs/util"; import { getChannelName } from "../../../context/revoltjs/util";
import Button from "../../../components/ui/Button"; import Button from "../../../components/ui/Button";
@ -24,8 +21,7 @@ interface Props {
} }
export const Overview = observer(({ server }: Props) => { export const Overview = observer(({ server }: Props) => {
const client = useContext(AppContext); const client = useClient();
const store = useData();
const [name, setName] = useState(server.name); const [name, setName] = useState(server.name);
const [description, setDescription] = useState(server.description ?? ""); const [description, setDescription] = useState(server.description ?? "");
@ -45,16 +41,14 @@ export const Overview = observer(({ server }: Props) => {
const [changed, setChanged] = useState(false); const [changed, setChanged] = useState(false);
function save() { function save() {
const changes: Partial< const changes: Record<string, any> = {};
Pick<Servers.Server, "name" | "description" | "system_messages">
> = {};
if (name !== server.name) changes.name = name; if (name !== server.name) changes.name = name;
if (description !== server.description) if (description !== server.description)
changes.description = description; changes.description = description;
if (!isEqual(systemMessages, server.system_messages)) if (!isEqual(systemMessages, server.system_messages))
changes.system_messages = systemMessages ?? undefined; changes.system_messages = systemMessages ?? undefined;
client.servers.edit(server._id, changes); server.edit(changes);
setChanged(false); setChanged(false);
} }
@ -68,17 +62,9 @@ export const Overview = observer(({ server }: Props) => {
fileType="icons" fileType="icons"
behaviour="upload" behaviour="upload"
maxFileSize={2_500_000} maxFileSize={2_500_000}
onUpload={(icon) => onUpload={(icon) => server.edit({ icon })}
client.servers.edit(server._id, { icon }) previewURL={server.generateIconURL({ max_side: 256 }, true)}
} remove={() => server.edit({ remove: "Icon" })}
previewURL={client.servers.getIconURL(
server._id,
{ max_side: 256 },
true,
)}
remove={() =>
client.servers.edit(server._id, { remove: "Icon" })
}
/> />
<div className={styles.name}> <div className={styles.name}>
<h3> <h3>
@ -120,17 +106,9 @@ export const Overview = observer(({ server }: Props) => {
fileType="banners" fileType="banners"
behaviour="upload" behaviour="upload"
maxFileSize={6_000_000} maxFileSize={6_000_000}
onUpload={(banner) => onUpload={(banner) => server.edit({ banner })}
client.servers.edit(server._id, { banner }) previewURL={server.generateBannerURL({ width: 1000 }, true)}
} remove={() => server.edit({ remove: "Banner" })}
previewURL={client.servers.getBannerURL(
server._id,
{ width: 1000 },
true,
)}
remove={() =>
client.servers.edit(server._id, { remove: "Banner" })
}
/> />
<h3> <h3>
@ -176,11 +154,10 @@ export const Overview = observer(({ server }: Props) => {
<Text id="general.disabled" /> <Text id="general.disabled" />
</option> </option>
{server.channels {server.channels
.map((id) => store.channels.get(id)!)
.filter((x) => typeof x !== "undefined") .filter((x) => typeof x !== "undefined")
.map((channel) => ( .map((channel) => (
<option value={channel._id}> <option value={channel!._id}>
{getChannelName(client, channel, true)} {getChannelName(channel!, true)}
</option> </option>
))} ))}
</ComboBox> </ComboBox>

View file

@ -1,18 +1,13 @@
import { Plus } from "@styled-icons/boxicons-regular"; import { Plus } from "@styled-icons/boxicons-regular";
import isEqual from "lodash.isequal"; import isEqual from "lodash.isequal";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Servers } from "revolt.js/dist/api/objects"; import { ChannelPermission, ServerPermission } from "revolt.js";
import { import { Server } from "revolt.js/dist/maps/Servers";
ChannelPermission,
ServerPermission,
} from "revolt.js/dist/api/permissions";
import styles from "./Panes.module.scss"; import styles from "./Panes.module.scss";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { useContext, useEffect, useState } from "preact/hooks"; import { useContext, useEffect, useState } from "preact/hooks";
import { Server } from "../../../mobx";
import { useIntermediate } from "../../../context/intermediate/Intermediate"; import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { AppContext } from "../../../context/revoltjs/RevoltClient";
@ -73,20 +68,20 @@ export const Roles = observer(({ server }: Props) => {
const save = () => { const save = () => {
if (!isEqual(perm, getPermissions(role))) { if (!isEqual(perm, getPermissions(role))) {
client.servers.setPermissions(server._id, role, { server.setPermissions(role, {
server: perm[0], server: perm[0],
channel: perm[1], channel: perm[1],
}); });
} }
if (!isEqual(name, roleName) || !isEqual(colour, roleColour)) { if (!isEqual(name, roleName) || !isEqual(colour, roleColour)) {
client.servers.editRole(server._id, role, { name, colour }); server.editRole(role, { name, colour });
} }
}; };
const deleteRole = () => { const deleteRole = () => {
setRole("default"); setRole("default");
client.servers.deleteRole(server._id, role); server.deleteRole(role);
}; };
return ( return (

View file

@ -1,6 +1,6 @@
import localForage from "localforage"; import localForage from "localforage";
import { createStore } from "redux"; import { createStore } from "redux";
import { Core } from "revolt.js/dist/api/objects"; import { RevoltConfiguration } from "revolt-api/types/Core";
import { Language } from "../context/Locale"; import { Language } from "../context/Locale";
@ -18,7 +18,7 @@ import { Typing } from "./reducers/typing";
import { Unreads } from "./reducers/unreads"; import { Unreads } from "./reducers/unreads";
export type State = { export type State = {
config: Core.RevoltNodeConfiguration; config: RevoltConfiguration;
locale: Language; locale: Language;
auth: AuthState; auth: AuthState;
settings: Settings; settings: Settings;

View file

@ -1,9 +1,9 @@
import type { Auth } from "revolt.js/dist/api/objects"; import { Session } from "revolt-api/types/Auth";
export interface AuthState { export interface AuthState {
accounts: { accounts: {
[key: string]: { [key: string]: {
session: Auth.Session; session: Session;
}; };
}; };
active?: string; active?: string;
@ -13,7 +13,7 @@ export type AuthAction =
| { type: undefined } | { type: undefined }
| { | {
type: "LOGIN"; type: "LOGIN";
session: Auth.Session; session: Session;
} }
| { | {
type: "LOGOUT"; type: "LOGOUT";

View file

@ -1,4 +1,5 @@
import type { Channel, Message } from "revolt.js"; import { Channel } from "revolt.js/dist/maps/Channels";
import { Message } from "revolt.js/dist/maps/Messages";
import type { SyncUpdateAction } from "./sync"; import type { SyncUpdateAction } from "./sync";
@ -35,7 +36,7 @@ export function shouldNotify(
case "none": case "none":
return false; return false;
case "mention": { case "mention": {
if (!message.mentions?.includes(user_id)) return false; if (!message.mention_ids?.includes(user_id)) return false;
} }
} }

View file

@ -1,5 +1,3 @@
import type { MessageObject } from "../../context/revoltjs/util";
export enum QueueStatus { export enum QueueStatus {
SENDING = "sending", SENDING = "sending",
ERRORED = "errored", ERRORED = "errored",
@ -10,7 +8,7 @@ export interface Reply {
mention: boolean; mention: boolean;
} }
export type QueuedMessageData = Omit<MessageObject, "content" | "replies"> & { export type QueuedMessageData = {
content: string; content: string;
replies: Reply[]; replies: Reply[];
}; };

View file

@ -1,16 +1,16 @@
import type { Core } from "revolt.js/dist/api/objects"; import type { RevoltConfiguration } from "revolt-api/types/Core";
export type ConfigAction = export type ConfigAction =
| { type: undefined } | { type: undefined }
| { | {
type: "SET_CONFIG"; type: "SET_CONFIG";
config: Core.RevoltNodeConfiguration; config: RevoltConfiguration;
}; };
export function config( export function config(
state = {} as Core.RevoltNodeConfiguration, state = {} as RevoltConfiguration,
action: ConfigAction, action: ConfigAction,
): Core.RevoltNodeConfiguration { ): RevoltConfiguration {
switch (action.type) { switch (action.type) {
case "SET_CONFIG": case "SET_CONFIG":
return action.config; return action.config;

View file

@ -1,7 +1,7 @@
import type { Sync } from "revolt.js/dist/api/objects"; import type { ChannelUnread } from "revolt-api/types/Sync";
export interface Unreads { export interface Unreads {
[key: string]: Partial<Omit<Sync.ChannelUnread, "_id">>; [key: string]: Partial<Omit<ChannelUnread, "_id">>;
} }
export type UnreadsAction = export type UnreadsAction =
@ -13,7 +13,7 @@ export type UnreadsAction =
} }
| { | {
type: "UNREADS_SET"; type: "UNREADS_SET";
unreads: Sync.ChannelUnread[]; unreads: ChannelUnread[];
} }
| { | {
type: "UNREADS_MENTION"; type: "UNREADS_MENTION";

View file

@ -1,8 +1,8 @@
/// <reference lib="webworker" /> /// <reference lib="webworker" />
import { IDBPDatabase, openDB } from "idb"; import { IDBPDatabase, openDB } from "idb";
import { getItem } from "localforage"; import { getItem } from "localforage";
import { Channel, Message, User } from "revolt.js"; // import { Channel, Message, User } from "revolt.js";
import { Server } from "revolt.js/dist/api/objects"; // import { Server } from "revolt.js/dist/api/objects";
import { precacheAndRoute } from "workbox-precaching"; import { precacheAndRoute } from "workbox-precaching";
import type { State } from "./redux"; import type { State } from "./redux";
@ -43,7 +43,8 @@ function decodeTime(id: string) {
self.addEventListener("push", (event) => { self.addEventListener("push", (event) => {
async function process() { async function process() {
if (event.data === null) return; if (event.data === null) return;
const data: Message = event.data.json(); // ! FIXME: removed until client data is saved to local storage
/* const data: Message = event.data.json();
const item = await localStorage.getItem("state"); const item = await localStorage.getItem("state");
if (!item) return; if (!item) return;
@ -142,7 +143,7 @@ self.addEventListener("push", (event) => {
channel?.channel_type === "TextChannel" channel?.channel_type === "TextChannel"
? `/server/${channel.server}/channel/${channel._id}` ? `/server/${channel.server}/channel/${channel._id}`
: `/channel/${data.channel}`, : `/channel/${data.channel}`,
}); }); */
} }
event.waitUntil(process()); event.waitUntil(process());

View file

@ -1114,14 +1114,6 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf"
integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==
"@insertish/mutable@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@insertish/mutable/-/mutable-1.1.0.tgz#06f95f855691ccb69ee3c339887a80bcd1498116"
integrity sha512-NH7aCGFAKRE1gFprrW/HsJoWCWQy18TZBarxLdeLVWdLFvkb2lD6Z5B70oOoUHFNpykiTC8IcRonsd9Xn13n8Q==
dependencies:
eventemitter3 "^4.0.7"
lodash.isequal "^4.5.0"
"@mdn/browser-compat-data@^2.0.7": "@mdn/browser-compat-data@^2.0.7":
version "2.0.7" version "2.0.7"
resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-2.0.7.tgz#72ec37b9c1e00ce0b4e0309d753be18e2da12ee3" resolved "https://registry.yarnpkg.com/@mdn/browser-compat-data/-/browser-compat-data-2.0.7.tgz#72ec37b9c1e00ce0b4e0309d753be18e2da12ee3"
@ -3573,17 +3565,23 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
revolt.js@4.4.0-alpha.0: revolt-api@0.5.1-alpha.10-patch.0:
version "4.4.0-alpha.0" version "0.5.1-alpha.10-patch.0"
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.4.0-alpha.0.tgz#6b8d7e5605c4e106800ebd7ddcda8215a8fb289b" resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.1-alpha.10-patch.0.tgz#97d31bec7dfa4573567097443acb059c4feaac20"
integrity sha512-TPb7FCC1xpAO0hJ19tXAC5ZwsceJ/yzkFlV/lESteVk4bboVINhRrsElYtJvjSKci7Ft70t0e4bbm8YSYjAblA== integrity sha512-UyM890HkGlYNQOxpHuEpUsJHLt8Ujnjg9/zPEDGpbvS4iy0jmHX23Hh8tOCfb/ewxbNrtT3G1HpSWKOneW/vYg==
revolt.js@5.0.0-alpha.5:
version "5.0.0-alpha.5"
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.0.0-alpha.5.tgz#96008a1bf91e80b17ff877b59ca3f4fd9151055d"
integrity sha512-HOMblFOR25pE1NMGK4EYgvWqfWADyiGTZlvFpio8CEca3lAmr2jWjJAly5BWMy1SwFcfHmtHIy6Lm5Pkgjop9Q==
dependencies: dependencies:
"@insertish/mutable" "1.1.0"
axios "^0.19.2" axios "^0.19.2"
eventemitter3 "^4.0.7" eventemitter3 "^4.0.7"
exponential-backoff "^3.1.0" exponential-backoff "^3.1.0"
isomorphic-ws "^4.0.1" isomorphic-ws "^4.0.1"
lodash.defaultsdeep "^4.6.1" lodash.defaultsdeep "^4.6.1"
lodash.isequal "^4.5.0"
mobx "^6.3.2"
tsc-watch "^4.1.0" tsc-watch "^4.1.0"
ulid "^2.3.0" ulid "^2.3.0"
ws "^7.2.1" ws "^7.2.1"