diff options
author | Bob Zeleznik <zzzman@gmail.com> | 2019-06-19 22:40:57 -0400 |
---|---|---|
committer | Bob Zeleznik <zzzman@gmail.com> | 2019-06-19 22:40:57 -0400 |
commit | 9e55bfaad39aa47ab0594c6af7f1aa68e2a8db7a (patch) | |
tree | 28033273b4c28837cf8afaea95d61d138e3aaa16 /src/client/views/ContextMenu.tsx | |
parent | 118ecb14ce519bcbade12b3d52e11b22fcc371b3 (diff) | |
parent | 827c58950b649629c84211d41fdd4d041287801e (diff) |
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web
Diffstat (limited to 'src/client/views/ContextMenu.tsx')
-rw-r--r-- | src/client/views/ContextMenu.tsx | 92 |
1 files changed, 70 insertions, 22 deletions
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 1133f70a1..59a0de2a0 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -1,5 +1,5 @@ import React = require("react"); -import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem"; +import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextMenuItem"; import { observable, action, computed } from "mobx"; import { observer } from "mobx-react"; import "./ContextMenu.scss"; @@ -14,13 +14,14 @@ library.add(faCircle); export class ContextMenu extends React.Component { static Instance: ContextMenu; - @observable private _items: Array<ContextMenuProps> = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault(), icon: "smile" }]; + @observable private _items: Array<ContextMenuProps> = []; @observable private _pageX: number = 0; @observable private _pageY: number = 0; @observable private _display: boolean = false; @observable private _searchString: string = ""; // afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be @observable private _yRelativeToTop: boolean = true; + @observable selectedIndex = -1; private _searchRef = React.createRef<HTMLInputElement>(); @@ -75,49 +76,67 @@ export class ContextMenu extends React.Component { this._display = false; } - @computed get filteredItems() { + @computed get filteredItems(): (OriginalMenuProps | string[])[] { const searchString = this._searchString.toLowerCase().split(" "); const matches = (descriptions: string[]): boolean => { - return searchString.every(s => descriptions.some(desc => desc.includes(s))); + return searchString.every(s => descriptions.some(desc => desc.toLowerCase().includes(s))); }; - const createGroupHeader = (contents: any) => { - return ( - <div className="contextMenu-group"> - <div className="contextMenu-description">{contents}</div> - </div> - ); - }; - const createItem = (item: ContextMenuProps) => <ContextMenuItem {...item} key={item.description} closeMenu={this.closeMenu} />; - const flattenItems = (items: ContextMenuProps[], groupFunc: (contents: any) => JSX.Element, getPath: () => string[]) => { - let eles: JSX.Element[] = []; + const flattenItems = (items: ContextMenuProps[], groupFunc: (groupName: any) => string[]) => { + let eles: (OriginalMenuProps | string[])[] = []; + const leaves: OriginalMenuProps[] = []; for (const item of items) { - const description = item.description.toLowerCase(); - const path = [...getPath(), description]; + const description = item.description; + const path = groupFunc(description); if ("subitems" in item) { - const children = flattenItems(item.subitems, contents => groupFunc(<>{item.description} -> {contents}</>), () => path); + const children = flattenItems(item.subitems, name => [...groupFunc(description), name]); if (children.length || matches(path)) { - eles.push(groupFunc(item.description)); + eles.push(path); eles = eles.concat(children); } } else { if (!matches(path)) { continue; } - eles.push(createItem(item)); + leaves.push(item); } } + eles = [...leaves, ...eles]; + return eles; }; - return flattenItems(this._items, createGroupHeader, () => []); + return flattenItems(this._items, name => [name]); + } + + @computed get flatItems(): OriginalMenuProps[] { + return this.filteredItems.filter(item => !Array.isArray(item)) as OriginalMenuProps[]; + } + + @computed get filteredViews() { + const createGroupHeader = (contents: any) => { + return ( + <div className="contextMenu-group"> + <div className="contextMenu-description">{contents}</div> + </div> + ); + }; + const createItem = (item: ContextMenuProps, selected: boolean) => <ContextMenuItem {...item} key={item.description} closeMenu={this.closeMenu} selected={selected} />; + let itemIndex = 0; + return this.filteredItems.map(value => { + if (Array.isArray(value)) { + return createGroupHeader(value.join(" -> ")); + } else { + return createItem(value, itemIndex++ === this.selectedIndex); + } + }); } @computed get menuItems() { if (!this._searchString) { return this._items.map(item => <ContextMenuItem {...item} key={item.description} closeMenu={this.closeMenu} />); } - return this.filteredItems; + return this.filteredViews; } render() { @@ -133,7 +152,7 @@ export class ContextMenu extends React.Component { <span className="icon-background"> <FontAwesomeIcon icon="search" size="lg" /> </span> - <input className="contextMenu-item contextMenu-description" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange} ref={this._searchRef} autoFocus /> + <input className="contextMenu-item contextMenu-description" type="text" placeholder="Search . . ." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} ref={this._searchRef} autoFocus /> </span> {this.menuItems} </div> @@ -141,7 +160,36 @@ export class ContextMenu extends React.Component { } @action + onKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "ArrowDown") { + if (this.selectedIndex < this.flatItems.length - 1) { + this.selectedIndex++; + } + e.preventDefault(); + } else if (e.key === "ArrowUp") { + if (this.selectedIndex > 0) { + this.selectedIndex--; + } + e.preventDefault(); + } else if (e.key === "Enter") { + const item = this.flatItems[this.selectedIndex]; + item.event(); + this.closeMenu(); + } + } + + @action onChange = (e: React.ChangeEvent<HTMLInputElement>) => { this._searchString = e.target.value; + if (!this._searchString) { + this.selectedIndex = -1; + } + else { + if (this.selectedIndex === -1) { + this.selectedIndex = 0; + } else { + this.selectedIndex = Math.min(this.flatItems.length - 1, this.selectedIndex); + } + } } }
\ No newline at end of file |