From 411fac2527fcbce63439df16bf49294cdf5fd13b Mon Sep 17 00:00:00 2001 From: Paul <paulmakles@gmail.com> Date: Thu, 29 Jul 2021 18:41:01 +0100 Subject: [PATCH] Remove useChannel --- src/components/common/AgeGate.tsx | 7 +- src/components/common/AutoComplete.tsx | 12 +-- src/components/common/ChannelIcon.tsx | 96 ++++++++++--------- .../common/messaging/MessageBox.tsx | 4 +- .../navigation/items/ButtonItem.tsx | 20 ++-- .../navigation/left/HomeSidebar.tsx | 41 ++++---- .../navigation/left/ServerListSidebar.tsx | 13 ++- .../navigation/left/ServerSidebar.tsx | 19 ++-- src/components/navigation/left/common.ts | 13 ++- .../navigation/right/MemberSidebar.tsx | 37 +++---- src/context/intermediate/Intermediate.tsx | 12 +-- src/context/intermediate/modals/Prompt.tsx | 10 +- .../intermediate/popovers/ChannelInfo.tsx | 21 ++-- .../intermediate/popovers/UserProfile.tsx | 19 ++-- src/context/revoltjs/hooks.ts | 7 -- src/context/revoltjs/util.tsx | 4 +- src/lib/ContextMenus.tsx | 38 ++++---- src/mobx/index.ts | 4 +- src/pages/channels/Channel.tsx | 21 ++-- src/pages/channels/ChannelHeader.tsx | 5 +- src/pages/channels/actions/HeaderActions.tsx | 2 +- .../channels/messaging/ConversationStart.tsx | 16 ++-- src/pages/settings/ChannelSettings.tsx | 12 ++- src/pages/settings/channel/Overview.tsx | 16 ++-- src/pages/settings/channel/Permissions.tsx | 14 +-- src/pages/settings/server/Categories.tsx | 16 ++-- src/pages/settings/server/Invites.tsx | 16 ++-- src/pages/settings/server/Overview.tsx | 21 ++-- 28 files changed, 259 insertions(+), 257 deletions(-) diff --git a/src/components/common/AgeGate.tsx b/src/components/common/AgeGate.tsx index 324005b..adfb365 100644 --- a/src/components/common/AgeGate.tsx +++ b/src/components/common/AgeGate.tsx @@ -1,10 +1,11 @@ +import { observer } from "mobx-react-lite"; import { useHistory } from "react-router-dom"; -import { Channel } from "revolt.js"; import styled from "styled-components"; import { Text } from "preact-i18n"; import { useState } from "preact/hooks"; +import { Channel } from "../../mobx"; import { dispatch, getState } from "../../redux"; import Button from "../ui/Button"; @@ -46,7 +47,7 @@ type Props = { channel: Channel; }; -export default function AgeGate(props: Props) { +export default observer((props: Props) => { const history = useHistory(); const [consent, setConsent] = useState( getState().sectionToggle["nsfw"] ?? false, @@ -105,4 +106,4 @@ export default function AgeGate(props: Props) { </div> </Base> ); -} +}); diff --git a/src/components/common/AutoComplete.tsx b/src/components/common/AutoComplete.tsx index faca2ff..e457cbf 100644 --- a/src/components/common/AutoComplete.tsx +++ b/src/components/common/AutoComplete.tsx @@ -5,7 +5,7 @@ import styled, { css } from "styled-components"; import { StateUpdater, useState } from "preact/hooks"; -import { User } from "../../mobx"; +import { Channel, User } from "../../mobx"; import { useData } from "../../mobx/State"; import { useClient } from "../../context/revoltjs/RevoltClient"; @@ -28,7 +28,7 @@ export type AutoCompleteState = } | { type: "channel"; - matches: Channels.TextChannel[]; + matches: Channel[]; } )); @@ -197,15 +197,13 @@ export function useAutoComplete( if (type === "channel" && searchClues?.channels) { const channels = client.servers .get(searchClues.channels.server) - ?.channels.map((x) => client.channels.get(x)) - .filter( - (x) => typeof x !== "undefined", - ) as Channels.TextChannel[]; + ?.channels.map((x) => store.channels.get(x)) + .filter((x) => typeof x !== "undefined") as Channel[]; const matches = ( search.length > 0 ? channels.filter((channel) => - channel.name.toLowerCase().match(regex), + channel.name!.toLowerCase().match(regex), ) : channels ) diff --git a/src/components/common/ChannelIcon.tsx b/src/components/common/ChannelIcon.tsx index fb605c7..301b009 100644 --- a/src/components/common/ChannelIcon.tsx +++ b/src/components/common/ChannelIcon.tsx @@ -1,65 +1,67 @@ import { Hash, VolumeFull } from "@styled-icons/boxicons-regular"; +import { observer } from "mobx-react-lite"; import { Channels } from "revolt.js/dist/api/objects"; import { useContext } from "preact/hooks"; +import { Channel } from "../../mobx"; + import { AppContext } from "../../context/revoltjs/RevoltClient"; import { ImageIconBase, IconBaseProps } from "./IconBase"; import fallback from "./assets/group.png"; -interface Props - extends IconBaseProps< - Channels.GroupChannel | Channels.TextChannel | Channels.VoiceChannel - > { +interface Props extends IconBaseProps<Channel> { isServerChannel?: boolean; } -export default function ChannelIcon( - props: Props & Omit<JSX.HTMLAttributes<HTMLImageElement>, keyof Props>, -) { - const client = useContext(AppContext); +export default observer( + ( + props: Props & Omit<JSX.HTMLAttributes<HTMLImageElement>, keyof Props>, + ) => { + const client = useContext(AppContext); - const { - size, - target, - attachment, - isServerChannel: server, - animate, - children, - as, - ...imgProps - } = props; - const iconURL = client.generateFileURL( - target?.icon ?? attachment, - { max_side: 256 }, - animate, - ); - const isServerChannel = - server || - (target && - (target.channel_type === "TextChannel" || - target.channel_type === "VoiceChannel")); + const { + size, + target, + attachment, + isServerChannel: server, + animate, + children, + as, + ...imgProps + } = props; + const iconURL = client.generateFileURL( + target?.icon ?? attachment, + { max_side: 256 }, + animate, + ); + const isServerChannel = + server || + (target && + (target.channel_type === "TextChannel" || + target.channel_type === "VoiceChannel")); - if (typeof iconURL === "undefined") { - if (isServerChannel) { - if (target?.channel_type === "VoiceChannel") { - return <VolumeFull size={size} />; + if (typeof iconURL === "undefined") { + if (isServerChannel) { + if (target?.channel_type === "VoiceChannel") { + return <VolumeFull size={size} />; + } + return <Hash size={size} />; } - return <Hash size={size} />; } - } - return ( - // ! fixme: replace fallback with <picture /> + <source /> - <ImageIconBase - {...imgProps} - width={size} - height={size} - loading="lazy" - aria-hidden="true" - square={isServerChannel} - src={iconURL ?? fallback} - /> - ); -} + return ( + // ! fixme: replace fallback with <picture /> + <source /> + <ImageIconBase + {...imgProps} + width={size} + height={size} + loading="lazy" + aria-hidden="true" + square={isServerChannel} + src={iconURL ?? fallback} + /> + ); + }, +); diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx index 0ebb500..2983295 100644 --- a/src/components/common/messaging/MessageBox.tsx +++ b/src/components/common/messaging/MessageBox.tsx @@ -1,7 +1,6 @@ import { Send, HappyAlt, ShieldX } from "@styled-icons/boxicons-solid"; import { Styleshare } from "@styled-icons/simple-icons"; import Axios, { CancelTokenSource } from "axios"; -import { Channel } from "revolt.js"; import { ChannelPermission } from "revolt.js/dist/api/permissions"; import styled from "styled-components"; import { ulid } from "ulid"; @@ -20,6 +19,7 @@ import { SMOOTH_SCROLL_ON_RECEIVE, } from "../../../lib/renderer/Singleton"; +import { Channel } from "../../../mobx"; import { dispatch, getState } from "../../../redux"; import { Reply } from "../../../redux/reducers/queue"; @@ -360,7 +360,7 @@ export default function MessageBox({ channel }: Props) { users: { type: "channel", id: channel._id }, channels: channel.channel_type === "TextChannel" - ? { server: channel.server } + ? { server: channel.server! } : undefined, }); diff --git a/src/components/navigation/items/ButtonItem.tsx b/src/components/navigation/items/ButtonItem.tsx index d19290d..734b522 100644 --- a/src/components/navigation/items/ButtonItem.tsx +++ b/src/components/navigation/items/ButtonItem.tsx @@ -10,7 +10,7 @@ import { Localizer, Text } from "preact-i18n"; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; import { stopPropagation } from "../../../lib/stopPropagation"; -import { User } from "../../../mobx"; +import { Channel, User } from "../../../mobx"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; @@ -34,8 +34,8 @@ type CommonProps = Omit< type UserProps = CommonProps & { user: User; - context?: Channels.Channel; - channel?: Channels.DirectMessageChannel; + context?: Channel; + channel?: Channel; }; export const UserButton = observer((props: UserProps) => { @@ -73,7 +73,7 @@ export const UserButton = observer((props: UserProps) => { { <div className={styles.subText}> {channel?.last_message && alert ? ( - channel.last_message.short + (channel.last_message as { short: string }).short ) : ( <UserStatus user={user} /> )} @@ -115,12 +115,12 @@ export const UserButton = observer((props: UserProps) => { }); type ChannelProps = CommonProps & { - channel: Channels.Channel & { unread?: string }; + channel: Channel & { unread?: string }; user?: User; compact?: boolean; }; -export function ChannelButton(props: ChannelProps) { +export const ChannelButton = observer((props: ChannelProps) => { const { active, alert, alertCount, channel, user, compact, ...divProps } = props; @@ -153,12 +153,12 @@ export function ChannelButton(props: ChannelProps) { {channel.channel_type === "Group" && ( <div className={styles.subText}> {channel.last_message && alert ? ( - channel.last_message.short + (channel.last_message as { short: string }).short ) : ( <Text id="quantities.members" - plural={channel.recipients.length} - fields={{ count: channel.recipients.length }} + plural={channel.recipients!.length} + fields={{ count: channel.recipients!.length }} /> )} </div> @@ -186,7 +186,7 @@ export function ChannelButton(props: ChannelProps) { </div> </div> ); -} +}); type ButtonProps = CommonProps & { onClick?: () => void; diff --git a/src/components/navigation/left/HomeSidebar.tsx b/src/components/navigation/left/HomeSidebar.tsx index e22d35b..d9cea63 100644 --- a/src/components/navigation/left/HomeSidebar.tsx +++ b/src/components/navigation/left/HomeSidebar.tsx @@ -43,10 +43,16 @@ const HomeSidebar = observer((props: Props) => { const { channel } = useParams<{ channel: string }>(); const { openScreen } = useIntermediate(); - const ctx = useForceUpdate(); - const channels = useDMs(ctx); + const store = useData(); + const channels = [...store.channels.values()] + .filter( + (x) => + x.channel_type === "DirectMessage" || + x.channel_type === "Group", + ) + .map((x) => mapChannelWithUnread(x, props.unreads)); - const obj = channels.find((x) => x?._id === channel); + const obj = store.channels.get(channel); if (channel && !obj) return <Redirect to="/" />; if (obj) useUnreads({ ...props, channel: obj }); @@ -60,12 +66,7 @@ const HomeSidebar = observer((props: Props) => { }); }, [channel]); - const channelsArr = channels - .filter((x) => x.channel_type !== "SavedMessages") - .map((x) => mapChannelWithUnread(x, props.unreads)); - - const store = useData(); - channelsArr.sort((b, a) => a.timestamp.localeCompare(b.timestamp)); + channels.sort((b, a) => a.timestamp.localeCompare(b.timestamp)); return ( <GenericSidebarBase padding> @@ -132,20 +133,22 @@ const HomeSidebar = observer((props: Props) => { }) } /> - {channelsArr.length === 0 && ( + {channels.length === 0 && ( <img src={placeholderSVG} loading="eager" /> )} - {channelsArr.map((x) => { + {channels.map((x) => { let user; - if (x.channel_type === "DirectMessage") { - if (!x.active) return null; + if (x.channel.channel_type === "DirectMessage") { + if (!x.channel.active) return null; - const recipient = client.channels.getRecipient(x._id); + const recipient = client.channels.getRecipient( + x.channel._id, + ); user = store.users.get(recipient); if (!user) { console.warn( - `Skipped DM ${x._id} because user was missing.`, + `Skipped DM ${x.channel._id} because user was missing.`, ); return null; } @@ -153,14 +156,14 @@ const HomeSidebar = observer((props: Props) => { return ( <ConditionalLink - active={x._id === channel} - to={`/channel/${x._id}`}> + active={x.channel._id === channel} + to={`/channel/${x.channel._id}`}> <ChannelButton user={user} - channel={x} + channel={x.channel} alert={x.unread} alertCount={x.alertCount} - active={x._id === channel} + active={x.channel._id === channel} /> </ConditionalLink> ); diff --git a/src/components/navigation/left/ServerListSidebar.tsx b/src/components/navigation/left/ServerListSidebar.tsx index 14f2432..bf8253f 100644 --- a/src/components/navigation/left/ServerListSidebar.tsx +++ b/src/components/navigation/left/ServerListSidebar.tsx @@ -186,16 +186,18 @@ export const ServerListSidebar = observer(({ unreads, lastOpened }: Props) => { const ctx = useForceUpdate(); const activeServers = useServers(undefined, ctx) as Servers.Server[]; - const channels = (useChannels(undefined, ctx) as Channel[]).map((x) => + const channels = [...store.channels.values()].map((x) => mapChannelWithUnread(x, unreads), ); - const unreadChannels = channels.filter((x) => x.unread).map((x) => x._id); + const unreadChannels = channels + .filter((x) => x.unread) + .map((x) => x.channel?._id); const servers = activeServers.map((server) => { let alertCount = 0; for (const id of server.channels) { - const channel = channels.find((x) => x._id === id); + const channel = channels.find((x) => x.channel?._id === id); if (channel?.alertCount) { alertCount += channel.alertCount; } @@ -224,8 +226,9 @@ export const ServerListSidebar = observer(({ unreads, lastOpened }: Props) => { let alertCount = 0; for (const x of channels) { if ( - ((x.channel_type === "DirectMessage" && x.active) || - x.channel_type === "Group") && + (x.channel?.channel_type === "DirectMessage" + ? x.channel?.active + : true) && x.unread ) { homeUnread = "unread"; diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx index f4d5340..e21b33b 100644 --- a/src/components/navigation/left/ServerSidebar.tsx +++ b/src/components/navigation/left/ServerSidebar.tsx @@ -1,3 +1,4 @@ +import { observer } from "mobx-react-lite"; import { Redirect, useParams } from "react-router"; import { Channels } from "revolt.js/dist/api/objects"; import styled from "styled-components"; @@ -8,6 +9,7 @@ import { useEffect } from "preact/hooks"; import ConditionalLink from "../../../lib/ConditionalLink"; import PaintCounter from "../../../lib/PaintCounter"; +import { useData } from "../../../mobx/State"; import { dispatch } from "../../../redux"; import { connectState } from "../../../redux/connector"; import { Unreads } from "../../../redux/reducers/unreads"; @@ -53,7 +55,7 @@ const ServerList = styled.div` } `; -function ServerSidebar(props: Props) { +const ServerSidebar = observer((props: Props) => { const { server: server_id, channel: channel_id } = useParams<{ server?: string; channel?: string }>(); const ctx = useForceUpdate(); @@ -61,13 +63,9 @@ function ServerSidebar(props: Props) { const server = useServer(server_id, ctx); if (!server) return <Redirect to="/" />; - const channels = ( - useChannels(server.channels, ctx).filter( - (entry) => typeof entry !== "undefined", - ) as Readonly<Channels.TextChannel | Channels.VoiceChannel>[] - ).map((x) => mapChannelWithUnread(x, props.unreads)); + const store = useData(); - const channel = channels.find((x) => x?._id === channel_id); + const channel = channel_id ? store.channels.get(channel_id) : undefined; if (channel_id && !channel) return <Redirect to={`/server/${server_id}`} />; if (channel) useUnreads({ ...props, channel }, ctx); @@ -85,7 +83,7 @@ function ServerSidebar(props: Props) { const elements = []; function addChannel(id: string) { - const entry = channels.find((x) => x._id === id); + const entry = store.channels.get(id); if (!entry) return; const active = channel?._id === entry._id; @@ -98,7 +96,8 @@ function ServerSidebar(props: Props) { <ChannelButton channel={entry} active={active} - alert={entry.unread} + // ! FIXME: pull it out directly + alert={mapChannelWithUnread(entry, props.unreads).unread} compact /> </ConditionalLink> @@ -141,7 +140,7 @@ function ServerSidebar(props: Props) { <PaintCounter small /> </ServerBase> ); -} +}); export default connectState(ServerSidebar, (state) => { return { diff --git a/src/components/navigation/left/common.ts b/src/components/navigation/left/common.ts index f8539c2..6f60ba6 100644 --- a/src/components/navigation/left/common.ts +++ b/src/components/navigation/left/common.ts @@ -1,7 +1,6 @@ -import { Channel } from "revolt.js"; - import { useLayoutEffect } from "preact/hooks"; +import { Channel } from "../../../mobx"; import { dispatch } from "../../../redux"; import { Unreads } from "../../../redux/reducers/unreads"; @@ -63,12 +62,12 @@ export function mapChannelWithUnread(channel: Channel, unreads: Unreads) { channel.channel_type === "DirectMessage" || channel.channel_type === "Group" ) { - last_message_id = channel.last_message?._id; + last_message_id = (channel.last_message as { _id: string })?._id; } else if (channel.channel_type === "TextChannel") { - last_message_id = channel.last_message; + last_message_id = channel.last_message as string; } else { return { - ...channel, + channel, unread: undefined, alertCount: undefined, timestamp: channel._id, @@ -85,7 +84,7 @@ export function mapChannelWithUnread(channel: Channel, unreads: Unreads) { unread = "mention"; } else if ( u.last_id && - last_message_id.localeCompare(u.last_id) > 0 + (last_message_id as string).localeCompare(u.last_id) > 0 ) { unread = "unread"; } @@ -95,7 +94,7 @@ export function mapChannelWithUnread(channel: Channel, unreads: Unreads) { } return { - ...channel, + channel, timestamp: last_message_id ?? channel._id, unread, alertCount, diff --git a/src/components/navigation/right/MemberSidebar.tsx b/src/components/navigation/right/MemberSidebar.tsx index 23d6555..8c2b813 100644 --- a/src/components/navigation/right/MemberSidebar.tsx +++ b/src/components/navigation/right/MemberSidebar.tsx @@ -8,6 +8,7 @@ import { ClientboundNotification } from "revolt.js/dist/websocket/notifications" import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; +import { Channel } from "../../../mobx"; import { useData } from "../../../mobx/State"; import { getState } from "../../../redux"; @@ -17,11 +18,7 @@ import { ClientStatus, StatusContext, } from "../../../context/revoltjs/RevoltClient"; -import { - HookContext, - useChannel, - useForceUpdate, -} from "../../../context/revoltjs/hooks"; +import { HookContext } from "../../../context/revoltjs/hooks"; import CollapsibleSection from "../../common/CollapsibleSection"; import Button from "../../ui/Button"; @@ -34,27 +31,19 @@ import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase"; import { UserButton } from "../items/ButtonItem"; import { ChannelDebugInfo } from "./ChannelDebugInfo"; -interface Props { - ctx: HookContext; -} - -export default function MemberSidebar(props: { channel?: Channels.Channel }) { - const ctx = useForceUpdate(); - const { channel: cid } = useParams<{ channel: string }>(); - const channel = props.channel ?? useChannel(cid, ctx); - +export default function MemberSidebar({ channel }: { channel?: Channel }) { switch (channel?.channel_type) { case "Group": - return <GroupMemberSidebar channel={channel} ctx={ctx} />; + return <GroupMemberSidebar channel={channel} />; case "TextChannel": - return <ServerMemberSidebar channel={channel} ctx={ctx} />; + return <ServerMemberSidebar channel={channel} />; default: return null; } } export const GroupMemberSidebar = observer( - ({ channel }: Props & { channel: Channels.GroupChannel }) => { + ({ channel }: { channel: Channel }) => { const { openScreen } = useIntermediate(); const store = useData(); @@ -77,7 +66,7 @@ export const GroupMemberSidebar = observer( voiceParticipants.sort((a, b) => a.username.localeCompare(b.username)); }*/ - members.sort((a, b) => { + members?.sort((a, b) => { // ! FIXME: should probably rewrite all this code const l = +( @@ -141,21 +130,21 @@ export const GroupMemberSidebar = observer( text={ <span> <Text id="app.main.categories.members" />{" "} - — {channel.recipients.length} + — {channel.recipients?.length ?? 0} </span> } /> }> - {members.length === 0 && ( + {members?.length === 0 && ( <img src={placeholderSVG} loading="eager" /> )} - {members.map( + {members?.map( (user) => user && ( <UserButton key={user._id} user={user} - context={channel} + context={channel!} onClick={() => openScreen({ id: "profile", @@ -173,7 +162,7 @@ export const GroupMemberSidebar = observer( ); export const ServerMemberSidebar = observer( - ({ channel }: Props & { channel: Channels.TextChannel }) => { + ({ channel }: { channel: Channel }) => { const [members, setMembers] = useState<Servers.Member[] | undefined>( undefined, ); @@ -193,7 +182,7 @@ export const ServerMemberSidebar = observer( typeof members === "undefined" ) { client.members - .fetchMembers(channel.server) + .fetchMembers(channel.server!) .then((members) => setMembers(members)); } }, [status]); diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx index 03b4125..72e7fe2 100644 --- a/src/context/intermediate/Intermediate.tsx +++ b/src/context/intermediate/Intermediate.tsx @@ -13,7 +13,7 @@ import { useContext, useEffect, useMemo, useState } from "preact/hooks"; import { internalSubscribe } from "../../lib/eventEmitter"; -import { User } from "../../mobx"; +import { Channel, User } from "../../mobx"; import { Action } from "../../components/ui/Modal"; @@ -34,15 +34,15 @@ export type Screen = actions: Action[]; } | ({ id: "special_prompt" } & ( - | { type: "leave_group"; target: Channels.GroupChannel } - | { type: "close_dm"; target: Channels.DirectMessageChannel } + | { type: "leave_group"; target: Channel } + | { type: "close_dm"; target: Channel } | { type: "leave_server"; target: Servers.Server } | { type: "delete_server"; target: Servers.Server } - | { type: "delete_channel"; target: Channels.TextChannel } + | { type: "delete_channel"; target: Channel } | { type: "delete_message"; target: Channels.Message } | { 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 } @@ -83,7 +83,7 @@ export type Screen = | { id: "image_viewer"; attachment?: Attachment; embed?: EmbedImage } | { id: "modify_account"; field: "username" | "email" | "password" } | { id: "profile"; user_id: string } - | { id: "channel_info"; channel_id: string } + | { id: "channel_info"; channel: Channel } | { id: "pending_requests"; users: User[] } | { id: "user_picker"; diff --git a/src/context/intermediate/modals/Prompt.tsx b/src/context/intermediate/modals/Prompt.tsx index 38096fc..ea9b276 100644 --- a/src/context/intermediate/modals/Prompt.tsx +++ b/src/context/intermediate/modals/Prompt.tsx @@ -9,7 +9,7 @@ import { useContext, useEffect, useState } from "preact/hooks"; import { TextReact } from "../../../lib/i18n"; -import { User } from "../../../mobx"; +import { Channel, User } from "../../../mobx"; import { useData } from "../../../mobx/State"; import Message from "../../../components/common/messaging/Message"; @@ -55,15 +55,15 @@ export function PromptModal({ } type SpecialProps = { onClose: () => void } & ( - | { type: "leave_group"; target: Channels.GroupChannel } - | { type: "close_dm"; target: Channels.DirectMessageChannel } + | { type: "leave_group"; target: Channel } + | { type: "close_dm"; target: Channel } | { type: "leave_server"; target: Servers.Server } | { type: "delete_server"; target: Servers.Server } - | { type: "delete_channel"; target: Channels.TextChannel } + | { type: "delete_channel"; target: Channel } | { type: "delete_message"; target: Channels.Message } | { 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 } diff --git a/src/context/intermediate/popovers/ChannelInfo.tsx b/src/context/intermediate/popovers/ChannelInfo.tsx index 18a492a..3b15cd7 100644 --- a/src/context/intermediate/popovers/ChannelInfo.tsx +++ b/src/context/intermediate/popovers/ChannelInfo.tsx @@ -1,23 +1,23 @@ import { X } from "@styled-icons/boxicons-regular"; +import { observer } from "mobx-react-lite"; import styles from "./ChannelInfo.module.scss"; +import { Channel } from "../../../mobx"; + import Modal from "../../../components/ui/Modal"; import Markdown from "../../../components/markdown/Markdown"; -import { useChannel, useForceUpdate } from "../../revoltjs/hooks"; +import { useClient } from "../../revoltjs/RevoltClient"; +import { 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" @@ -26,19 +26,20 @@ export function ChannelInfo({ channel_id, onClose }: Props) { return null; } + const client = useClient(); return ( <Modal visible={true} onClose={onClose}> <div className={styles.info}> <div className={styles.header}> - <h1>{getChannelName(ctx.client, channel, true)}</h1> + <h1>{getChannelName(client, channel, true)}</h1> <div onClick={onClose}> <X size={36} /> </div> </div> <p> - <Markdown content={channel.description} /> + <Markdown content={channel.description!} /> </p> </div> </Modal> ); -} +}); diff --git a/src/context/intermediate/popovers/UserProfile.tsx b/src/context/intermediate/popovers/UserProfile.tsx index 0e0105d..b8c4e2d 100644 --- a/src/context/intermediate/popovers/UserProfile.tsx +++ b/src/context/intermediate/popovers/UserProfile.tsx @@ -27,11 +27,7 @@ import { StatusContext, useClient, } from "../../revoltjs/RevoltClient"; -import { - useChannels, - useForceUpdate, - useUserPermission, -} from "../../revoltjs/hooks"; +import { useForceUpdate, useUserPermission } from "../../revoltjs/hooks"; import { useIntermediate } from "../Intermediate"; interface Props { @@ -65,7 +61,6 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) { const [tab, setTab] = useState("profile"); const ctx = useForceUpdate(); - const channels = useChannels(undefined, ctx); const permissions = useUserPermission(client.user!._id, ctx); const store = useData(); @@ -77,6 +72,12 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) { const user = store.users.get(user_id)!; const users = mutual?.users.map((id) => store.users.get(id)); + const mutualGroups = [...store.channels.values()].filter( + (channel) => + channel?.channel_type === "Group" && + channel.recipients!.includes(user_id), + ); + useLayoutEffect(() => { if (!user_id) return; if (typeof profile !== "undefined") setProfile(undefined); @@ -111,12 +112,6 @@ export function UserProfile({ user_id, onClose, dummy, dummyProfile }: Props) { } }, [profile, status]); - const mutualGroups = channels.filter( - (channel) => - channel?.channel_type === "Group" && - channel.recipients.includes(user_id), - ); - const backgroundURL = profile && client.users.getBackgroundURL(profile, { width: 1000 }, true); diff --git a/src/context/revoltjs/hooks.ts b/src/context/revoltjs/hooks.ts index e5ea687..99823c5 100644 --- a/src/context/revoltjs/hooks.ts +++ b/src/context/revoltjs/hooks.ts @@ -77,13 +77,6 @@ function useObject( : map.toArray(); } -export function useChannel(id?: string, context?: HookContext) { - if (typeof id === "undefined") return; - return useObject("channels", id, context) as - | Readonly<Channels.Channel> - | undefined; -} - export function useChannels(ids?: string[], context?: HookContext) { return useObject("channels", ids, context) as ( | Readonly<Channels.Channel> diff --git a/src/context/revoltjs/util.tsx b/src/context/revoltjs/util.tsx index 98489e8..439dbf6 100644 --- a/src/context/revoltjs/util.tsx +++ b/src/context/revoltjs/util.tsx @@ -1,8 +1,10 @@ import { Client } from "revolt.js"; -import { Channel, Message, User } from "revolt.js/dist/api/objects"; +import { Message } from "revolt.js/dist/api/objects"; import { Text } from "preact-i18n"; +import { Channel } from "../../mobx"; + import { Children } from "../../types/Preact"; export function takeError(error: any): string { diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx index 9cca0a2..d1e3bbe 100644 --- a/src/lib/ContextMenus.tsx +++ b/src/lib/ContextMenus.tsx @@ -34,7 +34,7 @@ import { import { Text } from "preact-i18n"; import { useContext } from "preact/hooks"; -import { User } from "../mobx"; +import { Channel, User } from "../mobx"; import { useData } from "../mobx/State"; import { dispatch } from "../redux"; import { connectState } from "../redux/connector"; @@ -53,7 +53,6 @@ import { useClient, } from "../context/revoltjs/RevoltClient"; import { - useChannel, useChannelPermission, useForceUpdate, useServer, @@ -86,7 +85,7 @@ type Action = | { action: "copy_id"; id: string } | { action: "copy_selection" } | { action: "copy_text"; content: string } - | { action: "mark_as_read"; channel: Channels.Channel } + | { action: "mark_as_read"; channel: Channel } | { action: "retry_message"; message: QueuedMessage } | { action: "cancel_message"; message: QueuedMessage } | { action: "mention"; user: string } @@ -115,20 +114,17 @@ type Action = | { action: "create_channel"; target: Servers.Server } | { action: "create_invite"; - target: - | Channels.GroupChannel - | Channels.TextChannel - | Channels.VoiceChannel; + target: Channel; } - | { action: "leave_group"; target: Channels.GroupChannel } + | { action: "leave_group"; target: Channel } | { action: "delete_channel"; - target: Channels.TextChannel | Channels.VoiceChannel; + target: Channel; } - | { action: "close_dm"; target: Channels.DirectMessageChannel } + | { action: "close_dm"; target: Channel } | { action: "leave_server"; target: Servers.Server } | { action: "delete_server"; target: Servers.Server } - | { action: "open_notification_options"; channel: Channels.Channel } + | { action: "open_notification_options"; channel: Channel } | { action: "open_settings" } | { action: "open_channel_settings"; id: string } | { action: "open_server_settings"; id: string } @@ -172,9 +168,10 @@ function ContextMenus(props: Props) { return; const message = - data.channel.channel_type === "TextChannel" + typeof data.channel.last_message === "string" ? data.channel.last_message - : data.channel.last_message._id; + : data.channel.last_message!._id; + dispatch({ type: "UNREADS_MARK_READ", channel: data.channel._id, @@ -529,8 +526,10 @@ function ContextMenus(props: Props) { pushDivider(); } - const channel = useChannel(cid, forceUpdate); - const contextualChannel = useChannel(cxid, forceUpdate); + const channel = cid ? store.channels.get(cid) : undefined; + const contextualChannel = cxid + ? store.channels.get(cxid) + : undefined; const targetChannel = channel ?? contextualChannel; const user = uid ? store.users.get(uid) : undefined; @@ -541,7 +540,7 @@ function ContextMenus(props: Props) { ? targetChannel : undefined; const server = useServer( - serverChannel ? serverChannel.server : sid, + serverChannel ? serverChannel.server! : sid, forceUpdate, ); @@ -551,7 +550,10 @@ function ContextMenus(props: Props) { const serverPermissions = server ? useServerPermission(server._id, forceUpdate) : serverChannel - ? useServerPermission(serverChannel.server, forceUpdate) + ? useServerPermission( + serverChannel.server!, + forceUpdate, + ) : 0; const userPermissions = user ? useUserPermission(user._id, forceUpdate) @@ -819,7 +821,7 @@ function ContextMenus(props: Props) { generateAction( { action: "open_server_channel_settings", - server: channel.server, + server: channel.server!, id: channel._id, }, "open_channel_settings", diff --git a/src/mobx/index.ts b/src/mobx/index.ts index fdc1ae4..700c5b2 100644 --- a/src/mobx/index.ts +++ b/src/mobx/index.ts @@ -73,7 +73,7 @@ export class User { export class Channel { _id: string; - type: Channels.Channel["channel_type"]; + channel_type: Channels.Channel["channel_type"]; // Direct Message active: Nullable<boolean> = null; @@ -98,7 +98,7 @@ export class Channel { constructor(data: Channels.Channel) { this._id = data._id; - this.type = data.channel_type; + this.channel_type = data.channel_type; switch (data.channel_type) { case "DirectMessage": { diff --git a/src/pages/channels/Channel.tsx b/src/pages/channels/Channel.tsx index 3cd4b2b..9f40f8e 100644 --- a/src/pages/channels/Channel.tsx +++ b/src/pages/channels/Channel.tsx @@ -1,3 +1,4 @@ +import { observer } from "mobx-react-lite"; import { useParams, useHistory } from "react-router-dom"; import { Channels } from "revolt.js/dist/api/objects"; import styled from "styled-components"; @@ -6,10 +7,10 @@ import { useState } from "preact/hooks"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; +import { Channel as MobXChannel } from "../../mobx"; +import { useData } from "../../mobx/State"; import { dispatch, getState } from "../../redux"; -import { useChannel, useForceUpdate } from "../../context/revoltjs/hooks"; - import AgeGate from "../../components/common/AgeGate"; import MessageBox from "../../components/common/messaging/MessageBox"; import JumpToBottom from "../../components/common/messaging/bars/JumpToBottom"; @@ -36,19 +37,19 @@ const ChannelContent = styled.div` `; export function Channel({ id }: { id: string }) { - const ctx = useForceUpdate(); - const channel = useChannel(id, ctx); - + const store = useData(); + const channel = store.channels.get(id); if (!channel) return null; if (channel.channel_type === "VoiceChannel") { return <VoiceChannel channel={channel} />; } + return <TextChannel channel={channel} />; } const MEMBERS_SIDEBAR_KEY = "sidebar_members"; -function TextChannel({ channel }: { channel: Channels.Channel }) { +const TextChannel = observer(({ channel }: { channel: MobXChannel }) => { const [showMembers, setMembers] = useState( getState().sectionToggle[MEMBERS_SIDEBAR_KEY] ?? true, ); @@ -61,7 +62,9 @@ function TextChannel({ channel }: { channel: Channels.Channel }) { gated={ (channel.channel_type === "TextChannel" || channel.channel_type === "Group") && - channel.name.includes("nsfw") + channel.name?.includes("nsfw") + ? true + : false }> <ChannelHeader channel={channel} @@ -96,9 +99,9 @@ function TextChannel({ channel }: { channel: Channels.Channel }) { </ChannelMain> </AgeGate> ); -} +}); -function VoiceChannel({ channel }: { channel: Channels.Channel }) { +function VoiceChannel({ channel }: { channel: MobXChannel }) { return ( <> <ChannelHeader channel={channel} /> diff --git a/src/pages/channels/ChannelHeader.tsx b/src/pages/channels/ChannelHeader.tsx index 69e44e1..f14ac51 100644 --- a/src/pages/channels/ChannelHeader.tsx +++ b/src/pages/channels/ChannelHeader.tsx @@ -2,14 +2,13 @@ import { At, Hash, Menu } from "@styled-icons/boxicons-regular"; import { Notepad, Group } from "@styled-icons/boxicons-solid"; import { observable } from "mobx"; import { observer } from "mobx-react-lite"; -import { Channel } from "revolt.js"; import styled from "styled-components"; import { useContext } from "preact/hooks"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; -import { User } from "../../mobx"; +import { Channel, User } from "../../mobx"; import { useData } from "../../mobx/State"; import { useIntermediate } from "../../context/intermediate/Intermediate"; @@ -131,7 +130,7 @@ export default observer(({ channel, toggleSidebar }: ChannelHeaderProps) => { onClick={() => openScreen({ id: "channel_info", - channel_id: channel._id, + channel, }) }> <Markdown diff --git a/src/pages/channels/actions/HeaderActions.tsx b/src/pages/channels/actions/HeaderActions.tsx index 647508a..b7eb775 100644 --- a/src/pages/channels/actions/HeaderActions.tsx +++ b/src/pages/channels/actions/HeaderActions.tsx @@ -41,7 +41,7 @@ export default function HeaderActions({ onClick={() => openScreen({ id: "user_picker", - omit: channel.recipients, + omit: channel.recipients!, callback: async (users) => { for (const user of users) { await client.channels.addMember( diff --git a/src/pages/channels/messaging/ConversationStart.tsx b/src/pages/channels/messaging/ConversationStart.tsx index acaef3c..447f24f 100644 --- a/src/pages/channels/messaging/ConversationStart.tsx +++ b/src/pages/channels/messaging/ConversationStart.tsx @@ -1,8 +1,11 @@ +import { observer } from "mobx-react-lite"; import styled from "styled-components"; import { Text } from "preact-i18n"; -import { useChannel, useForceUpdate } from "../../../context/revoltjs/hooks"; +import { useData } from "../../../mobx/State"; + +import { useClient } from "../../../context/revoltjs/RevoltClient"; import { getChannelName } from "../../../context/revoltjs/util"; const StartBase = styled.div` @@ -24,17 +27,18 @@ interface Props { id: string; } -export default function ConversationStart({ id }: Props) { - const ctx = useForceUpdate(); - const channel = useChannel(id, ctx); +export default observer(({ id }: Props) => { + const store = useData(); + const client = useClient(); + const channel = store.channels.get(id); if (!channel) return null; return ( <StartBase> - <h1>{getChannelName(ctx.client, channel, true)}</h1> + <h1>{getChannelName(client, channel, true)}</h1> <h4> <Text id="app.main.channel.start.group" /> </h4> </StartBase> ); -} +}); diff --git a/src/pages/settings/ChannelSettings.tsx b/src/pages/settings/ChannelSettings.tsx index 4e12183..79015ad 100644 --- a/src/pages/settings/ChannelSettings.tsx +++ b/src/pages/settings/ChannelSettings.tsx @@ -3,7 +3,9 @@ import { Route, useHistory, useParams } from "react-router-dom"; import { Text } from "preact-i18n"; -import { useChannel, useForceUpdate } from "../../context/revoltjs/hooks"; +import { useData } from "../../mobx/State"; + +import { useClient } from "../../context/revoltjs/RevoltClient"; import { getChannelName } from "../../context/revoltjs/util"; import Category from "../../components/ui/Category"; @@ -14,8 +16,10 @@ import Permissions from "./channel/Permissions"; export default function ChannelSettings() { const { channel: cid } = useParams<{ channel: string }>(); - const ctx = useForceUpdate(); - const channel = useChannel(cid, ctx); + + const store = useData(); + const client = useClient(); + const channel = store.channels.get(cid); if (!channel) return null; if ( channel.channel_type === "SavedMessages" || @@ -49,7 +53,7 @@ export default function ChannelSettings() { category: ( <Category variant="uniform" - text={getChannelName(ctx.client, channel, true)} + text={getChannelName(client, channel, true)} /> ), id: "overview", diff --git a/src/pages/settings/channel/Overview.tsx b/src/pages/settings/channel/Overview.tsx index b4a895e..cf1da07 100644 --- a/src/pages/settings/channel/Overview.tsx +++ b/src/pages/settings/channel/Overview.tsx @@ -1,3 +1,4 @@ +import { observer } from "mobx-react-lite"; import { Channels } from "revolt.js/dist/api/objects"; import styled, { css } from "styled-components"; @@ -6,6 +7,8 @@ import { useContext, useEffect, useState } from "preact/hooks"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; +import { Channel } from "../../../mobx"; + import { FileUploader } from "../../../context/revoltjs/FileUploads"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; @@ -13,10 +16,7 @@ import Button from "../../../components/ui/Button"; import InputBox from "../../../components/ui/InputBox"; interface Props { - channel: - | Channels.GroupChannel - | Channels.TextChannel - | Channels.VoiceChannel; + channel: Channel; } const Row = styled.div` @@ -32,13 +32,13 @@ const Row = styled.div` } `; -export default function Overview({ channel }: Props) { +export default observer(({ channel }: Props) => { const client = useContext(AppContext); - const [name, setName] = useState(channel.name); + const [name, setName] = useState(channel.name ?? undefined); const [description, setDescription] = useState(channel.description ?? ""); - useEffect(() => setName(channel.name), [channel.name]); + useEffect(() => setName(channel.name ?? undefined), [channel.name]); useEffect( () => setDescription(channel.description ?? ""), [channel.description], @@ -127,4 +127,4 @@ export default function Overview({ channel }: Props) { </p> </div> ); -} +}); diff --git a/src/pages/settings/channel/Permissions.tsx b/src/pages/settings/channel/Permissions.tsx index 963694a..c7fc235 100644 --- a/src/pages/settings/channel/Permissions.tsx +++ b/src/pages/settings/channel/Permissions.tsx @@ -1,8 +1,11 @@ +import { observer } from "mobx-react-lite"; import { Channels } from "revolt.js/dist/api/objects"; import { ChannelPermission } from "revolt.js/dist/api/permissions"; import { useContext, useEffect, useState } from "preact/hooks"; +import { Channel } from "../../../mobx"; + import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { useServer } from "../../../context/revoltjs/hooks"; @@ -21,21 +24,18 @@ const DEFAULT_PERMISSION_DM = ChannelPermission.UploadFiles; interface Props { - channel: - | Channels.GroupChannel - | Channels.TextChannel - | Channels.VoiceChannel; + channel: Channel; } // ! FIXME: bad code :) -export default function Permissions({ channel }: Props) { +export default observer(({ channel }: Props) => { const [selected, setSelected] = useState("default"); const client = useContext(AppContext); type R = { name: string; permissions: number }; const roles: { [key: string]: R } = {}; if (channel.channel_type !== "Group") { - const server = useServer(channel.server); + const server = useServer(channel.server!); const a = server?.roles ?? {}; for (const b of Object.keys(a)) { roles[b] = { @@ -110,4 +110,4 @@ export default function Permissions({ channel }: Props) { </Button> </div> ); -} +}); diff --git a/src/pages/settings/server/Categories.tsx b/src/pages/settings/server/Categories.tsx index 8f35bcf..6e75222 100644 --- a/src/pages/settings/server/Categories.tsx +++ b/src/pages/settings/server/Categories.tsx @@ -1,5 +1,6 @@ import { XCircle } from "@styled-icons/boxicons-regular"; import isEqual from "lodash.isequal"; +import { observer } from "mobx-react-lite"; import { Channels, Servers, Users } from "revolt.js/dist/api/objects"; import { Route } from "revolt.js/dist/api/routes"; import { ulid } from "ulid"; @@ -8,8 +9,9 @@ import styles from "./Panes.module.scss"; import { Text } from "preact-i18n"; import { useContext, useEffect, useState } from "preact/hooks"; +import { useData } from "../../../mobx/State"; + import { AppContext } from "../../../context/revoltjs/RevoltClient"; -import { useChannels } from "../../../context/revoltjs/hooks"; import ChannelIcon from "../../../components/common/ChannelIcon"; import UserIcon from "../../../components/common/user/UserIcon"; @@ -25,12 +27,12 @@ interface Props { } // ! FIXME: really bad code -export function Categories({ server }: Props) { +export const Categories = observer(({ server }: Props) => { const client = useContext(AppContext); - const channels = useChannels(server.channels) as ( - | Channels.TextChannel - | Channels.VoiceChannel - )[]; + const store = useData(); + const channels = server.channels + .map((id) => store.channels.get(id)!) + .filter((x) => typeof x !== "undefined"); const [cats, setCats] = useState<Servers.Category[]>( server.categories ?? [], @@ -150,4 +152,4 @@ export function Categories({ server }: Props) { })} </div> ); -} +}); diff --git a/src/pages/settings/server/Invites.tsx b/src/pages/settings/server/Invites.tsx index 1708d85..6e07244 100644 --- a/src/pages/settings/server/Invites.tsx +++ b/src/pages/settings/server/Invites.tsx @@ -9,7 +9,6 @@ import { useEffect, useState } from "preact/hooks"; import { useData } from "../../../mobx/State"; import { useClient } from "../../../context/revoltjs/RevoltClient"; -import { useChannels, useForceUpdate } from "../../../context/revoltjs/hooks"; import { getChannelName } from "../../../context/revoltjs/util"; import UserIcon from "../../../components/common/user/UserIcon"; @@ -26,14 +25,15 @@ export const Invites = observer(({ server }: Props) => { InvitesNS.ServerInvite[] | undefined >(undefined); - const ctx = useForceUpdate(); - const channels = useChannels(invites?.map((x) => x.channel) ?? [], ctx); - const store = useData(); + const client = useClient(); const users = invites?.map((invite) => store.users.get(invite.creator)); + const channels = invites?.map((invite) => + store.channels.get(invite.channel), + ); useEffect(() => { - ctx.client.servers + client.servers .fetchInvites(server._id) .then((invites) => setInvites(invites)); }, []); @@ -57,7 +57,7 @@ export const Invites = observer(({ server }: Props) => { {typeof invites === "undefined" && <Preloader type="ring" />} {invites?.map((invite, index) => { const creator = users![index]; - const channel = channels.find((x) => x?._id === invite.channel); + const channel = channels![index]; return ( <div @@ -72,14 +72,14 @@ export const Invites = observer(({ server }: Props) => { </span> <span> {channel && creator - ? getChannelName(ctx.client, channel, true) + ? getChannelName(client, channel, true) : "#??"} </span> <IconButton onClick={async () => { setDelete([...deleting, invite._id]); - await ctx.client.deleteInvite(invite._id); + await client.deleteInvite(invite._id); setInvites( invites?.filter( diff --git a/src/pages/settings/server/Overview.tsx b/src/pages/settings/server/Overview.tsx index e92e952..ba25104 100644 --- a/src/pages/settings/server/Overview.tsx +++ b/src/pages/settings/server/Overview.tsx @@ -1,4 +1,5 @@ import isEqual from "lodash.isequal"; +import { observer } from "mobx-react-lite"; import { Servers, Server } from "revolt.js/dist/api/objects"; import styles from "./Panes.module.scss"; @@ -7,6 +8,8 @@ import { useContext, useEffect, useState } from "preact/hooks"; import TextAreaAutoSize from "../../../lib/TextAreaAutoSize"; +import { useData } from "../../../mobx/State"; + import { FileUploader } from "../../../context/revoltjs/FileUploads"; import { AppContext } from "../../../context/revoltjs/RevoltClient"; import { getChannelName } from "../../../context/revoltjs/util"; @@ -19,8 +22,9 @@ interface Props { server: Servers.Server; } -export function Overview({ server }: Props) { +export const Overview = observer(({ server }: Props) => { const client = useContext(AppContext); + const store = useData(); const [name, setName] = useState(server.name); const [description, setDescription] = useState(server.description ?? ""); @@ -170,15 +174,14 @@ export function Overview({ server }: Props) { <option value="disabled"> <Text id="general.disabled" /> </option> - {server.channels.map((id) => { - const channel = client.channels.get(id); - if (!channel) return null; - return ( - <option value={id}> + {server.channels + .map((id) => store.channels.get(id)!) + .filter((x) => typeof x !== "undefined") + .map((channel) => ( + <option value={channel._id}> {getChannelName(client, channel, true)} </option> - ); - })} + ))} </ComboBox> </p> ))} @@ -190,4 +193,4 @@ export function Overview({ server }: Props) { </p> </div> ); -} +}); -- GitLab