From e0d0b2a9df1e923bef8e005b3603c3d226742a03 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Sun, 13 Feb 2022 10:42:03 -0600 Subject: [PATCH] make useAction...ClickedOutside use an array of elements to click outside --- .../webapp/elements/components/overlay.tsx | 2 +- .../contexts/components/context-menu.tsx | 10 ++- .../elements/overlays/overlay-image.tsx | 32 ++++--- .../webapp/elements/require/react-helper.tsx | 83 +++++++++++-------- 4 files changed, 75 insertions(+), 52 deletions(-) diff --git a/src/client/webapp/elements/components/overlay.tsx b/src/client/webapp/elements/components/overlay.tsx index 9023fb5..c6a18eb 100644 --- a/src/client/webapp/elements/components/overlay.tsx +++ b/src/client/webapp/elements/components/overlay.tsx @@ -20,7 +20,7 @@ const Overlay: FC = (props: OverlayProps) => { if (childRootRef) { // this is alright to do since childRootRef (the ref itself) should never change for each component using this element // eslint-disable-next-line react-hooks/rules-of-hooks - useActionWhenEscapeOrClickedOrContextOutsideEffect(childRootRef, () => setOverlay(null)); + useActionWhenEscapeOrClickedOrContextOutsideEffect([childRootRef], () => setOverlay(null)); } const keyDownHandler = useCallback( diff --git a/src/client/webapp/elements/contexts/components/context-menu.tsx b/src/client/webapp/elements/contexts/components/context-menu.tsx index 3aaf102..737cf59 100644 --- a/src/client/webapp/elements/contexts/components/context-menu.tsx +++ b/src/client/webapp/elements/contexts/components/context-menu.tsx @@ -18,10 +18,16 @@ const ContextMenu: FC = (props: ContextMenuProps) => { const rootRef = useRef(null); - useActionWhenEscapeOrClickedOrContextOutsideEffect(rootRef, close); + useActionWhenEscapeOrClickedOrContextOutsideEffect([rootRef], close); return ( - +
{children}
); diff --git a/src/client/webapp/elements/overlays/overlay-image.tsx b/src/client/webapp/elements/overlays/overlay-image.tsx index c1be13d..821aaa3 100644 --- a/src/client/webapp/elements/overlays/overlay-image.tsx +++ b/src/client/webapp/elements/overlays/overlay-image.tsx @@ -7,11 +7,19 @@ import React, { FC, useCallback, useMemo, useRef } from 'react'; import CombinedGuild from '../../guild-combined'; import ElementsUtil, { IAlignment } from '../require/elements-util'; import DownloadButton from '../components/button-download'; -import { useContextClickContextMenu } from '../require/react-helper'; +import { + useActionWhenEscapeOrClickedOrContextOutsideEffect, + useContextClickContextMenu, +} from '../require/react-helper'; import ImageContextMenu from '../contexts/context-menu-image'; import Overlay from '../components/overlay'; -import { useRecoilValue } from 'recoil'; -import { guildResourceSoftImgSrcState, guildResourceState, useRecoilValueSoftImgSrc } from '../require/atoms'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { + guildResourceSoftImgSrcState, + guildResourceState, + overlayState, + useRecoilValueSoftImgSrc, +} from '../require/atoms'; import { isFailed, isLoaded } from '../require/loadables'; export interface ImageOverlayProps { @@ -23,7 +31,11 @@ export interface ImageOverlayProps { const ImageOverlay: FC = (props: ImageOverlayProps) => { const { guild, resourceId, resourceName } = props; + const setOverlay = useSetRecoilState(overlayState); + const rootRef = useRef(null); + const imgRef = useRef(null); + const dltxtRef = useRef(null); // NOTE: These will update together. How convenient! const resource = useRecoilValue(guildResourceState({ guildId: guild.id, resourceId })); @@ -46,13 +58,7 @@ const ImageOverlay: FC = (props: ImageOverlayProps) => { [resource, resourceName], ); - const onBaseClick = useCallback( - (e: MouseEvent) => { - if (!rootRef.current) return 1; - return 2; - }, - [rootRef], - ); + useActionWhenEscapeOrClickedOrContextOutsideEffect([imgRef, dltxtRef], () => setOverlay(null)); const sizeText = useMemo(() => { if (isFailed(resource)) return 'Failed to load'; @@ -61,10 +67,10 @@ const ImageOverlay: FC = (props: ImageOverlayProps) => { }, [resource]); return ( - +
- {resourceName} -
+ {resourceName} +
{resourceName}
{sizeText}
diff --git a/src/client/webapp/elements/require/react-helper.tsx b/src/client/webapp/elements/require/react-helper.tsx index 4486b6b..cbd903a 100644 --- a/src/client/webapp/elements/require/react-helper.tsx +++ b/src/client/webapp/elements/require/react-helper.tsx @@ -362,75 +362,86 @@ export function useScrollableCallables( // makes sure to also allow you to fly-out a click starting inside of the ref'd element but was dragged outside /** calls the close action when you hit escape or click outside of the ref element */ export function useActionWhenEscapeOrClickedOrContextOutsideEffect( - ref: RefObject, + refs: RefObject[], actionFunc: () => 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 data = useRef< + Map; mouseDownTarget: Node | null; mouseUpTarget: Node | null }> + >(new Map()); + + useEffect(() => { + data.current.clear(); + for (let i = 0; i < refs.length; ++i) { + const ref = refs[i] as RefObject; + data.current.set(i, { ref, mouseDownTarget: null, mouseUpTarget: null }); + } + }, [refs]); const handleClickOutside = useCallback( (event: MouseEvent) => { //console.log('current:', ref.current, 'target:', event.target, 'mouseDownTarget:', mouseRef.current.mouseDownTarget, 'mouseUpTarget:', mouseRef.current.mouseUpTarget); + for (const [_idx, { ref, mouseDownTarget, mouseUpTarget }] of data.current) { + if (!ref.current) return; - if (!ref.current) return; + // event is inside this ref's element, do not run the action func + // casting here is OK. https://stackoverflow.com/q/61164018 + if (ref.current.contains(event.target as Node)) return; - // casting here is OK. https://stackoverflow.com/q/61164018 - if (ref.current.contains(event.target as Node)) return; - - if (mouseRef.current.mouseDownTarget !== null || mouseRef.current.mouseUpTarget !== null) return; + if (mouseDownTarget !== null || mouseUpTarget !== null) return; + } actionFunc(); }, - [ref, mouseRef, actionFunc], + [actionFunc], ); - 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)) { - mouseRef.current.mouseDownTarget = event.target as Node; + const handleMouseDown = useCallback((event: MouseEvent) => { + //console.log('mouse down. Contains: ' + ref.current.contains(event.target as Node)); + for (const [_idx, dataElement] of data.current) { + if (!dataElement.ref.current) return; + if (dataElement.ref.current.contains(event.target as Node)) { + dataElement.mouseDownTarget = event.target as Node; } else { - mouseRef.current.mouseDownTarget = null; + dataElement.mouseDownTarget = null; } - }, - [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)) { - mouseRef.current.mouseUpTarget = event.target as Node; + const handleMouseUp = useCallback((event: MouseEvent) => { + //console.log('mouse up. Contains: ' + ref.current.contains(event.target as Node)); + for (const [_idx, dataElement] of data.current) { + if (!dataElement.ref.current) return; + if (dataElement.ref.current.contains(event.target as Node)) { + dataElement.mouseUpTarget = event.target as Node; } else { - mouseRef.current.mouseUpTarget = null; + dataElement.mouseUpTarget = null; } - }, - [ref, mouseRef], - ); + } + }, []); const handleContextMenu = useCallback( (event: MouseEvent) => { - if (!ref.current) return; - if (ref.current.contains(event.target as Node)) return; + for (const [_idx, { ref }] of data.current) { + 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. actionFunc(); }, - [actionFunc, ref], + [actionFunc], ); const handleKeyDown = useCallback( (event: KeyboardEvent) => { - if (!ref.current) return; + for (const [_idx, { ref }] of data.current) { + if (!ref.current) return; + } if (event.key !== 'Escape') return; actionFunc(); }, - [actionFunc, ref], + [actionFunc], ); useEffect(() => {