diff options
Diffstat (limited to 'src/views')
| -rw-r--r-- | src/views/collections/CollectionDockingView.scss | 386 | ||||
| -rw-r--r-- | src/views/collections/CollectionDockingView.tsx | 202 | ||||
| -rw-r--r-- | src/views/collections/CollectionFreeFormView.scss (renamed from src/views/freeformcanvas/CollectionFreeFormView.scss) | 4 | ||||
| -rw-r--r-- | src/views/collections/CollectionFreeFormView.tsx (renamed from src/views/freeformcanvas/CollectionFreeFormView.tsx) | 72 | ||||
| -rw-r--r-- | src/views/nodes/DocumentView.tsx | 94 | ||||
| -rw-r--r-- | src/views/nodes/FieldTextBox.tsx | 22 | ||||
| -rw-r--r-- | src/views/nodes/TextNodeView.tsx | 8 |
7 files changed, 696 insertions, 92 deletions
diff --git a/src/views/collections/CollectionDockingView.scss b/src/views/collections/CollectionDockingView.scss new file mode 100644 index 000000000..43af7c538 --- /dev/null +++ b/src/views/collections/CollectionDockingView.scss @@ -0,0 +1,386 @@ + +.collectiondockingview-container { + position: relative; + top: 0; + left: 0; + overflow: hidden; + .flexlayout__layout { + width: 100%; + height: 100%; + position: relative; + 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; + } + + .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..3d0c39c9d --- /dev/null +++ b/src/views/collections/CollectionDockingView.tsx @@ -0,0 +1,202 @@ +import { observer } from "mobx-react"; +import { 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, 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" +import 'golden-layout/src/css/goldenlayout-base.css'; +import 'golden-layout/src/css/goldenlayout-dark-theme.css'; +import * as GoldenLayout from "golden-layout"; +import * as ReactDOM from 'react-dom'; + +@observer +export class CollectionDockingView extends React.Component<CollectionViewProps> { + + private static UseGoldenLayout = true; + public static LayoutString() { return '<CollectionDockingView Document={Document} fieldKey={DataKey} ContainingDocumentView={ContainingDocumentView}/>'; } + private _containerRef = React.createRef<HTMLDivElement>(); + @computed + private get modelForFlexLayout() { + const { fieldKey, Document: Document } = this.props; + const value: Document[] = Document.GetFieldValue(fieldKey, ListField, []); + var docs = value.map(doc => { + return { type: 'tabset', weight: 50, selected: 0, children: [ { type: "tab", name: doc.Title, component: doc.Id } ] }; + }); + return FlexLayout.Model.fromJson({ + global: {}, borders: [], + layout: { + "type": "row", + "weight": 100, + "children": docs + } + }); + } + @computed + private get modelForGoldenLayout(): any { + const { fieldKey, Document: Document } = this.props; + const value: Document[] = Document.GetFieldValue(fieldKey, ListField, []); + var docs = value.map(doc => { + return { type: 'component', componentName: 'documentViewComponent', componentState: { doc: doc } }; + }); + return new GoldenLayout({ content: [ { type: 'row', content: docs } ] }); + } + constructor(props: CollectionViewProps) { + super(props); + } + + public static BORDER_WIDTH = 2; + public static TAB_HEADER_HEIGHT = 20; + + @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: () => void = () => { + if (this._containerRef.current && CollectionDockingView.UseGoldenLayout) { + this.goldenLayoutFactory(); + } + } + private nextId = (function () { var _next_id = 0; return function () { return _next_id++; } })(); + + @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<Document>()) + 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<Document>()) + 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 && this.active) { + e.stopPropagation(); + } + } + } + + flexLayoutFactory = (node: any): any => { + var component = node.getComponent(); + if (component === "button") { + return <button>{node.getName()}</button>; + } + const { fieldKey, Document: Document } = this.props; + const value: Document[] = Document.GetFieldValue(fieldKey, ListField, []); + for (var i: number = 0; i < value.length; i++) { + if (value[ i ].Id === component) { + return (<DocumentView key={value[ i ].Id} ContainingCollectionView={this} Document={value[ i ]} ContainingDocumentView={this.props.ContainingDocumentView} />); + } + } + if (component === "text") { + return (<div className="panel">Panel {node.getName()}</div>); + } + } + + goldenLayoutFactory() { + var myLayout = this.modelForGoldenLayout; + + myLayout.on('stackCreated', function (stack: any) { + stack.header.controlsContainer.find('.lm_close') //get the close icon + .off('click') //unbind the current click handler + .click(function () { + if (confirm('really close this?')) { + stack.remove(); + } + }); + }); + + myLayout.on('tabCreated', function (tab: any) { + tab.setTitle(tab.contentItem.config.componentState.doc.Title); + tab.closeElement.off('click') //unbind the current click handler + .click(function () { + if (confirm('really close this?')) { + tab.contentItem.remove(); + } + }); + }); + + var me = this; + myLayout.registerComponent('documentViewComponent', function (container: any, state: any) { + // bcz: this is crufty + // calling html() causes a div tag to be added in the DOM with id 'containingDiv'. + // Apparently, we need to wait to allow a live html div element to actually be instantiated. + // After a timeout, we lookup the live html div element and add our React DocumentView to it. + var containingDiv = "component_" + me.nextId(); + container.getElement().html("<div id='" + containingDiv + "'></div>"); + setTimeout(function () { + ReactDOM.render(( + <DocumentView key={state.doc.Id} Document={state.doc} ContainingCollectionView={me} ContainingDocumentView={me.props.ContainingDocumentView} /> + ), + document.getElementById(containingDiv) + ) + }, 0); + }); + myLayout.container = this._containerRef.current; + myLayout.init(); + } + + + render() { + const { fieldKey, Document: Document } = this.props; + const value: Document[] = Document.GetFieldValue(fieldKey, ListField, []); + // bcz: not sure why, but I need these to force the flexlayout to update when the collection size changes. + var s = this.props.ContainingDocumentView!.ScalingToScreenSpace; + var w = Document.GetFieldValue(KeyStore.Width, NumberField, Number(0)) / s; + var h = Document.GetFieldValue(KeyStore.Height, NumberField, Number(0)) / s; + + if (CollectionDockingView.UseGoldenLayout) { + return ( + <div className="border" style={{ + borderStyle: "solid", + borderWidth: `${CollectionDockingView.BORDER_WIDTH}px`, + }}> + <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} ref={this._containerRef} + style={{ + width: "100%", + height: "100%" + }} > + </div> + </div> + ); + } else { + return ( + <div className="border" style={{ + borderStyle: "solid", + borderWidth: `${CollectionDockingView.BORDER_WIDTH}px`, + }}> + <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} ref={this._containerRef} + style={{ + width: s > 1 ? "100%" : w - 2 * CollectionDockingView.BORDER_WIDTH, + height: s > 1 ? "100%" : h - 2 * CollectionDockingView.BORDER_WIDTH + }} > + <FlexLayout.Layout model={this.modelForFlexLayout} factory={this.flexLayoutFactory} /> + </div> + </div> + ); + } + } +}
\ No newline at end of file diff --git a/src/views/freeformcanvas/CollectionFreeFormView.scss b/src/views/collections/CollectionFreeFormView.scss index 6ad6ad86c..870e48556 100644 --- a/src/views/freeformcanvas/CollectionFreeFormView.scss +++ b/src/views/collections/CollectionFreeFormView.scss @@ -1,9 +1,7 @@ .collectionfreeformview-container { - position: absolute; + position: relative; top: 0; left: 0; - width: 100%; - height: 100%; overflow: hidden; .collectionfreeformview { position: absolute; diff --git a/src/views/freeformcanvas/CollectionFreeFormView.tsx b/src/views/collections/CollectionFreeFormView.tsx index 00b912294..736bcb786 100644 --- a/src/views/freeformcanvas/CollectionFreeFormView.tsx +++ b/src/views/collections/CollectionFreeFormView.tsx @@ -1,33 +1,26 @@ -import {observer} from "mobx-react"; -import {Key, KeyStore} from "../../fields/Key"; +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<DocumentView>; -} +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"; +import { CollectionDockingView } from "./CollectionDockingView"; @observer -export class CollectionFreeFormView extends React.Component<IProps> { +export class CollectionFreeFormView extends React.Component<CollectionViewProps> { private _containerRef = React.createRef<HTMLDivElement>(); private _canvasRef = React.createRef<HTMLDivElement>(); - constructor(props: IProps) { + constructor(props: CollectionViewProps) { super(props); } @@ -37,30 +30,24 @@ export class CollectionFreeFormView extends React.Component<IProps> { 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; + var topMost = this.props.ContainingDocumentView != undefined && ( + this.props.ContainingDocumentView.props.ContainingCollectionView == undefined || + this.props.ContainingDocumentView.props.ContainingCollectionView instanceof CollectionDockingView); 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; + const xOffset = de.data[ "xOffset" ] as number || 0; + const yOffset = de.data[ "yOffset" ] as number || 0; + let { LocalX, LocalY } = this.props.ContainingDocumentView!.TransformToLocalPoint(de.x - xOffset, de.y - yOffset); + doc.x = LocalX; + doc.y = LocalY; } e.stopPropagation(); } @@ -117,11 +104,11 @@ export class CollectionFreeFormView extends React.Component<IProps> { 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); + let { LocalX, Ss, 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 newContainerX = LocalX * deltaScale + Panxx + Xx; var newContainerY = LocalY * deltaScale + Panyy + Yy; let dx = ContainerX - newContainerX; @@ -185,7 +172,7 @@ export class CollectionFreeFormView extends React.Component<IProps> { onDragOver = (e: React.DragEvent): void => { } render() { - const {fieldKey, Document: Document} = this.props; + const { fieldKey, Document: Document } = this.props; const value: Document[] = Document.GetFieldValue(fieldKey, ListField, []); const panx: number = Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); @@ -199,9 +186,8 @@ export class CollectionFreeFormView extends React.Component<IProps> { <div className="collectionfreeformview-container" onPointerDown={this.onPointerDown} onWheel={this.onPointerWheel} onContextMenu={(e) => e.preventDefault()} style={{ width: "100%", height: `calc(100% - 2*${CollectionFreeFormView.BORDER_WIDTH}px)`, - overflow: "hidden" }} onDrop={this.onDrop} onDragOver={this.onDragOver} ref={this._containerRef}> - <div className="collectionfreeformview" style={{transform: `translate(${panx}px, ${pany}px) scale(${currScale}, ${currScale})`, transformOrigin: `left, top`}} ref={this._canvasRef}> + <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px) scale(${currScale}, ${currScale})`, transformOrigin: `left, top` }} ref={this._canvasRef}> <div className="node-container"> {value.map(doc => { diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx index be072a458..1e4cc1cca 100644 --- a/src/views/nodes/DocumentView.tsx +++ b/src/views/nodes/DocumentView.tsx @@ -4,27 +4,47 @@ 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"; import { ContextMenu } from "../ContextMenu"; 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<CollectionFreeFormView>; + ContainingCollectionView: Opt<CollectionView>; ContainingDocumentView: Opt<DocumentView> } +export interface CollectionViewProps { + fieldKey: Key; + Document: Document; + ContainingDocumentView: Opt<DocumentView>; +} + +// 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<IProps> { +class DocumentContents extends React.Component<DocumentViewProps> { @computed get layout(): string { @@ -44,16 +64,16 @@ class DocumentContents extends React.Component<IProps> { let doc = this.props.Document; let bindings = { ...this.props } as any; for (const key of this.layoutKeys) { - bindings[key.Name + "Key"] = key; + bindings[ key.Name + "Key" ] = key; } for (const key of this.layoutFields) { let field = doc.GetField(key); if (field) { - bindings[key.Name] = field.GetValue(); + bindings[ key.Name ] = field.GetValue(); } } return <JsxParser - components={{ FieldTextBox, CollectionFreeFormView }} + components={{ FieldTextBox, CollectionFreeFormView, CollectionDockingView }} bindings={bindings} jsx={this.layout} showWarnings={true} @@ -66,12 +86,15 @@ class DocumentContents extends React.Component<IProps> { } @observer -export class DocumentView extends React.Component<IProps> { +export class DocumentView extends React.Component<DocumentViewProps> { private _mainCont = React.createRef<HTMLDivElement>(); private _contextMenuCanOpen = false; private _downX: number = 0; private _downY: number = 0; + constructor(props: DocumentViewProps) { + super(props); + } get screenRect(): ClientRect | DOMRect { if (this._mainCont.current) { return this._mainCont.current.getBoundingClientRect(); @@ -135,10 +158,9 @@ export class DocumentView extends React.Component<IProps> { // @computed public get ScalingToScreenSpace(): number { - let containingDocView = this.props.ContainingDocumentView; - if (containingDocView != undefined) { - let ss = containingDocView.props.Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1)); - return containingDocView.ScalingToScreenSpace * ss; + if (this.props.ContainingDocumentView != undefined) { + let ss = this.props.ContainingDocumentView.props.Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1)); + return this.props.ContainingDocumentView.ScalingToScreenSpace * ss; } return 1; } @@ -147,7 +169,7 @@ export class DocumentView extends React.Component<IProps> { // Converts a coordinate in the screen space of the app into a local document coordinate. // public TransformToLocalPoint(screenX: number, screenY: number) { - let ContainerX = screenX; + let ContainerX = screenX - CollectionFreeFormView.BORDER_WIDTH; let ContainerY = screenY - CollectionFreeFormView.BORDER_WIDTH; // if this collection view is nested within another collection view, then @@ -158,16 +180,16 @@ export class DocumentView extends React.Component<IProps> { ContainerY = LocalY - CollectionFreeFormView.BORDER_WIDTH; } - let W = this.props.Document.GetFieldValue(KeyStore.Width, NumberField, Number(0)); - let Xx = this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0)); - let Yy = this.props.Document.GetFieldValue(KeyStore.Y, NumberField, Number(0)); + let dockingViewChromeHack = this.props.ContainingCollectionView instanceof CollectionDockingView; + let Xx = dockingViewChromeHack ? 0 : this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0)); + let Yy = dockingViewChromeHack ? CollectionDockingView.TAB_HEADER_HEIGHT : this.props.Document.GetFieldValue(KeyStore.Y, NumberField, Number(0)); let Ss = this.props.Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1)); let Panxx = this.props.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)); let Panyy = this.props.Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)); - let LocalX = (ContainerX - (Xx + Panxx) - W / 2) / Ss + W / 2; + let LocalX = (ContainerX - (Xx + Panxx)) / Ss; let LocalY = (ContainerY - (Yy + Panyy)) / Ss; - return { LocalX, Ss, W, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY }; + return { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY }; } // @@ -175,11 +197,12 @@ export class DocumentView extends React.Component<IProps> { // public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: number, ScreenY: number } { - let W = this.props.Document.GetFieldValue(KeyStore.Width, NumberField, Number(0)); + let dockingViewChromeHack = this.props.ContainingCollectionView instanceof CollectionDockingView; + let W = CollectionFreeFormView.BORDER_WIDTH; // this.props.Document.GetFieldValue(KeyStore.Width, NumberField, Number(0)); let H = CollectionFreeFormView.BORDER_WIDTH; - let Xx = this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0)); - let Yy = this.props.Document.GetFieldValue(KeyStore.Y, NumberField, Number(0)); - let parentX = (localX - W / 2) * Ss + (Xx + Panxx) + W / 2; + let Xx = dockingViewChromeHack ? 0 : this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0)); + let Yy = dockingViewChromeHack ? CollectionDockingView.TAB_HEADER_HEIGHT : this.props.Document.GetFieldValue(KeyStore.Y, NumberField, Number(0)); + let parentX = (localX - W) * Ss + (Xx + Panxx) + W; let parentY = (localY - H) * Ss + (Yy + Panyy) + H; // if this collection view is nested within another collection view, then @@ -200,9 +223,11 @@ export class DocumentView extends React.Component<IProps> { this._downX = e.clientX; this._downY = e.clientY; this._contextMenuCanOpen = e.button == 2; - if (this.active) { + if (this.active && !e.isDefaultPrevented()) { e.stopPropagation(); - // e.preventDefault(); + if (e.buttons === 2) { + e.preventDefault(); + } document.removeEventListener("pointermove", this.onPointerMove) document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp) @@ -215,10 +240,10 @@ export class DocumentView extends React.Component<IProps> { if (this._mainCont.current != null && this.props.ContainingCollectionView != null) { this._contextMenuCanOpen = false; const rect = this.screenRect; - let dragData: { [id: string]: any } = {}; - dragData["document"] = this; - dragData["xOffset"] = e.x - rect.left; - dragData["yOffset"] = e.y - rect.top; + let dragData: { [ id: string ]: any } = {}; + dragData[ "document" ] = this; + dragData[ "xOffset" ] = e.x - rect.left; + dragData[ "yOffset" ] = e.y - rect.top; DragManager.StartDrag(this._mainCont.current, dragData, { handlers: { dragComplete: this.dragComplete, @@ -272,12 +297,14 @@ export class DocumentView extends React.Component<IProps> { } } + render() { + var freestyling = this.props.ContainingCollectionView === undefined || this.props.ContainingCollectionView instanceof CollectionFreeFormView; return ( <div className="node" ref={this._mainCont} style={{ - transform: this.transform, - width: this.width, - height: this.height, + transform: freestyling ? this.transform : "", + width: freestyling ? this.width : "100%", + height: freestyling ? this.height : "100%", }} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown}> @@ -285,5 +312,4 @@ export class DocumentView extends React.Component<IProps> { </div> ); } - }
\ No newline at end of file diff --git a/src/views/nodes/FieldTextBox.tsx b/src/views/nodes/FieldTextBox.tsx index 4539bbab0..20dfde1d2 100644 --- a/src/views/nodes/FieldTextBox.tsx +++ b/src/views/nodes/FieldTextBox.tsx @@ -14,11 +14,10 @@ import { undo, redo, history } from "prosemirror-history" import { Opt } from "../../fields/Field"; import "./FieldTextBox.scss" +import { DocumentFieldViewProps } from "./DocumentView"; +import { SelectionManager } from "../../util/SelectionManager"; + -interface IProps { - fieldKey: Key; - doc: Document; -} // FieldTextBox: Displays an editable plain text node that maps to a specified Key of a Document // @@ -36,12 +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<IProps> { +export class FieldTextBox extends React.Component<DocumentFieldViewProps> { + + public static LayoutString() { return "<FieldTextBox doc={Document} containingDocumentView={ContainingDocumentView} fieldKey={DataKey} />"; } private _ref: React.RefObject<HTMLDivElement>; private _editorView: Opt<EditorView>; private _reactionDisposer: Opt<IReactionDisposer>; - constructor(props: IProps) { + constructor(props: DocumentFieldViewProps) { super(props); this._ref = React.createRef(); @@ -111,8 +112,13 @@ export class FieldTextBox extends React.Component<IProps> { const { fieldKey, doc } = this.props; doc.SetFieldValue(fieldKey, e.target.value, TextField); } - + onPointerDown = (e: React.PointerEvent): void => { + let me = this; + if (e.buttons === 1 && SelectionManager.IsSelected(me.props.containingDocumentView)) { + e.stopPropagation(); + } + } render() { - return (<div className="fieldTextBox-cont" ref={this._ref} />) + return (<div className="fieldTextBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} />) } }
\ No newline at end of file diff --git a/src/views/nodes/TextNodeView.tsx b/src/views/nodes/TextNodeView.tsx index 4831e658c..ab762df12 100644 --- a/src/views/nodes/TextNodeView.tsx +++ b/src/views/nodes/TextNodeView.tsx @@ -1,7 +1,7 @@ -import { observer } from "mobx-react"; -import { StaticTextNodeStore } from "../../stores/StaticTextNodeStore"; +import {observer} from "mobx-react"; +import {StaticTextNodeStore} from "../../stores/StaticTextNodeStore"; import "./NodeView.scss"; -import { TopBar } from "./TopBar"; +import {TopBar} from "./TopBar"; import React = require("react"); interface IProps { @@ -14,7 +14,7 @@ export class TextNodeView extends React.Component<IProps> { render() { let store = this.props.store; return ( - <div className="node text-node" style={{ transform: store.Transform }}> + <div className="node text-node" style={{transform: store.Transform}}> <TopBar store={store} /> <div className="scroll-box"> <div className="content"> |
