import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import './RadialMenu.scss'; import { RadialMenuItem, RadialMenuProps } from './RadialMenuItem'; @observer export class RadialMenu extends React.Component { // eslint-disable-next-line no-use-before-define static Instance: RadialMenu; static readonly buffer = 20; @observable private _mouseX: number = -1; @observable private _mouseY: number = -1; @observable private _shouldDisplay: boolean = false; @observable private _mouseDown: boolean = false; @observable private _closest: number = -1; @observable private _pageX: number = 0; @observable private _pageY: number = 0; @observable _display: boolean = false; @observable private _yRelativeToTop: boolean = true; @observable private _items: Array = []; private _reactionDisposer?: IReactionDisposer; constructor(props: any) { super(props); makeObservable(this); RadialMenu.Instance = this; } componentDidMount() { document.addEventListener('pointerdown', this.onPointerDown); document.addEventListener('pointerup', this.onPointerUp); this.previewcircle(); this._reactionDisposer = reaction( () => this._shouldDisplay, () => this._shouldDisplay && !this._mouseDown && runInAction(() => { this._display = true; }) ); } componentDidUpdate() { this.previewcircle(); } componentWillUnmount() { document.removeEventListener('pointerdown', this.onPointerDown); document.removeEventListener('pointerup', this.onPointerUp); this._reactionDisposer && this._reactionDisposer(); } @computed get menuItems() { // eslint-disable-next-line react/jsx-props-no-spreading return this._items.map((item, index) => ); } catchTouch = (te: React.TouchEvent) => { te.stopPropagation(); te.preventDefault(); }; @action onPointerDown = (e: PointerEvent) => { this._mouseDown = true; this._mouseX = e.clientX; this._mouseY = e.clientY; document.addEventListener('pointermove', this.onPointerMove); }; @action onPointerMove = (e: PointerEvent) => { const curX = e.clientX; const curY = e.clientY; const deltX = this._mouseX - curX; const deltY = this._mouseY - curY; const scale = Math.hypot(deltY, deltX); if (scale < 150 && scale > 50) { const rad = Math.atan2(deltY, deltX) + Math.PI; let closest = 0; let closestval = 999999999; for (let x = 0; x < this._items.length; x++) { const curmin = (x / this._items.length) * 2 * Math.PI; if (rad - curmin < closestval && rad - curmin > 0) { closestval = rad - curmin; closest = x; } } this._closest = closest; } else { this._closest = -1; } }; @action onPointerUp = (e: PointerEvent) => { this._mouseDown = false; const curX = e.clientX; const curY = e.clientY; if (this._mouseX !== curX || this._mouseY !== curY) { this._shouldDisplay = false; } this._shouldDisplay && (this._display = true); document.removeEventListener('pointermove', this.onPointerMove); if (this._closest !== -1 && this._items?.length > this._closest) { this._items[this._closest].event(); } }; @action closeMenu = () => { this.clearItems(); this._display = false; this._shouldDisplay = false; }; @action clearItems() { this._items = []; } previewcircle() { if (document.getElementById('newCanvas') !== null) { const c: any = document.getElementById('newCanvas'); if (c.getContext) { const ctx = c.getContext('2d'); ctx.beginPath(); ctx.arc(150, 150, 50, 0, 2 * Math.PI); ctx.fillStyle = 'white'; ctx.fill(); ctx.font = '12px Arial'; ctx.fillStyle = 'black'; ctx.textAlign = 'center'; let description = ''; if (this._closest !== -1) { description = this._items[this._closest].description; } if (description.length > 15) { description = description.slice(0, 12); description += '...'; } ctx.fillText(description, 150, 150, 90); } } } render() { if (!this._display) { return null; } const style = this._yRelativeToTop ? { left: this._pageX - 130, top: this._pageY - 130 } : { left: this._pageX - 130, top: this._pageY - 130 }; return (
{' '} Your browser does not support the HTML5 canvas tag. {this.menuItems}
); } }