From 762ac2bf354e4cc2c4b15f42502da939f5061646 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 9 Aug 2024 16:48:18 -0400
Subject: a bunch more typing fixes.

---
 src/client/views/ContextMenuItem.tsx | 180 +++++++++++------------------------
 1 file changed, 53 insertions(+), 127 deletions(-)

(limited to 'src/client/views/ContextMenuItem.tsx')

diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 3b87ea58b..5b4eb704b 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -8,32 +8,27 @@ import { SnappingManager } from '../util/SnappingManager';
 import { UndoManager } from '../util/UndoManager';
 import { ObservableReactComponent } from './ObservableReactComponent';
 
-export interface OriginalMenuProps {
+export interface ContextMenuProps {
+    icon: IconProp | JSX.Element;
     description: string;
-    event: (stuff?: unknown) => void;
-    undoable?: boolean;
-    noexpand?: boolean;
-    subitems?: ContextMenuProps[];
-    icon: IconProp | JSX.Element; // maybe should be optional (icon?)
-    closeMenu?: () => void;
-}
-
-export interface SubmenuProps {
-    description: string;
-    // eslint-disable-next-line no-use-before-define
-    subitems: ContextMenuProps[];
-    noexpand?: boolean;
     addDivider?: boolean;
-    icon: IconProp; // maybe should be optional (icon?)
     closeMenu?: () => void;
-}
 
-export type ContextMenuProps = OriginalMenuProps | SubmenuProps;
+    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<ContextMenuProps & { selected?: boolean }> {
-    @observable private _items: Array<ContextMenuProps> = [];
-    @observable private overItem = false;
+    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);
@@ -41,132 +36,63 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
     }
 
     componentDidMount() {
-        runInAction(() => {
-            this._items.length = 0;
-        });
-        if ((this._props as SubmenuProps)?.subitems) {
-            (this._props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i)));
-        }
+        runInAction(() => this._items.push(...(this._props.subitems ?? [])));
     }
 
     handleEvent = async (e: React.MouseEvent<HTMLDivElement>) => {
-        if ('event' in this._props) {
+        if (this._props.event) {
             this._props.closeMenu?.();
-            const batch = this._props.undoable !== false ? UndoManager.StartBatch(`Click Menu item: ${this._props.description}`) : undefined;
+            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();
         }
     };
 
-    currentTimeout?: NodeJS.Timeout | undefined;
-    static readonly timeout = 300;
-    _overPosY = 0;
-    _overPosX = 0;
+    setOverItem = (over: boolean) => {
+        this._hoverTimeout = setTimeout( action(() => {  this._overItem = over;  }), ContextMenuItem.HOVER_TIMEOUT ); // prettier-ignore
+    };
+
     onPointerEnter = (e: React.MouseEvent) => {
-        if (this.currentTimeout) {
-            clearTimeout(this.currentTimeout);
-            this.currentTimeout = undefined;
-        }
-        if (this.overItem) {
-            return;
-        }
+        this._hoverTimeout && clearTimeout(this._hoverTimeout);
         this._overPosY = e.clientY;
         this._overPosX = e.clientX;
-        this.currentTimeout = setTimeout(
-            action(() => {
-                this.overItem = true;
-            }),
-            ContextMenuItem.timeout
-        );
+        !this._overItem && this.setOverItem(true);
     };
 
     onPointerLeave = () => {
-        if (this.currentTimeout) {
-            clearTimeout(this.currentTimeout);
-            this.currentTimeout = undefined;
-        }
-        if (!this.overItem) {
-            return;
-        }
-        this.currentTimeout = setTimeout(
-            action(() => {
-                this.overItem = false;
-            }),
-            ContextMenuItem.timeout
-        );
+        this._hoverTimeout && clearTimeout(this._hoverTimeout);
+        this._overItem && this.setOverItem(false);
     };
 
-    isJSXElement(val: unknown): val is JSX.Element {
-        return React.isValidElement(val);
-    }
+    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%';
 
-    render() {
-        if ('event' in this._props) {
-            return (
-                <div className={'contextMenu-item' + (this._props.selected ? ' contextMenu-itemSelected' : '')} onPointerDown={this.handleEvent}>
-                    {this._props.icon ? <span className="contextMenu-item-icon-background">{this.isJSXElement(this._props.icon) ? this._props.icon : <FontAwesomeIcon icon={this._props.icon} size="sm" />}</span> : null}
-                    <div className="contextMenu-description">{this._props.description.replace(':', '')}</div>
-                    <div
-                        className="contextMenu-item-background"
-                        style={{
-                            background: SnappingManager.userColor,
-                        }}
-                    />
-                </div>
-            );
-        }
-        if ('subitems' in this._props) {
-            const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center';
-            const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : '';
+        return (
+            <div className={`contextMenuItem${this._props.selected ? '-Selected' : ''}`} //
+                 onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} onPointerDown={this.handleEvent} 
+                 style={{ alignItems, borderTop: this._props.addDivider ? 'solid 1px' : undefined }}
+            >
+                <div className="contextMenuItem-background" style={{ background: SnappingManager.userColor, filter: this._overItem ? 'opacity(0.2)' : '' }} />
+                <span className="contextMenuItem-icon" style={{ alignItems: 'center', alignSelf: 'center' }}>
+                    {React.isValidElement(this._props.icon) ? this._props.icon : this._props.icon ? <FontAwesomeIcon icon={this._props.icon as IconProp} size="sm" /> : null}
+                </span>
+                <div className="contextMenu-description"> {this._props.description} </div>
+                {!submenu.length ? null : (
+                    !this._overItem ? 
+                        <FontAwesomeIcon icon="angle-right" size="lg" style={{ position: 'absolute', right: '10px' }} /> : (
+                        <div className="contextMenu-subMenu-cont" style={{ marginLeft, marginTop, background: SnappingManager.userBackgroundColor }}>
+                            {submenu}
+                        </div>
+                    )
+                )}
+            </div>
+        ); // prettier-ignore
+    };
 
-            // here
-            const submenu = !this.overItem ? null : (
-                <div
-                    className="contextMenu-subMenu-cont"
-                    style={{
-                        marginLeft: window.innerWidth - this._overPosX - 50 > 0 ? '90%' : '20%',
-                        marginTop,
-                        background: SnappingManager.userBackgroundColor,
-                    }}>
-                    {this._items.map(prop => (
-                        <ContextMenuItem {...prop} key={prop.description} closeMenu={this._props.closeMenu} />
-                    ))}
-                </div>
-            );
-            if (!(this._props as SubmenuProps).noexpand) {
-                return (
-                    <div className="contextMenu-inlineMenu">
-                        {this._items.map(prop => (
-                            <ContextMenuItem {...prop} key={prop.description} closeMenu={this._props.closeMenu} />
-                        ))}
-                    </div>
-                );
-            }
-            return (
-                <div
-                    className={'contextMenu-item' + (this._props.selected ? ' contextMenu-itemSelected' : '')}
-                    style={{ alignItems: where, borderTop: this._props.addDivider ? 'solid 1px' : undefined }}
-                    onMouseLeave={this.onPointerLeave}
-                    onMouseEnter={this.onPointerEnter}>
-                    {this._props.icon ? (
-                        <span className="contextMenu-item-icon-background" onMouseEnter={this.onPointerLeave} style={{ alignItems: 'center', alignSelf: 'center' }}>
-                            <FontAwesomeIcon icon={this._props.icon} size="sm" />
-                        </span>
-                    ) : null}
-                    <div className="contextMenu-description" onMouseEnter={this.onPointerEnter} style={{ alignItems: 'center', alignSelf: 'center' }}>
-                        {this._props.description}
-                        <FontAwesomeIcon icon="angle-right" size="lg" style={{ position: 'absolute', right: '10px' }} />
-                    </div>
-                    <div
-                        className="contextMenu-item-background"
-                        style={{
-                            background: SnappingManager.userColor,
-                        }}
-                    />
-                    {submenu}
-                </div>
-            );
-        }
-        return null;
+    render() {
+        const submenu = this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this._props.closeMenu} />);
+        return this.props.event || this._props.noexpand ? this.renderItem(submenu) : <div className="contextMenu-inlineMenu">{submenu}</div>;
     }
 }
-- 
cgit v1.2.3-70-g09d2

