diff options
Diffstat (limited to 'src')
24 files changed, 575 insertions, 274 deletions
diff --git a/src/client/Server.ts b/src/client/Server.ts index 85e55a84e..0cb6e17c2 100644 --- a/src/client/Server.ts +++ b/src/client/Server.ts @@ -1,16 +1,16 @@ -import { Field, FieldWaiting, FIELD_ID, FIELD_WAITING, FieldValue } from "../fields/Field" +import { Field, FieldWaiting, FieldId, FIELD_WAITING, FieldValue, Opt } from "../fields/Field" import { Key, KeyStore } from "../fields/Key" import { ObservableMap, action } from "mobx"; import { Document } from "../fields/Document" import { SocketStub } from "./SocketStub"; export class Server { - private static ClientFieldsCached: ObservableMap<FIELD_ID, Field | FIELD_WAITING> = new ObservableMap(); + private static ClientFieldsCached: ObservableMap<FieldId, Field | FIELD_WAITING> = new ObservableMap(); // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached). // Call this is from within a reaction and test whether the return value is FieldWaiting. // 'hackTimeout' is here temporarily for simplicity when debugging things. - public static GetField(fieldid: FIELD_ID, callback: (field: Field) => void = (f) => { }, hackTimeout: number = -1) { + public static GetField(fieldid: FieldId, callback: (field: Field) => void = (f) => { }, hackTimeout: number = -1) { if (!this.ClientFieldsCached.get(fieldid)) { this.ClientFieldsCached.set(fieldid, FieldWaiting); //simulating a server call with a registered callback action @@ -24,15 +24,18 @@ export class Server { } static times = 0; // hack for testing - public static GetDocumentField(doc: Document, key: Key) { + public static GetDocumentField(doc: Document, key: Key): FieldValue<Field> { var hackTimeout: number = key == KeyStore.Data ? (this.times++ == 0 ? 5000 : 1000) : key == KeyStore.X ? 2500 : 500; - return this.GetField(doc._proxies.get(key), - action((fieldfromserver: Field) => { - doc._proxies.delete(key); - doc.fields.set(key, fieldfromserver); - }) - , hackTimeout); + let fieldId = doc._proxies.get(key); + if (fieldId) { + return this.GetField(fieldId, + action((fieldfromserver: Field) => { + doc._proxies.delete(key); + doc.fields.set(key, fieldfromserver); + }) + , hackTimeout); + } } public static AddDocument(document: Document) { diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts index 58dedbf82..cea30cb8b 100644 --- a/src/client/SocketStub.ts +++ b/src/client/SocketStub.ts @@ -1,11 +1,11 @@ -import { Field, FIELD_ID } from "../fields/Field" +import { Field, FieldId } from "../fields/Field" import { Key, KeyStore } from "../fields/Key" import { ObservableMap, action } from "mobx"; import { Document } from "../fields/Document" export class SocketStub { - static FieldStore: ObservableMap<FIELD_ID, Field> = new ObservableMap(); + static FieldStore: ObservableMap<FieldId, Field> = new ObservableMap(); public static SEND_ADD_DOCUMENT(document: Document) { // Send a serialized version of the document to the server @@ -19,7 +19,7 @@ export class SocketStub { document.fields.forEach((f, key) => (this.FieldStore.get(document.Id) as Document)._proxies.set(key, (f as Field).Id)); } - public static SEND_FIELD_REQUEST(fieldid: FIELD_ID, callback: (field: Field) => void, timeout: number) { + public static SEND_FIELD_REQUEST(fieldid: FieldId, callback: (field: Field) => void, timeout: number) { if (timeout < 0)// this is a hack to make things easier to setup until we have a server... won't be neededa fter that. callback(this.FieldStore.get(fieldid) as Field); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 12eddaec9..72fa608ad 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -10,13 +10,15 @@ import { CollectionSchemaView } from "../views/collections/CollectionSchemaView" import { ImageField } from "../../fields/ImageField"; import { ImageBox } from "../views/nodes/ImageBox"; import { CollectionFreeFormView } from "../views/collections/CollectionFreeFormView"; -import { FIELD_ID } from "../../fields/Field"; +import { FieldId } from "../../fields/Field"; interface DocumentOptions { x?: number; y?: number; width?: number; height?: number; + nativeWidth?: number; + nativeHeight?: number; title?: string; } @@ -34,6 +36,12 @@ export namespace Documents { if (options.height) { doc.SetData(KeyStore.Height, options.height, NumberField); } + if (options.nativeWidth) { + doc.SetData(KeyStore.NativeWidth, options.nativeWidth, NumberField); + } + if (options.nativeHeight) { + doc.SetData(KeyStore.NativeHeight, options.nativeHeight, NumberField); + } if (options.title) { doc.SetData(KeyStore.Title, options.title, TextField); } @@ -107,7 +115,7 @@ export namespace Documents { } - let imageProtoId: FIELD_ID; + let imageProtoId: FieldId; function GetImagePrototype(): Document { if (imageProtoId === undefined) { let imageProto = new Document(); @@ -115,11 +123,12 @@ export namespace Documents { imageProto.Set(KeyStore.Title, new TextField("IMAGE PROTO")); imageProto.Set(KeyStore.X, new NumberField(0)); imageProto.Set(KeyStore.Y, new NumberField(0)); - imageProto.Set(KeyStore.NativeWidth, new NumberField(606)); - imageProto.Set(KeyStore.Width, new NumberField(606)); - imageProto.Set(KeyStore.Height, new NumberField(446)); - imageProto.Set(KeyStore.Layout, new TextField("<CollectionFreeFormView DocumentForCollection={Document} CollectionFieldKey={AnnotationsKey} BackgroundView={BackgroundView} ContainingDocumentView={DocumentView} />")); - imageProto.Set(KeyStore.AnnotatedLayout, new TextField(ImageBox.LayoutString())); + imageProto.Set(KeyStore.NativeWidth, new NumberField(300)); + imageProto.Set(KeyStore.NativeHeight, new NumberField(300)); + imageProto.Set(KeyStore.Width, new NumberField(300)); + imageProto.Set(KeyStore.Height, new NumberField(300)); + imageProto.Set(KeyStore.Layout, new TextField(CollectionFreeFormView.LayoutString("AnnotationsKey"))); + imageProto.Set(KeyStore.BackgroundLayout, new TextField(ImageBox.LayoutString())); // imageProto.SetField(KeyStore.Layout, new TextField('<div style={"background-image: " + {Data}} />')); imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data, KeyStore.Annotations])); Server.AddDocument(imageProto); @@ -152,7 +161,7 @@ export namespace Documents { collectionProto.Set(KeyStore.PanY, new NumberField(0)); collectionProto.Set(KeyStore.Width, new NumberField(300)); collectionProto.Set(KeyStore.Height, new NumberField(300)); - collectionProto.Set(KeyStore.Layout, new TextField(CollectionFreeFormView.LayoutString())); + collectionProto.Set(KeyStore.Layout, new TextField(CollectionFreeFormView.LayoutString("DataKey"))); collectionProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); } return collectionProto; diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts new file mode 100644 index 000000000..7861ed308 --- /dev/null +++ b/src/client/util/Transform.ts @@ -0,0 +1,94 @@ +export class Transform { + private _translateX: number = 0; + private _translateY: number = 0; + private _scale: number = 1; + + static get Identity(): Transform { + return new Transform(0, 0, 1); + } + + constructor(x: number, y: number, scale: number) { + this._translateX = x; + this._translateY = y; + this._scale = scale; + } + + translate = (x: number, y: number): Transform => { + this._translateX += x; + this._translateY += y; + return this; + } + + translated = (x: number, y: number): Transform => { + return this.copy().translate(x, y); + } + + preTranslate = (x: number, y: number): Transform => { + this._translateX += x * this._scale; + this._translateY += y * this._scale; + return this; + } + + preTranslated = (x: number, y: number): Transform => { + return this.copy().preTranslate(x, y); + } + + scale = (scale: number): Transform => { + this._scale *= scale; + return this; + } + + scaled = (scale: number): Transform => { + return this.copy().scale(scale); + } + + preScale = (scale: number): Transform => { + this._scale *= scale; + this._translateX *= scale; + this._translateY *= scale; + return this; + } + + preScaled = (scale: number): Transform => { + return this.copy().preScale(scale); + } + + transform = (transform: Transform): Transform => { + this._translateX += transform._translateX * this._scale; + this._translateY += transform._translateY * this._scale; + this._scale *= transform._scale; + return this; + } + + transformed = (transform: Transform): Transform => { + return this.copy().transform(transform); + } + + preTransform = (transform: Transform): Transform => { + this._translateX = transform._translateX + this._translateX * transform._scale; + this._translateY = transform._translateY + this._translateY * transform._scale; + this._scale *= transform._scale; + return this; + } + + preTransformed = (transform: Transform): Transform => { + return this.copy().preTransform(transform); + } + + transformPoint = (x: number, y: number): [number, number] => { + x *= this._scale; + x += this._translateX; + y *= this._scale; + y += this._translateY; + return [x, y]; + } + + inverse = () => { + return new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale) + } + + copy = () => { + return new Transform(this._translateX, this._translateY, this._scale); + } + +}
\ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 8a94bff36..1224495f6 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -4,6 +4,7 @@ import { SelectionManager } from "../util/SelectionManager"; import { observer } from "mobx-react"; import './DocumentDecorations.scss' import { CollectionFreeFormView } from "./collections/CollectionFreeFormView"; +import { KeyStore } from '../../fields/Key' @observer export class DocumentDecorations extends React.Component { @@ -109,10 +110,12 @@ export class DocumentDecorations extends React.Component { let scale = element.width / rect.width; let actualdW = Math.max(element.width + (dW * scale), 20); let actualdH = Math.max(element.height + (dH * scale), 20); - element.x += dX * (actualdW - element.width); - element.y += dY * (actualdH - element.height); - element.width = actualdW; - element.height = actualdH; + element.props.Document.SetNumber(KeyStore.X, element.props.Document.GetNumber(KeyStore.X, 0) + dX * (actualdW - element.width)); + element.props.Document.SetNumber(KeyStore.Y, element.props.Document.GetNumber(KeyStore.Y, 0) + dY * (actualdH - element.height)); + if (Math.abs(dW) > Math.abs(dH)) + element.width = actualdW; + else + element.height = actualdH; } }) } diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 2e784d3f9..3d1c2ebf4 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -6,6 +6,7 @@ export interface EditableProps { GetValue(): string; SetValue(value: string): boolean; contents: any; + height: number } @observer @@ -29,9 +30,10 @@ export class EditableView extends React.Component<EditableProps> { style={{ width: "100%" }}></input> } else { return ( - <div> + <div style={{ alignItems: "center", display: "flex", height: "100%", maxHeight: `${this.props.height}` }} + onClick={action(() => this.editing = true)} + > {this.props.contents} - <button onClick={action(() => this.editing = true)}>Edit</button> </div> ) } diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 31a709744..aec9618ae 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -13,6 +13,7 @@ import "./Main.scss"; import { ContextMenu } from './ContextMenu'; import { DocumentView } from './nodes/DocumentView'; import { ImageField } from '../../fields/ImageField'; +import { Transform } from '../util/Transform'; configure({ @@ -39,17 +40,18 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { //runInAction(() => { - let doc1 = Documents.TextDocument({ title: "hello" }); + let doc1 = Documents.TextDocument({ title: "hello", width: 400, height: 300 }); let doc2 = doc1.MakeDelegate(); doc2.Set(KS.X, new NumberField(150)); doc2.Set(KS.Y, new NumberField(20)); let doc3 = Documents.ImageDocument("https://psmag.com/.image/t_share/MTMyNzc2NzM1MDY1MjgzMDM4/shutterstock_151341212jpg.jpg", { - x: 450, y: 100, title: "cat 1" + x: 450, y: 100, title: "dog", width: 606, height: 386, nativeWidth: 606, nativeHeight: 386 }); //doc3.Set(KeyStore.Data, new ImageField); const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://psmag.com/.image/t_share/MTMyNzc2NzM1MDY1MjgzMDM4/shutterstock_151341212jpg.jpg", { - x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v + x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v, nativeWidth: 606, nativeHeight: 386 })); + schemaDocs.push(doc3); schemaDocs[0].SetData(KS.Author, "Tyler", TextField); schemaDocs[4].SetData(KS.Author, "Bob", TextField); schemaDocs.push(doc2); @@ -61,7 +63,7 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { // 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 2" // }); - let docset2 = [doc4, doc1, doc3]; + let docset2 = [doc3, doc4, doc2]; let doc6 = Documents.CollectionDocument(docset2, { x: 350, y: 100, width: 600, height: 600, title: "docking collection" }); @@ -75,7 +77,7 @@ 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(doc2); mainNodes.Data.push(doc6); mainContainer.Set(KeyStore.Data, mainNodes); } @@ -84,7 +86,10 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { ReactDOM.render(( <div style={{ position: "absolute", width: "100%", height: "100%" }}> - <DocumentView Document={mainContainer} Scaling={1} ContainingCollectionView={undefined} DocumentView={undefined} /> + <DocumentView Document={mainContainer} + AddDocument={undefined} RemoveDocument={undefined} GetTransform={() => Transform.Identity} + ParentScaling={1} + ContainingCollectionView={undefined} DocumentView={undefined} /> <DocumentDecorations /> <ContextMenu /> </div>), diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 037b85712..1653994cf 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,19 +1,20 @@ -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 } from "../nodes/DocumentView"; -import { ListField } from "../../../fields/ListField"; -import { NumberField } from "../../../fields/NumberField"; -import "./CollectionDockingView.scss" +import * as GoldenLayout from "golden-layout"; 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 { action, computed, reaction, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../fields/Document"; +import { KeyStore } from "../../../fields/Key"; +import { ListField } from "../../../fields/ListField"; import { DragManager } from "../../util/DragManager"; +import { Transform } from "../../util/Transform"; +import { DocumentView } from "../nodes/DocumentView"; +import "./CollectionDockingView.scss"; import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase"; +import React = require("react"); +import * as ReactDOM from 'react-dom'; +import Measure from "react-measure"; @observer export class CollectionDockingView extends CollectionViewBase { @@ -42,7 +43,7 @@ export class CollectionDockingView extends CollectionViewBase { const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; const value: Document[] = Document.GetData(fieldKey, ListField, []); var docs = value.map(doc => { - return { type: 'component', componentName: 'documentViewComponent', componentState: { doc: doc } }; + return { type: 'component', componentName: 'documentViewComponent', componentState: { doc: doc, scaling: 1 } }; }); return new GoldenLayout({ settings: { @@ -65,7 +66,6 @@ export class CollectionDockingView extends CollectionViewBase { } private nextId = (function () { var _next_id = 0; return function () { return _next_id++; } })(); - @action onResize = (event: any) => { var cur = this.props.ContainingDocumentView!.MainContent.current; @@ -95,7 +95,11 @@ export class CollectionDockingView extends CollectionViewBase { const value: Document[] = Document.GetData(fieldKey, ListField, []); for (var i: number = 0; i < value.length; i++) { if (value[i].Id === component) { - return (<DocumentView key={value[i].Id} Scaling={1} ContainingCollectionView={this} Document={value[i]} DocumentView={undefined} />); + return (<DocumentView key={value[i].Id} Document={value[i]} + AddDocument={this.addDocument} RemoveDocument={this.removeDocument} + GetTransform={() => Transform.Identity} + ParentScaling={1} + ContainingCollectionView={this} DocumentView={undefined} />); } } if (component === "text") { @@ -113,7 +117,7 @@ export class CollectionDockingView extends CollectionViewBase { var newItemConfig = { type: 'component', componentName: 'documentViewComponent', - componentState: { doc: dragDoc } + componentState: { doc: dragDoc, scaling: 1 } }; this._dragElement = dragElement; this._dragParent = dragElement.parentElement; @@ -154,7 +158,6 @@ export class CollectionDockingView extends CollectionViewBase { CollectionDockingView.myLayout._maximizedStack = null; } } - // // Creates a vertical split on the right side of the docking view, and then adds the Document to that split // @@ -235,13 +238,19 @@ export class CollectionDockingView extends CollectionViewBase { var containingDiv = "component_" + me.nextId(); container.getElement().html("<div id='" + containingDiv + "'></div>"); setTimeout(function () { - ReactDOM.render(( - <DocumentView key={state.doc.Id} Scaling={1} Document={state.doc} ContainingCollectionView={me} DocumentView={undefined} /> - ), - document.getElementById(containingDiv) - ); - if (CollectionDockingView.myLayout._maxstack != null) { - CollectionDockingView.myLayout._maxstack.click(); + let divContainer = document.getElementById(containingDiv); + if (divContainer) { + let props: DockingProps = { + ContainingDiv: containingDiv, + Document: state.doc, + Container: container, + CollectionDockingView: me, + HtmlElement: divContainer + } + ReactDOM.render((<RenderClass {...props} />), divContainer); + if (CollectionDockingView.myLayout._maxstack) { + CollectionDockingView.myLayout._maxstack.click(); + } } }, 0); }); @@ -255,8 +264,8 @@ export class CollectionDockingView extends CollectionViewBase { const value: Document[] = Document.GetData(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 != undefined ? this.props.ContainingDocumentView!.ScalingToScreenSpace : 1; - var w = Document.GetData(KeyStore.Width, NumberField, Number(0)) / s; - var h = Document.GetData(KeyStore.Height, NumberField, Number(0)) / s; + var w = Document.GetNumber(KeyStore.Width, 0) / s; + var h = Document.GetNumber(KeyStore.Height, 0) / s; var chooseLayout = () => { if (!CollectionDockingView.UseGoldenLayout) @@ -264,18 +273,50 @@ export class CollectionDockingView extends CollectionViewBase { } 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 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, + borderStyle: "solid", + borderWidth: `${COLLECTION_BORDER_WIDTH}px`, + }} > + {chooseLayout()} </div> ); } +} + +interface DockingProps { + ContainingDiv: string, + Document: Document, + Container: any, + HtmlElement: HTMLElement, + CollectionDockingView: CollectionDockingView +} +@observer +export class RenderClass extends React.Component<DockingProps> { + @observable + private _parentScaling = 1; // used to transfer the dimensions of the content pane in the DOM to the ParentScaling prop of the DocumentView + + render() { + let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); + var layout = this.props.Document.GetText(KeyStore.Layout, ""); + var content = + <DocumentView key={this.props.Document.Id} Document={this.props.Document} + AddDocument={this.props.CollectionDockingView.addDocument} + RemoveDocument={this.props.CollectionDockingView.removeDocument} + GetTransform={() => Transform.Identity} + ParentScaling={this._parentScaling} + ContainingCollectionView={this.props.CollectionDockingView} DocumentView={undefined} /> + + if (nativeWidth > 0 && (layout.indexOf("CollectionFreeForm") == -1 || layout.indexOf("AnnotationsKey") != -1)) { + return <Measure onResize={ + action((r: any) => this._parentScaling = nativeWidth > 0 ? r.entry.width / nativeWidth : 1)} + > + {({ measureRef }) => <div ref={measureRef}> {content} </div>} + </Measure> + } + return <div> {content} </div> + } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss index 78d332322..fa1372925 100644 --- a/src/client/views/collections/CollectionFreeFormView.scss +++ b/src/client/views/collections/CollectionFreeFormView.scss @@ -1,4 +1,6 @@ .collectionfreeformview-container { + border-style: solid; + box-sizing: border-box; position: relative; top: 0; left: 0; @@ -10,6 +12,7 @@ top: 0; left: 0; } +<<<<<<< HEAD } .border { @@ -28,4 +31,6 @@ #prevCursor { animation: blink 1s infinite; +======= +>>>>>>> 3f98d6ec6050e7faa15179871f0d9669c1188a78 }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 1393557cd..af1889c3f 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -14,14 +14,13 @@ import { NumberField } from "../../../fields/NumberField"; import { Documents } from "../../documents/Documents"; import { FieldWaiting } from "../../../fields/Field"; import { Server } from "tls"; -var FontAwesomeIcon = require('react-fontawesome'); +import { Transform } from "../../util/Transform"; @observer export class CollectionFreeFormView extends CollectionViewBase { - public static LayoutString() { return CollectionViewBase.LayoutString("CollectionFreeFormView"); } + public static LayoutString(fieldKey: string = "DataKey") { return CollectionViewBase.LayoutString("CollectionFreeFormView", fieldKey); } private _containerRef = React.createRef<HTMLDivElement>(); private _canvasRef = React.createRef<HTMLDivElement>(); - private _nodeContainerRef = React.createRef<HTMLDivElement>(); private _lastX: number = 0; private _lastY: number = 0; private _downX: number = 0; @@ -38,6 +37,8 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get nativeWidth() { return this.props.DocumentForCollection.GetNumber(KeyStore.NativeWidth, 0); } + @computed + get nativeHeight() { return this.props.DocumentForCollection.GetNumber(KeyStore.NativeHeight, 0); } @computed get zoomScaling() { return this.props.DocumentForCollection.GetNumber(KeyStore.Scale, 1); } @@ -60,10 +61,8 @@ export class CollectionFreeFormView extends CollectionViewBase { const currScale = this.resizeScaling * this.zoomScaling * this.props.ContainingDocumentView!.ScalingToScreenSpace; const screenX = de.x - xOffset; const screenY = de.y - yOffset; - const docX = (screenX - translateX) / currScale; - const docY = (screenY - translateY) / currScale; - doc.x = docX; - doc.y = docY; + doc.props.Document.SetNumber(KeyStore.X, (screenX - translateX) / currScale); + doc.props.Document.SetNumber(KeyStore.Y, (screenY - translateY) / currScale); this.bringToFront(doc); } e.stopPropagation(); @@ -134,10 +133,16 @@ export class CollectionFreeFormView extends CollectionViewBase { @action onPointerWheel = (e: React.WheelEvent): void => { e.stopPropagation(); - - 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; + e.preventDefault(); + let modes = ['pixels', 'lines', 'page']; + let coefficient = 1000; + // if (modes[e.deltaMode] == 'pixels') coefficient = 50; + // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height?? + + let { LocalX, LocalY, ContainerX, ContainerY } = this.props.ContainingDocumentView!.TransformToLocalPoint(e.pageX, e.pageY); + var Xx = this.props.ContainingDocumentView!.LeftCorner(); + var Yy = this.props.ContainingDocumentView!.TopCorner(); + var deltaScale = (1 - (e.deltaY / coefficient)) * this.props.ContainingDocumentView!.props.Document.GetNumber(KeyStore.Scale, 1); var newDeltaScale = this.isAnnotationOverlay ? Math.max(1, deltaScale) : deltaScale; this.props.DocumentForCollection.SetNumber(KeyStore.Scale, newDeltaScale); @@ -146,8 +151,8 @@ export class CollectionFreeFormView extends CollectionViewBase { @action private SetPan(panX: number, panY: number) { - const newPanX = Math.max(-(this.resizeScaling * this.zoomScaling - this.resizeScaling) * this.nativeWidth, Math.min(0, panX)); - const newPanY = Math.min(0, panY); + const newPanX = Math.max((1 - this.zoomScaling) * this.nativeWidth, Math.min(0, panX)); + const newPanY = Math.max((1 - this.zoomScaling) * this.nativeHeight, Math.min(0, panY)); this.props.DocumentForCollection.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX); this.props.DocumentForCollection.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY); } @@ -159,8 +164,8 @@ export class CollectionFreeFormView extends CollectionViewBase { let fReader = new FileReader() let file = e.dataTransfer.items[0].getAsFile(); let that = this; - const panx: number = this.props.DocumentForCollection.GetData(KeyStore.PanX, NumberField, Number(0)); - const pany: number = this.props.DocumentForCollection.GetData(KeyStore.PanY, NumberField, Number(0)); + const panx: number = this.props.DocumentForCollection.GetNumber(KeyStore.PanX, 0); + const pany: number = this.props.DocumentForCollection.GetNumber(KeyStore.PanY, 0); let x = e.pageX - panx let y = e.pageY - pany @@ -196,7 +201,7 @@ export class CollectionFreeFormView extends CollectionViewBase { if (!e.ctrlKey && !e.altKey && !e.shiftKey) { if (this._previewCursorVisible) { //make textbox - let { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY } = this.props.ContainingDocumentView!.TransformToLocalPoint(this._downX, this._downY); + let { LocalX, LocalY } = this.props.ContainingDocumentView!.TransformToLocalPoint(this._downX, this._downY); let newBox = Documents.TextDocument({ width: 200, height: 100, x: LocalX, y: LocalY, title: "new" }); this.addDocument(newBox); } @@ -221,47 +226,65 @@ export class CollectionFreeFormView extends CollectionViewBase { } } + @computed + get translate(): [number, number] { + const x = this.props.DocumentForCollection.GetNumber(KeyStore.PanX, 0); + const y = this.props.DocumentForCollection.GetNumber(KeyStore.PanY, 0); + return [x, y]; + } + + @computed + get scale(): number { + return this.props.DocumentForCollection.GetNumber(KeyStore.Scale, 1); + } + + getTransform = (): Transform => { + const [x, y] = this.translate; + return this.props.GetTransform().scaled(this.scale).translate(x, y); + } + render() { const Document: Document = this.props.DocumentForCollection; const value: Document[] = Document.GetList<Document>(this.props.CollectionFieldKey, []); const panx: number = Document.GetNumber(KeyStore.PanX, 0); const pany: number = Document.GetNumber(KeyStore.PanY, 0); + var me = this; let cursor = null; //toggle for preview cursor -> will be on when user taps freeform if (this._previewCursorVisible) { //get local position and place cursor there! - let { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY } = this.props.ContainingDocumentView!.TransformToLocalPoint(this._downX, this._downY); + let { LocalX, LocalY } = this.props.ContainingDocumentView!.TransformToLocalPoint(this._downX, this._downY); cursor = <div id="prevCursor" onKeyPress={this.onKeyDown} style={{ color: "black", transform: `translate(${LocalX}px, ${LocalY}px)` }}>I</div> } return ( - <div className="border" style={{ - borderWidth: `${COLLECTION_BORDER_WIDTH} px`, - }}> - <div - className="collectionfreeformview-container" - onPointerDown={this.onPointerDown} - onWheel={this.onPointerWheel} - onContextMenu={(e) => e.preventDefault()} - onDrop={this.onDrop} - onDragOver={this.onDragOver} - onKeyPress={this.onKeyDown} - ref={this._containerRef}> - <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px) scale(${this.zoomScaling}, ${this.zoomScaling})`, transformOrigin: `left, top` }} ref={this._canvasRef}> - {this.props.BackgroundView} - <div className="node-container" ref={this._nodeContainerRef} onKeyPress={this.onKeyDown}> - - - {cursor} - - {value.map(doc => { - return (<CollectionFreeFormDocumentView Scaling={this.resizeScaling} key={doc.Id} ContainingCollectionView={this} Document={doc} DocumentView={undefined} />); - })} - </div> - </div> + <div className="collectionfreeformview-container" + onPointerDown={this.onPointerDown} + onWheel={this.onPointerWheel} + onContextMenu={(e) => e.preventDefault()} + onDrop={this.onDrop} + onDragOver={this.onDragOver} + style={{ + borderWidth: `${COLLECTION_BORDER_WIDTH}px`, + }} + ref={this._containerRef}> + <div className="collectionfreeformview" + style={{ width: "100%", transformOrigin: "left top", transform: ` translate(${panx}px, ${pany}px) scale(${this.zoomScaling}, ${this.zoomScaling})` }} + ref={this._canvasRef}> + + {this.props.BackgroundView} + {cursor} + {value.map(doc => { + return (<CollectionFreeFormDocumentView key={doc.Id} Document={doc} + AddDocument={this.addDocument} + RemoveDocument={this.removeDocument} + GetTransform={this.getTransform} + ParentScaling={1} + ContainingCollectionView={this} DocumentView={undefined} />); + })} </div> </div> ); diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index 707b44db6..633e3ca1b 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -1,3 +1,62 @@ + +.collectionSchemaView-container { + border-style: solid; + box-sizing: border-box; + position: absolute; + width: 100%; + height: 100%; + .collectionfreeformview-container { + border-width: 0px; + .collectionfreeformview > .jsx-parser{ + position:absolute + } + } + .imageBox-cont { + position:relative; + max-height:100%; + } + .ReactTable { + position: absolute; + display: inline-block; + width: 100%; + overflow: auto; + height: 100%; + background: white; + box-sizing: border-box; + } + .ReactTable .rt-thead.-header { + background:grey; + } + .ReactTable .rt-th, .ReactTable .rt-td { + max-height: 75px; + } + .ReactTable .rt-tbody .rt-tr-group:last-child { + border-bottom: grey; + border-bottom-style: solid; + border-bottom-width: 1; + } + .ReactTable .rt-td { + border-width: 1; + border-right-color: #aaa + } + .ReactTable .rt-tr-group { + border-width: 1; + border-bottom-color: #aaa + } + .imageBox-cont img { + object-fit: contain; + height: 100% + } + .documentView-node:first-child { + background: grey; + .imageBox-cont img { + object-fit: contain; + max-width: 100%; + height: 100% + } + } +} + .Resizer { box-sizing: border-box; background: #000; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index b2ee2f5f2..76706f520 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -7,13 +7,15 @@ import { observable, action, computed } from "mobx"; import SplitPane from "react-split-pane" import "./CollectionSchemaView.scss" import { ScrollBox } from "../../util/ScrollBox"; -import { CollectionViewBase } from "./CollectionViewBase"; +import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase"; import { DocumentView } from "../nodes/DocumentView"; import { EditableView } from "../EditableView"; import { CompileScript, ToField } from "../../util/Scripting"; -import { KeyStore as KS, Key } from "../../../fields/Key"; +import { KeyStore as KS, Key, KeyStore } from "../../../fields/Key"; import { Document } from "../../../fields/Document"; import { Field } from "../../../fields/Field"; +import { Transform } from "../../util/Transform"; +import Measure from "react-measure"; @observer export class CollectionSchemaView extends CollectionViewBase { @@ -26,13 +28,13 @@ export class CollectionSchemaView extends CollectionViewBase { let props: FieldViewProps = { doc: rowProps.value[0], fieldKey: rowProps.value[1], - DocumentViewForField: undefined + DocumentViewForField: undefined, } let contents = ( <FieldView {...props} /> ) return ( - <EditableView contents={contents} GetValue={() => { + <EditableView contents={contents} height={36} GetValue={() => { let field = props.doc.Get(props.fieldKey); if (field && field instanceof Field) { return field.ToScriptString(); @@ -97,47 +99,62 @@ export class CollectionSchemaView extends CollectionViewBase { } } + + @observable + private _parentScaling = 1; // used to transfer the dimensions of the content pane in the DOM to the ParentScaling prop of the DocumentView render() { const { DocumentForCollection: Document, CollectionFieldKey: fieldKey } = this.props; const children = Document.GetList<Document>(fieldKey, []); const columns = Document.GetList(KS.ColumnsKey, [KS.Title, KS.Data, KS.Author]) let content; + var me = this; if (this.selectedIndex != -1) { content = ( - <DocumentView Scaling={1} Document={children[this.selectedIndex]} DocumentView={undefined} ContainingCollectionView={this} /> + <Measure onResize={action((r: any) => { + var doc = children[this.selectedIndex]; + var n = doc.GetNumber(KeyStore.NativeWidth, 0); + if (n > 0 && r.entry.width > 0) { + this._parentScaling = r.entry.width / n; + } + })}> + {({ measureRef }) => + <div ref={measureRef}> + <DocumentView Document={children[this.selectedIndex]} + AddDocument={this.addDocument} RemoveDocument={this.removeDocument} + GetTransform={() => Transform.Identity}//TODO This should probably be an actual transform + ParentScaling={this._parentScaling} + DocumentView={undefined} ContainingCollectionView={me} /> + </div> + } + </Measure> ) } else { content = <div /> } return ( - <div onPointerDown={this.onPointerDown} > - <SplitPane split={"vertical"} defaultSize="60%"> - <ScrollBox> - <ReactTable - data={children} - pageSize={children.length} - page={0} - showPagination={false} - style={{ - display: "inline-block", - width: "100%" - }} - columns={columns.map(col => { - return ( - { - Header: col.Name, - accessor: (doc: Document) => [doc, col], - id: col.Id - }) - })} - column={{ - ...ReactTableDefaults.column, - Cell: this.renderCell - }} - getTrProps={this.getTrProps} - /> - </ScrollBox> + <div onPointerDown={this.onPointerDown} className="collectionSchemaView-container" + style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px`, }} > + <SplitPane split={"vertical"} defaultSize="60%" style={{ height: "100%", position: "relative", overflow: "none" }}> + <ReactTable + data={children} + pageSize={children.length} + page={0} + showPagination={false} + columns={columns.map(col => { + return ( + { + Header: col.Name, + accessor: (doc: Document) => [doc, col], + id: col.Id + }) + })} + column={{ + ...ReactTableDefaults.column, + Cell: this.renderCell + }} + getTrProps={this.getTrProps} + /> {content} </SplitPane> </div> diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 274f26ecc..2f304f666 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -10,15 +10,21 @@ import React = require("react"); import { DocumentView } from "../nodes/DocumentView"; import { CollectionDockingView } from "./CollectionDockingView"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; +import { Transform } from "../../util/Transform"; export interface CollectionViewProps { CollectionFieldKey: Key; DocumentForCollection: Document; ContainingDocumentView: Opt<DocumentView>; + GetTransform: () => Transform; BackgroundView: Opt<DocumentView>; +<<<<<<< HEAD DownX: number; DownY: number; +======= + ParentScaling: number; +>>>>>>> 3f98d6ec6050e7faa15179871f0d9669c1188a78 } export const COLLECTION_BORDER_WIDTH = 2; @@ -26,8 +32,8 @@ export const COLLECTION_BORDER_WIDTH = 2; @observer export class CollectionViewBase extends React.Component<CollectionViewProps> { - public static LayoutString(collectionType: string) { - return `<${collectionType} DocumentForCollection={Document} CollectionFieldKey={DataKey} ContainingDocumentView={DocumentView}/>`; + public static LayoutString(collectionType: string, fieldKey: string = "DataKey") { + return `<${collectionType} ParentScaling={ParentScaling} DocumentForCollection={Document} CollectionFieldKey={${fieldKey}} ContainingDocumentView={DocumentView} BackgroundView={BackgroundView} />`; } @computed public get active(): boolean { @@ -46,15 +52,18 @@ export class CollectionViewBase extends React.Component<CollectionViewProps> { } @action - removeDocument = (doc: Document): void => { + removeDocument = (doc: Document): boolean => { //TODO This won't create the field if it doesn't already exist const value = this.props.DocumentForCollection.GetData(this.props.CollectionFieldKey, ListField, new Array<Document>()) - if (value.indexOf(doc) !== -1) { - value.splice(value.indexOf(doc), 1) + let index = value.indexOf(doc); + if (index !== -1) { + value.splice(index, 1) SelectionManager.DeselectAll() ContextMenu.Instance.clearItems() + return true; } + return false } }
\ No newline at end of file diff --git a/src/client/views/nodes/AnnotationView.tsx b/src/client/views/nodes/AnnotationView.tsx deleted file mode 100644 index 2e50370a1..000000000 --- a/src/client/views/nodes/AnnotationView.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React = require('react') -import { CollectionViewProps } from '../collections/CollectionViewBase'; - -export class AnnotationView extends React.Component<CollectionViewProps> { - - render() { - return ( - <></> - ); - } - -}
\ No newline at end of file diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index bb8dea53b..a2fbe96d2 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -7,10 +7,10 @@ import { SelectionManager } from "../../util/SelectionManager"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; import { ContextMenu } from "../ContextMenu"; -import "./NodeView.scss"; +import "./DocumentView.scss"; import React = require("react"); import { DocumentView, DocumentViewProps } from "./DocumentView"; -var FontAwesomeIcon = require('react-fontawesome'); +import { Transform } from "../../util/Transform"; @observer @@ -18,7 +18,7 @@ export class CollectionFreeFormDocumentView extends DocumentView { private _contextMenuCanOpen = false; private _downX: number = 0; private _downY: number = 0; - //determines whether the blinking cursor for indicating whether a text will be made on key down is visible + // private _mainCont = React.createRef<HTMLDivElement>(); constructor(props: DocumentViewProps) { super(props); @@ -30,50 +30,55 @@ export class CollectionFreeFormDocumentView extends DocumentView { return new DOMRect(); } - @computed - get x(): number { - return this.props.Document.GetData(KeyStore.X, NumberField, Number(0)); + public LeftCorner(): number { + return this.props.Document.GetNumber(KeyStore.X, 0) + super.LeftCorner(); } - @computed - get y(): number { - return this.props.Document.GetData(KeyStore.Y, NumberField, Number(0)); - } - - set x(x: number) { - this.props.Document.SetData(KeyStore.X, x, NumberField) - } - - set y(y: number) { - this.props.Document.SetData(KeyStore.Y, y, NumberField) + public TopCorner(): number { + return this.props.Document.GetNumber(KeyStore.Y, 0) + super.TopCorner(); } @computed get transform(): string { - return `scale(${this.props.Scaling}, ${this.props.Scaling}) translate(${this.x}px, ${this.y}px)`; + return `scale(${this.props.ParentScaling}, ${this.props.ParentScaling}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px)`; } @computed get width(): number { - return this.props.Document.GetData(KeyStore.Width, NumberField, Number(0)); + return this.props.Document.GetNumber(KeyStore.Width, 0); + } + + @computed + get nativeWidth(): number { + return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } set width(w: number) { this.props.Document.SetData(KeyStore.Width, w, NumberField) + if (this.nativeWidth > 0 && this.nativeHeight > 0) { + this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w) + } } @computed get height(): number { - return this.props.Document.GetData(KeyStore.Height, NumberField, Number(0)); + return this.props.Document.GetNumber(KeyStore.Height, 0); + } + @computed + get nativeHeight(): number { + return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } set height(h: number) { - this.props.Document.SetData(KeyStore.Height, h, NumberField) + this.props.Document.SetData(KeyStore.Height, h, NumberField); + if (this.nativeWidth > 0 && this.nativeHeight > 0) { + this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h) + } } @computed get zIndex(): number { - return this.props.Document.GetData(KeyStore.ZIndex, NumberField, Number(0)); + return this.props.Document.GetNumber(KeyStore.ZIndex, 0); } set zIndex(h: number) { @@ -205,22 +210,26 @@ export class CollectionFreeFormDocumentView extends DocumentView { } } - render() { - var freestyling = this.props.ContainingCollectionView instanceof CollectionFreeFormView; + getTransform = (): Transform => { + return this.props.GetTransform().translated(this.props.Document.GetNumber(KeyStore.X, 0), this.props.Document.GetNumber(KeyStore.Y, 0)); + } + render() { + var parentScaling = this.nativeWidth > 0 ? this.width / this.nativeWidth : 1; return ( - <div className="node" ref={this._mainCont} style={{ - transformOrigin: "left top", - transform: freestyling ? this.transform : "", - width: freestyling ? this.width : "100%", - height: freestyling ? this.height : "100%", - position: freestyling ? "absolute" : "relative", - zIndex: freestyling ? this.zIndex : 0, - }} + <div className="documentView-node" ref={this._mainCont} onContextMenu={this.onContextMenu} - onPointerDown={this.onPointerDown}> - - <DocumentView {...this.props} DocumentView={this} /> + onPointerDown={this.onPointerDown} + style={{ + transformOrigin: "left top", + transform: this.transform, + width: this.width, + height: this.height, + position: "absolute", + zIndex: this.zIndex, + }}> + + <DocumentView {...this.props} ref={this._renderDoc} ParentScaling={parentScaling} GetTransform={this.getTransform} DocumentView={this} /> </div> ); } diff --git a/src/client/views/nodes/NodeView.scss b/src/client/views/nodes/DocumentView.scss index dac1c0a8e..8e2ebd690 100644 --- a/src/client/views/nodes/NodeView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -1,4 +1,4 @@ -.node { +.documentView-node { position: absolute; background: #cdcdcd; overflow: hidden; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index bba5becc3..c5270e0cd 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -4,8 +4,6 @@ import { Document } from "../../../fields/Document"; import { Opt, FieldWaiting } 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 { Utils } from "../../../Utils"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; @@ -13,31 +11,44 @@ import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "../collections/CollectionViewBase"; import { FormattedTextBox } from "../nodes/FormattedTextBox"; import { ImageBox } from "../nodes/ImageBox"; -import "./NodeView.scss"; +import "./DocumentView.scss"; import React = require("react"); +import { Transform } from "../../util/Transform"; const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this? export interface DocumentViewProps { - Document: Document; DocumentView: Opt<DocumentView> // needed only to set ContainingDocumentView on CollectionViewProps when invoked from JsxParser -- is there a better way? ContainingCollectionView: Opt<CollectionViewBase>; - Scaling: number; + + Document: Document; + AddDocument?: (doc: Document) => void; + RemoveDocument?: (doc: Document) => boolean; + GetTransform: () => Transform; + ParentScaling: number; } + @observer export class DocumentView extends React.Component<DocumentViewProps> { + protected _renderDoc = React.createRef<any>(); protected _mainCont = React.createRef<any>(); get MainContent() { return this._mainCont; } + + @computed + get parentScaling(): number { + return this._renderDoc.current ? this._renderDoc.current.props.ParentScaling : this.props.ParentScaling > 0 ? this.props.ParentScaling : 1; + } + @computed get layout(): string { - return this.props.Document.GetData(KeyStore.Layout, TextField, String("<p>Error loading layout data</p>")); + return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); } @computed - get annotatedlayout(): string { - return this.props.Document.GetData(KeyStore.AnnotatedLayout, TextField, String("<p>Error loading layout data</p>")); + get backgroundLayout(): string { + return this.props.Document.GetText(KeyStore.BackgroundLayout, ""); } @computed @@ -63,75 +74,78 @@ export class DocumentView extends React.Component<DocumentViewProps> { return 1; } + + public LeftCorner(): number { + if (this.props.ContainingCollectionView) { + if (this.props.ContainingCollectionView instanceof CollectionDockingView) { + // this is a hacky way to account for the titles/pane placement/etc of a CollectionDockingView + // this only works if the collectionDockingView is the root collection, too. + // need to find a better way. + var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!); + return rx + COLLECTION_BORDER_WIDTH; + } + return COLLECTION_BORDER_WIDTH; // assumes all collections have the same border + } + return 0; + } + + public TopCorner(): number { + if (this.props.ContainingCollectionView) { + if (this.props.ContainingCollectionView instanceof CollectionDockingView) { + // this is a hacky way to account for the titles/pane placement/etc of a CollectionDockingView + // this only works if the collectionDockingView is the root collection, too. + // need to find a better way. + var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!); + return ry + COLLECTION_BORDER_WIDTH; + } + return COLLECTION_BORDER_WIDTH; // assumes all collections have the same border + } + return 0; + } // - // Converts a coordinate in the screen space of the app into a local document coordinate. + // Converts a coordinate in the screen space of the app to a local point in the space of the DocumentView. + // This also returns the point in the coordinate space of this document's containing CollectionView // public TransformToLocalPoint(screenX: number, screenY: number) { // if this collection view is nested within another collection view, then // first transform the screen point into the parent collection's coordinate space. - let { LocalX: parentX, LocalY: parentY } = this.props.ContainingCollectionView != undefined && - this.props.ContainingCollectionView.props.ContainingDocumentView != undefined ? - this.props.ContainingCollectionView.props.ContainingDocumentView.TransformToLocalPoint(screenX, screenY) : - { LocalX: screenX, LocalY: screenY }; + let containingCollectionViewDoc = this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.ContainingDocumentView : undefined; + let { LocalX: parentX, LocalY: parentY } = !containingCollectionViewDoc ? { LocalX: screenX, LocalY: screenY } : + containingCollectionViewDoc.TransformToLocalPoint(screenX, screenY); let ContainerX: number = parentX - COLLECTION_BORDER_WIDTH; let ContainerY: number = parentY - COLLECTION_BORDER_WIDTH; - var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0)); - var Yy = this.props.Document.GetData(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.MainContent.current!); - Xx = rx - COLLECTION_BORDER_WIDTH; - Yy = ry - COLLECTION_BORDER_WIDTH; - } - let Ss = this.props.Document.GetNumber(KeyStore.Scale, 1); - let Panxx = this.props.Document.GetData(KeyStore.PanX, NumberField, Number(0)); - let Panyy = this.props.Document.GetData(KeyStore.PanY, NumberField, Number(0)); - let LocalX = (ContainerX - (Xx + Panxx)) / Ss; - let LocalY = (ContainerY - (Yy + Panyy)) / Ss; + let Panxx = this.props.Document.GetNumber(KeyStore.PanX, 0); + let Panyy = this.props.Document.GetNumber(KeyStore.PanY, 0); + let LocalX = (ContainerX - (this.LeftCorner() + Panxx)) / Ss; + let LocalY = (ContainerY - (this.TopCorner() + Panyy)) / Ss; - return { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY }; + return { LocalX, LocalY, ContainerX, ContainerY }; } // - // Converts a point in the coordinate space of a document to a screen space coordinate. + // Converts a point in the coordinate space of the document to coordinate in app screen coordinates // - public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: number, ScreenY: number } { - - var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0)); - var Yy = this.props.Document.GetData(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.MainContent.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; - + public TransformToScreenPoint(localX: number, localY: number, applyViewXf: boolean = false): { ScreenX: number, ScreenY: number } { + var parentScaling = applyViewXf ? this.parentScaling : 1; + let Panxx = applyViewXf ? this.props.Document.GetNumber(KeyStore.PanX, 0) : 0; + let Panyy = applyViewXf ? this.props.Document.GetNumber(KeyStore.PanY, 0) : 0; + var Zoom = applyViewXf ? this.props.Document.GetNumber(KeyStore.Scale, 1) : 1; + + let parentX = this.LeftCorner() + (Panxx + (localX - COLLECTION_BORDER_WIDTH) * Zoom) * parentScaling; + let parentY = this.TopCorner() + (Panyy + (localY - COLLECTION_BORDER_WIDTH) * Zoom) * parentScaling; // if this collection view is nested within another collection view, then // first transform the local point into the parent collection's coordinate space. - let containingDocView = this.props.ContainingCollectionView != undefined ? this.props.ContainingCollectionView.props.ContainingDocumentView : undefined; - if (containingDocView != undefined) { - let ss = containingDocView.props.Document.GetNumber(KeyStore.Scale, 1) * this.props.Scaling; - let panxx = containingDocView.props.Document.GetData(KeyStore.PanX, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss; - let panyy = containingDocView.props.Document.GetData(KeyStore.PanY, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss; - let { ScreenX, ScreenY } = containingDocView.TransformToScreenPoint(parentX, parentY, ss, panxx, panyy); + let containingDocView = this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.ContainingDocumentView : undefined; + if (containingDocView) { + let { ScreenX, ScreenY } = containingDocView.TransformToScreenPoint(parentX + COLLECTION_BORDER_WIDTH * parentScaling, parentY + COLLECTION_BORDER_WIDTH * parentScaling, true); parentX = ScreenX; parentY = ScreenY; } return { ScreenX: parentX, ScreenY: parentY }; } - render() { let bindings = { ...this.props } as any; for (const key of this.layoutKeys) { @@ -144,16 +158,28 @@ export class DocumentView extends React.Component<DocumentViewProps> { if (bindings.DocumentView === undefined) { bindings.DocumentView = this; // set the DocumentView to this if it hasn't already been set by a sub-class during its render method. } - var annotated = <JsxParser - components={{ FormattedTextBox: FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView }} - bindings={bindings} - jsx={this.annotatedlayout} - showWarnings={true} - onError={(test: any) => { console.log(test) }} - />; - bindings["BackgroundView"] = this.annotatedlayout ? annotated : null; + if (this.backgroundLayout) { + var backgroundview = <JsxParser + components={{ FormattedTextBox: FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView }} + bindings={bindings} + jsx={this.backgroundLayout} + showWarnings={true} + onError={(test: any) => { console.log(test) }} + />; + bindings["BackgroundView"] = backgroundview; + } + var nativewidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); + var nativeheight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0); + var width = nativewidth > 0 ? nativewidth + "px" : "100%"; + var height = nativeheight > 0 ? nativeheight + "px" : "100%"; return ( - <div className="node" ref={this._mainCont} style={{ width: "100%", height: "100%", }}> + <div className="documentView-node" ref={this._mainCont} + style={{ + width: width, + height: height, + transformOrigin: "top left", + transform: `scale(${this.props.ParentScaling},${this.props.ParentScaling})` + }}> <JsxParser components={{ FormattedTextBox: FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView }} bindings={bindings} diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 492367fce..5139d5d6b 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -9,6 +9,6 @@ } .formattedTextBox-cont { - background: white; + background: beige; padding: 1vw; }
\ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index eead43b9f..f6bd0c0f8 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -12,6 +12,7 @@ import React = require("react") import { RichTextField } from "../../../fields/RichTextField"; import { FieldViewProps, FieldView } from "./FieldView"; import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; +import { observer } from "mobx-react"; // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document @@ -117,7 +118,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { return (<div className="formattedTextBox-cont" style={{ color: "initial", - whiteSpace: "initial" + whiteSpace: "initial", }} onPointerDown={this.onPointerDown} ref={this._ref} />) diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 83392311b..36f5e0fe0 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -1,7 +1,14 @@ .imageBox-cont { padding: 0vw; - position: absolute + position: absolute; + width: 100%; + max-width: 100%; + max-height: 100% +} +.imageBox-cont img { + max-width: 100%; + max-height: 100% } .imageBox-button { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index b96e717ed..2fa70734d 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -85,11 +85,11 @@ export class ImageBox extends React.Component<FieldViewProps> { let field = this.props.doc.Get(this.props.fieldKey); let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" : field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif"; - let width = this.props.doc.GetNumber(KeyStore.Width, 1); + let nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 1); return ( <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} > - <img src={path} width={width} alt="Image not found" /> + <img src={path} width={nativeWidth} alt="Image not found" /> {this.lightbox(path)} </div>) } diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 6f9752a8e..c682d8e94 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -1,15 +1,14 @@ -import { Field, Cast, Opt, FieldWaiting, FIELD_ID, FieldValue } from "./Field" +import { Field, Cast, Opt, FieldWaiting, FieldId, FieldValue } from "./Field" import { Key, KeyStore } from "./Key" import { NumberField } from "./NumberField"; import { ObservableMap, computed, action, observable } from "mobx"; import { TextField } from "./TextField"; import { ListField } from "./ListField"; -import { findDOMNode } from "react-dom"; import { Server } from "../client/Server"; export class Document extends Field { - public fields: ObservableMap<Key, Opt<Field>> = new ObservableMap(); - public _proxies: ObservableMap<Key, FIELD_ID> = new ObservableMap(); + public fields: ObservableMap<Key, Field> = new ObservableMap(); + public _proxies: ObservableMap<Key, FieldId> = new ObservableMap(); @computed public get Title() { diff --git a/src/fields/Field.ts b/src/fields/Field.ts index 6adee9b61..4a3968699 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -10,21 +10,21 @@ export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T return undefined; } -export let FieldWaiting: FIELD_WAITING = "<Waiting>"; +export const FieldWaiting: FIELD_WAITING = "<Waiting>"; export type FIELD_WAITING = "<Waiting>"; -export type FIELD_ID = string | undefined; +export type FieldId = string; export type Opt<T> = T | undefined; export type FieldValue<T> = Opt<T> | FIELD_WAITING; export abstract class Field { //FieldUpdated: TypedEvent<Opt<FieldUpdatedArgs>> = new TypedEvent<Opt<FieldUpdatedArgs>>(); - private id: FIELD_ID; - get Id(): FIELD_ID { + private id: FieldId; + get Id(): FieldId { return this.id; } - constructor(id: FIELD_ID = undefined) { + constructor(id: Opt<FieldId> = undefined) { this.id = id || Utils.GenerateGuid(); } diff --git a/src/fields/Key.ts b/src/fields/Key.ts index 67a5f86fb..8d92b89b6 100644 --- a/src/fields/Key.ts +++ b/src/fields/Key.ts @@ -43,13 +43,14 @@ export namespace KeyStore { export const PanY = new Key("PanY"); export const Scale = new Key("Scale"); export const NativeWidth = new Key("NativeWidth"); + export const NativeHeight = new Key("NativeHeight"); export const Width = new Key("Width"); export const Height = new Key("Height"); export const ZIndex = new Key("ZIndex"); export const Data = new Key("Data"); export const Annotations = new Key("Annotations"); export const Layout = new Key("Layout"); - export const AnnotatedLayout = new Key("AnnotatedLayout"); + export const BackgroundLayout = new Key("BackgroundLayout"); export const LayoutKeys = new Key("LayoutKeys"); export const LayoutFields = new Key("LayoutFields"); export const ColumnsKey = new Key("SchemaColumns"); |