From 9410fe3829211bfac5047fad83500ee9dbac7fc8 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Wed, 29 Dec 2021 19:46:53 -0600 Subject: [PATCH] guild list context menu (disconnect button) --- .../webapp/elements/components/overlay.tsx | 2 +- .../contexts/components/context-menu.tsx | 2 +- .../lists/components/guild-list-element.tsx | 32 ++++++++++++++- .../webapp/elements/lists/guild-list.tsx | 2 +- .../webapp/elements/require/react-helper.tsx | 40 +++++++++++++------ 5 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/client/webapp/elements/components/overlay.tsx b/src/client/webapp/elements/components/overlay.tsx index bfbc542..3837a60 100644 --- a/src/client/webapp/elements/components/overlay.tsx +++ b/src/client/webapp/elements/components/overlay.tsx @@ -25,7 +25,7 @@ const Overlay: FC = (props: OverlayProps) => { }, []); if (childRootRef) { - ReactHelper.useCloseWhenClickedOutsideEffect(childRootRef, () => { removeSelf(); }); + ReactHelper.useCloseWhenClickedOrContextOutsideEffect(childRootRef, () => { removeSelf(); }); } const keyDownHandler = useCallback((e: KeyboardEvent) => { diff --git a/src/client/webapp/elements/contexts/components/context-menu.tsx b/src/client/webapp/elements/contexts/components/context-menu.tsx index 3cfc238..91fc403 100644 --- a/src/client/webapp/elements/contexts/components/context-menu.tsx +++ b/src/client/webapp/elements/contexts/components/context-menu.tsx @@ -17,7 +17,7 @@ const ContextMenu: FC = (props: ContextMenuProps) => { const rootRef = useRef(null); - ReactHelper.useCloseWhenClickedOutsideEffect(rootRef, close); + ReactHelper.useCloseWhenClickedOrContextOutsideEffect(rootRef, close); return ( diff --git a/src/client/webapp/elements/lists/components/guild-list-element.tsx b/src/client/webapp/elements/lists/components/guild-list-element.tsx index 1f724db..79cb5ed 100644 --- a/src/client/webapp/elements/lists/components/guild-list-element.tsx +++ b/src/client/webapp/elements/lists/components/guild-list-element.tsx @@ -1,18 +1,21 @@ -import React, { FC, useMemo, useRef } from 'react'; +import React, { FC, MouseEvent, useCallback, useMemo, useRef, useState } from 'react'; import CombinedGuild from '../../../guild-combined'; +import GuildsManager from '../../../guilds-manager'; +import ContextMenu from '../../contexts/components/context-menu'; import BasicHover, { BasicHoverSide } from '../../contexts/context-hover-basic'; import BaseElements from '../../require/base-elements'; import GuildSubscriptions from '../../require/guild-subscriptions'; import ReactHelper from '../../require/react-helper'; export interface GuildListElementProps { + guildsManager: GuildsManager; guild: CombinedGuild; activeGuild: CombinedGuild | null; setSelfActiveGuild: () => void; } const GuildListElement: FC = (props: GuildListElementProps) => { - const { guild, activeGuild, setSelfActiveGuild } = props; + const { guildsManager, guild, activeGuild, setSelfActiveGuild } = props; const rootRef = useRef(null); @@ -42,6 +45,29 @@ const GuildListElement: FC = (props: GuildListElementProp ) }, [ guildMeta, selfMember ]); + const leaveGuildCallable = useCallback(async () => { + guild.disconnect(); + await guildsManager.removeGuild(guild); + }, [ guildsManager, guild ]) + + const [ contextClickPos, setContextClickPos ] = useState<{ x: number, y: number }>({ x: 0, y: 0 }); + const [ contextClickMenu, toggleMenu, closeMenu, openMenu ] = ReactHelper.useContextMenu((close: () => void) => { + return ( + +
+
Leave Guild
+
+
+ ) + }, [ leaveGuildCallable ]); + const onContextMenu = useCallback((e: MouseEvent) => { + setContextClickPos({ x: e.clientX, y: e.clientY }); + openMenu(); + }, [ openMenu ]); + const className = useMemo(() => { console.log('active: ' + activeGuild?.id + '/ me: ' + guild.id); return activeGuild && guild.id === activeGuild.id ? 'guild active' : 'guild'; @@ -52,12 +78,14 @@ const GuildListElement: FC = (props: GuildListElementProp
guild
{contextHover} + {contextClickMenu}
); } diff --git a/src/client/webapp/elements/lists/guild-list.tsx b/src/client/webapp/elements/lists/guild-list.tsx index 5286c7a..e62a465 100644 --- a/src/client/webapp/elements/lists/guild-list.tsx +++ b/src/client/webapp/elements/lists/guild-list.tsx @@ -28,7 +28,7 @@ const GuildList: FC = (props: GuildListProps) => { }, [ activeGuild ]); const guildElements = useMemo(() => { - return guilds.map((guild: CombinedGuild) => { setActiveGuild(guild); } } />); + return guilds.map((guild: CombinedGuild) => { setActiveGuild(guild); } } />); }, [ guilds ]); return ( diff --git a/src/client/webapp/elements/require/react-helper.tsx b/src/client/webapp/elements/require/react-helper.tsx index b436a55..8d29cbd 100644 --- a/src/client/webapp/elements/require/react-helper.tsx +++ b/src/client/webapp/elements/require/react-helper.tsx @@ -342,40 +342,49 @@ export default class ReactHelper { } // Makes sure to also allow you to fly-out a click starting inside of the ref'd element but was dragged outside - static useCloseWhenClickedOutsideEffect(ref: RefObject, close: () => void) { - const [ mouseDownTarget, setMouseDownTarget ] = useState(null); - const [ mouseUpTarget, setMouseUpTarget ] = useState(null); + static useCloseWhenClickedOrContextOutsideEffect(ref: RefObject, close: () => void) { + // Have to use a ref here and not states since we can't re-assign state between mouseup and click + const mouseRef = useRef<{ mouseDownTarget: Node | null, mouseUpTarget: Node | null}>({ mouseDownTarget: null, mouseUpTarget: null }); const handleClickOutside = useCallback((event: MouseEvent) => { - //console.log('current:', ref.current, 'target:', event.target, 'mouseDownTarget:', mouseDownTarget, 'mouseUpTarget:', mouseUpTarget); + //console.log('current:', ref.current, 'target:', event.target, 'mouseDownTarget:', mouseRef.current.mouseDownTarget, 'mouseUpTarget:', mouseRef.current.mouseUpTarget); if (!ref.current) return; // Casting here is OK. https://stackoverflow.com/q/61164018 if (ref.current.contains(event.target as Node)) return; - if (mouseDownTarget !== null || mouseUpTarget !== null) return; + if (mouseRef.current.mouseDownTarget !== null || mouseRef.current.mouseUpTarget !== null) return; close(); - }, [ ref, mouseDownTarget, mouseUpTarget, close ]); + }, [ ref, mouseRef, close ]); const handleMouseDown = useCallback((event: MouseEvent) => { if (!ref.current) return; + //console.log('mouse down. Contains: ' + ref.current.contains(event.target as Node)); if (ref.current.contains(event.target as Node)) { - setMouseDownTarget(event.target); + mouseRef.current.mouseDownTarget = event.target as Node; } else { - setMouseDownTarget(null); + mouseRef.current.mouseDownTarget = null; } - }, [ ref ]); + }, [ ref, mouseRef ]); const handleMouseUp = useCallback((event: MouseEvent) => { if (!ref.current) return; + //console.log('mouse up. Contains: ' + ref.current.contains(event.target as Node)); if (ref.current.contains(event.target as Node)) { - setMouseUpTarget(event.target); + mouseRef.current.mouseUpTarget = event.target as Node; } else { - setMouseUpTarget(null); + mouseRef.current.mouseUpTarget = null; } - }, [ ref ]); + }, [ ref, mouseRef ]); + + const handleContextMenu = useCallback((event: MouseEvent) => { + if (!ref.current) return; + if (ref.current.contains(event.target as Node)) return; + // Context menu is fired on mouse-down so no need to do special checks. + close(); + }, [ ref ]) useEffect(() => { document.addEventListener('click', handleClickOutside); @@ -397,6 +406,13 @@ export default class ReactHelper { document.addEventListener('mouseup', handleMouseUp); } }, [ handleMouseUp ]); + + useEffect(() => { + document.addEventListener('contextmenu', handleContextMenu); + return () => { + document.removeEventListener('contextmenu', handleContextMenu); + } + }, [ handleClickOutside ]); } static useAlignment(