guild list context menu

(disconnect button)
This commit is contained in:
Michael Peters 2021-12-29 19:46:53 -06:00
parent 22c258bafd
commit 9410fe3829
5 changed files with 61 additions and 17 deletions

View File

@ -25,7 +25,7 @@ const Overlay: FC<OverlayProps> = (props: OverlayProps) => {
}, []);
if (childRootRef) {
ReactHelper.useCloseWhenClickedOutsideEffect(childRootRef, () => { removeSelf(); });
ReactHelper.useCloseWhenClickedOrContextOutsideEffect(childRootRef, () => { removeSelf(); });
}
const keyDownHandler = useCallback((e: KeyboardEvent) => {

View File

@ -17,7 +17,7 @@ const ContextMenu: FC<ContextMenuProps> = (props: ContextMenuProps) => {
const rootRef = useRef<HTMLDivElement>(null);
ReactHelper.useCloseWhenClickedOutsideEffect(rootRef, close);
ReactHelper.useCloseWhenClickedOrContextOutsideEffect(rootRef, close);
return (
<Context rootRef={rootRef} relativeToRef={relativeToRef} relativeToPos={relativeToPos} alignment={alignment}>

View File

@ -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<GuildListElementProps> = (props: GuildListElementProps) => {
const { guild, activeGuild, setSelfActiveGuild } = props;
const { guildsManager, guild, activeGuild, setSelfActiveGuild } = props;
const rootRef = useRef<HTMLDivElement>(null);
@ -42,6 +45,29 @@ const GuildListElement: FC<GuildListElementProps> = (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 (
<ContextMenu
alignment={{ top: 'centerY', left: 'centerX' }}
relativeToPos={contextClickPos} close={close}
>
<div className="guild-context">
<div className="item red leave-guild" onClick={leaveGuildCallable}>Leave Guild</div>
</div>
</ContextMenu>
)
}, [ leaveGuildCallable ]);
const onContextMenu = useCallback((e: MouseEvent<HTMLDivElement>) => {
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<GuildListElementProps> = (props: GuildListElementProp
<div
className={className} ref={rootRef}
onClick={setSelfActiveGuild}
onContextMenu={onContextMenu}
onMouseEnter={mouseEnterCallable} onMouseLeave={mouseLeaveCallable}
>
<div className="pill" />
<img src={iconSrc} alt="guild" />
</div>
{contextHover}
{contextClickMenu}
</div>
);
}

View File

@ -28,7 +28,7 @@ const GuildList: FC<GuildListProps> = (props: GuildListProps) => {
}, [ activeGuild ]);
const guildElements = useMemo(() => {
return guilds.map((guild: CombinedGuild) => <GuildListElement key={guild.id} guild={guild} activeGuild={activeGuild} setSelfActiveGuild={() => { setActiveGuild(guild); } } />);
return guilds.map((guild: CombinedGuild) => <GuildListElement key={guild.id} guildsManager={guildsManager} guild={guild} activeGuild={activeGuild} setSelfActiveGuild={() => { setActiveGuild(guild); } } />);
}, [ guilds ]);
return (

View File

@ -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<HTMLElement>, close: () => void) {
const [ mouseDownTarget, setMouseDownTarget ] = useState<EventTarget | null>(null);
const [ mouseUpTarget, setMouseUpTarget ] = useState<EventTarget | null>(null);
static useCloseWhenClickedOrContextOutsideEffect(ref: RefObject<HTMLElement>, 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(