diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/DocumentDecorations.scss | 1 | ||||
-rw-r--r-- | src/Main.tsx | 21 | ||||
-rw-r--r-- | src/documents/Documents.ts | 6 | ||||
-rw-r--r-- | src/fields/Key.ts | 1 | ||||
-rw-r--r-- | src/viewmodels/DocumentViewModel.ts | 10 | ||||
-rw-r--r-- | src/views/ContextMenu.scss | 34 | ||||
-rw-r--r-- | src/views/ContextMenu.tsx | 68 | ||||
-rw-r--r-- | src/views/ContextMenuItem.tsx | 17 | ||||
-rw-r--r-- | src/views/freeformcanvas/CollectionFreeFormView.tsx | 96 | ||||
-rw-r--r-- | src/views/freeformcanvas/FreeFormCanvas.tsx | 6 | ||||
-rw-r--r-- | src/views/nodes/DocumentView.tsx | 48 | ||||
-rw-r--r-- | src/views/nodes/FieldTextBox.scss | 10 | ||||
-rw-r--r-- | src/views/nodes/FieldTextBox.tsx | 5 |
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 |