From 0402105238f24785a1229dbbb37f2e4dba958f88 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 29 Jan 2019 10:47:28 -0500 Subject: playing with a docking view. --- src/DocumentDecorations.tsx | 14 +- src/Main.tsx | 19 +- src/documents/Documents.ts | 36 +- src/viewmodels/DocumentViewModel.ts | 11 - src/views/collections/CollectionDockingView.scss | 393 +++++++++++++++++++++ src/views/collections/CollectionDockingView.tsx | 154 ++++++++ src/views/collections/CollectionFreeFormView.scss | 6 + src/views/collections/CollectionFreeFormView.tsx | 207 +++++++++++ .../freeformcanvas/CollectionFreeFormView.scss | 13 - .../freeformcanvas/CollectionFreeFormView.tsx | 216 ----------- src/views/nodes/DocumentView.tsx | 35 +- src/views/nodes/FieldTextBox.tsx | 49 ++- 12 files changed, 857 insertions(+), 296 deletions(-) delete mode 100644 src/viewmodels/DocumentViewModel.ts create mode 100644 src/views/collections/CollectionDockingView.scss create mode 100644 src/views/collections/CollectionDockingView.tsx create mode 100644 src/views/collections/CollectionFreeFormView.scss create mode 100644 src/views/collections/CollectionFreeFormView.tsx delete mode 100644 src/views/freeformcanvas/CollectionFreeFormView.scss delete mode 100644 src/views/freeformcanvas/CollectionFreeFormView.tsx (limited to 'src') diff --git a/src/DocumentDecorations.tsx b/src/DocumentDecorations.tsx index 8145ba732..7899db9c9 100644 --- a/src/DocumentDecorations.tsx +++ b/src/DocumentDecorations.tsx @@ -1,10 +1,10 @@ -import {observable, computed} from "mobx"; +import { observable, computed } from "mobx"; import React = require("react"); -import {DocumentView} from "./views/nodes/DocumentView"; -import {SelectionManager} from "./util/SelectionManager"; -import {observer} from "mobx-react"; +import { DocumentView } from "./views/nodes/DocumentView"; +import { SelectionManager } from "./util/SelectionManager"; +import { observer } from "mobx-react"; import './DocumentDecorations.scss' -import {CollectionFreeFormView} from "./views/freeformcanvas/CollectionFreeFormView"; +import { CollectionFreeFormView } from "./views/collections/CollectionFreeFormView"; @observer export class DocumentDecorations extends React.Component { @@ -20,7 +20,7 @@ export class DocumentDecorations extends React.Component { } @computed - get Bounds(): {x: number, y: number, b: number, r: number} { + get Bounds(): { x: number, y: number, b: number, r: number } { return SelectionManager.SelectedDocuments().reduce((bounds, element) => { var spt = element.TransformToScreenPoint(0, 0); var bpt = element.TransformToScreenPoint(element.width, element.height); @@ -28,7 +28,7 @@ export class DocumentDecorations extends React.Component { x: Math.min(spt.ScreenX, bounds.x), y: Math.min(spt.ScreenY, bounds.y), r: Math.max(bpt.ScreenX, bounds.r), b: Math.max(bpt.ScreenY, bounds.b) } - }, {x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE}); + }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); } @computed diff --git a/src/Main.tsx b/src/Main.tsx index 7560abca7..6ccb940f4 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -12,10 +12,9 @@ import { configure, runInAction, action } from 'mobx'; import { NodeStore } from './stores/NodeStore'; import { Documents } from './documents/Documents'; import { DocumentDecorations } from './DocumentDecorations'; -import { CollectionFreeFormView } from './views/freeformcanvas/CollectionFreeFormView'; +import { CollectionFreeFormView } from './views/collections/CollectionFreeFormView'; import { ListField } from './fields/ListField'; import { DocumentView } from './views/nodes/DocumentView'; -import { DocumentViewModel } from './viewmodels/DocumentViewModel'; import { ContextMenu } from './views/ContextMenu'; configure({ @@ -60,16 +59,20 @@ runInAction(() => { x: 0, y: 400 }); let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { - x: 650, y: 500 + x: 650, y: 500, width: 600, height: 600 + }); + let doc6 = Documents.DockDocument("", { + x: 350, y: 100 }); let mainNodes = mainContainer.GetFieldT(KeyStore.Data, ListField); if (!mainNodes) { mainNodes = new ListField(); mainContainer.SetField(KeyStore.Data, mainNodes); } - mainNodes.Data.push(doc1); - mainNodes.Data.push(doc2); - mainNodes.Data.push(doc4); - mainNodes.Data.push(doc3); - mainNodes.Data.push(doc5); + // mainNodes.Data.push(doc1); + // mainNodes.Data.push(doc2); + // mainNodes.Data.push(doc4); + // mainNodes.Data.push(doc3); + // mainNodes.Data.push(doc5); + mainNodes.Data.push(doc6); }); \ No newline at end of file diff --git a/src/documents/Documents.ts b/src/documents/Documents.ts index 145da7557..a0bd021d8 100644 --- a/src/documents/Documents.ts +++ b/src/documents/Documents.ts @@ -1,9 +1,10 @@ -import {Document} from "../fields/Document"; -import {KeyStore} from "../fields/Key"; -import {TextField} from "../fields/TextField"; -import {NumberField} from "../fields/NumberField"; -import {ListField} from "../fields/ListField"; -import {FieldTextBox} from "../views/nodes/FieldTextBox"; +import { Document } from "../fields/Document"; +import { KeyStore } from "../fields/Key"; +import { TextField } from "../fields/TextField"; +import { NumberField } from "../fields/NumberField"; +import { ListField } from "../fields/ListField"; +import { FieldTextBox } from "../views/nodes/FieldTextBox"; +import { CollectionDockingView } from "../views/collections/CollectionDockingView"; interface DocumentOptions { x?: number; @@ -52,6 +53,29 @@ export namespace Documents { return doc; } + + let dockProto: Document; + function GetDockPrototype(): Document { + if (!dockProto) { + dockProto = new Document(); + dockProto.SetField(KeyStore.X, new NumberField(0)); + dockProto.SetField(KeyStore.Y, new NumberField(0)); + dockProto.SetField(KeyStore.Width, new NumberField(300)); + dockProto.SetField(KeyStore.Height, new NumberField(150)); + dockProto.SetField(KeyStore.Layout, new TextField(CollectionDockingView.LayoutString())); + dockProto.SetField(KeyStore.LayoutKeys, new ListField([ KeyStore.Data ])); + } + return dockProto; + } + + export function DockDocument(text: string, options: DocumentOptions = {}): Document { + let doc = GetDockPrototype().MakeDelegate(); + setupOptions(doc, options); + // doc.SetField(KeyStore.Data, new TextField(text)); + return doc; + } + + let imageProto: Document; function GetImagePrototype(): Document { if (!imageProto) { diff --git a/src/viewmodels/DocumentViewModel.ts b/src/viewmodels/DocumentViewModel.ts deleted file mode 100644 index 008275f3c..000000000 --- a/src/viewmodels/DocumentViewModel.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Document } from "../fields/Document"; - -export class DocumentViewModel { - constructor(private doc: Document) { - - } - - get Doc(): Document { - return this.doc; - } -} \ No newline at end of file diff --git a/src/views/collections/CollectionDockingView.scss b/src/views/collections/CollectionDockingView.scss new file mode 100644 index 000000000..dc29defdc --- /dev/null +++ b/src/views/collections/CollectionDockingView.scss @@ -0,0 +1,393 @@ + +.collectiondockingview-container { + position: relative; + top: 0; + left: 0; + overflow: hidden; + .collectiondockingview { + position: relative; + top: 0; + left: 0;.flexlayout__layout { + left: 0; + top: 0; + right: 0; + bottom: 0; + position: absolute; + overflow:hidden; + } + + .flexlayout__splitter { + background-color: black; + } + + .flexlayout__splitter:hover { + background-color: #333; + } + + .flexlayout__splitter_drag { + border-radius: 5px; + background-color: #444; + z-index: 1000; + } + + .flexlayout__outline_rect { + position: absolute; + cursor: move; + border: 2px solid #cfe8ff; + box-shadow: inset 0 0 60px rgba(0, 0, 0, .2); + border-radius: 5px; + z-index: 1000; + box-sizing: border-box; + } + + .flexlayout__outline_rect_edge { + cursor: move; + border: 2px solid #b7d1b5; + box-shadow: inset 0 0 60px rgba(0, 0, 0, .2); + border-radius: 5px; + z-index: 1000; + box-sizing: border-box; + } + + .flexlayout__edge_rect { + position: absolute; + z-index: 1000; + box-shadow: inset 0 0 5px rgba(0, 0, 0, .2); + background-color: lightgray; + } + + .flexlayout__drag_rect { + position: absolute; + cursor: move; + border: 2px solid #aaaaaa; + box-shadow: inset 0 0 60px rgba(0, 0, 0, .3); + border-radius: 5px; + z-index: 1000; + box-sizing: border-box; + background-color:#eeeeee; + opacity: 0.9; + text-align: center; + + display: flex; + justify-content: center; + flex-direction: column; + overflow:hidden; + padding:10px; + word-wrap: break-word; + } + + .flexlayout__tabset { + overflow: hidden; + background-color: #222; + box-sizing: border-box; + } + + .flexlayout__tab { + overflow: auto; + position:absolute; + box-sizing: border-box; + background-color: #222; + color:white; + } + + .flexlayout__tab_button { + cursor: pointer; + padding: 2px 8px 3px 8px; + margin: 2px; + /*box-shadow: inset 0px 0px 5px rgba(0, 0, 0, .15);*/ + /*border-top-left-radius: 3px;*/ + /*border-top-right-radius: 3px;*/ + float:left; + vertical-align: top; + box-sizing: border-box; + + } + + .flexlayout__tab_button--selected { + color: #ddd; + background-color: #222; + } + + .flexlayout__tab_button--unselected { + color: gray; + } + + .flexlayout__tab_button_leading { + float: left; + display:inline-block; + } + + .flexlayout__tab_button_content { + float: left; + display:inline-block; + } + + .flexlayout__tab_button_textbox { + float: left; + border: none; + color:lightgreen; + background-color:#222; + } + .flexlayout__tab_button_textbox:focus { + outline: none; + } + + .flexlayout__tab_button_trailing { + float: left; + display:inline-block; + margin-left:5px; + margin-top:3px; + width:8px; + height:8px; + } + + .flexlayout__tab_button:hover .flexlayout__tab_button_trailing, + .flexlayout__tab_button--selected .flexlayout__tab_button_trailing{ + //background: transparent url("../images/close_white.png") no-repeat center; + } + + .flexlayout__tab_button_overflow { + float: left; + width: 20px; + height:15px; + margin-top:2px; + padding-left:12px; + border:none; + font-size: 10px; + color:lightgray; + font-family: Arial, sans-serif; + //background: transparent url("../images/more.png") no-repeat left; + } + + .flexlayout__tabset_header + { + position: absolute; + left: 0; + right: 0; + color:#eee; + background-color: #212121; + padding:3px 3px 3px 5px; + /*box-shadow: inset 0px 0px 3px 0px rgba(136, 136, 136, 0.54);*/ + box-sizing: border-box; + } + + .flexlayout__tab_header_inner { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 10000px; + } + + .flexlayout__tab_header_outer { + background-color: black; + position: absolute; + left: 0; + right: 0; + /*top: 0px;*/ + /*height: 100px;*/ + overflow: hidden; + } + + .flexlayout__tabset-selected + { + //background-image: linear-gradient(#111, #444); + } + + .flexlayout__tabset-maximized + { + //background-image: linear-gradient(#666, #333); + } + + .flexlayout__tab_toolbar { + position:absolute; + display:flex; + flex-direction: row-reverse; + align-items: center; + top:0; + bottom:0; + right:0; + } + + .flexlayout__tab_toolbar_button-min { + width:20px; + height:20px; + border:none; + outline-width: 0; + //background: transparent url("../images/maximize.png") no-repeat center; + + } + .flexlayout__tab_toolbar_button-max { + width:20px; + height:20px; + border:none; + outline-width: 0; + //background: transparent url("../images/restore.png") no-repeat center; + } + + .flexlayout__popup_menu { + } + + .flexlayout__popup_menu_item { + padding: 2px 10px 2px 10px; + color:#ddd; + } + + .flexlayout__popup_menu_item:hover { + background-color: #444444; + } + + .flexlayout__popup_menu_container { + box-shadow: inset 0 0 5px rgba(0, 0, 0, .15); + border: 1px solid #555; + background: #222; + border-radius:3px; + position:absolute; + z-index:1000; + } + + + .flexlayout__border_top { + background-color:black; + border-bottom: 1px solid #ddd; + box-sizing: border-box; + overflow:hidden; + } + + .flexlayout__border_bottom { + background-color:black; + border-top: 1px solid #333; + box-sizing: border-box; + overflow:hidden; + } + .flexlayout__border_left { + background-color:black; + border-right: 1px solid #333; + box-sizing: border-box; + overflow:hidden; + } + + .flexlayout__border_right { + background-color:black; + border-left: 1px solid #333; + box-sizing: border-box; + overflow:hidden; + } + + .flexlayout__border_inner_bottom{ + display: flex; + } + + .flexlayout__border_inner_left { + position:absolute; + white-space: nowrap; + right: 23px; + transform-origin: top right; + transform: rotate(-90deg); + } + + .flexlayout__border_inner_right { + position:absolute; + white-space: nowrap; + left: 23px; + transform-origin: top left; + transform: rotate(90deg); + } + + .flexlayout__border_button { + background-color: #222; + color:white; + display:inline-block; + white-space:nowrap; + + cursor: pointer; + padding: 2px 8px 3px 8px; + margin: 2px; + vertical-align: top; + box-sizing: border-box; + } + + .flexlayout__border_button--selected { + color: #ddd; + background-color: #222; + } + + .flexlayout__border_button--unselected { + color: gray; + } + + .flexlayout__border_button_leading { + float: left; + display:inline; + } + + .flexlayout__border_button_content { + display:inline-block; + } + + .flexlayout__border_button_textbox { + float: left; + border: none; + color:green; + background-color:#ddd; + } + .flexlayout__border_button_textbox:focus { + outline: none; + } + + .flexlayout__border_button_trailing { + display:inline-block; + margin-left:5px; + margin-top:3px; + width:8px; + height:8px; + } + + .flexlayout__border_button:hover .flexlayout__border_button_trailing, + .flexlayout__border_button--selected .flexlayout__border_button_trailing{ + //background: transparent url("../images/close_white.png") no-repeat center; + } + + + .flexlayout__border_toolbar_left { + position:absolute; + display: flex; + flex-direction: column-reverse; + align-items: center; + bottom:0; + left:0; + right:0; + } + + .flexlayout__border_toolbar_right { + position:absolute; + display: flex; + flex-direction: column-reverse; + align-items: center; + bottom:0; + left:0; + right:0; + } + + .flexlayout__border_toolbar_top { + position:absolute; + display: flex; + flex-direction: row-reverse; + align-items: center; + top:0; + bottom:0; + right:0; + } + + .flexlayout__border_toolbar_bottom { + position:absolute; + display: flex; + flex-direction: row-reverse; + align-items: center; + top:0; + bottom:0; + right:0; + } + + } +} \ No newline at end of file diff --git a/src/views/collections/CollectionDockingView.tsx b/src/views/collections/CollectionDockingView.tsx new file mode 100644 index 000000000..51323e94b --- /dev/null +++ b/src/views/collections/CollectionDockingView.tsx @@ -0,0 +1,154 @@ +import { observer } from "mobx-react"; +import { Key, KeyStore } from "../../fields/Key"; +import React = require("react"); +import FlexLayout from "flexlayout-react"; +import { action, observable, computed } from "mobx"; +import { Document } from "../../fields/Document"; +import { DocumentView, DocumentFieldViewProps, CollectionViewProps } from "../nodes/DocumentView"; +import { ListField } from "../../fields/ListField"; +import { NumberField } from "../../fields/NumberField"; +import { SSL_OP_SINGLE_DH_USE } from "constants"; +import { SelectionManager } from "../../util/SelectionManager"; +import { ContextMenu } from "../ContextMenu"; +import "./CollectionDockingView.scss" + + +@observer +export class CollectionDockingView extends React.Component { + + public static LayoutString() { return ''; } + + + private _times: number = 0; + private _containerRef = React.createRef(); + private _canvasRef = React.createRef(); + private _json = { + global: {}, + borders: [], + layout: { + "type": "row", + "weight": 100, + "children": [ + { + "type": "tabset", + "weight": 50, + "selected": 0, + "children": [ + { + "type": "tab", + "name": "CHILD #1", + "component": "grid", + } + ] + }, + { + "type": "tabset", + "weight": 50, + "selected": 0, + "children": [ + { + "type": "tab", + "name": "CHILD #2", + "component": "grid", + } + ] + } + ] + } + }; + private _model: any; + constructor(props: CollectionViewProps) { + super(props); + this._model = FlexLayout.Model.fromJson(this._json); + } + + public static BORDER_WIDTH = 2; + + @computed + public get active(): boolean { + var isSelected = (this.props.ContainingDocumentView != undefined && SelectionManager.IsSelected(this.props.ContainingDocumentView)); + var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this); + var topMost = this.props.ContainingDocumentView != undefined && this.props.ContainingDocumentView.props.ContainingCollectionView == undefined; + return isSelected || childSelected || topMost; + } + + componentDidMount() { + // if (this._containerRef.current) { + // DragManager.MakeDropTarget(this._containerRef.current, { + // handlers: { + // drop: this.drop + // } + // }); + // } + } + + + @action + addDocument = (doc: Document): void => { + //TODO This won't create the field if it doesn't already exist + const value = this.props.Document.GetFieldValue(this.props.fieldKey, ListField, new Array()) + value.push(doc); + } + + @action + removeDocument = (doc: Document): void => { + //TODO This won't create the field if it doesn't already exist + const value = this.props.Document.GetFieldValue(this.props.fieldKey, ListField, new Array()) + if (value.indexOf(doc) !== -1) { + value.splice(value.indexOf(doc), 1) + + SelectionManager.DeselectAll() + ContextMenu.Instance.clearItems() + } + } + @action + onPointerDown = (e: React.PointerEvent): void => { + if (e.button === 2 && this.active) { + e.stopPropagation(); + e.preventDefault(); + } else { + if (e.buttons === 1 && SelectionManager.IsSelected(this.props.ContainingDocumentView!)) { + e.stopPropagation(); + } + } + } + factory = (node: any): any => { + var component = node.getComponent(); + if (component === "button") { + return ; + } + if (component === "grid") { + let which = this._times++ % 3; + if (which == 0) + return
+ if (which == 1) + return
+ if (which == 2) + return
+ } + } + + render() { + const { fieldKey, Document: Document } = this.props; + + const value: Document[] = Document.GetFieldValue(fieldKey, ListField, []); + const panx: number = Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); + const pany: number = Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)); + const currScale: number = Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1)); + return ( +
+
e.preventDefault()} style={{ + width: "100%", + height: `calc(100% - 2*${CollectionDockingView.BORDER_WIDTH}px)`, + }} ref={this._containerRef}> +
+ +
+
+
+ ); + } +} \ No newline at end of file diff --git a/src/views/collections/CollectionFreeFormView.scss b/src/views/collections/CollectionFreeFormView.scss new file mode 100644 index 000000000..31e9985bc --- /dev/null +++ b/src/views/collections/CollectionFreeFormView.scss @@ -0,0 +1,6 @@ +.collectionfreeformview-container { + position: relative; + top: 0; + left: 0; + overflow: hidden; +} \ No newline at end of file diff --git a/src/views/collections/CollectionFreeFormView.tsx b/src/views/collections/CollectionFreeFormView.tsx new file mode 100644 index 000000000..479d883d6 --- /dev/null +++ b/src/views/collections/CollectionFreeFormView.tsx @@ -0,0 +1,207 @@ +import { observer } from "mobx-react"; +import { Key, KeyStore } from "../../fields/Key"; +import React = require("react"); +import { action, observable, computed } from "mobx"; +import { Document } from "../../fields/Document"; +import { DocumentView, CollectionViewProps } from "../nodes/DocumentView"; +import { ListField } from "../../fields/ListField"; +import { NumberField } from "../../fields/NumberField"; +import { SSL_OP_SINGLE_DH_USE } from "constants"; +import { SelectionManager } from "../../util/SelectionManager"; +import { Documents } from "../../documents/Documents"; +import { ContextMenu } from "../ContextMenu"; +import { DragManager } from "../../util/DragManager"; +import "./CollectionFreeFormView.scss"; +import { Utils } from "../../Utils"; + +@observer +export class CollectionFreeFormView extends React.Component { + private _containerRef = React.createRef(); + private _canvasRef = React.createRef(); + + constructor(props: CollectionViewProps) { + super(props); + } + + public static BORDER_WIDTH = 2; + + @computed + public get active(): boolean { + var isSelected = (this.props.ContainingDocumentView != undefined && SelectionManager.IsSelected(this.props.ContainingDocumentView)); + var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this); + var topMost = this.props.ContainingDocumentView != undefined && this.props.ContainingDocumentView.props.ContainingCollectionView == undefined; + return isSelected || childSelected || topMost; + } + + drop = (e: Event, de: DragManager.DropEvent) => { + const ele = this._canvasRef.current; + if (!ele) { + return; + } + const doc = de.data[ "document" ]; + const xOffset = de.data[ "xOffset" ] as number || 0; + const yOffset = de.data[ "yOffset" ] as number || 0; + if (doc instanceof DocumentView) { + if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) { + doc.props.ContainingCollectionView.removeDocument(doc.props.Document); + this.addDocument(doc.props.Document); + } + const { scale, translateX, translateY } = Utils.GetScreenTransform(ele); + const screenX = de.x - xOffset; + const screenY = de.y - yOffset; + const docX = (screenX - translateX) / scale; + const docY = (screenY - translateY) / scale; + doc.x = docX; + doc.y = docY; + } + e.stopPropagation(); + } + + componentDidMount() { + if (this._containerRef.current) { + DragManager.MakeDropTarget(this._containerRef.current, { + handlers: { + drop: this.drop + } + }); + } + } + + _lastX: number = 0; + _lastY: number = 0; + @action + onPointerDown = (e: React.PointerEvent): void => { + if (e.button === 2 && this.active) { + e.stopPropagation(); + e.preventDefault(); + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + this._lastX = e.pageX; + this._lastY = e.pageY; + } + } + + @action + onPointerUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + e.stopPropagation(); + } + + @action + onPointerMove = (e: PointerEvent): void => { + if (!e.cancelBubble) { + e.preventDefault(); + e.stopPropagation(); + let currScale: number = this.props.ContainingDocumentView!.ScalingToScreenSpace; + let x = this.props.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); + let y = this.props.Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)); + this.props.Document.SetFieldValue(KeyStore.PanX, x + (e.pageX - this._lastX) / currScale, NumberField); + this.props.Document.SetFieldValue(KeyStore.PanY, y + (e.pageY - this._lastY) / currScale, NumberField); + this._lastX = e.pageX; + this._lastY = e.pageY; + } + } + + @action + onPointerWheel = (e: React.WheelEvent): void => { + e.stopPropagation(); + + let { LocalX, Ss, W, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY } = this.props.ContainingDocumentView!.TransformToLocalPoint(e.pageX, e.pageY); + + var deltaScale = (1 - (e.deltaY / 1000)) * Ss; + + var newContainerX = LocalX * deltaScale + W / 2 - W / 2 * deltaScale + Panxx + Xx; + var newContainerY = LocalY * deltaScale + Panyy + Yy; + + let dx = ContainerX - newContainerX; + let dy = ContainerY - newContainerY; + + this.props.Document.SetField(KeyStore.Scale, new NumberField(deltaScale)); + this.props.Document.SetFieldValue(KeyStore.PanX, Panxx + dx, NumberField); + this.props.Document.SetFieldValue(KeyStore.PanY, Panyy + dy, NumberField); + } + + 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.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); + const pany: number = this.props.Document.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.Document.GetFieldT(KeyStore.Data, ListField); + if (!docs) { + docs = new ListField(); + that.props.Document.SetField(KeyStore.Data, docs) + } + docs.Data.push(doc); + } + }), false) + + if (file) { + fReader.readAsDataURL(file) + } + } + + @action + addDocument = (doc: Document): void => { + //TODO This won't create the field if it doesn't already exist + const value = this.props.Document.GetFieldValue(this.props.fieldKey, ListField, new Array()) + value.push(doc); + } + + @action + removeDocument = (doc: Document): void => { + //TODO This won't create the field if it doesn't already exist + const value = this.props.Document.GetFieldValue(this.props.fieldKey, ListField, new Array()) + if (value.indexOf(doc) !== -1) { + value.splice(value.indexOf(doc), 1) + + SelectionManager.DeselectAll() + ContextMenu.Instance.clearItems() + } + } + + onDragOver = (e: React.DragEvent): void => { + } + render() { + const { fieldKey, Document: Document } = this.props; + + const value: Document[] = Document.GetFieldValue(fieldKey, ListField, []); + const panx: number = Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); + const pany: number = Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)); + const currScale: number = Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1)); + return ( +
+
e.preventDefault()} style={{ + width: "100%", + height: `calc(100% - 2*${CollectionFreeFormView.BORDER_WIDTH}px)`, + }} onDrop={this.onDrop} onDragOver={this.onDragOver} ref={this._containerRef}> +
+ +
+ {value.map(doc => { + return (); + })} +
+
+
+
+ ); + } +} \ No newline at end of file diff --git a/src/views/freeformcanvas/CollectionFreeFormView.scss b/src/views/freeformcanvas/CollectionFreeFormView.scss deleted file mode 100644 index 6ad6ad86c..000000000 --- a/src/views/freeformcanvas/CollectionFreeFormView.scss +++ /dev/null @@ -1,13 +0,0 @@ -.collectionfreeformview-container { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - overflow: hidden; - .collectionfreeformview { - position: absolute; - top: 0; - left: 0; - } -} \ No newline at end of file diff --git a/src/views/freeformcanvas/CollectionFreeFormView.tsx b/src/views/freeformcanvas/CollectionFreeFormView.tsx deleted file mode 100644 index 00b912294..000000000 --- a/src/views/freeformcanvas/CollectionFreeFormView.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import {observer} from "mobx-react"; -import {Key, KeyStore} from "../../fields/Key"; -import React = require("react"); -import {action, observable, computed} from "mobx"; -import {Document} from "../../fields/Document"; -import {DocumentViewModel} from "../../viewmodels/DocumentViewModel"; -import {DocumentView} from "../nodes/DocumentView"; -import {ListField} from "../../fields/ListField"; -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"; -import {Opt} from "../../fields/Field"; -import {DragManager} from "../../util/DragManager"; -import {Utils} from "../../Utils"; - -interface IProps { - fieldKey: Key; - Document: Document; - ContainingDocumentView: Opt; -} - -@observer -export class CollectionFreeFormView extends React.Component { - private _containerRef = React.createRef(); - private _canvasRef = React.createRef(); - - constructor(props: IProps) { - super(props); - } - - public static BORDER_WIDTH = 2; - - @computed - public get active(): boolean { - var isSelected = (this.props.ContainingDocumentView != undefined && SelectionManager.IsSelected(this.props.ContainingDocumentView)); - var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this); - var topMost = this.props.ContainingDocumentView != undefined && this.props.ContainingDocumentView.props.ContainingCollectionView == undefined; - return isSelected || childSelected || topMost; - } - - drop = (e: Event, de: DragManager.DropEvent) => { - const ele = this._canvasRef.current; - if (!ele) { - return; - } - const doc = de.data[ "document" ]; - const xOffset = de.data[ "xOffset" ] as number || 0; - const yOffset = de.data[ "yOffset" ] as number || 0; - if (doc instanceof DocumentView) { - if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) { - doc.props.ContainingCollectionView.removeDocument(doc.props.Document); - this.addDocument(doc.props.Document); - } - const {scale, translateX, translateY} = Utils.GetScreenTransform(ele); - const screenX = de.x - xOffset; - const screenY = de.y - yOffset; - const docX = (screenX - translateX) / scale; - const docY = (screenY - translateY) / scale; - doc.x = docX; - doc.y = docY; - } - e.stopPropagation(); - } - - componentDidMount() { - if (this._containerRef.current) { - DragManager.MakeDropTarget(this._containerRef.current, { - handlers: { - drop: this.drop - } - }); - } - } - - _lastX: number = 0; - _lastY: number = 0; - @action - onPointerDown = (e: React.PointerEvent): void => { - if (e.button === 2 && this.active) { - e.stopPropagation(); - e.preventDefault(); - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - this._lastX = e.pageX; - this._lastY = e.pageY; - } - } - - @action - onPointerUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); - } - - @action - onPointerMove = (e: PointerEvent): void => { - if (!e.cancelBubble) { - e.preventDefault(); - e.stopPropagation(); - let currScale: number = this.props.ContainingDocumentView!.ScalingToScreenSpace; - let x = this.props.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); - let y = this.props.Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)); - this.props.Document.SetFieldValue(KeyStore.PanX, x + (e.pageX - this._lastX) / currScale, NumberField); - this.props.Document.SetFieldValue(KeyStore.PanY, y + (e.pageY - this._lastY) / currScale, NumberField); - this._lastX = e.pageX; - this._lastY = e.pageY; - } - } - - @action - onPointerWheel = (e: React.WheelEvent): void => { - e.stopPropagation(); - - let {LocalX, Ss, W, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY} = this.props.ContainingDocumentView!.TransformToLocalPoint(e.pageX, e.pageY); - - var deltaScale = (1 - (e.deltaY / 1000)) * Ss; - - var newContainerX = LocalX * deltaScale + W / 2 - W / 2 * deltaScale + Panxx + Xx; - var newContainerY = LocalY * deltaScale + Panyy + Yy; - - let dx = ContainerX - newContainerX; - let dy = ContainerY - newContainerY; - - this.props.Document.SetField(KeyStore.Scale, new NumberField(deltaScale)); - this.props.Document.SetFieldValue(KeyStore.PanX, Panxx + dx, NumberField); - this.props.Document.SetFieldValue(KeyStore.PanY, Panyy + dy, NumberField); - } - - 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.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); - const pany: number = this.props.Document.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.Document.GetFieldT(KeyStore.Data, ListField); - if (!docs) { - docs = new ListField(); - that.props.Document.SetField(KeyStore.Data, docs) - } - docs.Data.push(doc); - } - }), false) - - if (file) { - fReader.readAsDataURL(file) - } - } - - @action - addDocument = (doc: Document): void => { - //TODO This won't create the field if it doesn't already exist - const value = this.props.Document.GetFieldValue(this.props.fieldKey, ListField, new Array()) - value.push(doc); - } - - @action - removeDocument = (doc: Document): void => { - //TODO This won't create the field if it doesn't already exist - const value = this.props.Document.GetFieldValue(this.props.fieldKey, ListField, new Array()) - if (value.indexOf(doc) !== -1) { - value.splice(value.indexOf(doc), 1) - - SelectionManager.DeselectAll() - ContextMenu.Instance.clearItems() - } - } - - onDragOver = (e: React.DragEvent): void => { - } - render() { - const {fieldKey, Document: Document} = this.props; - - const value: Document[] = Document.GetFieldValue(fieldKey, ListField, []); - const panx: number = Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); - const pany: number = Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)); - const currScale: number = Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1)); - return ( -
-
e.preventDefault()} style={{ - width: "100%", - height: `calc(100% - 2*${CollectionFreeFormView.BORDER_WIDTH}px)`, - overflow: "hidden" - }} onDrop={this.onDrop} onDragOver={this.onDragOver} ref={this._containerRef}> -
- -
- {value.map(doc => { - return (); - })} -
-
-
-
- ); - } -} \ No newline at end of file diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx index 6e874c5a8..3ee70213f 100644 --- a/src/views/nodes/DocumentView.tsx +++ b/src/views/nodes/DocumentView.tsx @@ -4,11 +4,11 @@ import { computed, observable, action } from "mobx"; import { KeyStore, Key } from "../../fields/Key"; import { NumberField } from "../../fields/NumberField"; import { TextField } from "../../fields/TextField"; -import { DocumentViewModel } from "../../viewmodels/DocumentViewModel"; import { ListField } from "../../fields/ListField"; import { FieldTextBox } from "../nodes/FieldTextBox" import { Document } from "../../fields/Document"; -import { CollectionFreeFormView } from "../freeformcanvas/CollectionFreeFormView" +import { CollectionFreeFormView } from "../collections/CollectionFreeFormView" +import { CollectionDockingView } from "../collections/CollectionDockingView" import "./NodeView.scss" import { SelectionManager } from "../../util/SelectionManager"; import { DocumentDecorations } from "../../DocumentDecorations"; @@ -17,14 +17,35 @@ import { Opt } from "../../fields/Field"; import { DragManager } from "../../util/DragManager"; const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this? -interface IProps { +interface DocumentViewProps { Document: Document; - ContainingCollectionView: Opt; + ContainingCollectionView: Opt; ContainingDocumentView: Opt } +export interface CollectionViewProps { + fieldKey: Key; + Document: Document; + ContainingDocumentView: Opt; +} + +// these properties are set via the render() method of the DocumentView when it creates this node. +// However, these properties are set below in the LayoutString() static method +export interface DocumentFieldViewProps { + fieldKey: Key; + doc: Document; + containingDocumentView: DocumentView +} + +interface CollectionView { + addDocument: (doc: Document) => void; + removeDocument: (doc: Document) => void; + active: boolean; + props: CollectionViewProps; +} + @observer -class DocumentContents extends React.Component { +class DocumentContents extends React.Component { @computed get layout(): string { @@ -53,7 +74,7 @@ class DocumentContents extends React.Component { } } return { } @observer -export class DocumentView extends React.Component { +export class DocumentView extends React.Component { private _mainCont = React.createRef(); private _contextMenuCanOpen = false; private _downX: number = 0; diff --git a/src/views/nodes/FieldTextBox.tsx b/src/views/nodes/FieldTextBox.tsx index 8568d04c5..20dfde1d2 100644 --- a/src/views/nodes/FieldTextBox.tsx +++ b/src/views/nodes/FieldTextBox.tsx @@ -1,30 +1,23 @@ -import {Key, KeyStore} from "../../fields/Key"; -import {Document} from "../../fields/Document"; -import {observer} from "mobx-react"; -import {TextField} from "../../fields/TextField"; +import { Key, KeyStore } from "../../fields/Key"; +import { Document } from "../../fields/Document"; +import { observer } from "mobx-react"; +import { TextField } from "../../fields/TextField"; import React = require("react") -import {action, observable, reaction, IReactionDisposer} from "mobx"; +import { action, observable, reaction, IReactionDisposer } from "mobx"; -import {schema} from "prosemirror-schema-basic"; -import {EditorState, Transaction} from "prosemirror-state" -import {EditorView} from "prosemirror-view" -import {keymap} from "prosemirror-keymap" -import {baseKeymap} from "prosemirror-commands" -import {undo, redo, history} from "prosemirror-history" -import {Opt} from "../../fields/Field"; +import { schema } from "prosemirror-schema-basic"; +import { EditorState, Transaction } from "prosemirror-state" +import { EditorView } from "prosemirror-view" +import { keymap } from "prosemirror-keymap" +import { baseKeymap } from "prosemirror-commands" +import { undo, redo, history } from "prosemirror-history" +import { Opt } from "../../fields/Field"; import "./FieldTextBox.scss" -import {DocumentView} from "./DocumentView"; -import {SelectionManager} from "../../util/SelectionManager"; +import { DocumentFieldViewProps } from "./DocumentView"; +import { SelectionManager } from "../../util/SelectionManager"; -// these properties are set via the render() method of the DocumentView when it creates this node. -// However, these properties are set below in the LayoutString() static method -interface IProps { - fieldKey: Key; - doc: Document; - containingDocumentView: DocumentView -} // FieldTextBox: Displays an editable plain text node that maps to a specified Key of a Document // @@ -42,14 +35,14 @@ 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. // -export class FieldTextBox extends React.Component { +export class FieldTextBox extends React.Component { - public static LayoutString() {return "";} + public static LayoutString() { return ""; } private _ref: React.RefObject; private _editorView: Opt; private _reactionDisposer: Opt; - constructor(props: IProps) { + constructor(props: DocumentFieldViewProps) { super(props); this._ref = React.createRef(); @@ -61,19 +54,19 @@ export class FieldTextBox extends React.Component { if (this._editorView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - const {doc, fieldKey} = this.props; + const { doc, fieldKey } = this.props; doc.SetFieldValue(fieldKey, JSON.stringify(state.toJSON()), TextField); } } componentDidMount() { let state: EditorState; - const {doc, fieldKey} = this.props; + const { doc, fieldKey } = this.props; const config = { schema, plugins: [ history(), - keymap({"Mod-z": undo, "Mod-y": redo}), + keymap({ "Mod-z": undo, "Mod-y": redo }), keymap(baseKeymap) ] }; @@ -116,7 +109,7 @@ export class FieldTextBox extends React.Component { @action onChange(e: React.ChangeEvent) { - const {fieldKey, doc} = this.props; + const { fieldKey, doc } = this.props; doc.SetFieldValue(fieldKey, e.target.value, TextField); } onPointerDown = (e: React.PointerEvent): void => { -- cgit v1.2.3-70-g09d2