aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/nodes/RadialMenu.scss83
-rw-r--r--src/client/views/nodes/RadialMenu.tsx218
-rw-r--r--src/client/views/nodes/RadialMenuItem.tsx96
3 files changed, 397 insertions, 0 deletions
diff --git a/src/client/views/nodes/RadialMenu.scss b/src/client/views/nodes/RadialMenu.scss
new file mode 100644
index 000000000..ce0c263ef
--- /dev/null
+++ b/src/client/views/nodes/RadialMenu.scss
@@ -0,0 +1,83 @@
+@import "../globalCssVariables";
+
+.radialMenu-cont {
+ position: absolute;
+ z-index: $radialMenu-zindex;
+ flex-direction: column;
+}
+
+.radialMenu-subMenu-cont {
+ position: absolute;
+ display: flex;
+ z-index: 1000;
+ flex-direction: column;
+ border-radius: 15px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+
+.radialMenu-item {
+ // width: 11vw; //10vw
+ display: flex; //comment out to allow search icon to be inline with search text
+ 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-style: none;
+ white-space: nowrap;
+ font-size: 13px;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+}
+
+s
+.radialMenu-itemSelected {
+ border-style: none;
+}
+
+.radialMenu-group {
+ // width: 11vw; //10vw
+ 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);
+ // padding: 10px 0px 10px 0px;
+ white-space: nowrap;
+ font-size: 13px;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ padding-left: 5px;
+}
+
+
+.radialMenu-description {
+ margin-left: 5px;
+ text-align: left;
+ display: inline; //need this?
+}
+
+
+
+.icon-background {
+ pointer-events: all;
+ height:100%;
+ margin-top: 15px;
+ background-color: transparent;
+ width: 35px;
+ text-align: center;
+ font-size: 20px;
+ margin-left: 5px;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx
new file mode 100644
index 000000000..4fbb40c5f
--- /dev/null
+++ b/src/client/views/nodes/RadialMenu.tsx
@@ -0,0 +1,218 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { action, observable, computed, IReactionDisposer, reaction, runInAction } from "mobx";
+import { RadialMenuItem, RadialMenuProps, OriginalMenuProps } from "./RadialMenuItem";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import Measure from "react-measure";
+import "./RadialMenu.scss";
+
+
+
+@observer
+export class RadialMenu extends React.Component {
+ static Instance: RadialMenu;
+ static readonly buffer = 20;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ RadialMenu.Instance = this;
+ }
+
+ @observable private _mouseX: number = -1;
+ @observable private _mouseY: number = -1;
+ @observable private _shouldDisplay: boolean = false;
+ @observable private _mouseDown: boolean = false;
+ private _reactionDisposer?: IReactionDisposer;
+
+
+ @action
+ onPointerDown = (e: PointerEvent) => {
+ this._mouseDown = true;
+ this._mouseX = e.clientX;
+ this._mouseY = e.clientY;
+ document.addEventListener("pointermove", this.onPointerMove);
+ }
+
+ @observable
+ private _closest:number=-1;
+
+ @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++){
+ let 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[this._closest].event();
+ }
+ }
+ componentWillUnmount() {
+ document.removeEventListener("pointerdown", this.onPointerDown);
+
+ document.removeEventListener("pointerup", this.onPointerUp);
+ this._reactionDisposer && this._reactionDisposer();
+ }
+
+ @action
+ componentDidMount = () => {
+ document.addEventListener("pointerdown", this.onPointerDown);
+ document.addEventListener("pointerup", this.onPointerUp);
+
+ this._reactionDisposer = reaction(
+ () => this._shouldDisplay,
+ () => this._shouldDisplay && !this._mouseDown && runInAction(() => this._display = true)
+ );
+ }
+
+ @observable private _pageX: number = 0;
+ @observable private _pageY: number = 0;
+ @observable private _display: boolean = false;
+ @observable private _yRelativeToTop: boolean = true;
+
+
+ @observable private _width: number = 0;
+ @observable private _height: number = 0;
+
+
+ getItems() {
+ return this._items;
+ }
+
+ findByDescription = (target: string, toLowerCase = false) => {
+ return this._items.find(menuItem => {
+ let reference = menuItem.description;
+ toLowerCase && (reference = reference.toLowerCase());
+ return reference === target;
+ });
+ }
+
+
+ @action
+ addItem(item: RadialMenuProps) {
+ if (this._items.indexOf(item) === -1) {
+ this._items.push(item);
+ }
+ }
+
+ @observable
+ private _items: Array<RadialMenuProps> = [];
+
+ @action
+ displayMenu = (x: number, y: number) => {
+ //maxX and maxY will change if the UI/font size changes, but will work for any amount
+ //of items added to the menu
+
+ this._pageX = x;
+ this._pageY = y;
+ this._shouldDisplay = true;
+ }
+
+ get pageX() {
+ const x = this._pageX;
+ if (x < 0) {
+ return 0;
+ }
+ const width = this._width;
+ if (x + width > window.innerWidth - RadialMenu.buffer) {
+ return window.innerWidth - RadialMenu.buffer - width;
+ }
+ return x;
+ }
+
+ get pageY() {
+ const y = this._pageY;
+ if (y < 0) {
+ return 0;
+ }
+ const height = this._height;
+ if (y + height > window.innerHeight - RadialMenu.buffer) {
+ return window.innerHeight - RadialMenu.buffer - height;
+ }
+ return y;
+ }
+
+ @computed get menuItems() {
+ return this._items.map((item,index) => <RadialMenuItem {...item} key={item.description} closeMenu={this.closeMenu} max={this._items.length} min={index} selected={this._closest} />);
+ }
+
+ @action
+ closeMenu = () => {
+ this.clearItems();
+ this._display = false;
+ this._shouldDisplay = false;
+ }
+
+ @action
+ openMenu = () => {
+ this._shouldDisplay;
+ this._display = true;
+ }
+
+ @action
+ clearItems() {
+ this._items = [];
+ }
+
+ render() {
+ if (!this._display) {
+ return null;
+ }
+ const style = this._yRelativeToTop ? { left: this._mouseX-150, top: this._mouseY-150 } :
+ { left: this._mouseX-150, bottom: this._mouseY+150 };
+
+ const contents = (
+ <>
+ {this.menuItems}
+ </>
+ );
+ // return (
+ // <Measure offset onResize={action((r: any) => { this._width = r.offset.width; this._height = r.offset.height; })}>
+ // {({ measureRef }) => (
+ // <div className="radialMenu-cont" style={style} ref={measureRef}>
+ // {contents}
+ // </div>
+ // )
+ // }
+ // </Measure>
+ // );
+ return (
+
+ <div className="radialMenu-cont" style={style}>
+ {contents}
+ </div>
+
+ );
+ }
+
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/RadialMenuItem.tsx b/src/client/views/nodes/RadialMenuItem.tsx
new file mode 100644
index 000000000..727d1c3be
--- /dev/null
+++ b/src/client/views/nodes/RadialMenuItem.tsx
@@ -0,0 +1,96 @@
+import React = require("react");
+import { observable, action } from "mobx";
+import { observer } from "mobx-react";
+import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
+import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { UndoManager } from "../../util/UndoManager";
+
+library.add(faAngleRight);
+
+export interface OriginalMenuProps {
+ description: string;
+ event: (stuff?: any) => void;
+ undoable?: boolean;
+ icon: IconProp; //maybe should be optional (icon?)
+ closeMenu?: () => void;
+ min?: number;
+ max?:number;
+ selected:number;
+}
+
+
+export type RadialMenuProps = OriginalMenuProps;
+
+@observer
+export class RadialMenuItem extends React.Component<RadialMenuProps & { selected?: boolean }> {
+
+ componentDidMount = () =>{
+ this.setcircle();
+ }
+
+ componentDidUpdate = () =>{
+ this.setcircle();
+ }
+
+ handleEvent = async (e: React.MouseEvent<HTMLDivElement>) => {
+ if ("event" in this.props) {
+ this.props.closeMenu && this.props.closeMenu();
+ let batch: UndoManager.Batch | undefined;
+ if (this.props.undoable !== false) {
+ batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`);
+ }
+ await this.props.event({ x: e.clientX, y: e.clientY });
+ batch && batch.end();
+ }
+ }
+
+
+ setcircle(){
+ let circlemin=0;
+ let circlemax=1
+ this.props.min? circlemin=this.props.min:null;
+ this.props.max? circlemax=this.props.max:null;
+ if (document.getElementById("myCanvas")!==null){
+ var c : any= document.getElementById("myCanvas");
+ let color = "white"
+ switch(circlemin%3){
+ case 1:
+ color = "#c2c2c5";
+ break;
+ case 0:
+ color = "white";
+ break;
+ case 2:
+ color = "lightgray";
+ break;
+ }
+ if (circlemax%3===1 && circlemin===circlemax-1){
+ color="#c2c2c5";
+ }
+ console.log(this.props.selected,this.props.min)
+
+ if (this.props.selected === this.props.min){
+ console.log(this.props.selected,this.props.min)
+ color="#808080";
+
+ }
+ if (c.getContext){
+ var ctx = c.getContext("2d");
+ ctx.beginPath();
+ ctx.arc(150, 150, 150, (circlemin/circlemax)*2*Math.PI, ((circlemin+1)/circlemax) * 2 * Math.PI);
+ ctx.arc(150, 150, 50, ((circlemin+1)/circlemax)*2*Math.PI, (circlemin/circlemax) * 2 * Math.PI,true);
+ ctx.fillStyle=color;
+ ctx.fill()
+ }
+ }
+ }
+
+ render() {
+ return (
+ <div className={"radialMenu-item" + (this.props.selected ? " radialMenu-itemSelected" : "")} onClick={this.handleEvent}>
+ <canvas id="myCanvas" height="300" width="300"> Your browser does not support the HTML5 canvas tag.</canvas>
+ </div>
+ );
+ }
+} \ No newline at end of file