/* eslint-disable react/jsx-props-no-spreading */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { SnappingManager } from '../util/SnappingManager'; import { UndoManager } from '../util/UndoManager'; import { ObservableReactComponent } from './ObservableReactComponent'; export interface ContextMenuProps { icon: IconProp | JSX.Element; description: string; addDivider?: boolean; closeMenu?: () => void; subitems?: ContextMenuProps[]; noexpand?: boolean; // whether to render the submenu items as a flyout from this item, or inline in place of this item undoable?: boolean; // whether to wrap the event callback in an UndoBatch or not event?: (stuff?: unknown) => void; } @observer export class ContextMenuItem extends ObservableReactComponent { static readonly HOVER_TIMEOUT = 100; _hoverTimeout?: NodeJS.Timeout; _overPosY = 0; _overPosX = 0; @observable _items: ContextMenuProps[] = []; @observable _overItem = false; constructor(props: ContextMenuProps & { selected?: boolean }) { super(props); makeObservable(this); } componentDidMount() { runInAction(() => this._items.push(...(this._props.subitems ?? []))); } handleEvent = async (e: React.MouseEvent) => { if (this._props.event) { this._props.closeMenu?.(); const batch = this._props.undoable ? UndoManager.StartBatch(`Click Menu item: ${this._props.description}`) : undefined; await this._props.event({ x: e.clientX, y: e.clientY }); batch?.end(); } }; setOverItem = (over: boolean) => { this._hoverTimeout = setTimeout( action(() => { this._overItem = over; }), ContextMenuItem.HOVER_TIMEOUT ); // prettier-ignore }; onPointerEnter = (e: React.MouseEvent) => { this._hoverTimeout && clearTimeout(this._hoverTimeout); this._overPosY = e.clientY; this._overPosX = e.clientX; !this._overItem && this.setOverItem(true); }; onPointerLeave = () => { this._hoverTimeout && clearTimeout(this._hoverTimeout); this._overItem && this.setOverItem(false); }; renderItem = (submenu: JSX.Element[]) => { const alignItems = this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center'; const marginTop = this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : ''; const marginLeft = window.innerWidth - this._overPosX - 50 > 0 ? '90%' : '20%'; return (
{React.isValidElement(this._props.icon) ? this._props.icon : this._props.icon ? : null}
{this._props.description}
{!submenu.length ? null : ( !this._overItem ? : (
{submenu}
) )}
); // prettier-ignore }; render() { const submenu = this._items.map(prop => ); return this.props.event || this._props.noexpand ? this.renderItem(submenu) :
{submenu}
; } }