aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoryipstanley <stanley_yip@brown.edu>2019-01-21 21:42:03 -0500
committeryipstanley <stanley_yip@brown.edu>2019-01-21 21:42:03 -0500
commit6a04f60ce76fbd8274dd9daf285042f9cab16656 (patch)
tree51b122980818195d79627ea8265ae7b47a7db3ca /src
parentb0a8b59b967eae3d5812fd8e54e9f4f7a3fb80fc (diff)
context menu basics working
Diffstat (limited to 'src')
-rw-r--r--src/DocumentDecorations.scss1
-rw-r--r--src/Main.tsx21
-rw-r--r--src/documents/Documents.ts6
-rw-r--r--src/fields/Key.ts1
-rw-r--r--src/viewmodels/DocumentViewModel.ts10
-rw-r--r--src/views/ContextMenu.scss34
-rw-r--r--src/views/ContextMenu.tsx68
-rw-r--r--src/views/ContextMenuItem.tsx17
-rw-r--r--src/views/freeformcanvas/CollectionFreeFormView.tsx96
-rw-r--r--src/views/freeformcanvas/FreeFormCanvas.tsx6
-rw-r--r--src/views/nodes/DocumentView.tsx48
-rw-r--r--src/views/nodes/FieldTextBox.scss10
-rw-r--r--src/views/nodes/FieldTextBox.tsx5
13 files changed, 285 insertions, 38 deletions
diff --git a/src/DocumentDecorations.scss b/src/DocumentDecorations.scss
index fff4d201a..5a9ff7d01 100644
--- a/src/DocumentDecorations.scss
+++ b/src/DocumentDecorations.scss
@@ -1,6 +1,5 @@
#documentDecorations-container {
position: absolute;
- z-index: 1000;
display: grid;
grid-template-rows: 20px 1fr 20px;
grid-template-columns: 20px 1fr 20px;
diff --git a/src/Main.tsx b/src/Main.tsx
index 0a683c858..cb91c33a3 100644
--- a/src/Main.tsx
+++ b/src/Main.tsx
@@ -9,7 +9,7 @@ import { FreeFormCanvas } from './views/freeformcanvas/FreeFormCanvas';
import { Key, KeyStore as KS, KeyStore } from './fields/Key';
import { NumberField } from './fields/NumberField';
import { Document } from './fields/Document';
-import { configure, runInAction } from 'mobx';
+import { configure, runInAction, action } from 'mobx';
import { NodeStore } from './stores/NodeStore';
import { Documents } from './documents/Documents';
import { DocumentDecorations } from './DocumentDecorations';
@@ -17,6 +17,7 @@ import { CollectionFreeFormView } from './views/freeformcanvas/CollectionFreeFor
import { ListField } from './fields/ListField';
import { DocumentView } from './views/nodes/DocumentView';
import { DocumentViewModel } from './viewmodels/DocumentViewModel';
+import { ContextMenu } from './views/ContextMenu';
configure({
enforceActions: "observed"
@@ -26,11 +27,27 @@ const mainNodeCollection = new Array<Document>();
let mainContainer = Documents.CollectionDocument(mainNodeCollection, {
x: 0, y: 0, width: window.screen.width, height: window.screen.height
})
+let mainContvm = new DocumentViewModel(mainContainer);
+mainContvm.IsMainDoc = true;
+
+window.addEventListener("drop", function(e) {
+ e.preventDefault();
+}, false)
+window.addEventListener("dragover", function(e) {
+ e.preventDefault();
+}, false)
+document.addEventListener("pointerdown", action(function(e: PointerEvent) {
+ if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
+ ContextMenu.Instance.clearItems()
+ }
+}), true)
+
ReactDOM.render((
<div style={{display: "grid", width: "100vw", height: "100vh"}}>
<h1>Dash Web</h1>
+ <DocumentView dvm={mainContvm} parent={undefined} />
<DocumentDecorations />
- <DocumentView dvm={new DocumentViewModel(mainContainer)} />
+ <ContextMenu />
</div>), document.getElementById('root'));
diff --git a/src/documents/Documents.ts b/src/documents/Documents.ts
index cc08f4123..75cc3b491 100644
--- a/src/documents/Documents.ts
+++ b/src/documents/Documents.ts
@@ -35,7 +35,7 @@ export namespace Documents {
textProto.SetField(KeyStore.Y, new NumberField(0));
textProto.SetField(KeyStore.Width, new NumberField(300));
textProto.SetField(KeyStore.Height, new NumberField(150));
- textProto.SetField(KeyStore.Layout, new TextField("<FieldTextBox doc={doc} fieldKey={DataKey} />"));
+ textProto.SetField(KeyStore.Layout, new TextField("<FieldTextBox doc={dvm.Doc} fieldKey={DataKey} />"));
textProto.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
}
return textProto;
@@ -73,11 +73,11 @@ export namespace Documents {
function GetCollectionPrototype(): Document {
if(!collectionProto) {
collectionProto = new Document();
- collectionProto.SetField(KeyStore.X, new NumberField(150));
+ collectionProto.SetField(KeyStore.X, new NumberField(0));
collectionProto.SetField(KeyStore.Y, new NumberField(0));
collectionProto.SetField(KeyStore.Width, new NumberField(300));
collectionProto.SetField(KeyStore.Height, new NumberField(300));
- collectionProto.SetField(KeyStore.Layout, new TextField('<CollectionFreeFormView doc={doc} fieldKey={DataKey} isSelected={isSelected}/>'));
+ collectionProto.SetField(KeyStore.Layout, new TextField('<CollectionFreeFormView dvm={dvm} fieldKey={DataKey} isSelected={isSelected}/>'));
collectionProto.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
}
return collectionProto;
diff --git a/src/fields/Key.ts b/src/fields/Key.ts
index db30f545d..1f8ba0be5 100644
--- a/src/fields/Key.ts
+++ b/src/fields/Key.ts
@@ -36,6 +36,7 @@ export namespace KeyStore {
export let Y = new Key("Y");
export let PanX = new Key("PanX");
export let PanY = new Key("PanY");
+ export let Scale = new Key("Scale");
export let Width = new Key("Width");
export let Height = new Key("Height");
export let Data = new Key("Data");
diff --git a/src/viewmodels/DocumentViewModel.ts b/src/viewmodels/DocumentViewModel.ts
index f3154db54..21e88f964 100644
--- a/src/viewmodels/DocumentViewModel.ts
+++ b/src/viewmodels/DocumentViewModel.ts
@@ -9,13 +9,13 @@ export class DocumentViewModel {
return this.doc;
}
- private _selected = false;
+ private _isMainDoc = false
- get Selected() : boolean {
- return this._selected;
+ get IsMainDoc(): boolean {
+ return this._isMainDoc;
}
- set Selected(isSelected: boolean) {
- this._selected = isSelected;
+ set IsMainDoc(v: boolean) {
+ this._isMainDoc = v;
}
} \ No newline at end of file
diff --git a/src/views/ContextMenu.scss b/src/views/ContextMenu.scss
new file mode 100644
index 000000000..234f82eb9
--- /dev/null
+++ b/src/views/ContextMenu.scss
@@ -0,0 +1,34 @@
+.contextMenu-cont {
+ position: absolute;
+ display: flex;
+ z-index: 1000;
+ box-shadow: #AAAAAA .2vw .2vw .4vw;
+}
+
+.contextMenu-item {
+ width: 10vw;
+ height: 4vh;
+ background: #DDDDDD;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ -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;
+}
+
+.contextMenu-item:hover {
+ transition: all .1s;
+ background: #AAAAAA
+}
+
+.contextMenu-description {
+ font-size: 1.5vw;
+ text-align: left;
+ width: 8vw;
+} \ No newline at end of file
diff --git a/src/views/ContextMenu.tsx b/src/views/ContextMenu.tsx
new file mode 100644
index 000000000..299ce91c6
--- /dev/null
+++ b/src/views/ContextMenu.tsx
@@ -0,0 +1,68 @@
+import React = require("react");
+import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
+import { observable } from "mobx";
+import { observer } from "mobx-react";
+import "./ContextMenu.scss"
+
+@observer
+export class ContextMenu extends React.Component {
+ static Instance: ContextMenu
+
+ @observable private _items: Array<ContextMenuProps> = [{description:"test", event:(e:React.MouseEvent) => e.preventDefault()}];
+ @observable private _pageX: number = 0;
+ @observable private _pageY: number = 0;
+ @observable private _display: string = "none";
+
+ private ref: React.RefObject<HTMLDivElement>;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ this.ref = React.createRef()
+
+ ContextMenu.Instance = this;
+ }
+
+ clearItems() {
+ this._items = []
+ this._display = "none"
+ }
+
+ addItem(item: ContextMenuProps) {
+ if (this._items.indexOf(item) === -1) {
+ this._items.push(item);
+ }
+ }
+
+ getItems() {
+ return this._items;
+ }
+
+ displayMenu(x: number, y: number) {
+ this._pageX = x
+ this._pageY = y
+
+ this._display = "flex"
+ }
+
+ intersects = (x: number, y: number): boolean => {
+ if (this.ref.current && this._display !== "none") {
+ if (x >= this._pageX && x <= this._pageX + this.ref.current.getBoundingClientRect().width) {
+ if (y >= this._pageY && y <= this._pageY + this.ref.current.getBoundingClientRect().height) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ render() {
+ return(
+ <div className="contextMenu-cont" style={{left: this._pageX, top: this._pageY, display: this._display }} ref={this.ref}>
+ {this._items.map(prop => {
+ return <ContextMenuItem {...prop} key={prop.description}/>
+ })}
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/views/ContextMenuItem.tsx b/src/views/ContextMenuItem.tsx
new file mode 100644
index 000000000..beacb44d2
--- /dev/null
+++ b/src/views/ContextMenuItem.tsx
@@ -0,0 +1,17 @@
+import React = require("react");
+import { ContextMenu } from "./ContextMenu";
+
+export interface ContextMenuProps {
+ description: string;
+ event: (e: React.MouseEvent<HTMLDivElement>) => void;
+}
+
+export class ContextMenuItem extends React.Component<ContextMenuProps> {
+ render() {
+ return(
+ <div className="contextMenu-item" onClick={this.props.event}>
+ <div className="contextMenu-description">{this.props.description}</div>
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/views/freeformcanvas/CollectionFreeFormView.tsx b/src/views/freeformcanvas/CollectionFreeFormView.tsx
index 4c8fcec10..84b30ac38 100644
--- a/src/views/freeformcanvas/CollectionFreeFormView.tsx
+++ b/src/views/freeformcanvas/CollectionFreeFormView.tsx
@@ -2,7 +2,7 @@ import { observer } from "mobx-react";
import { Key, KeyStore } from "../../fields/Key";
import "./FreeFormCanvas.scss";
import React = require("react");
-import { action } from "mobx";
+import { action, observable } from "mobx";
import { Document } from "../../fields/Document";
import { DocumentViewModel } from "../../viewmodels/DocumentViewModel";
import { DocumentView } from "../nodes/DocumentView";
@@ -11,10 +11,12 @@ import { NumberField } from "../../fields/NumberField";
import { SSL_OP_SINGLE_DH_USE } from "constants";
import { DocumentDecorations } from "../../DocumentDecorations";
import { SelectionManager } from "../../util/SelectionManager";
+import { Documents } from "../../documents/Documents";
+import { ContextMenu } from "../ContextMenu";
interface IProps {
fieldKey: Key;
- doc: Document;
+ dvm: DocumentViewModel;
isSelected: boolean;
}
@@ -29,10 +31,14 @@ export class CollectionFreeFormView extends React.Component<IProps> {
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (!this.props.isSelected) {
+ if (!this.props.isSelected && !this.props.dvm.IsMainDoc) {
return;
}
+ if (this.props.dvm.IsMainDoc) {
+ SelectionManager.DeselectAll()
+ }
+
e.stopPropagation();
if (e.button === 2) {
this._isPointerDown = true;
@@ -60,14 +66,14 @@ export class CollectionFreeFormView extends React.Component<IProps> {
if (!this._isPointerDown) {
return;
}
- const { doc } = this.props;
+ const { dvm } = this.props;
+ const doc = dvm.Doc;
let x = doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
let y = doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
doc.SetFieldValue(KeyStore.PanX, x + e.movementX, NumberField);
doc.SetFieldValue(KeyStore.PanY, y + e.movementY, NumberField);
-
DocumentDecorations.Instance.forceUpdate()
}
@@ -76,16 +82,75 @@ export class CollectionFreeFormView extends React.Component<IProps> {
e.stopPropagation();
let scaleAmount = 1 - (e.deltaY / 1000);
- //this.props.store.Scale *= scaleAmount;
+ let currScale = this.props.dvm.Doc.GetFieldValue(KeyStore.Scale, NumberField, Number(1));
+ this.props.dvm.Doc.SetField(KeyStore.Scale, new NumberField(currScale * scaleAmount));
+
+ const panx: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
+ const pany: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+ let dx = (e.pageX - window.screen.width / 2) * currScale * (scaleAmount - 1)
+ let dy = (e.pageY - window.screen.height / 2) * currScale * (scaleAmount - 1)
+
+ this.props.dvm.Doc.SetFieldValue(KeyStore.PanX, panx - dx, NumberField);
+ this.props.dvm.Doc.SetFieldValue(KeyStore.PanY, pany - dy, NumberField);
+
+ DocumentDecorations.Instance.forceUpdate()
+ }
+
+ onDrop = (e: React.DragEvent): void => {
+ e.stopPropagation()
+ e.preventDefault()
+ let fReader = new FileReader()
+ let file = e.dataTransfer.items[0].getAsFile();
+ let that = this;
+ const panx: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
+ const pany: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+ let x = e.pageX - panx
+ let y = e.pageY - pany
+
+ fReader.addEventListener("load", action("drop", (event) => {
+ if (fReader.result) {
+ let url = "" + fReader.result;
+ let doc = Documents.ImageDocument(url, {
+ x: x, y: y
+ })
+ let docs = that.props.dvm.Doc.GetFieldT(KeyStore.Data, ListField);
+ if (!docs) {
+ docs = new ListField<Document>();
+ that.props.dvm.Doc.SetField(KeyStore.Data, docs)
+ }
+ docs.Data.push(doc);
+ }
+ }), false)
+
+ if (file) {
+ fReader.readAsDataURL(file)
+ }
+ }
+
+ @action
+ removeDocument = (doc: Document): void => {
+ const value: Document[] = this.props.dvm.Doc.GetFieldValue(this.props.fieldKey, ListField, [])
+ if (value.indexOf(doc) !== -1) {
+ value.splice(value.indexOf(doc), 1)
+
+ SelectionManager.DeselectAll()
+ ContextMenu.Instance.clearItems()
+ }
+ }
+
+ onDragOver = (e: React.DragEvent): void => {
+ // console.log(e.dataTransfer)
}
render() {
- const { fieldKey, doc } = this.props;
- const value: Document[] = doc.GetFieldValue(fieldKey, ListField, []);
- const panx: number = doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
- const pany: number = doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+ const { fieldKey, dvm } = this.props;
+
+ const value: Document[] = dvm.Doc.GetFieldValue(fieldKey, ListField, []);
+ const panx: number = dvm.Doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
+ const pany: number = dvm.Doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+ const currScale: number = dvm.Doc.GetFieldValue(KeyStore.Scale, NumberField, Number(1));
+ // DocumentDecorations.Instance.forceUpdate()
return (
-
<div className="border" style={{
borderStyle: "solid",
borderWidth: "2px"
@@ -94,11 +159,12 @@ export class CollectionFreeFormView extends React.Component<IProps> {
width: "100%",
height: "calc(100% - 4px)",
overflow: "hidden"
- }}>
- <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px)`, transformOrigin: '50% 50%', width: "100%", height: "100%" }}>
- <div className="node-container" style={{width: "100%", height: "100%"}}>
+ }} onDrop={this.onDrop} onDragOver={this.onDragOver}>
+ <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px) scale(${currScale}, ${currScale})`, transformOrigin: `50%, 50%`}}>
+
+ <div className="node-container">
{value.map(doc => {
- return (<DocumentView key={doc.Id} dvm={new DocumentViewModel(doc)} />);
+ return (<DocumentView key={doc.Id} parent={this} dvm={new DocumentViewModel(doc)} />);
})}
</div>
</div>
diff --git a/src/views/freeformcanvas/FreeFormCanvas.tsx b/src/views/freeformcanvas/FreeFormCanvas.tsx
index de5e88fa1..d7dc0ecaa 100644
--- a/src/views/freeformcanvas/FreeFormCanvas.tsx
+++ b/src/views/freeformcanvas/FreeFormCanvas.tsx
@@ -79,9 +79,9 @@ export class FreeFormCanvas extends React.Component<IProps> {
<div className="freeformcanvas-container" onPointerDown={this.onPointerDown} onWheel={this.onPointerWheel} onContextMenu={(e) => e.preventDefault()}>
<div className="freeformcanvas" style={{ transform: store.Transform, transformOrigin: '50% 50%' }}>
<div className="node-container">
- {this.props.store.Docs.map(doc => {
- return (<DocumentView key={doc.Id} dvm={new DocumentViewModel(doc)} />);
- })}
+ {/* {this.props.store.Docs.map(doc => {
+ return (<DocumentView key={doc.Id} parent={null} dvm={new DocumentViewModel(doc)} />);
+ })} */}
</div>
</div>
</div>
diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx
index e37172943..ee6269430 100644
--- a/src/views/nodes/DocumentView.tsx
+++ b/src/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
import { observer } from "mobx-react";
import React = require("react");
-import { computed, observable } from "mobx";
+import { computed, observable, action } from "mobx";
import { KeyStore, Key } from "../../fields/Key";
import { NumberField } from "../../fields/NumberField";
import { TextField } from "../../fields/TextField";
@@ -12,15 +12,19 @@ import { CollectionFreeFormView } from "../freeformcanvas/CollectionFreeFormView
import "./NodeView.scss"
import { SelectionManager } from "../../util/SelectionManager";
import { DocumentDecorations } from "../../DocumentDecorations";
+import { ContextMenu } from "../ContextMenu";
+import { Opt } from "../../fields/Field";
const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
interface IProps {
dvm: DocumentViewModel;
+ parent: Opt<CollectionFreeFormView>;
}
@observer
export class DocumentView extends React.Component<IProps> {
private _mainCont = React.createRef<HTMLDivElement>();
+ private _contextMenuCanOpen = false;
get mainCont(): React.RefObject<HTMLDivElement> {
return this._mainCont
@@ -91,8 +95,10 @@ export class DocumentView extends React.Component<IProps> {
onPointerDown = (e: React.PointerEvent): void => {
e.stopPropagation();
+
if (e.button === 2) {
this._isPointerDown = true;
+ this._contextMenuCanOpen = true;
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -120,6 +126,7 @@ export class DocumentView extends React.Component<IProps> {
if (!this._isPointerDown) {
return;
}
+ this._contextMenuCanOpen = false
this.x += e.movementX;
this.y += e.movementY;
DocumentDecorations.Instance.opacity = 0
@@ -132,10 +139,39 @@ export class DocumentView extends React.Component<IProps> {
}
}
+ onClick = (e: React.MouseEvent): void => { }
+
+ deleteClicked = (e: React.MouseEvent): void => {
+ if (this.props.parent) {
+ this.props.parent.removeDocument(this.props.dvm.Doc)
+ }
+ }
+
+ @action
+ onContextMenu = (e: React.MouseEvent): void => {
+ e.preventDefault()
+
+ if (!this._contextMenuCanOpen) {
+ return;
+ }
+
+ if (this.props.dvm.IsMainDoc) {
+ ContextMenu.Instance.clearItems()
+ }
+ else {
+ // DocumentViews should stop propogation of this event
+ e.stopPropagation();
+
+ ContextMenu.Instance.clearItems();
+ ContextMenu.Instance.addItem({description: "Delete", event: this.deleteClicked})
+ ContextMenu.Instance.displayMenu(e.pageX, e.pageY)
+ }
+ }
+
render() {
let doc = this.props.dvm.Doc;
let bindings: any = {
- doc: doc,
+ dvm: this.props.dvm,
isSelected: this.selected
};
for (const key of this.layoutKeys) {
@@ -154,11 +190,9 @@ export class DocumentView extends React.Component<IProps> {
width: this.width,
height: this.height,
}}
- onContextMenu={
- (e) => {
- e.preventDefault()
- }}
- onPointerDown={this.onPointerDown}>
+ onContextMenu={this.onContextMenu}
+ onPointerDown={this.onPointerDown}
+ onClick={this.onClick}>
<JsxParser
components={{ FieldTextBox, FreeFormCanvas, CollectionFreeFormView }}
bindings={bindings}
diff --git a/src/views/nodes/FieldTextBox.scss b/src/views/nodes/FieldTextBox.scss
new file mode 100644
index 000000000..5c95fe2b2
--- /dev/null
+++ b/src/views/nodes/FieldTextBox.scss
@@ -0,0 +1,10 @@
+.ProseMirror {
+ margin-top: -1em;
+ width: 100%;
+ height: 100%;
+}
+
+.fieldTextBox-cont {
+ background: white;
+ padding: 1vw;
+} \ No newline at end of file
diff --git a/src/views/nodes/FieldTextBox.tsx b/src/views/nodes/FieldTextBox.tsx
index dbac3906a..3b8743627 100644
--- a/src/views/nodes/FieldTextBox.tsx
+++ b/src/views/nodes/FieldTextBox.tsx
@@ -13,6 +13,8 @@ import {baseKeymap} from "prosemirror-commands"
import {undo, redo, history} from "prosemirror-history"
import { Opt } from "../../fields/Field";
+import "./FieldTextBox.scss"
+
interface IProps {
fieldKey:Key;
doc:Document;
@@ -34,7 +36,6 @@ interface IProps {
// specified Key and assigns it to an HTML input node. When changes are made tot his node,
// this will edit the document and assign the new value to that field.
//
-@observer
export class FieldTextBox extends React.Component<IProps> {
private _ref: React.RefObject<HTMLDivElement>;
private _editorView: Opt<EditorView>;
@@ -112,6 +113,6 @@ export class FieldTextBox extends React.Component<IProps> {
}
render() {
- return (<div ref={this._ref} />)
+ return (<div className="fieldTextBox-cont" ref={this._ref} />)
}
} \ No newline at end of file