aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/ContextMenu.scss23
-rw-r--r--src/client/views/ContextMenu.tsx83
-rw-r--r--src/client/views/ContextMenuItem.tsx7
-rw-r--r--src/client/views/MainView.tsx2
4 files changed, 84 insertions, 31 deletions
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index e363c5158..a1a2b06f1 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -53,6 +53,29 @@
font-size: 20px;
}
+.contextMenu-group {
+ // width: 11vw; //10vw
+ height: 30px; //2vh
+ background: rgb(200, 200, 200);
+ display: flex; //comment out to allow search icon to be inline with search text
+ justify-content: left;
+ align-items: center;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ transition: all .1s;
+ border-width: .11px;
+ border-style: none;
+ border-color: $intermediate-color; // rgb(187, 186, 186);
+ border-bottom-style: solid;
+ // padding: 10px 0px 10px 0px;
+ white-space: nowrap;
+ font-size: 20px;
+}
+
.contextMenu-item:hover {
transition: all 0.1s;
background: $lighter-alt-accent;
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index eb1937683..1133f70a1 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,6 +1,6 @@
import React = require("react");
import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
-import { observable, action } from "mobx";
+import { observable, action, computed } from "mobx";
import { observer } from "mobx-react";
import "./ContextMenu.scss";
import { library } from '@fortawesome/fontawesome-svg-core';
@@ -17,11 +17,12 @@ export class ContextMenu extends React.Component {
@observable private _items: Array<ContextMenuProps> = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault(), icon: "smile" }];
@observable private _pageX: number = 0;
@observable private _pageY: number = 0;
- @observable private _display: string = "none";
+ @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;
+ private _searchRef = React.createRef<HTMLInputElement>();
private ref: React.RefObject<HTMLDivElement>;
@@ -36,7 +37,6 @@ export class ContextMenu extends React.Component {
@action
clearItems() {
this._items = [];
- this._display = "none";
}
@action
@@ -62,34 +62,70 @@ export class ContextMenu extends React.Component {
this._searchString = "";
- this._display = "flex";
- }
-
- intersects = (x: number, y: number): boolean => {
- if (this.ref.current && this._display !== "none") {
- let menuSize = { width: this.ref.current.getBoundingClientRect().width, height: this.ref.current.getBoundingClientRect().height };
+ this._display = true;
- let upperLeft = { x: this._pageX, y: this._yRelativeToTop ? this._pageY : window.innerHeight - (this._pageY + menuSize.height) };
- let bottomRight = { x: this._pageX + menuSize.width, y: this._yRelativeToTop ? this._pageY + menuSize.height : window.innerHeight - this._pageY };
-
- if (x >= upperLeft.x && x <= bottomRight.x) {
- if (y >= upperLeft.y && y <= bottomRight.y) {
- return true;
- }
- }
+ if (this._searchRef.current) {
+ this._searchRef.current.focus();
}
- return false;
}
@action
closeMenu = () => {
this.clearItems();
+ this._display = false;
}
- render() {
- let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY, display: this._display } :
- { left: this._pageX, bottom: this._pageY, display: this._display };
+ @computed get filteredItems() {
+ const searchString = this._searchString.toLowerCase().split(" ");
+ const matches = (descriptions: string[]): boolean => {
+ return searchString.every(s => descriptions.some(desc => desc.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[] = [];
+
+ for (const item of items) {
+ const description = item.description.toLowerCase();
+ const path = [...getPath(), description];
+ if ("subitems" in item) {
+ const children = flattenItems(item.subitems, contents => groupFunc(<>{item.description} -> {contents}</>), () => path);
+ if (children.length || matches(path)) {
+ eles.push(groupFunc(item.description));
+ eles = eles.concat(children);
+ }
+ } else {
+ if (!matches(path)) {
+ continue;
+ }
+ eles.push(createItem(item));
+ }
+ }
+ return eles;
+ };
+ return flattenItems(this._items, createGroupHeader, () => []);
+ }
+
+ @computed get menuItems() {
+ if (!this._searchString) {
+ return this._items.map(item => <ContextMenuItem {...item} key={item.description} closeMenu={this.closeMenu} />);
+ }
+ return this.filteredItems;
+ }
+
+ render() {
+ if (!this._display) {
+ return null;
+ }
+ let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY } :
+ { left: this._pageX, bottom: this._pageY };
return (
<div className="contextMenu-cont" style={style} ref={this.ref}>
@@ -97,10 +133,9 @@ 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} />
+ <input className="contextMenu-item contextMenu-description" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange} ref={this._searchRef} autoFocus />
</span>
- {this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1).
- map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.closeMenu} />)}
+ {this.menuItems}
</div>
);
}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index dc0751049..88ebd95bc 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -21,9 +21,6 @@ export interface SubmenuProps {
closeMenu?: () => void;
}
-export interface ContextMenuItemProps {
- type: ContextMenuProps | SubmenuProps;
-}
export type ContextMenuProps = OriginalMenuProps | SubmenuProps;
@observer
@@ -67,7 +64,6 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
return;
}
this.currentTimeout = setTimeout(action(() => this.overItem = false), ContextMenuItem.timeout);
-
}
render() {
@@ -84,8 +80,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps> {
</div>
</div>
);
- }
- else {
+ } else if ("subitems" in this.props) {
let submenu = !this.overItem ? (null) :
<div className="contextMenu-subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
{this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7f979cd3b..e3d4ff8b5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -118,7 +118,7 @@ export class MainView extends React.Component {
const targets = document.elementsFromPoint(e.x, e.y);
if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) {
- ContextMenu.Instance.clearItems();
+ ContextMenu.Instance.closeMenu();
}
}), true);
}