diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/DocumentDecorations.scss | 2 | ||||
-rw-r--r-- | src/Main.tsx | 15 | ||||
-rw-r--r-- | src/util/DragManager.ts | 31 | ||||
-rw-r--r-- | src/views/collections/CollectionDockingView.scss | 39 | ||||
-rw-r--r-- | src/views/collections/CollectionDockingView.tsx | 172 | ||||
-rw-r--r-- | src/views/collections/CollectionFreeFormView.tsx | 30 | ||||
-rw-r--r-- | src/views/nodes/DocumentView.tsx | 124 | ||||
-rw-r--r-- | src/views/nodes/FieldTextBox.tsx | 28 |
8 files changed, 288 insertions, 153 deletions
diff --git a/src/DocumentDecorations.scss b/src/DocumentDecorations.scss index 2840d782b..252b0f53f 100644 --- a/src/DocumentDecorations.scss +++ b/src/DocumentDecorations.scss @@ -10,7 +10,7 @@ .documentDecorations-resizer { pointer-events: auto; background: lightblue; - opacity: 0.8; + opacity: 0.4; } #documentDecorations-topLeftResizer, #documentDecorations-bottomRightResizer { diff --git a/src/Main.tsx b/src/Main.tsx index ab87fc316..8c91399b1 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -24,7 +24,7 @@ configure({ const mainNodeCollection = new Array<Document>(); let mainContainer = Documents.DockDocument(mainNodeCollection, { - x: 0, y: 0, width: window.screen.width, height: window.screen.height, title: "main container" + x: 0, y: 0, title: "main container" }) window.addEventListener("drop", function (e) { @@ -47,7 +47,7 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { doc2.SetField(KS.X, new NumberField(150)); doc2.SetField(KS.Y, new NumberField(20)); let doc3 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { - x: 450, y: 500 + x: 450, y: 500, title: "cat 1" }); const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v @@ -61,10 +61,10 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { x: 0, y: 400, title: "mini collection" }); let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { - x: 650, y: 500, width: 600, height: 600, title: "cat" + x: 650, y: 500, width: 600, height: 600, title: "cat 2" }); let docset2 = new Array<Document>(doc4, doc1, doc3); - let doc6 = Documents.DockDocument(docset2, { + let doc6 = Documents.CollectionDocument(docset2, { x: 350, y: 100, width: 600, height: 600, title: "docking collection" }); let mainNodes = null;// mainContainer.GetFieldT(KeyStore.Data, ListField); @@ -77,15 +77,14 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { // mainNodes.Data.push(doc3); mainNodes.Data.push(doc5); // mainNodes.Data.push(doc1); - mainNodes.Data.push(doc2); - //mainNodes.Data.push(doc6); + //mainNodes.Data.push(doc2); + mainNodes.Data.push(doc6); mainContainer.SetField(KeyStore.Data, mainNodes); } //); ReactDOM.render(( - <div style={{ display: "grid" }}> - <h1>Dash Web</h1> + <div style={{ position: "absolute", width: "100%", height: "100%" }}> <DocumentView Document={mainContainer} ContainingCollectionView={undefined} ContainingDocumentView={undefined} /> <DocumentDecorations /> <ContextMenu /> diff --git a/src/util/DragManager.ts b/src/util/DragManager.ts index ca5b6c0ea..5b7609819 100644 --- a/src/util/DragManager.ts +++ b/src/util/DragManager.ts @@ -2,9 +2,18 @@ import { Opt } from "../fields/Field"; import { DocumentView } from "../views/nodes/DocumentView"; import { DocumentDecorations } from "../DocumentDecorations"; import { SelectionManager } from "./SelectionManager"; +import { CollectionDockingView } from "../views/collections/CollectionDockingView"; +import { Document } from "../fields/Document"; export namespace DragManager { - export let rootId = "root"; + export function Root() { + const root = document.getElementById("root"); + if (!root) { + throw new Error("No root element found"); + } + return root; + } + let dragDiv: HTMLDivElement; export enum DragButtons { @@ -33,7 +42,7 @@ export namespace DragManager { } export class DropEvent { - constructor(readonly x: number, readonly y: number, readonly data: { [ id: string ]: any }) { } + constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { } } export interface DropHandlers { @@ -44,7 +53,7 @@ export namespace DragManager { if ("canDrop" in element.dataset) { throw new Error("Element is already droppable, can't make it droppable again"); } - element.dataset[ "canDrop" ] = "true"; + element.dataset["canDrop"] = "true"; const handler = (e: Event) => { const ce = e as CustomEvent<DropEvent>; options.handlers.drop(e, ce.detail); @@ -52,21 +61,17 @@ export namespace DragManager { element.addEventListener("dashOnDrop", handler); return () => { element.removeEventListener("dashOnDrop", handler); - delete element.dataset[ "canDrop" ] + delete element.dataset["canDrop"] }; } let _lastPointerX: number = 0; let _lastPointerY: number = 0; - export function StartDrag(ele: HTMLElement, dragData: { [ id: string ]: any }, options: DragOptions) { + export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options: DragOptions) { if (!dragDiv) { - const root = document.getElementById(rootId); - if (!root) { - throw new Error("No root element found"); - } dragDiv = document.createElement("div"); - root.appendChild(dragDiv); + DragManager.Root().appendChild(dragDiv); } const w = ele.offsetWidth, h = ele.offsetHeight; const rect = ele.getBoundingClientRect(); @@ -79,8 +84,8 @@ export namespace DragManager { dragElement.style.transformOrigin = "0 0"; dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; dragDiv.appendChild(dragElement); - _lastPointerX = dragData[ "xOffset" ] + rect.left; - _lastPointerY = dragData[ "yOffset" ] + rect.top; + _lastPointerX = dragData["xOffset"] + rect.left; + _lastPointerY = dragData["yOffset"] + rect.top; let hideSource = false; if (typeof options.hideSource === "boolean") { @@ -112,7 +117,7 @@ export namespace DragManager { document.addEventListener("pointerup", upHandler); } - function FinishDrag(dragEle: HTMLElement, e: PointerEvent, options: DragOptions, dragData: { [ index: string ]: any }) { + function FinishDrag(dragEle: HTMLElement, e: PointerEvent, options: DragOptions, dragData: { [index: string]: any }) { dragDiv.removeChild(dragEle); const target = document.elementFromPoint(e.x, e.y); if (!target) { diff --git a/src/views/collections/CollectionDockingView.scss b/src/views/collections/CollectionDockingView.scss index 43af7c538..118662d89 100644 --- a/src/views/collections/CollectionDockingView.scss +++ b/src/views/collections/CollectionDockingView.scss @@ -4,10 +4,23 @@ top: 0; left: 0; overflow: hidden; + + .lm_controls>li { + opacity: 0.6; + transform: scale(1.2); + } + .lm_maximised .lm_controls .lm_maximise { + opacity:1; + transform: scale(0.8); + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==) !important; + } + .flexlayout__layout { - width: 100%; - height: 100%; - position: relative; + left: 0; + top: 0; + right: 0; + bottom: 0; + position: absolute; overflow:hidden; } @@ -79,9 +92,10 @@ .flexlayout__tab { overflow: auto; - position: absolute; + position:absolute; box-sizing: border-box; background-color: #222; + color:black; } .flexlayout__tab_button { @@ -107,12 +121,10 @@ } .flexlayout__tab_button_leading { - float: left; display:inline-block; } .flexlayout__tab_button_content { - float: left; display:inline-block; } @@ -127,7 +139,6 @@ } .flexlayout__tab_button_trailing { - float: left; display:inline-block; margin-left:5px; margin-top:3px; @@ -137,7 +148,7 @@ .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; + background: transparent url("../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center; } .flexlayout__tab_button_overflow { @@ -150,7 +161,7 @@ font-size: 10px; color:lightgray; font-family: Arial, sans-serif; - //background: transparent url("../images/more.png") no-repeat left; + background: transparent url("../../../node_modules/flexlayout-react/images/more.png") no-repeat left; } .flexlayout__tabset_header @@ -185,12 +196,12 @@ .flexlayout__tabset-selected { - //background-image: linear-gradient(#111, #444); + background-image: linear-gradient(#111, #444); } .flexlayout__tabset-maximized { - //background-image: linear-gradient(#666, #333); + background-image: linear-gradient(#666, #333); } .flexlayout__tab_toolbar { @@ -208,7 +219,7 @@ height:20px; border:none; outline-width: 0; - //background: transparent url("../images/maximize.png") no-repeat center; + background: transparent url("../../../node_modules/flexlayout-react/images/maximize.png") no-repeat center; } .flexlayout__tab_toolbar_button-max { @@ -216,7 +227,7 @@ height:20px; border:none; outline-width: 0; - //background: transparent url("../images/restore.png") no-repeat center; + background: transparent url("../../../node_modules/flexlayout-react/images/restore.png") no-repeat center; } .flexlayout__popup_menu { @@ -339,7 +350,7 @@ .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; + background: transparent url("../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center; } diff --git a/src/views/collections/CollectionDockingView.tsx b/src/views/collections/CollectionDockingView.tsx index 3d0c39c9d..a547ea1e8 100644 --- a/src/views/collections/CollectionDockingView.tsx +++ b/src/views/collections/CollectionDockingView.tsx @@ -4,7 +4,7 @@ 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 { DocumentView, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "../nodes/DocumentView"; import { ListField } from "../../fields/ListField"; import { NumberField } from "../../fields/NumberField"; import { SSL_OP_SINGLE_DH_USE } from "constants"; @@ -15,6 +15,7 @@ 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'; +import { DragManager } from "../../util/DragManager"; @observer export class CollectionDockingView extends React.Component<CollectionViewProps> { @@ -45,15 +46,16 @@ export class CollectionDockingView extends React.Component<CollectionViewProps> var docs = value.map(doc => { return { type: 'component', componentName: 'documentViewComponent', componentState: { doc: doc } }; }); - return new GoldenLayout({ content: [ { type: 'row', content: docs } ] }); + return new GoldenLayout({ + settings: { + selectionEnabled: true + }, 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)); @@ -65,8 +67,12 @@ export class CollectionDockingView extends React.Component<CollectionViewProps> componentDidMount: () => void = () => { if (this._containerRef.current && CollectionDockingView.UseGoldenLayout) { this.goldenLayoutFactory(); + window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window } } + componentWillUnmount: () => void = () => { + window.removeEventListener('resize', this.onResize); + } private nextId = (function () { var _next_id = 0; return function () { return _next_id++; } })(); @action @@ -87,6 +93,15 @@ export class CollectionDockingView extends React.Component<CollectionViewProps> ContextMenu.Instance.clearItems() } } + + @action + onResize = (event: any) => { + var cur = this.props.ContainingDocumentView!.MainContent.current; + + // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed + CollectionDockingView.myLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height); + } + @action onPointerDown = (e: React.PointerEvent): void => { if (e.button === 2 && this.active) { @@ -116,31 +131,94 @@ export class CollectionDockingView extends React.Component<CollectionViewProps> } } + public static myLayout: any = null; + + private static _dragDiv: any = null; + private static _dragParent: HTMLElement | null = null; + private static _dragElement: HTMLDivElement; + private static _dragFakeElement: HTMLDivElement; + public static StartOtherDrag(dragElement: HTMLDivElement, dragDoc: Document) { + var newItemConfig = { + type: 'component', + componentName: 'documentViewComponent', + componentState: { doc: dragDoc } + }; + this._dragElement = dragElement; + this._dragParent = dragElement.parentElement; + // bcz: we want to copy this document into the header, not move it there. + // However, GoldenLayout is setup to move things, so we have to do some kludgy stuff: + + // - create a temporary invisible div and register that as a DragSource with GoldenLayout + this._dragDiv = document.createElement("div"); + this._dragDiv.style.opacity = 0; + DragManager.Root().appendChild(this._dragDiv); + CollectionDockingView.myLayout.createDragSource(this._dragDiv, newItemConfig); + + // - add our document to that div so that GoldenLayout will get the move events its listening for + this._dragDiv.appendChild(this._dragElement); + + // - add a duplicate of our document to the original document's container + // (GoldenLayout will be removing our original one) + this._dragFakeElement = dragElement.cloneNode(true) as HTMLDivElement; + this._dragParent!.appendChild(this._dragFakeElement); + + // all of this must be undone when the document has been dropped (see tabCreated) + } + + _makeFullScreen: boolean = false; + _maximizedStack: any = null; + public static OpenFullScreen(dv: DocumentView) { + var newItemConfig = { + type: 'component', + componentName: 'documentViewComponent', + componentState: { doc: dv.props.Document } + }; + CollectionDockingView.myLayout._makeFullScreen = true; + CollectionDockingView.myLayout.root.contentItems[ 0 ].addChild(newItemConfig); + } + public static CloseFullScreen() { + if (CollectionDockingView.myLayout._maximizedStack != null) { + CollectionDockingView.myLayout._maximizedStack.header.controlsContainer.find('.lm_close').click(); + CollectionDockingView.myLayout._maximizedStack = null; + } + } goldenLayoutFactory() { - var myLayout = this.modelForGoldenLayout; + CollectionDockingView.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 + CollectionDockingView.myLayout.on('tabCreated', function (tab: any) { + if (CollectionDockingView._dragDiv) { + CollectionDockingView._dragDiv.removeChild(CollectionDockingView._dragElement); + CollectionDockingView._dragParent!.removeChild(CollectionDockingView._dragFakeElement); + CollectionDockingView._dragParent!.appendChild(CollectionDockingView._dragElement); + DragManager.Root().removeChild(CollectionDockingView._dragDiv); + CollectionDockingView._dragDiv = null; + } + tab.setTitle(tab.contentItem.config.componentState.doc.Title); + tab.closeElement.off('click') //unbind the current click handler .click(function () { - if (confirm('really close this?')) { - stack.remove(); - } + //if (confirm('really close this?')) { + tab.contentItem.remove(); + //} }); }); - myLayout.on('tabCreated', function (tab: any) { - tab.setTitle(tab.contentItem.config.componentState.doc.Title); - tab.closeElement.off('click') //unbind the current click handler + CollectionDockingView.myLayout.on('stackCreated', function (stack: any) { + if (CollectionDockingView.myLayout._makeFullScreen) { + CollectionDockingView.myLayout._maximizedStack = stack; + CollectionDockingView.myLayout._maxstack = stack.header.controlsContainer.find('.lm_maximise'); + } + stack.header.controlsContainer.find('.lm_popout').hide(); + stack.header.controlsContainer.find('.lm_close') //get the close icon + .off('click') //unbind the current click handler .click(function () { - if (confirm('really close this?')) { - tab.contentItem.remove(); - } + //if (confirm('really close this?')) { + stack.remove(); + //} }); }); var me = this; - myLayout.registerComponent('documentViewComponent', function (container: any, state: any) { + CollectionDockingView.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. @@ -152,11 +230,14 @@ export class CollectionDockingView extends React.Component<CollectionViewProps> <DocumentView key={state.doc.Id} Document={state.doc} ContainingCollectionView={me} ContainingDocumentView={me.props.ContainingDocumentView} /> ), document.getElementById(containingDiv) - ) + ); + if (CollectionDockingView.myLayout._maxstack != null) { + CollectionDockingView.myLayout._maxstack.click(); + } }, 0); }); - myLayout.container = this._containerRef.current; - myLayout.init(); + CollectionDockingView.myLayout.container = this._containerRef.current; + CollectionDockingView.myLayout.init(); } @@ -168,35 +249,24 @@ export class CollectionDockingView extends React.Component<CollectionViewProps> 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> - ); + var chooseLayout = () => { + if (!CollectionDockingView.UseGoldenLayout) + return <FlexLayout.Layout model={this.modelForFlexLayout} factory={this.flexLayoutFactory} />; } + + return ( + <div className="border" style={{ + borderStyle: "solid", + borderWidth: `${COLLECTION_BORDER_WIDTH}px`, + }}> + <div className="collectiondockingview-container" id="menuContainer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} ref={this._containerRef} + style={{ + width: CollectionDockingView.UseGoldenLayout || s > 1 ? "100%" : w - 2 * COLLECTION_BORDER_WIDTH, + height: CollectionDockingView.UseGoldenLayout || s > 1 ? "100%" : h - 2 * COLLECTION_BORDER_WIDTH + }} > + {chooseLayout()} + </div> + </div> + ); } }
\ No newline at end of file diff --git a/src/views/collections/CollectionFreeFormView.tsx b/src/views/collections/CollectionFreeFormView.tsx index b4dd140d0..ffb39426d 100644 --- a/src/views/collections/CollectionFreeFormView.tsx +++ b/src/views/collections/CollectionFreeFormView.tsx @@ -3,7 +3,7 @@ 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 { DocumentView, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "../nodes/DocumentView"; import { ListField } from "../../fields/ListField"; import { NumberField } from "../../fields/NumberField"; import { SSL_OP_SINGLE_DH_USE } from "constants"; @@ -24,8 +24,6 @@ export class CollectionFreeFormView extends React.Component<CollectionViewProps> super(props); } - public static BORDER_WIDTH = 2; - @computed public get active(): boolean { var isSelected = (this.props.ContainingDocumentView != undefined && SelectionManager.IsSelected(this.props.ContainingDocumentView)); @@ -37,17 +35,22 @@ export class CollectionFreeFormView extends React.Component<CollectionViewProps> } drop = (e: Event, de: DragManager.DropEvent) => { - const doc = de.data[ "document" ]; + const doc = de.data["document"]; 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 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; + const xOffset = de.data["xOffset"] as number || 0; + const yOffset = de.data["yOffset"] as number || 0; + const { scale, translateX, translateY } = Utils.GetScreenTransform(this._canvasRef.current!); + let sscale = this.props.ContainingDocumentView!.props.Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1)) + const screenX = de.x - xOffset; + const screenY = de.y - yOffset; + const docX = (screenX - translateX) / sscale / scale; + const docY = (screenY - translateY) / sscale / scale; + doc.x = docX; + doc.y = docY; } e.stopPropagation(); } @@ -67,8 +70,6 @@ export class CollectionFreeFormView extends React.Component<CollectionViewProps> @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); @@ -123,7 +124,7 @@ export class CollectionFreeFormView extends React.Component<CollectionViewProps> e.stopPropagation() e.preventDefault() let fReader = new FileReader() - let file = e.dataTransfer.items[ 0 ].getAsFile(); + 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)); @@ -178,14 +179,15 @@ export class CollectionFreeFormView extends React.Component<CollectionViewProps> const panx: number = Document.GetNumberField(KeyStore.PanX, 0); const pany: number = Document.GetNumberField(KeyStore.PanY, 0); const currScale: number = Document.GetNumberField(KeyStore.Scale, 1); + console.log("DocsR " + value.length); return ( <div className="border" style={{ borderStyle: "solid", - borderWidth: `${CollectionFreeFormView.BORDER_WIDTH}px`, + borderWidth: `${COLLECTION_BORDER_WIDTH}px`, }}> <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)`, + height: `calc(100% - 2*${COLLECTION_BORDER_WIDTH}px)`, }} 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}> diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx index 20dc6540b..634cd78be 100644 --- a/src/views/nodes/DocumentView.tsx +++ b/src/views/nodes/DocumentView.tsx @@ -1,26 +1,30 @@ +import { action, computed } from "mobx"; import { observer } from "mobx-react"; -import React = require("react"); -import { computed, observable, action } from "mobx"; -import { KeyStore, Key } from "../../fields/Key"; +import { Document } from "../../fields/Document"; +import { Opt } from "../../fields/Field"; +import { Key, KeyStore } from "../../fields/Key"; +import { ListField } from "../../fields/ListField"; import { NumberField } from "../../fields/NumberField"; import { TextField } from "../../fields/TextField"; -import { ListField } from "../../fields/ListField"; -import { FieldTextBox } from "../nodes/FieldTextBox" -import { Document } from "../../fields/Document"; -import { CollectionFreeFormView } from "../collections/CollectionFreeFormView" -import { CollectionDockingView } from "../collections/CollectionDockingView" import { CollectionSchemaView } from "../collections/CollectionSchemaView" import "./NodeView.scss" +import { DragManager } from "../../util/DragManager"; import { SelectionManager } from "../../util/SelectionManager"; +import { Utils } from "../../Utils"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; import { ContextMenu } from "../ContextMenu"; -import { Opt } from "../../fields/Field"; -import { DragManager } from "../../util/DragManager"; +import { FieldTextBox } from "../nodes/FieldTextBox"; +import "./NodeView.scss"; +import React = require("react"); +import { cpus } from "os"; +import { relative } from "path"; const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this? interface DocumentViewProps { Document: Document; ContainingCollectionView: Opt<CollectionView>; - ContainingDocumentView: Opt<DocumentView> + ContainingDocumentView: Opt<DocumentView>; } export interface CollectionViewProps { @@ -29,14 +33,18 @@ export interface CollectionViewProps { 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 +// +// these properties get assigned through the render() method of the DocumentView when it creates this node. +// However, that only happens because the properties are "defined" in FieldTextBox's LayoutString() method +// export interface DocumentFieldViewProps { fieldKey: Key; doc: Document; containingDocumentView: DocumentView } +export const COLLECTION_BORDER_WIDTH = 2; + interface CollectionView { addDocument: (doc: Document) => void; removeDocument: (doc: Document) => void; @@ -80,10 +88,7 @@ class DocumentContents extends React.Component<DocumentViewProps> { showWarnings={true} onError={(test: any) => { console.log(test) }} /> - - } - } @observer @@ -103,6 +108,10 @@ export class DocumentView extends React.Component<DocumentViewProps> { return new DOMRect(); } + get MainContent() { + return this._mainCont; + } + @computed get x(): number { return this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0)); @@ -170,20 +179,25 @@ export class DocumentView extends React.Component<DocumentViewProps> { // Converts a coordinate in the screen space of the app into a local document coordinate. // public TransformToLocalPoint(screenX: number, screenY: number) { - let ContainerX = screenX - CollectionFreeFormView.BORDER_WIDTH; - let ContainerY = screenY - CollectionFreeFormView.BORDER_WIDTH; - // if this collection view is nested within another collection view, then // first transform the screen point into the parent collection's coordinate space. - if (this.props.ContainingDocumentView != undefined) { - let { LocalX, LocalY } = this.props.ContainingDocumentView!.TransformToLocalPoint(screenX, screenY); - ContainerX = LocalX - CollectionFreeFormView.BORDER_WIDTH; - ContainerY = LocalY - CollectionFreeFormView.BORDER_WIDTH; + let { LocalX: parentX, LocalY: parentY } = this.props.ContainingDocumentView != undefined ? + this.props.ContainingDocumentView!.TransformToLocalPoint(screenX, screenY) : + { LocalX: screenX, LocalY: screenY }; + let ContainerX: number = parentX - COLLECTION_BORDER_WIDTH; + let ContainerY: number = parentY - COLLECTION_BORDER_WIDTH; + + var Xx = this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0)); + var Yy = this.props.Document.GetFieldValue(KeyStore.Y, NumberField, Number(0)); + // CollectionDockingViews change the location of their children frames without using a Dash transformation. + // They also ignore any transformation that may have been applied to their content document. + // NOTE: this currently assumes CollectionDockingViews aren't nested. + if (this.props.ContainingCollectionView instanceof CollectionDockingView) { + var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this._mainCont.current!); + Xx = rx - COLLECTION_BORDER_WIDTH; + Yy = ry - COLLECTION_BORDER_WIDTH; } - 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)); @@ -198,11 +212,19 @@ export class DocumentView extends React.Component<DocumentViewProps> { // public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: number, ScreenY: number } { - 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 = 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)); + var Xx = this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0)); + var Yy = this.props.Document.GetFieldValue(KeyStore.Y, NumberField, Number(0)); + // CollectionDockingViews change the location of their children frames without using a Dash transformation. + // They also ignore any transformation that may have been applied to their content document. + // NOTE: this currently assumes CollectionDockingViews aren't nested. + if (this.props.ContainingCollectionView instanceof CollectionDockingView) { + var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this._mainCont.current!); + Xx = rx - COLLECTION_BORDER_WIDTH; + Yy = ry - COLLECTION_BORDER_WIDTH; + } + + let W = COLLECTION_BORDER_WIDTH; + let H = COLLECTION_BORDER_WIDTH; let parentX = (localX - W) * Ss + (Xx + Panxx) + W; let parentY = (localY - H) * Ss + (Yy + Panyy) + H; @@ -211,8 +233,8 @@ export class DocumentView extends React.Component<DocumentViewProps> { let containingDocView = this.props.ContainingDocumentView; if (containingDocView != undefined) { let ss = containingDocView.props.Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1)); - let panxx = containingDocView.props.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)) + CollectionFreeFormView.BORDER_WIDTH * ss; - let panyy = containingDocView.props.Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)) + CollectionFreeFormView.BORDER_WIDTH * ss; + let panxx = containingDocView.props.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss; + let panyy = containingDocView.props.Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss; let { ScreenX, ScreenY } = containingDocView.TransformToScreenPoint(parentX, parentY, ss, panxx, panyy); parentX = ScreenX; parentY = ScreenY; @@ -223,6 +245,12 @@ export class DocumentView extends React.Component<DocumentViewProps> { onPointerDown = (e: React.PointerEvent): void => { this._downX = e.clientX; this._downY = e.clientY; + var me = this; + if (e.shiftKey && e.buttons === 1) { + CollectionDockingView.StartOtherDrag(this._mainCont.current!, this.props.Document); + e.stopPropagation(); + return; + } this._contextMenuCanOpen = e.button == 2; if (this.active && !e.isDefaultPrevented()) { e.stopPropagation(); @@ -237,7 +265,12 @@ export class DocumentView extends React.Component<DocumentViewProps> { } onPointerMove = (e: PointerEvent): void => { + if (e.cancelBubble) { + this._contextMenuCanOpen = false; + return; + } if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { + this._contextMenuCanOpen = false; if (this._mainCont.current != null && this.props.ContainingCollectionView != null) { this._contextMenuCanOpen = false; const rect = this.screenRect; @@ -271,6 +304,20 @@ export class DocumentView extends React.Component<DocumentViewProps> { this.props.ContainingCollectionView.removeDocument(this.props.Document) } } + @action + fullScreenClicked = (e: React.MouseEvent): void => { + CollectionDockingView.OpenFullScreen(this); + ContextMenu.Instance.clearItems(); + ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked }); + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) + } + @action + closeFullScreenClicked = (e: React.MouseEvent): void => { + CollectionDockingView.CloseFullScreen(); + ContextMenu.Instance.clearItems(); + ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }) + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) + } @action onContextMenu = (e: React.MouseEvent): void => { @@ -283,29 +330,34 @@ export class DocumentView extends React.Component<DocumentViewProps> { return; } - var topMost = this.props.ContainingCollectionView == undefined; + var topMost = this.props.ContainingCollectionView == undefined || + this.props.ContainingCollectionView instanceof CollectionDockingView; if (topMost) { ContextMenu.Instance.clearItems() + ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }) + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) } else { // DocumentViews should stop propogation of this event e.stopPropagation(); ContextMenu.Instance.clearItems(); + ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }) ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked }) - ContextMenu.Instance.displayMenu(e.pageX, e.pageY) + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) SelectionManager.SelectDoc(this, e.ctrlKey); } } render() { - var freestyling = this.props.ContainingCollectionView === undefined || this.props.ContainingCollectionView instanceof CollectionFreeFormView; + var freestyling = this.props.ContainingCollectionView instanceof CollectionFreeFormView; return ( <div className="node" ref={this._mainCont} style={{ transform: freestyling ? this.transform : "", width: freestyling ? this.width : "100%", height: freestyling ? this.height : "100%", + position: freestyling ? "absolute" : "relative", }} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown}> diff --git a/src/views/nodes/FieldTextBox.tsx b/src/views/nodes/FieldTextBox.tsx index 20dfde1d2..9809f3aed 100644 --- a/src/views/nodes/FieldTextBox.tsx +++ b/src/views/nodes/FieldTextBox.tsx @@ -1,22 +1,18 @@ -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, IReactionDisposer, reaction } from "mobx"; +import { baseKeymap } from "prosemirror-commands"; +import { history, redo, undo } from "prosemirror-history"; +import { keymap } from "prosemirror-keymap"; 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 { EditorState, Transaction } from "prosemirror-state"; +import { EditorView } from "prosemirror-view"; +import { Document } from "../../fields/Document"; import { Opt } from "../../fields/Field"; - -import "./FieldTextBox.scss" -import { DocumentFieldViewProps } from "./DocumentView"; +import { Key } from "../../fields/Key"; +import { TextField } from "../../fields/TextField"; import { SelectionManager } from "../../util/SelectionManager"; - +import { DocumentView, DocumentFieldViewProps } from "./DocumentView"; +import "./FieldTextBox.scss"; +import React = require("react") // FieldTextBox: Displays an editable plain text node that maps to a specified Key of a Document |