From ece2345197901c168123db2273d8db7b43fa764c Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Mon, 11 Dec 2023 15:28:21 -0500 Subject: [PATCH] rip out context menus and popouts system --- src/components/Avatar.tsx | 14 +- src/components/ChannelHeader.tsx | 56 +----- .../ChannelList/ChannelListItem.tsx | 33 ---- src/components/ContextMenu.tsx | 56 ------ src/components/ContextMenuItem.tsx | 84 --------- src/components/GuildItem.tsx | 39 +--- src/components/GuildSidebar.tsx | 2 - src/components/MemberList/MemberListItem.tsx | 20 +-- src/components/PopoutRenderer.tsx | 145 --------------- src/components/UserPanel.tsx | 13 -- src/components/markdown/Mention.tsx | 12 -- src/components/messaging/Message.tsx | 15 +- .../messaging/MessageAttachment.tsx | 17 +- src/components/messaging/MessageAuthor.tsx | 26 +-- src/components/messaging/MessageInput.tsx | 2 - src/components/modals/JoinServerModal.tsx | 5 +- src/contexts/ContextMenuContext.tsx | 47 ----- src/contexts/PopoutContext.tsx | 48 ----- src/index.tsx | 14 +- src/pages/LoginPage.tsx | 5 +- src/pages/subpages/ChannelPage.tsx | 10 -- src/utils/ContextMenus.ts | 167 ------------------ 22 files changed, 17 insertions(+), 813 deletions(-) delete mode 100644 src/components/ContextMenu.tsx delete mode 100644 src/components/ContextMenuItem.tsx delete mode 100644 src/components/PopoutRenderer.tsx delete mode 100644 src/contexts/ContextMenuContext.tsx delete mode 100644 src/contexts/PopoutContext.tsx delete mode 100644 src/utils/ContextMenus.ts diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index e3be94a..10c7726 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -2,13 +2,11 @@ import { PresenceUpdateStatus } from "@spacebarchat/spacebar-api-types/v9"; import { observer } from "mobx-react-lite"; import React from "react"; import styled from "styled-components"; -import { PopoutContext } from "../contexts/PopoutContext"; import AccountStore from "../stores/AccountStore"; import { useAppStore } from "../stores/AppStore"; import Presence from "../stores/objects/Presence"; import User from "../stores/objects/User"; import Container from "./Container"; -import UserProfilePopout from "./UserProfilePopout"; const Wrapper = styled(Container)<{ size: number; hasClick?: boolean }>` width: ${(props) => props.size}px; @@ -49,7 +47,6 @@ interface Props { function Avatar(props: Props) { const app = useAppStore(); - const popoutContext = React.useContext(PopoutContext); const ref = React.useRef(null); const user = props.user ?? app.account; @@ -59,16 +56,7 @@ function Avatar(props: Props) { e.preventDefault(); e.stopPropagation(); - if (!ref.current) return; - - const rect = ref.current.getBoundingClientRect(); - if (!rect) return; - - popoutContext.open({ - element: , - position: rect, - placement: props.popoutPlacement, - }); + // TODO: }; const clickProp = props.onClick === null ? {} : { onClick: props.onClick ?? openPopout }; diff --git a/src/components/ChannelHeader.tsx b/src/components/ChannelHeader.tsx index c2b6874..dd171de 100644 --- a/src/components/ChannelHeader.tsx +++ b/src/components/ChannelHeader.tsx @@ -1,13 +1,9 @@ -import { StackedModalProps, useModals } from "@mattjennings/react-modal-stack"; import { observer } from "mobx-react-lite"; -import React, { ComponentType } from "react"; +import React from "react"; import styled from "styled-components"; -import { ContextMenuContext } from "../contexts/ContextMenuContext"; import { useAppStore } from "../stores/AppStore"; -import { IContextMenuItem } from "./ContextMenuItem"; import Icon, { IconProps } from "./Icon"; import { SectionHeader } from "./SectionHeader"; -import LeaveServerModal from "./modals/LeaveServerModal"; const Wrapper = styled(SectionHeader)` background-color: var(--background-secondary); @@ -29,62 +25,12 @@ const HeaderText = styled.header` function ChannelHeader() { const app = useAppStore(); - const contextMenu = React.useContext(ContextMenuContext); - const { openModal } = useModals(); - const [contextMenuItems, setContextMenuItems] = React.useState([]); const [icon, setIcon] = React.useState("mdiChevronDown"); - React.useEffect(() => { - if (app.activeGuild && app.activeGuild.ownerId !== app.account?.id) { - setContextMenuItems([ - { - label: "Leave Server", - color: "var(--danger)", - onClick: async () => { - openModal(LeaveServerModal as ComponentType, { - guild: app.activeGuild, - }); - }, - iconProps: { - icon: "mdiLocationExit", - color: "var(--danger)", - }, - hover: { - color: "var(--text)", - backgroundColor: "var(--danger)", - }, - }, - ]); - } else { - setContextMenuItems([]); - } - }, [app.activeGuild]); - function openMenu(e: React.MouseEvent) { e.stopPropagation(); - if (contextMenu.visible) { - // "toggles" the menu - contextMenu.close(); - setIcon("mdiChevronDown"); - return; - } - - const horizontalPadding = 5; - const verticalPadding = 10; - contextMenu.open({ - position: { - x: e.currentTarget.offsetLeft + horizontalPadding, // centers the menu under the header - y: e.currentTarget.offsetHeight + horizontalPadding, // add a slight gap between the header and the menu - }, - items: contextMenuItems, - style: { - width: e.currentTarget.clientWidth - verticalPadding, // adds "margin" to the left and right of the menu - boxSizing: "border-box", - }, - }); - setIcon("mdiClose"); } diff --git a/src/components/ChannelList/ChannelListItem.tsx b/src/components/ChannelList/ChannelListItem.tsx index 80e7f36..5f90a9f 100644 --- a/src/components/ChannelList/ChannelListItem.tsx +++ b/src/components/ChannelList/ChannelListItem.tsx @@ -1,11 +1,7 @@ -import { useModals } from "@mattjennings/react-modal-stack"; import React from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; -import { ContextMenuContext } from "../../contexts/ContextMenuContext"; -import { modalController } from "../../controllers/modals/ModalController"; import Channel from "../../stores/objects/Channel"; -import { IContextMenuItem } from "../ContextMenuItem"; import Icon from "../Icon"; import Tooltip from "../Tooltip"; @@ -46,34 +42,6 @@ interface Props { function ChannelListItem({ channel, isCategory, active }: Props) { const navigate = useNavigate(); - const { openModal } = useModals(); - - const contextMenu = React.useContext(ContextMenuContext); - const [contextMenuItems, setContextMenuItems] = React.useState([ - { - index: 1, - label: "Copy Channel ID", - onClick: () => { - navigator.clipboard.writeText(channel.id); - }, - iconProps: { - icon: "mdiIdentifier", - }, - }, - { - index: 0, - label: "Create Channel Invite", - onClick: () => { - modalController.push({ - type: "create_invite", - target: channel, - }); - }, - iconProps: { - icon: "mdiAccountPlus", - }, - }, - ]); const [hovered, setHovered] = React.useState(false); return ( @@ -86,7 +54,6 @@ function ChannelListItem({ channel, isCategory, active }: Props) { navigate(`/channels/${channel.guildId}/${channel.id}`); }} - onContextMenu={(e) => contextMenu.open2(e, contextMenuItems)} > void; - close: () => void; - visible: boolean; - position: { - x: number; - y: number; - }; - items: IContextMenuItem[]; - style?: React.CSSProperties; -} - -function ContextMenu({ position, close, items, style }: Props) { - // Close the context menu when the user clicks outside of it - React.useEffect(() => { - const listener = () => { - close(); - }; - - document.addEventListener("click", listener); - return () => { - document.removeEventListener("click", listener); - }; - }, []); - - return ( - - {items - .filter((a) => a.visible !== false) - .sort((a, b) => (a.index ?? 0) - (b.index ?? 0)) - .map((item, index) => { - return ; - })} - - ); -} - -export default ContextMenu; diff --git a/src/components/ContextMenuItem.tsx b/src/components/ContextMenuItem.tsx deleted file mode 100644 index 41c6b39..0000000 --- a/src/components/ContextMenuItem.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from "react"; -import styled from "styled-components"; -import Container from "./Container"; -import Icon, { IconProps } from "./Icon"; - -export interface IContextMenuItem { - index?: number; - label: string; - color?: string; - onClick: React.MouseEventHandler; - iconProps?: IconProps; - hover?: { - color?: string; - backgroundColor?: string; - }; - visible?: boolean; -} - -const ContextMenuContainer = styled(Container)` - border-radius: 4px; - min-height: 32px; - cursor: pointer; -`; - -// we handle the hover state ourselves to prevent "lag" with the icon color -const Wrapper = styled(Container)<{ hover?: IContextMenuItem["hover"]; hovered?: boolean }>` - border-radius: 4px; - padding: 6px 8px; - flex: 1 1 auto; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - display: flex; - justify-content: space-between; - align-items: center; - color: ${(props) => (props.hovered ? props.hover?.color ?? "var(--text)" : props.color ?? "var(--text)")}; - background-color: ${(props) => (props.hovered ? props.hover?.backgroundColor ?? "var(--primary)" : "transparent")}; -`; - -interface Props { - item: IContextMenuItem; - index: number; - close: () => void; -} - -function ContextMenuItem({ item, index, close }: Props) { - const [isHovered, setIsHovered] = React.useState(false); - - return ( - { - await item.onClick(e); - close(); - }} - onMouseEnter={() => setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > - -
- {item.label} -
- {item.iconProps && ( - - )} -
-
- ); -} - -export default ContextMenuItem; diff --git a/src/components/GuildItem.tsx b/src/components/GuildItem.tsx index 8849b21..bf02e5b 100644 --- a/src/components/GuildItem.tsx +++ b/src/components/GuildItem.tsx @@ -3,15 +3,12 @@ import { observer } from "mobx-react-lite"; import React from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; -import { ContextMenuContext } from "../contexts/ContextMenuContext"; -import { modalController } from "../controllers/modals/ModalController"; import useLogger from "../hooks/useLogger"; import { useAppStore } from "../stores/AppStore"; import Guild from "../stores/objects/Guild"; import { Permissions } from "../utils/Permissions"; import REST from "../utils/REST"; import Container from "./Container"; -import { IContextMenuItem } from "./ContextMenuItem"; import SidebarPill, { PillType } from "./SidebarPill"; import Tooltip from "./Tooltip"; @@ -55,40 +52,6 @@ function GuildItem({ guild, active }: Props) { const [pillType, setPillType] = React.useState("none"); const [isHovered, setHovered] = React.useState(false); - const contextMenu = React.useContext(ContextMenuContext); - const [contextMenuItems, setContextMenuItems] = React.useState([ - { - index: 1, - label: "Copy Guild ID", - onClick: () => { - navigator.clipboard.writeText(guild.id); - }, - iconProps: { - icon: "mdiIdentifier", - }, - }, - { - index: 0, - label: "Create Invite", - onClick: () => { - // get first channel with view permissions in guild - const channel = guild.channels.find((x) => { - const permission = Permissions.getPermission(app.account!.id, guild, x); - return permission.has("VIEW_CHANNEL") && x.type !== ChannelType.GuildCategory; - }); - if (!channel) return logger.error("No suitable channel found for invite creation"); - - modalController.push({ - type: "create_invite", - target: channel, - }); - }, - iconProps: { - icon: "mdiAccountPlus", - }, - }, - ]); - React.useEffect(() => { if (app.activeChannelId && app.activeGuildId === guild.id) return setPillType("active"); else if (isHovered) return setPillType("hover"); @@ -105,7 +68,7 @@ function GuildItem({ guild, active }: Props) { }; return ( - contextMenu.open2(e, contextMenuItems)}> + ` padding: ${(props) => (props.isCategory ? "16px 8px 0 0" : "1px 8px 0 0")}; @@ -64,28 +58,16 @@ interface Props { function MemberListItem({ item }: Props) { const app = useAppStore(); - const popoutContext = React.useContext(PopoutContext); - - const contextMenu = React.useContext(ContextMenuContext); - const [contextMenuItems, setContextMenuItems] = React.useState([ - ...ContextMenus.User(item.user!), - ...ContextMenus.Member(app.account!, item, item.guild!), - ]); const presence = app.presences.get(item.guild.id)?.get(item.user!.id); return ( contextMenu.open2(e, contextMenuItems)} onClick={(e) => { e.preventDefault(); e.stopPropagation(); - popoutContext.open({ - element: , - position: e.currentTarget.getBoundingClientRect(), - placement: "right", - }); + // TODO: user popout }} > diff --git a/src/components/PopoutRenderer.tsx b/src/components/PopoutRenderer.tsx deleted file mode 100644 index 363a18b..0000000 --- a/src/components/PopoutRenderer.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React from "react"; -import Measure, { BoundingRect, ContentRect } from "react-measure"; -import { PopoutOpenProps } from "../contexts/PopoutContext"; - -const OFFSET = 10; - -function isRectZero(rect: BoundingRect) { - return ( - rect.bottom === 0 && - rect.left === 0 && - rect.right === 0 && - rect.top === 0 && - rect.width === 0 && - rect.height === 0 - ); -} - -interface Props { - open: (props: PopoutOpenProps) => void; - close: () => void; - position: DOMRect; - element: React.ReactNode; - isOpen: boolean; - placement?: "left" | "right" | "top" | "bottom"; -} - -function PopoutRenderer({ position, element, placement, close }: Props) { - const [rect, setRect] = React.useState({}); - const [positionStyle, setPositionStyle] = React.useState({ - visibility: "hidden", - }); - - React.useEffect(() => { - const listener = () => { - close(); - }; - - document.addEventListener("click", listener); - return () => { - document.removeEventListener("click", listener); - }; - }, []); - - React.useEffect(() => { - if (rect.bounds && !isRectZero(rect.bounds)) { - switch (placement) { - default: - case "right": { - let x = position.left + position.width + OFFSET; - let y = position.top; - if (x + rect.bounds.width > window.innerWidth) { - x = position.left - rect.bounds.width - OFFSET; - } - if (y + rect.bounds.height > window.innerHeight) { - y = window.innerHeight - rect.bounds.height - OFFSET; - } - setPositionStyle({ - visibility: "visible", - top: y, - left: x, - }); - break; - } - case "left": { - let x = position.left - rect.bounds.width - OFFSET; - let y = position.top; - if (x < 0) { - x = position.left + position.width + OFFSET; - } - if (y + rect.bounds.height > window.innerHeight) { - y = window.innerHeight - rect.bounds.height - OFFSET; - } - setPositionStyle({ - visibility: "visible", - top: y, - left: x, - }); - break; - } - case "top": { - // center x - let x = position.left - rect.bounds.width / 2 + position.width / 2; - let y = position.top - rect.bounds.height - OFFSET; - if (x + rect.bounds.width > window.innerWidth) { - x = window.innerWidth - rect.bounds.width - OFFSET; - } - if (y < 0) { - y = position.top + position.height + OFFSET; - } - setPositionStyle({ - visibility: "visible", - top: y, - left: x, - }); - break; - } - case "bottom": { - let x = position.left - position.width / 1; - let y = position.top + position.height + OFFSET; - if (x + rect.bounds.width > window.innerWidth) { - x = window.innerWidth - rect.bounds.width - OFFSET; - } - if (y + rect.bounds.height > window.innerHeight) { - y = window.innerHeight - rect.bounds.height - OFFSET; - } - setPositionStyle({ - visibility: "visible", - top: y, - left: x, - }); - break; - } - } - } - }, [rect, element]); - - const handleResize = (contentRect: ContentRect) => setRect(contentRect); - - return ( -
- - {({ measureRef }) => ( -
- {element} -
- )} -
-
- ); -} - -export default PopoutRenderer; diff --git a/src/components/UserPanel.tsx b/src/components/UserPanel.tsx index b10ba2d..cd21092 100644 --- a/src/components/UserPanel.tsx +++ b/src/components/UserPanel.tsx @@ -1,13 +1,11 @@ import React from "react"; import styled from "styled-components"; -import { PopoutContext } from "../contexts/PopoutContext"; import { modalController } from "../controllers/modals/ModalController"; import { useAppStore } from "../stores/AppStore"; import Avatar from "./Avatar"; import Icon from "./Icon"; import IconButton from "./IconButton"; import Tooltip from "./Tooltip"; -import UserProfilePopout from "./UserProfilePopout"; const Section = styled.section` flex: 0 0 auto; @@ -71,7 +69,6 @@ const ActionsWrapper = styled.div` function UserPanel() { const app = useAppStore(); - const popoutContext = React.useContext(PopoutContext); const ref = React.useRef(null); const openSettingsModal = () => { @@ -85,16 +82,6 @@ function UserPanel() { const openPopout = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - - if (!ref.current) return; - const rect = ref.current.getBoundingClientRect(); - if (!rect) return; - - popoutContext.open({ - element: , - position: rect, - placement: "top", - }); }; return ( diff --git a/src/components/markdown/Mention.tsx b/src/components/markdown/Mention.tsx index ec74e9e..ce13213 100644 --- a/src/components/markdown/Mention.tsx +++ b/src/components/markdown/Mention.tsx @@ -1,13 +1,11 @@ import React, { memo } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; -import { PopoutContext } from "../../contexts/PopoutContext"; import { useAppStore } from "../../stores/AppStore"; import Channel from "../../stores/objects/Channel"; import Role from "../../stores/objects/Role"; import User from "../../stores/objects/User"; import { hexToRGB, rgbToHsl } from "../../utils/Utils"; -import UserProfilePopout from "../UserProfilePopout"; const Container = styled.span<{ color?: string; withHover?: boolean }>` padding: 0 2px; @@ -29,22 +27,12 @@ interface MentionProps { } function UserMention({ id }: MentionProps) { const app = useAppStore(); - const popoutContext = React.useContext(PopoutContext); const [user, setUser] = React.useState(null); const ref = React.useRef(null); const click = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - - if (!user || !ref.current) return; - - const rect = ref.current.getBoundingClientRect(); - - popoutContext.open({ - element: , - position: rect, - }); }; React.useEffect(() => { diff --git a/src/components/messaging/Message.tsx b/src/components/messaging/Message.tsx index 7fd2046..152f9b0 100644 --- a/src/components/messaging/Message.tsx +++ b/src/components/messaging/Message.tsx @@ -1,12 +1,9 @@ import { observer } from "mobx-react-lite"; -import React, { memo } from "react"; -import { ContextMenuContext } from "../../contexts/ContextMenuContext"; +import { memo } from "react"; import { useAppStore } from "../../stores/AppStore"; import { MessageLike } from "../../stores/objects/Message"; import { QueuedMessageStatus } from "../../stores/objects/QueuedMessage"; -import ContextMenus from "../../utils/ContextMenus"; import Avatar from "../Avatar"; -import { IContextMenuItem } from "../ContextMenuItem"; import Markdown from "../markdown/MarkdownRenderer"; import MessageAttachment from "./MessageAttachment"; import MessageAuthor from "./MessageAuthor"; @@ -21,10 +18,6 @@ interface Props { function Message({ message, header }: Props) { const app = useAppStore(); - const contextMenu = React.useContext(ContextMenuContext); - const [contextMenuItems, setContextMenuItems] = React.useState([ - ...ContextMenus.Message(app, message, app.account), - ]); const guild = message.guild_id ? app.guilds.get(message.guild_id) : undefined; const isEveryoneMentioned = "mention_everyone" in message && message.mention_everyone; @@ -35,11 +28,7 @@ function Message({ message, header }: Props) { message.mention_roles.some((role) => guild.members.me?.roles.some((role) => role.id === role.id)); return ( - contextMenu.open2(e, contextMenuItems)} - mention={isEveryoneMentioned || isUserMentioned || isRoleMentioned} - > + {header ? ( diff --git a/src/components/messaging/MessageAttachment.tsx b/src/components/messaging/MessageAttachment.tsx index 9d0d587..878f542 100644 --- a/src/components/messaging/MessageAttachment.tsx +++ b/src/components/messaging/MessageAttachment.tsx @@ -1,17 +1,11 @@ -import { useModals } from "@mattjennings/react-modal-stack"; import { APIAttachment } from "@spacebarchat/spacebar-api-types/v9"; -import React from "react"; import styled from "styled-components"; -import { ContextMenuContext } from "../../contexts/ContextMenuContext"; import useLogger from "../../hooks/useLogger"; -import ContextMenus from "../../utils/ContextMenus"; import { calculateImageRatio, calculateScaledDimensions } from "../../utils/Message"; import { getFileDetails, zoomFit } from "../../utils/Utils"; -import { IContextMenuItem } from "../ContextMenuItem"; import Audio from "../media/Audio"; import File from "../media/File"; import Video from "../media/Video"; -import AttachmentPreviewModal from "../modals/AttachmentPreviewModal"; const Attachment = styled.div<{ withPointer?: boolean }>` cursor: ${(props) => (props.withPointer ? "pointer" : "default")}; @@ -25,17 +19,13 @@ const Image = styled.img` interface AttachmentProps { attachment: APIAttachment; - contextMenuItems?: IContextMenuItem[]; maxWidth?: number; maxHeight?: number; } -export default function MessageAttachment({ attachment, contextMenuItems, maxWidth, maxHeight }: AttachmentProps) { +export default function MessageAttachment({ attachment, maxWidth, maxHeight }: AttachmentProps) { const logger = useLogger("MessageAttachment"); - const { openModal } = useModals(); - const contextMenu = React.useContext(ContextMenuContext); - const url = attachment.proxy_url && attachment.proxy_url.length > 0 ? attachment.proxy_url : attachment.url; const details = getFileDetails(attachment); @@ -64,13 +54,10 @@ export default function MessageAttachment({ attachment, contextMenuItems, maxWid - contextMenu.open2(e, [...(contextMenuItems ?? []), ...ContextMenus.MessageAttachment(attachment)]) - } onClick={() => { if (!attachment.content_type?.startsWith("image")) return; const { width, height } = zoomFit(attachment.width!, attachment.height!); - openModal(AttachmentPreviewModal, { attachment, width, height }); + // TODO: preview modal }} > {finalElement} diff --git a/src/components/messaging/MessageAuthor.tsx b/src/components/messaging/MessageAuthor.tsx index 37c52d0..068b558 100644 --- a/src/components/messaging/MessageAuthor.tsx +++ b/src/components/messaging/MessageAuthor.tsx @@ -1,12 +1,8 @@ import { observer } from "mobx-react-lite"; import React from "react"; import styled from "styled-components"; -import { ContextMenuContext } from "../../contexts/ContextMenuContext"; -import { PopoutContext } from "../../contexts/PopoutContext"; import { useAppStore } from "../../stores/AppStore"; import { MessageLike } from "../../stores/objects/Message"; -import ContextMenus from "../../utils/ContextMenus"; -import UserProfilePopout from "../UserProfilePopout"; const Container = styled.div` font-size: 16px; @@ -25,8 +21,6 @@ interface Props { function MessageAuthor({ message }: Props) { const app = useAppStore(); - const contextMenu = React.useContext(ContextMenuContext); - const popoutContext = React.useContext(PopoutContext); const [color, setColor] = React.useState(undefined); const ref = React.useRef(null); @@ -44,16 +38,7 @@ function MessageAuthor({ message }: Props) { e.preventDefault(); e.stopPropagation(); - if (!ref.current) return; - - const rect = ref.current.getBoundingClientRect(); - if (!rect) return; - - popoutContext.open({ - element: , - position: rect, - placement: "right", - }); + // TODO: user popout }; return ( @@ -62,14 +47,7 @@ function MessageAuthor({ message }: Props) { style={{ color, }} - onContextMenu={(e) => { - e.preventDefault(); - e.stopPropagation(); - contextMenu.open2(e, [ - ...ContextMenus.User(message.author), - ...(message.guild_id ? ContextMenus.Member2(app, message.author, message.guild_id) : []), - ]); - }} + onClick={openPopout} > {message.author.username} diff --git a/src/components/messaging/MessageInput.tsx b/src/components/messaging/MessageInput.tsx index f63eadd..2ceb87f 100644 --- a/src/components/messaging/MessageInput.tsx +++ b/src/components/messaging/MessageInput.tsx @@ -1,6 +1,5 @@ import Channel from "../../stores/objects/Channel"; -import { useModals } from "@mattjennings/react-modal-stack"; import { ChannelType, MessageType, RESTPostAPIChannelMessageJSONBody } from "@spacebarchat/spacebar-api-types/v9"; import { observer } from "mobx-react-lite"; import React from "react"; @@ -60,7 +59,6 @@ function MessageInput({ channel }: Props) { const logger = useLogger("MessageInput"); const [content, setContent] = React.useState(""); const [attachments, setAttachments] = React.useState([]); - const { openModal } = useModals(); /** * Debounced stopTyping diff --git a/src/components/modals/JoinServerModal.tsx b/src/components/modals/JoinServerModal.tsx index a6114f4..2c79ae0 100644 --- a/src/components/modals/JoinServerModal.tsx +++ b/src/components/modals/JoinServerModal.tsx @@ -1,4 +1,3 @@ -import { useModals } from "@mattjennings/react-modal-stack"; import { Routes } from "@spacebarchat/spacebar-api-types/v9"; import { useForm } from "react-hook-form"; import { useNavigate } from "react-router-dom"; @@ -27,7 +26,6 @@ type FormValues = { function JoinServerModal({ ...props }: ModalProps<"join_server">) { const logger = useLogger("JoinServerModal"); - const { openModal, closeAllModals } = useModals(); const app = useAppStore(); const navigate = useNavigate(); @@ -46,7 +44,8 @@ function JoinServerModal({ ...props }: ModalProps<"join_server">) { .post(Routes.invite(code)) .then((r) => { navigate(`/channels/${r.guild_id}/${r.channel_id}`); - closeAllModals(); + // modalController.closeAll(); + // TODO: }) .catch((r) => { if ("message" in r) { diff --git a/src/contexts/ContextMenuContext.tsx b/src/contexts/ContextMenuContext.tsx deleted file mode 100644 index aa98ca8..0000000 --- a/src/contexts/ContextMenuContext.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from "react"; -import { IContextMenuItem } from "../components/ContextMenuItem"; - -export interface ContextMenuOpenProps { - position: { x: number; y: number }; - items: { label: string; onClick: React.MouseEventHandler }[]; - style?: React.CSSProperties; -} - -const useValue = () => { - const [visible, setVisible] = React.useState(false); - const [position, setPosition] = React.useState({ x: 0, y: 0 }); - const [items, setItems] = React.useState([]); - const [style, setStyle] = React.useState({}); - - const open = (props: ContextMenuOpenProps) => { - setPosition(props.position); - setItems(props.items); - setStyle(props.style); - setVisible(true); - }; - - const open2 = (e: React.MouseEvent, items: IContextMenuItem[]) => { - e.preventDefault(); - e.stopPropagation(); - setPosition({ x: e.pageX, y: e.pageY }); - setItems(items); - setVisible(true); - }; - - return { - open, - open2, - close: () => setVisible(false), - visible, - position, - items, - style, - }; -}; - -export const ContextMenuContext = React.createContext({} as ReturnType); - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const ContextMenuContextProvider: React.FC = (props) => { - return {props.children}; -}; diff --git a/src/contexts/PopoutContext.tsx b/src/contexts/PopoutContext.tsx deleted file mode 100644 index 631fdbb..0000000 --- a/src/contexts/PopoutContext.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from "react"; - -export interface PopoutOpenProps { - position: DOMRect; - element: React.ReactNode; - placement?: "left" | "right" | "top" | "bottom"; -} - -const useValue = () => { - const [position, setPosition] = React.useState(new DOMRect(0, 0, 0, 0)); - const [element, setElement] = React.useState(); - const [isOpen, setIsOpen] = React.useState(false); - const [placement, setPlacement] = React.useState<"left" | "right" | "top" | "bottom">(); - - const close = () => { - setIsOpen(false); - setElement(undefined); - }; - - const open = (props: PopoutOpenProps) => { - // clicking again on the same trigger should close it - if (isOpen && JSON.stringify(position) === JSON.stringify(props.position)) { - close(); - return; - } - setPosition(props.position); - setElement(props.element); - setIsOpen(true); - setPlacement(props.placement ?? "right"); - }; - - return { - open, - close, - position, - element, - isOpen, - setIsOpen, - placement, - }; -}; - -export const PopoutContext = React.createContext({} as ReturnType); - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const PopoutContextProvider: React.FC = (props) => { - return {props.children}; -}; diff --git a/src/index.tsx b/src/index.tsx index 4cb6af5..7983116 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -19,8 +19,6 @@ import { BrowserRouter } from "react-router-dom"; import { ErrorBoundaryContext } from "react-use-error-boundary"; import App from "./App"; import { BannerContextProvider } from "./contexts/BannerContext"; -import { ContextMenuContextProvider } from "./contexts/ContextMenuContext"; -import { PopoutContextProvider } from "./contexts/PopoutContext"; import Theme from "./contexts/Theme"; import ModalRenderer from "./controllers/modals/ModalRenderer"; import "./index.css"; @@ -32,14 +30,10 @@ dayjs.extend(calendar, calendarStrings); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - - - - - - + + + + , diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 1a74efa..8d84e6e 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,5 +1,4 @@ import HCaptchaLib from "@hcaptcha/react-hcaptcha"; -import { useModals } from "@mattjennings/react-modal-stack"; import { Routes } from "@spacebarchat/spacebar-api-types/v9"; import React from "react"; import { useForm } from "react-hook-form"; @@ -24,7 +23,6 @@ import { } from "../components/AuthComponents"; import { TextDivider } from "../components/Divider"; import HCaptcha, { HeaderContainer } from "../components/HCaptcha"; -import ForgotPasswordModal from "../components/modals/ForgotPasswordModal"; import useLogger from "../hooks/useLogger"; import { AUTH_NO_BRANDING, useAppStore } from "../stores/AppStore"; import { Globals } from "../utils/Globals"; @@ -56,7 +54,6 @@ function LoginPage() { const captchaRef = React.useRef(null); const [debounce, setDebounce] = React.useState(null); const [isCheckingInstance, setCheckingInstance] = React.useState(false); - const { openModal } = useModals(); const { register, @@ -208,7 +205,7 @@ function LoginPage() { }; const forgotPassword = () => { - openModal(ForgotPasswordModal); + // TODO: forgot password modal }; if (captchaSiteKey) { diff --git a/src/pages/subpages/ChannelPage.tsx b/src/pages/subpages/ChannelPage.tsx index 3a38776..7e70b1d 100644 --- a/src/pages/subpages/ChannelPage.tsx +++ b/src/pages/subpages/ChannelPage.tsx @@ -5,14 +5,9 @@ import styled from "styled-components"; import Banner from "../../components/Banner"; import ChannelSidebar from "../../components/ChannelSidebar"; import ContainerComponent from "../../components/Container"; -import ContextMenu from "../../components/ContextMenu"; import ErrorBoundary from "../../components/ErrorBoundary"; import GuildSidebar from "../../components/GuildSidebar"; -import PopoutRenderer from "../../components/PopoutRenderer"; import Chat from "../../components/messaging/Chat"; -import { BannerContext } from "../../contexts/BannerContext"; -import { ContextMenuContext } from "../../contexts/ContextMenuContext"; -import { PopoutContext } from "../../contexts/PopoutContext"; import { useAppStore } from "../../stores/AppStore"; const Container = styled(ContainerComponent)` @@ -29,9 +24,6 @@ const Wrapper = styled.div` function ChannelPage() { const app = useAppStore(); - const contextMenuContext = React.useContext(ContextMenuContext); - const popoutContext = React.useContext(PopoutContext); - const bannerContext = React.useContext(BannerContext); const { guildId, channelId } = useParams<{ guildId: string; channelId: string }>(); @@ -44,8 +36,6 @@ function ChannelPage() { - {contextMenuContext.visible && } - {popoutContext.element && } diff --git a/src/utils/ContextMenus.ts b/src/utils/ContextMenus.ts deleted file mode 100644 index 1e4f5a9..0000000 --- a/src/utils/ContextMenus.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { APIAttachment } from "@spacebarchat/spacebar-api-types/v9"; -import { IContextMenuItem } from "../components/ContextMenuItem"; -import AccountStore from "../stores/AccountStore"; -import AppStore from "../stores/AppStore"; -import Guild from "../stores/objects/Guild"; -import GuildMember from "../stores/objects/GuildMember"; -import { MessageLike } from "../stores/objects/Message"; -import User from "../stores/objects/User"; -import { Permissions } from "./Permissions"; - -export default { - User: (user: User | AccountStore): IContextMenuItem[] => { - return [ - { - label: "Copy User ID", - onClick: () => { - navigator.clipboard.writeText(user.id); - }, - iconProps: { - icon: "mdiIdentifier", - }, - }, - ]; - }, - Message: (app: AppStore, message: MessageLike, account: AccountStore | null): IContextMenuItem[] => { - const channel = app.channels.get(message.channel_id); - const permissions = Permissions.getPermission(account?.id, channel?.guild, channel); - const canDeleteMessage = permissions.has("MANAGE_MESSAGES") || message.author.id === account?.id; - - const items: IContextMenuItem[] = [ - { - label: "Copy Message ID", - onClick: () => { - navigator.clipboard.writeText(message.id); - }, - iconProps: { - icon: "mdiIdentifier", - }, - }, - { - label: "Copy Raw Text", - onClick: () => { - navigator.clipboard.writeText(message.content); - }, - iconProps: { - icon: "mdiRaw", - }, - }, - ]; - - if (canDeleteMessage) { - items.push({ - label: "Delete Message", - onClick: () => { - message.delete(); - }, - iconProps: { - icon: "mdiTrashCanOutline", - color: "red", - }, - color: "red", - hover: { - backgroundColor: "red", - color: "white", - }, - }); - } - - return items; - }, - MessageAttachment: (attachment: APIAttachment): IContextMenuItem[] => { - return [ - { - label: "Copy Attachment URL", - onClick: () => { - navigator.clipboard.writeText(attachment.url); - }, - iconProps: { - icon: "mdiLink", - }, - }, - ]; - }, - // TODO: check if target has higher role - Member: (me: AccountStore, them: GuildMember, guild?: Guild): IContextMenuItem[] => { - const permissions = Permissions.getPermission(me.id, guild); - - const items: IContextMenuItem[] = []; - - // if (permissions.has("KICK_MEMBERS")) { - // items.push({ - // label: `Kick ${them.user!.username}`, - // onClick: () => { - // // openModal(KickModal, { - // // member: them, - // // }); - // }, - // color: "red", - // hover: { - // backgroundColor: "red", - // color: "white", - // }, - // }); - // } - - // if (permissions.has("BAN_MEMBERS")) { - // items.push({ - // label: `Ban ${them.user!.username}`, - // onClick: () => { - // // member.kick() - // console.log("ban member"); - // }, - // color: "red", - // hover: { - // backgroundColor: "red", - // color: "white", - // }, - // }); - // } - - return items; - }, - // TODO: check if target has higher role - Member2: (app: AppStore, them: User, guildId: string): IContextMenuItem[] => { - const me = app.account!; - const guild = app.guilds.get(guildId); - if (!guild) return []; - const member = guild.members.get(them.id); - if (!member) return []; - const permissions = Permissions.getPermission(me.id, guild); - - const items: IContextMenuItem[] = []; - - if (permissions.has("KICK_MEMBERS")) { - items.push({ - label: `Kick ${them.username}`, - onClick: () => { - // openModal(KickModal, { - // member, - // }); - }, - color: "red", - hover: { - backgroundColor: "red", - color: "white", - }, - }); - } - - if (permissions.has("BAN_MEMBERS")) { - items.push({ - label: `Ban ${them.username}`, - onClick: () => { - // member.kick() - console.log("ban member"); - }, - color: "red", - hover: { - backgroundColor: "red", - color: "white", - }, - }); - } - - return items; - }, -};