useContextMenu hook

This commit is contained in:
Michael Peters 2021-12-25 19:04:55 -06:00
parent 2bae6c72cd
commit 54536c1b03
7 changed files with 66 additions and 47 deletions

View File

@ -4,11 +4,10 @@ import CombinedGuild from '../../guild-combined';
import ChannelOverlay from '../overlays/overlay-channel';
import GuildSettingsOverlay from '../overlays/overlay-guild-settings';
import BaseElements from '../require/base-elements';
import ElementsUtil, { IAlignment } from '../require/elements-util';
import ElementsUtil from '../require/elements-util';
import ContextMenu from './components/context-menu';
export interface GuildTitleContextMenuProps {
alignment: IAlignment;
close: () => void;
relativeToRef: RefObject<HTMLElement>
guild: CombinedGuild;
@ -17,7 +16,7 @@ export interface GuildTitleContextMenuProps {
}
const GuildTitleContextMenu: FC<GuildTitleContextMenuProps> = (props: GuildTitleContextMenuProps) => {
const { alignment, close, relativeToRef, guild, guildMeta, selfMember } = props;
const { close, relativeToRef, guild, guildMeta, selfMember } = props;
const openGuildSettings = useCallback(() => {
close();
@ -51,6 +50,10 @@ const GuildTitleContextMenu: FC<GuildTitleContextMenuProps> = (props: GuildTitle
if (guildSettingsElement === null && createChannelElement === null) return null;
const alignment = useMemo(() => {
return { top: 'bottom', centerX: 'centerX' }
}, []);
return (
<ContextMenu alignment={alignment} close={close} relativeToRef={relativeToRef}>
<div className="guild-title-context-menu">

View File

@ -1,13 +1,11 @@
import React, { FC } from 'react';
import React, { FC, useMemo } from 'react';
import * as electron from 'electron';
import ReactHelper from '../require/react-helper';
import ContextMenu from './components/context-menu';
import * as FileType from 'file-type';
import sharp from 'sharp';
import { IAlignment } from '../require/elements-util';
export interface ImageContextMenuProps {
alignment: IAlignment;
relativeToPos: { x: number, y: number };
close: () => void;
resourceName: string;
@ -16,7 +14,7 @@ export interface ImageContextMenuProps {
}
const ImageContextMenu: FC<ImageContextMenuProps> = (props: ImageContextMenuProps) => {
const { alignment, relativeToPos, close, resourceName, resourceBuff, isPreview } = props;
const { relativeToPos, close, resourceName, resourceBuff, isPreview } = props;
const previewText = isPreview ? ' Preview' : '';
@ -56,6 +54,8 @@ const ImageContextMenu: FC<ImageContextMenuProps> = (props: ImageContextMenuProp
}
);
const alignment = useMemo(() => ({ top: 'centerY', left: 'centerX' }), []);
return (
<ContextMenu alignment={alignment} relativeToPos={relativeToPos} close={close}>
<div className="image">

View File

@ -53,7 +53,7 @@ const PreviewImageElement: FC<PreviewImageElementProps> = (props: PreviewImageEl
const [ imgSrc ] = GuildSubscriptions.useSoftImageSrcResourceSubscription(guild, resourcePreviewId);
const openImageOverlay = useCallback(() => {
// Note: document here isn't 100% guaranteed but we should be getting rid of this eventually anyway
// Note: document here isn't 100% guaranteed (I think) but we should be getting rid of this eventually anyway
ElementsUtil.presentReactOverlay(document, <ImageOverlay guild={guild} resourceId={resourceId} resourceName={resourceName} />);
}, [ guild, resourceId, resourceName ]);

View File

@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import React, { FC, useCallback, useMemo, useState, MouseEvent, useRef } from 'react';
import React, { FC, useMemo, useState, useRef, MouseEvent, useCallback } from 'react';
import CombinedGuild from '../../guild-combined';
import ElementsUtil from '../require/elements-util';
import DownloadButton from '../components/button-download';
@ -31,33 +31,30 @@ const ImageOverlay: FC<ImageOverlayProps> = (props: ImageOverlayProps) => {
'./img/loading.svg',
[ guild, resource ]
);
const [ contextMenuOpen, setContextMenuOpen ] = useState<boolean>(false);
const alignment = useMemo(() => ({ top: 'centerY', left: 'centerX' }), []);
const [ relativeToPos, setRelativeToPos ] = useState<{ x: number, y: number }>({ x: 0, y: 0 });
const contextMenu = useMemo(() => {
const [ contextMenu, toggleContextMenu ] = ReactHelper.useContextMenu((close: () => void) => {
if (!resource) return null;
return (
<ImageContextMenu
alignment={alignment} relativeToPos={relativeToPos} close={() => { setContextMenuOpen(false); }}
relativeToPos={relativeToPos} close={close}
resourceName={resourceName} resourceBuff={resource.data} isPreview={false}
/>
);
}, [ resource, alignment, relativeToPos, resourceName ]);
}, [ resource, relativeToPos, resourceName ]);
const toggleContextMenu = useCallback((e: MouseEvent<HTMLImageElement>) => {
LOG.debug('toggle context menu');
setRelativeToPos({ x: e.clientX, y: e.clientY });
setContextMenuOpen(oldContextMenuOpen => !!contextMenu && !oldContextMenuOpen);
}, [ contextMenu ]);
const onContextMenu = useCallback((event: MouseEvent<HTMLImageElement>) => {
setRelativeToPos({ x: event.clientX, y: event.clientY });
toggleContextMenu();
}, [ toggleContextMenu ]);
const sizeText = useMemo(() => resource ? ElementsUtil.humanSize(resource.data.length) : 'Loading Size...', [ resource ]);
return (
<Overlay childRootRef={rootRef}>
<div ref={rootRef} className="content popup-image">
<img src={resourceImgSrc} alt={resourceName} title={resourceName} onContextMenu={toggleContextMenu}></img>
<img src={resourceImgSrc} alt={resourceName} title={resourceName} onContextMenu={onContextMenu}></img>
<div className="download">
<div className="info">
<div className="name">{resourceName}</div>
@ -67,7 +64,7 @@ const ImageOverlay: FC<ImageOverlayProps> = (props: ImageOverlayProps) => {
downloadBuff={resource?.data} downloadBuffErr={!!resourceError}
resourceName={resourceName}></DownloadButton>
</div>
{contextMenuOpen ? contextMenu : null}
{contextMenu}
</div>
</Overlay>
);

View File

@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import { DependencyList, Dispatch, MutableRefObject, RefObject, SetStateAction, UIEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { DependencyList, Dispatch, MutableRefObject, ReactNode, RefObject, SetStateAction, UIEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
import ReactDOMServer from "react-dom/server";
import { ShouldNeverHappenError } from "../../data-types";
import Util from '../../util';
@ -395,4 +395,33 @@ export default class ReactHelper {
}
}, [ handleMouseUp ]);
}
static useContextMenu(
createContextMenu: (close: () => void) => ReactNode,
createContextMenuDeps: DependencyList
): [
contextMenu: ReactNode,
toggle: () => void,
close: () => void,
open: () => void,
isOpen: boolean
] {
const [ isOpen, setIsOpen ] = useState<boolean>(false);
const close = useCallback(() => {
setIsOpen(false);
}, []);
const contextMenu = useMemo(() => {
return createContextMenu(close);
}, createContextMenuDeps);
const toggle = useCallback(() => {
setIsOpen(oldIsOpen => !!contextMenu && !oldIsOpen);
}, [ contextMenu ]);
const open = useCallback(() => {
setIsOpen(!!contextMenu);
}, [ contextMenu ]);
return [ isOpen ? contextMenu : null, toggle, close, open, isOpen ];
}
}

View File

@ -3,12 +3,13 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import React, { FC, useCallback, useMemo, useRef, useState } from 'react';
import React, { FC, useMemo, useRef } from 'react';
import { Member } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import MemberElement, { DummyMember } from '../lists/components/member-element';
import GuildSubscriptions from '../require/guild-subscriptions';
import ConnectionInfoContextMenu from '../context-menus/context-menu-connection-info';
import ReactHelper from '../require/react-helper';
export interface ConnectionInfoProps {
guild: CombinedGuild;
@ -19,8 +20,6 @@ const ConnectionInfo: FC<ConnectionInfoProps> = (props: ConnectionInfoProps) =>
const rootRef = useRef<HTMLDivElement>(null);
const [ contextMenuOpen, setContextMenuOpen ] = useState<boolean>(false);
const [ selfMember ] = GuildSubscriptions.useSelfMemberSubscription(guild);
const displayMember = useMemo((): Member | DummyMember => {
@ -36,19 +35,19 @@ const ConnectionInfo: FC<ConnectionInfoProps> = (props: ConnectionInfoProps) =>
return selfMember;
}, [ selfMember ]);
const contextMenu = useMemo(() => {
const [ contextMenu, toggleContextMenu ] = ReactHelper.useContextMenu((close: () => void) => {
if (!selfMember) return null;
return <ConnectionInfoContextMenu guild={guild} selfMember={selfMember} relativeToRef={rootRef} close={() => { setContextMenuOpen(false); }} />
return (
<ConnectionInfoContextMenu
guild={guild} selfMember={selfMember} relativeToRef={rootRef} close={close}
/>
);
}, [ guild, selfMember, rootRef ]);
const toggleContextMenu = useCallback(() => {
setContextMenuOpen(oldContextMenuOpen => !!contextMenu && !oldContextMenuOpen);
}, [ contextMenu ]);
return (
<div ref={rootRef} className="connection-info">
<div onClick={toggleContextMenu}><MemberElement guild={guild} member={displayMember} /></div>
{contextMenuOpen ? contextMenu : null}
{contextMenu}
</div>
);
}

View File

@ -1,7 +1,8 @@
import React, { FC, useCallback, useMemo, useRef, useState } from 'react';
import React, { FC, useRef } from 'react';
import CombinedGuild from '../../guild-combined';
import GuildTitleContextMenu from '../context-menus/context-menu-guild-title';
import GuildSubscriptions from '../require/guild-subscriptions';
import ReactHelper from '../require/react-helper';
export interface GuildTitleProps {
guild: CombinedGuild;
@ -12,37 +13,27 @@ const GuildTitle: FC<GuildTitleProps> = (props: GuildTitleProps) => {
const rootRef = useRef<HTMLDivElement>(null);
const [ contextMenuOpen, setContextMenuOpen ] = useState<boolean>(false);
// TODO: Handle fetch error
const [ guildMeta, fetchError ] = GuildSubscriptions.useGuildMetadataSubscription(guild);
const [ selfMember ] = GuildSubscriptions.useSelfMemberSubscription(guild);
const alignment = useMemo(() => {
return { top: 'bottom', centerX: 'centerX' }
}, []);
const contextMenu = useMemo(() => {
const [ contextMenu, toggleContextMenu ] = ReactHelper.useContextMenu((close: () => void) => {
if (!guildMeta) return null;
if (!selfMember) return null;
return (
<GuildTitleContextMenu
alignment={alignment} relativeToRef={rootRef} close={() => { setContextMenuOpen(false); }}
relativeToRef={rootRef} close={close}
guild={guild} guildMeta={guildMeta} selfMember={selfMember}
/>
);
}, [ guild, guildMeta, selfMember, rootRef ]);
const toggleContextMenu = useCallback(() => {
setContextMenuOpen(oldContextMenuOpen => !!contextMenu && !oldContextMenuOpen);
}, [ contextMenu ]);
return (
<div className="guild-title" ref={rootRef}>
<div className="guild-name-container" onClick={toggleContextMenu}>
<span className="guild-name">{guildMeta?.name ?? null}</span>
</div>
{contextMenuOpen ? contextMenu : null}
{contextMenu}
</div>
);
}