From f8611ddea56a0e8ae19c1a8cfd084e49fe576c0b Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 29 Jul 2021 15:51:19 +0100 Subject: [PATCH] Finish migrating user state over to MobX. --- src/components/common/AutoComplete.tsx | 15 +- src/components/common/messaging/Message.tsx | 203 ++++++++-------- .../common/messaging/SystemMessage.tsx | 226 +++++++++--------- .../messaging/attachments/MessageReply.tsx | 10 +- .../common/messaging/bars/ReplyBar.tsx | 14 +- .../common/messaging/bars/TypingIndicator.tsx | 20 +- src/components/common/user/UserCheckbox.tsx | 5 +- src/components/common/user/UserHeader.tsx | 8 +- src/components/common/user/UserShort.tsx | 4 +- .../navigation/BottomNavigation.tsx | 13 +- .../intermediate/popovers/UserPicker.tsx | 23 +- 11 files changed, 286 insertions(+), 255 deletions(-) diff --git a/src/components/common/AutoComplete.tsx b/src/components/common/AutoComplete.tsx index 966894f7..faca2ffb 100644 --- a/src/components/common/AutoComplete.tsx +++ b/src/components/common/AutoComplete.tsx @@ -1,9 +1,13 @@ -import { SYSTEM_USER_ID, User } from "revolt.js"; +import { useStore } from "react-redux"; +import { SYSTEM_USER_ID } from "revolt.js"; import { Channels } from "revolt.js/dist/api/objects"; import styled, { css } from "styled-components"; import { StateUpdater, useState } from "preact/hooks"; +import { User } from "../../mobx"; +import { useData } from "../../mobx/State"; + import { useClient } from "../../context/revoltjs/RevoltClient"; import { emojiDictionary } from "../../assets/emojis"; @@ -53,6 +57,7 @@ export function useAutoComplete( const [state, setState] = useState({ type: "none" }); const [focused, setFocused] = useState(false); const client = useClient(); + const store = useData(); function findSearchString( el: HTMLTextAreaElement, @@ -127,7 +132,7 @@ export function useAutoComplete( let users: User[] = []; switch (searchClues.users.type) { case "all": - users = client.users.toArray(); + users = [...store.users.values()]; break; case "channel": { const channel = client.channels.get( @@ -136,8 +141,8 @@ export function useAutoComplete( switch (channel?.channel_type) { case "Group": case "DirectMessage": - users = client.users - .mapKeys(channel.recipients) + users = channel.recipients + .map((x) => store.users.get(x)) .filter( (x) => typeof x !== "undefined", ) as User[]; @@ -150,7 +155,7 @@ export function useAutoComplete( (x) => x._id.substr(0, 26) === server, ) .map((x) => - client.users.get(x._id.substr(26)), + store.users.get(x._id.substr(26)), ) .filter( (x) => typeof x !== "undefined", diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx index c55d251e..a7a92d36 100644 --- a/src/components/common/messaging/Message.tsx +++ b/src/components/common/messaging/Message.tsx @@ -1,7 +1,10 @@ +import { observer } from "mobx-react-lite"; + import { attachContextMenu } from "preact-context-menu"; import { memo } from "preact/compat"; import { useContext, useState } from "preact/hooks"; +import { useData } from "../../../mobx/State"; import { QueuedMessage } from "../../../redux/reducers/queue"; import { useIntermediate } from "../../../context/intermediate/Intermediate"; @@ -34,109 +37,117 @@ interface Props { head?: boolean; } -function Message({ - highlight, - attachContext, - message, - contrast, - content: replacement, - head: preferHead, - queued, -}: Props) { - // TODO: Can improve re-renders here by providing a list - // TODO: of dependencies. We only need to update on u/avatar. - const user = useUser(message.author); - const client = useContext(AppContext); - const { openScreen } = useIntermediate(); +const Message = observer( + ({ + highlight, + attachContext, + message, + contrast, + content: replacement, + head: preferHead, + queued, + }: Props) => { + const store = useData(); + const user = store.users.get(message.author); - const content = message.content as string; - const head = preferHead || (message.replies && message.replies.length > 0); + const client = useContext(AppContext); + const { openScreen } = useIntermediate(); - // ! FIXME: tell fatal to make this type generic - // bree: Fatal please... - const userContext = attachContext - ? (attachContextMenu("Menu", { - user: message.author, - contextualChannel: message.channel, - }) as any) - : undefined; + const content = message.content as string; + const head = + preferHead || (message.replies && message.replies.length > 0); - const openProfile = () => - openScreen({ id: "profile", user_id: message.author }); + // ! FIXME: tell fatal to make this type generic + // bree: Fatal please... + const userContext = attachContext + ? (attachContextMenu("Menu", { + user: message.author, + contextualChannel: message.channel, + }) as any) + : undefined; - // ! FIXME: animate on hover - const [animate, setAnimate] = useState(false); + const openProfile = () => + openScreen({ id: "profile", user_id: message.author }); - return ( -
- {message.replies?.map((message_id, index) => ( - - ))} - 0)} - contrast={contrast} - sending={typeof queued !== "undefined"} - mention={message.mentions?.includes(client.user!._id)} - failed={typeof queued?.error !== "undefined"} - onContextMenu={ - attachContext - ? attachContextMenu("Menu", { - message, - contextualChannel: message.channel, - queued, - }) - : undefined - } - onMouseEnter={() => setAnimate(true)} - onMouseLeave={() => setAnimate(false)}> - - {head ? ( - - ) : ( - - )} - - - {head && ( - - + {message.replies?.map((message_id, index) => ( + + ))} + 0) + } + contrast={contrast} + sending={typeof queued !== "undefined"} + mention={message.mentions?.includes(client.user!._id)} + failed={typeof queued?.error !== "undefined"} + onContextMenu={ + attachContext + ? attachContextMenu("Menu", { + message, + contextualChannel: message.channel, + queued, + }) + : undefined + } + onMouseEnter={() => setAnimate(true)} + onMouseLeave={() => setAnimate(false)}> + + {head ? ( + - - - )} - {replacement ?? } - {queued?.error && ( - - )} - {message.attachments?.map((attachment, index) => ( - 0 || content.length > 0} - /> - ))} - {message.embeds?.map((embed, index) => ( - - ))} - - -
- ); -} + ) : ( + + )} + + + {head && ( + + + + + )} + {replacement ?? } + {queued?.error && ( + + )} + {message.attachments?.map((attachment, index) => ( + 0 || content.length > 0} + /> + ))} + {message.embeds?.map((embed, index) => ( + + ))} + + + + ); + }, +); export default memo(Message); diff --git a/src/components/common/messaging/SystemMessage.tsx b/src/components/common/messaging/SystemMessage.tsx index a19c0fed..59051baf 100644 --- a/src/components/common/messaging/SystemMessage.tsx +++ b/src/components/common/messaging/SystemMessage.tsx @@ -1,10 +1,13 @@ -import { User } from "revolt.js"; +import { observer } from "mobx-react-lite"; import styled from "styled-components"; import { attachContextMenu } from "preact-context-menu"; import { TextReact } from "../../../lib/i18n"; +import { User } from "../../../mobx"; +import { useData } from "../../../mobx/State"; + import { useForceUpdate, useUser } from "../../../context/revoltjs/hooks"; import { MessageObject } from "../../../context/revoltjs/util"; @@ -39,132 +42,131 @@ interface Props { hideInfo?: boolean; } -export function SystemMessage({ - attachContext, - message, - highlight, - hideInfo, -}: Props) { - const ctx = useForceUpdate(); +export const SystemMessage = observer( + ({ attachContext, message, highlight, hideInfo }: Props) => { + const store = useData(); - let data: SystemMessageParsed; - const content = message.content; - if (typeof content === "object") { - switch (content.type) { + let data: SystemMessageParsed; + const content = message.content; + if (typeof content === "object") { + switch (content.type) { + case "text": + data = content; + break; + case "user_added": + case "user_remove": + data = { + type: content.type, + user: store.users.get(content.id)!, + by: store.users.get(content.by)!, + }; + break; + case "user_joined": + case "user_left": + case "user_kicked": + case "user_banned": + data = { + type: content.type, + user: store.users.get(content.id)!, + }; + break; + case "channel_renamed": + data = { + type: "channel_renamed", + name: content.name, + by: store.users.get(content.by)!, + }; + break; + case "channel_description_changed": + case "channel_icon_changed": + data = { + type: content.type, + by: store.users.get(content.by)!, + }; + break; + default: + data = { type: "text", content: JSON.stringify(content) }; + } + } else { + data = { type: "text", content }; + } + + let children; + switch (data.type) { case "text": - data = content; + children = {data.content}; break; case "user_added": case "user_remove": - data = { - type: content.type, - user: useUser(content.id, ctx) as User, - by: useUser(content.by, ctx) as User, - }; + children = ( + , + other_user: , + }} + /> + ); break; case "user_joined": case "user_left": case "user_kicked": case "user_banned": - data = { - type: content.type, - user: useUser(content.id, ctx) as User, - }; + children = ( + , + }} + /> + ); break; case "channel_renamed": - data = { - type: "channel_renamed", - name: content.name, - by: useUser(content.by, ctx) as User, - }; + children = ( + , + name: {data.name}, + }} + /> + ); break; case "channel_description_changed": case "channel_icon_changed": - data = { - type: content.type, - by: useUser(content.by, ctx) as User, - }; + children = ( + , + }} + /> + ); break; - default: - data = { type: "text", content: JSON.stringify(content) }; } - } else { - data = { type: "text", content }; - } - let children; - switch (data.type) { - case "text": - children = {data.content}; - break; - case "user_added": - case "user_remove": - children = ( - , - other_user: , - }} - /> - ); - break; - case "user_joined": - case "user_left": - case "user_kicked": - case "user_banned": - children = ( - , - }} - /> - ); - break; - case "channel_renamed": - children = ( - , - name: {data.name}, - }} - /> - ); - break; - case "channel_description_changed": - case "channel_icon_changed": - children = ( - , - }} - /> - ); - break; - } - - return ( - - {!hideInfo && ( - - - - )} - {children} - - ); -} + return ( + + {!hideInfo && ( + + + + )} + {children} + + ); + }, +); diff --git a/src/components/common/messaging/attachments/MessageReply.tsx b/src/components/common/messaging/attachments/MessageReply.tsx index 7cb34577..b849e3db 100644 --- a/src/components/common/messaging/attachments/MessageReply.tsx +++ b/src/components/common/messaging/attachments/MessageReply.tsx @@ -1,5 +1,6 @@ import { Reply } from "@styled-icons/boxicons-regular"; import { File } from "@styled-icons/boxicons-solid"; +import { observer } from "mobx-react-lite"; import { useHistory } from "react-router-dom"; import { SYSTEM_USER_ID } from "revolt.js"; import { Users } from "revolt.js/dist/api/objects"; @@ -10,6 +11,8 @@ import { useLayoutEffect, useState } from "preact/hooks"; import { useRenderState } from "../../../../lib/renderer/Singleton"; +import { useData } from "../../../../mobx/State"; + import { useForceUpdate, useUser } from "../../../../context/revoltjs/hooks"; import { mapMessage, MessageObject } from "../../../../context/revoltjs/util"; @@ -120,7 +123,7 @@ export const ReplyBase = styled.div<{ `} `; -export function MessageReply({ index, channel, id }: Props) { +export const MessageReply = observer(({ index, channel, id }: Props) => { const ctx = useForceUpdate(); const view = useRenderState(channel); if (view?.type !== "RENDER") return null; @@ -152,7 +155,8 @@ export function MessageReply({ index, channel, id }: Props) { ); } - const user = useUser(message.author, ctx); + const store = useData(); + const user = store.users.get(message.author); const history = useHistory(); return ( @@ -203,4 +207,4 @@ export function MessageReply({ index, channel, id }: Props) { )} ); -} +}); diff --git a/src/components/common/messaging/bars/ReplyBar.tsx b/src/components/common/messaging/bars/ReplyBar.tsx index 6d7ed916..5de69066 100644 --- a/src/components/common/messaging/bars/ReplyBar.tsx +++ b/src/components/common/messaging/bars/ReplyBar.tsx @@ -4,6 +4,7 @@ import { File, XCircle, } from "@styled-icons/boxicons-regular"; +import { observer } from "mobx-react-lite"; import { SYSTEM_USER_ID } from "revolt.js"; import styled from "styled-components"; @@ -13,6 +14,7 @@ import { StateUpdater, useEffect } from "preact/hooks"; import { internalSubscribe } from "../../../../lib/eventEmitter"; import { useRenderState } from "../../../../lib/renderer/Singleton"; +import { useData } from "../../../../mobx/State"; import { Reply } from "../../../../redux/reducers/queue"; import { useUsers } from "../../../../context/revoltjs/hooks"; @@ -56,7 +58,7 @@ const Base = styled.div` // ! FIXME: Move to global config const MAX_REPLIES = 5; -export default function ReplyBar({ channel, replies, setReplies }: Props) { +export default observer(({ channel, replies, setReplies }: Props) => { useEffect(() => { return internalSubscribe( "ReplyBar", @@ -73,7 +75,9 @@ export default function ReplyBar({ channel, replies, setReplies }: Props) { const ids = replies.map((x) => x.id); const messages = view.messages.filter((x) => ids.includes(x._id)); - const users = useUsers(messages.map((x) => x.author)); + + const store = useData(); + const users = messages.map((x) => store.users.get(x.author)); return (
@@ -90,9 +94,7 @@ export default function ReplyBar({ channel, replies, setReplies }: Props) { ); - const user = users.find((x) => message!.author === x?._id); - if (!user) return; - + const user = users[index]; return ( @@ -143,4 +145,4 @@ export default function ReplyBar({ channel, replies, setReplies }: Props) { })}
); -} +}); diff --git a/src/components/common/messaging/bars/TypingIndicator.tsx b/src/components/common/messaging/bars/TypingIndicator.tsx index 6cbee0e4..8c2fe0be 100644 --- a/src/components/common/messaging/bars/TypingIndicator.tsx +++ b/src/components/common/messaging/bars/TypingIndicator.tsx @@ -1,3 +1,4 @@ +import { observer } from "mobx-react-lite"; import { User } from "revolt.js"; import styled from "styled-components"; @@ -6,10 +7,14 @@ import { useContext } from "preact/hooks"; import { TextReact } from "../../../../lib/i18n"; +import { useData } from "../../../../mobx/State"; import { connectState } from "../../../../redux/connector"; import { TypingUser } from "../../../../redux/reducers/typing"; -import { AppContext } from "../../../../context/revoltjs/RevoltClient"; +import { + AppContext, + useClient, +} from "../../../../context/revoltjs/RevoltClient"; import { useUsers } from "../../../../context/revoltjs/hooks"; import { Username } from "../../user/UserShort"; @@ -61,12 +66,13 @@ const Base = styled.div` } `; -export function TypingIndicator({ typing }: Props) { +export const TypingIndicator = observer(({ typing }: Props) => { if (typing && typing.length > 0) { - const client = useContext(AppContext); - const users = useUsers(typing.map((x) => x.id)).filter( - (x) => typeof x !== "undefined", - ) as User[]; + const client = useClient(); + const store = useData(); + const users = typing + .map((x) => store.users.get(x.id)!) + .filter((x) => typeof x !== "undefined"); users.sort((a, b) => a._id.toUpperCase().localeCompare(b._id.toUpperCase()), @@ -123,7 +129,7 @@ export function TypingIndicator({ typing }: Props) { } return null; -} +}); export default connectState<{ id: string }>(TypingIndicator, (state, props) => { return { diff --git a/src/components/common/user/UserCheckbox.tsx b/src/components/common/user/UserCheckbox.tsx index c125afd3..6f882307 100644 --- a/src/components/common/user/UserCheckbox.tsx +++ b/src/components/common/user/UserCheckbox.tsx @@ -1,8 +1,9 @@ -import { User } from "revolt.js"; +import { User } from "../../../mobx"; import Checkbox, { CheckboxProps } from "../../ui/Checkbox"; import UserIcon from "./UserIcon"; +import { Username } from "./UserShort"; type UserProps = Omit & { user: User }; @@ -10,7 +11,7 @@ export default function UserCheckbox({ user, ...props }: UserProps) { return ( - {user.username} + ); } diff --git a/src/components/common/user/UserHeader.tsx b/src/components/common/user/UserHeader.tsx index cd63f6cc..d45bfbd1 100644 --- a/src/components/common/user/UserHeader.tsx +++ b/src/components/common/user/UserHeader.tsx @@ -1,6 +1,6 @@ import { Cog } from "@styled-icons/boxicons-solid"; +import { observer } from "mobx-react-lite"; import { Link } from "react-router-dom"; -import { User } from "revolt.js"; import styled from "styled-components"; import { openContextMenu } from "preact-context-menu"; @@ -9,6 +9,8 @@ import { Localizer } from "preact-i18n"; import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice"; +import { User } from "../../../mobx"; + import { useIntermediate } from "../../../context/intermediate/Intermediate"; import Header from "../../ui/Header"; @@ -49,7 +51,7 @@ interface Props { user: User; } -export default function UserHeader({ user }: Props) { +export default observer(({ user }: Props) => { const { writeClipboard } = useIntermediate(); return ( @@ -81,4 +83,4 @@ export default function UserHeader({ user }: Props) { )} ); -} +}); diff --git a/src/components/common/user/UserShort.tsx b/src/components/common/user/UserShort.tsx index 2ca54e93..3eab5a10 100644 --- a/src/components/common/user/UserShort.tsx +++ b/src/components/common/user/UserShort.tsx @@ -21,7 +21,7 @@ export const Username = observer( let username = user?.username; let color; - // ! FIXME: this must be really bad for perf. + /* // ! FIXME: this must be really bad for perf. if (user) { let { server } = useParams<{ server?: string }>(); if (server) { @@ -44,7 +44,7 @@ export const Username = observer( } } } - } + } */ return ( diff --git a/src/components/navigation/BottomNavigation.tsx b/src/components/navigation/BottomNavigation.tsx index 755c09a8..71a1a519 100644 --- a/src/components/navigation/BottomNavigation.tsx +++ b/src/components/navigation/BottomNavigation.tsx @@ -1,14 +1,16 @@ import { Search } from "@styled-icons/boxicons-regular"; import { Message, Group, Inbox } from "@styled-icons/boxicons-solid"; +import { observer } from "mobx-react-lite"; import { useHistory, useLocation } from "react-router"; import styled, { css } from "styled-components"; import ConditionalLink from "../../lib/ConditionalLink"; +import { useData } from "../../mobx/State"; import { connectState } from "../../redux/connector"; import { LastOpened } from "../../redux/reducers/last_opened"; -import { useSelf } from "../../context/revoltjs/hooks"; +import { useClient } from "../../context/revoltjs/RevoltClient"; import UserIcon from "../common/user/UserIcon"; import IconButton from "../ui/IconButton"; @@ -51,8 +53,11 @@ interface Props { lastOpened: LastOpened; } -export function BottomNavigation({ lastOpened }: Props) { - const user = useSelf(); +export const BottomNavigation = observer(({ lastOpened }: Props) => { + const client = useClient(); + const store = useData(); + const user = store.users.get(client.user!._id); + const history = useHistory(); const path = useLocation().pathname; @@ -114,7 +119,7 @@ export function BottomNavigation({ lastOpened }: Props) { ); -} +}); export default connectState(BottomNavigation, (state) => { return { diff --git a/src/context/intermediate/popovers/UserPicker.tsx b/src/context/intermediate/popovers/UserPicker.tsx index 748bdc0f..3eaeb1d5 100644 --- a/src/context/intermediate/popovers/UserPicker.tsx +++ b/src/context/intermediate/popovers/UserPicker.tsx @@ -1,14 +1,14 @@ -import { User, Users } from "revolt.js/dist/api/objects"; +import { Users } from "revolt.js/dist/api/objects"; import styles from "./UserPicker.module.scss"; import { Text } from "preact-i18n"; import { useState } from "preact/hooks"; +import { useData } from "../../../mobx/State"; + import UserCheckbox from "../../../components/common/user/UserCheckbox"; import Modal from "../../../components/ui/Modal"; -import { useUsers } from "../../revoltjs/hooks"; - interface Props { omit?: string[]; onClose: () => void; @@ -19,7 +19,7 @@ export function UserPicker(props: Props) { const [selected, setSelected] = useState([]); const omit = [...(props.omit || []), "00000000000000000000000000"]; - const users = useUsers(); + const store = useData(); return (
- {( - users.filter( + {[...store.users.values()] + .filter( (x) => x && x.relationship === Users.Relationship.Friend && !omit.includes(x._id), - ) as User[] - ) - .map((x) => { - return { - ...x, - selected: selected.includes(x._id), - }; - }) + ) .map((x) => ( { if (v) { setSelected([...selected, x._id]);