aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Schicke <tyler_schicke@brown.edu>2019-02-19 06:21:26 -0500
committerTyler Schicke <tyler_schicke@brown.edu>2019-02-19 06:21:26 -0500
commitb305a084cd43afe24d84221a1b4e55c252f5af8a (patch)
treed8c5ee1651fb0a1104d9636860c9417e5e306fa2
parentf9d55f59c9db0f9ea7e98729f9e0892a828ee3f6 (diff)
parentec2b0b56058fce137ff28ae3ec125f9e695f315c (diff)
Merge branch 'master' of github-tsch-brown:browngraphicslab/Dash-Web into server_database_merge
-rw-r--r--.vscode/launch.json6
-rw-r--r--package-lock.json65
-rw-r--r--package.json5
-rw-r--r--src/client/Server.ts27
-rw-r--r--src/client/SocketStub.ts13
-rw-r--r--src/client/documents/Documents.ts32
-rw-r--r--src/client/util/DragManager.ts12
-rw-r--r--src/client/util/SelectionManager.ts12
-rw-r--r--src/client/util/Transform.ts113
-rw-r--r--src/client/views/DocumentDecorations.tsx46
-rw-r--r--src/client/views/EditableView.tsx6
-rw-r--r--src/client/views/Main.tsx14
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx125
-rw-r--r--src/client/views/collections/CollectionFreeFormView.scss9
-rw-r--r--src/client/views/collections/CollectionFreeFormView.tsx189
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss59
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx97
-rw-r--r--src/client/views/collections/CollectionViewBase.tsx41
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx190
-rw-r--r--src/client/views/nodes/DocumentView.scss (renamed from src/client/views/nodes/NodeView.scss)2
-rw-r--r--src/client/views/nodes/DocumentView.tsx276
-rw-r--r--src/client/views/nodes/FieldView.tsx6
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss2
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx7
-rw-r--r--src/client/views/nodes/ImageBox.scss8
-rw-r--r--src/client/views/nodes/ImageBox.tsx14
-rw-r--r--src/fields/BasicField.ts4
-rw-r--r--src/fields/Document.ts30
-rw-r--r--src/fields/DocumentReference.ts4
-rw-r--r--src/fields/Field.ts10
-rw-r--r--src/fields/ImageField.ts4
-rw-r--r--src/fields/Key.ts2
-rw-r--r--src/fields/KeyStore.ts8
-rw-r--r--src/fields/ListField.ts4
-rw-r--r--src/fields/NumberField.ts4
-rw-r--r--src/fields/RichTextField.ts4
-rw-r--r--src/fields/TextField.ts4
-rw-r--r--src/server/Message.ts1
-rw-r--r--src/server/index.ts4
39 files changed, 941 insertions, 518 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 6819ffbf2..8aa197fa5 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,7 +11,11 @@
"sourceMaps": true,
"breakOnLoad": true,
"url": "http://localhost:1050",
- "webRoot": "${workspaceFolder}"
+ "webRoot": "${workspaceFolder}",
+ "skipFiles": [
+ "${workspaceRoot}/node_modules/**/*.js",
+ "${workspaceRoot}/node_modules/**/*.ts",
+ ]
},
{
"type": "node",
diff --git a/package-lock.json b/package-lock.json
index 35e7bdec0..eaa193964 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,6 +4,21 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+ "@babel/runtime": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.3.1.tgz",
+ "integrity": "sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==",
+ "requires": {
+ "regenerator-runtime": "^0.12.0"
+ },
+ "dependencies": {
+ "regenerator-runtime": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
+ "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
+ }
+ }
+ },
"@fortawesome/fontawesome-common-types": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.14.tgz",
@@ -336,6 +351,14 @@
"@types/react": "*"
}
},
+ "@types/react-measure": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/react-measure/-/react-measure-2.0.4.tgz",
+ "integrity": "sha512-0puxiERCQ5Az4LyO+2r9bh6ECXTuy2PpsO+LY8ICezEi3YgIVaJwqRsplpNU18dZMqkpsdJlTB2cvSvkY0eR5Q==",
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/react-table": {
"version": "6.7.21",
"resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.7.21.tgz",
@@ -2550,6 +2573,11 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
+ "element-resize-event": {
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/element-resize-event/-/element-resize-event-2.0.9.tgz",
+ "integrity": "sha1-L14VgaKW61J1IQwUG8VjQuIY+HY="
+ },
"elliptic": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
@@ -3332,13 +3360,11 @@
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -3355,8 +3381,7 @@
},
"concat-map": {
"version": "0.0.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"console-control-strings": {
"version": "1.1.0",
@@ -3485,7 +3510,6 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -3813,6 +3837,11 @@
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
"dev": true
},
+ "get-node-dimensions": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/get-node-dimensions/-/get-node-dimensions-1.2.1.tgz",
+ "integrity": "sha512-2MSPMu7S1iOTL+BOa6K1S62hB2zUAYNF/lV0gSVlOaacd087lc6nR1H1r0e3B1CerTo+RceOmi1iJW+vp21xcQ=="
+ },
"get-stdin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
@@ -10277,6 +10306,14 @@
"scheduler": "^0.12.0"
}
},
+ "react-dimensions": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/react-dimensions/-/react-dimensions-1.3.1.tgz",
+ "integrity": "sha512-go5vMuGUxaB5PiTSIk+ZfAxLbHwcIgIfLhkBZ2SIMQjaCgnpttxa30z5ijEzfDjeOCTGRpxvkzcmE4Vt4Ppvyw==",
+ "requires": {
+ "element-resize-event": "^2.0.4"
+ }
+ },
"react-dom": {
"version": "16.7.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.7.0.tgz",
@@ -10326,6 +10363,17 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
+ "react-measure": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/react-measure/-/react-measure-2.2.4.tgz",
+ "integrity": "sha512-gpZA4J8sKy1TzTfnOXiiTu01GV8B5OyfF9k7Owt38T6Xxlll19PBE13HKTtauEmDdJO5u4o3XcTiGqCw5wpfjw==",
+ "requires": {
+ "@babel/runtime": "^7.2.0",
+ "get-node-dimensions": "^1.2.1",
+ "prop-types": "^15.6.2",
+ "resize-observer-polyfill": "^1.5.0"
+ }
+ },
"react-modal": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.8.1.tgz",
@@ -10599,6 +10647,11 @@
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
+ "resize-observer-polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+ },
"resolve": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
diff --git a/package.json b/package.json
index 94bf5a217..32567b121 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"@types/prosemirror-transform": "^1.1.0",
"@types/prosemirror-view": "^1.3.0",
"@types/pug": "^2.0.4",
+ "@types/react-measure": "^2.0.4",
"@types/react-table": "^6.7.21",
"@types/socket.io": "^2.1.2",
"@types/socket.io-client": "^1.4.32",
@@ -75,8 +76,8 @@
"flexlayout-react": "^0.3.3",
"golden-layout": "^1.5.9",
"jsonwebtoken": "^8.4.0",
- "jsx-to-string": "^1.4.0",
"lodash": "^4.17.11",
+ "jsx-to-string": "^1.4.0",
"mobx": "^5.9.0",
"mobx-react": "^5.3.5",
"mobx-react-devtools": "^6.0.3",
@@ -98,10 +99,12 @@
"prosemirror-view": "^1.7.1",
"pug": "^2.0.3",
"react": "^16.5.2",
+ "react-dimensions": "^1.3.1",
"react-dom": "^16.7.0",
"react-golden-layout": "^1.0.6",
"react-image-lightbox": "^5.1.0",
"react-jsx-parser": "^1.13.0",
+ "react-measure": "^2.2.4",
"react-mosaic": "0.0.20",
"react-split-pane": "^0.1.85",
"react-table": "^6.9.0",
diff --git a/src/client/Server.ts b/src/client/Server.ts
index 2077c0c57..69780a7b5 100644
--- a/src/client/Server.ts
+++ b/src/client/Server.ts
@@ -1,7 +1,6 @@
-import { Field, FieldWaiting, FIELD_ID, FIELD_WAITING, FieldValue, Opt } from "../fields/Field"
import { Key } from "../fields/Key"
-import { KeyStore } from "../fields/KeyStore"
-import { ObservableMap, action, reaction, when } from "mobx";
+import { ObservableMap, action, reaction } from "mobx";
+import { Field, FieldWaiting, FIELD_WAITING, Opt, FieldId } from "../fields/Field"
import { Document } from "../fields/Document"
import { SocketStub } from "./SocketStub";
import * as OpenSocket from 'socket.io-client';
@@ -9,7 +8,7 @@ import { Utils } from "./../Utils";
import { MessageStore, Types } from "./../server/Message";
export class Server {
- public static ClientFieldsCached: ObservableMap<FIELD_ID, Field | FIELD_WAITING> = new ObservableMap();
+ public static ClientFieldsCached: ObservableMap<FieldId, Field | FIELD_WAITING> = new ObservableMap();
static Socket: SocketIOClient.Socket = OpenSocket("http://localhost:1234");
static GUID: string = Utils.GenerateGuid()
@@ -17,7 +16,7 @@ export class Server {
// 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: Opt<Field>) => void = (f) => { }, doc?: Document, key?: Key, hackTimeout: number = -1) {
+ public static GetField(fieldid: FieldId, callback: (field: Opt<Field>) => void): void {
let cached = this.ClientFieldsCached.get(fieldid);
if (!cached) {
this.ClientFieldsCached.set(fieldid, FieldWaiting);
@@ -46,10 +45,9 @@ export class Server {
}
})
}
- return cached;
}
- public static GetFields(fieldIds: FIELD_ID[], callback: (fields: { [id: string]: Field }) => any) {
+ public static GetFields(fieldIds: FieldId[], callback: (fields: { [id: string]: Field }) => any) {
SocketStub.SEND_FIELDS_REQUEST(fieldIds, (fields) => {
for (let key in fields) {
let field = fields[key];
@@ -76,12 +74,15 @@ export class Server {
// console.log("how did you get a key that isnt a key wtf")
// }
// })
- return this.GetField(doc._proxies.get(key.Id),
- action((fieldfromserver: Opt<Field>) => {
- if (fieldfromserver) {
- doc.fields.set(key.Id, { key, field: fieldfromserver });
- }
- }), doc, key);
+ let field = doc._proxies.get(key.Id);
+ if (field) {
+ this.GetField(field,
+ action((fieldfromserver: Opt<Field>) => {
+ if (fieldfromserver) {
+ doc.fields.set(key.Id, { key, field: fieldfromserver });
+ }
+ }));
+ }
}
public static AddDocument(document: Document) {
diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts
index 2bddf4e97..18df4ca0a 100644
--- a/src/client/SocketStub.ts
+++ b/src/client/SocketStub.ts
@@ -1,16 +1,15 @@
-import { Field, FIELD_ID, Opt } from "../fields/Field"
import { Key } from "../fields/Key"
-import { KeyStore } from "../fields/KeyStore"
-import { ObservableMap, action } from "mobx";
+import { Field, FieldId, Opt } from "../fields/Field"
+import { ObservableMap } from "mobx";
import { Document } from "../fields/Document"
-import { MessageStore, SetFieldArgs, GetFieldArgs, DocumentTransfer, Types } from "../server/Message";
+import { MessageStore, DocumentTransfer } from "../server/Message";
import { Utils } from "../Utils";
import { Server } from "./Server";
import { ServerUtils } from "../server/ServerUtil";
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
@@ -35,7 +34,7 @@ export class SocketStub {
Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(document.ToJson()))
}
- public static SEND_FIELD_REQUEST(fieldid: FIELD_ID, callback: (field: Opt<Field>) => void) {
+ public static SEND_FIELD_REQUEST(fieldid: FieldId, callback: (field: Opt<Field>) => void) {
if (fieldid) {
Utils.EmitCallback(Server.Socket, MessageStore.GetField, fieldid, (field: any) => {
if (field) {
@@ -47,7 +46,7 @@ export class SocketStub {
}
}
- public static SEND_FIELDS_REQUEST(fieldIds: FIELD_ID[], callback: (fields: { [key: string]: Field }) => any) {
+ public static SEND_FIELDS_REQUEST(fieldIds: FieldId[], callback: (fields: { [key: string]: Field }) => any) {
Utils.EmitCallback(Server.Socket, MessageStore.GetFields, fieldIds, (fields: any[]) => {
let fieldMap: any = {};
for (let field of fields) {
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f779dcd03..abb9544a5 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;
}
@@ -33,19 +35,25 @@ export namespace Documents {
}
function setupOptions(doc: Document, options: DocumentOptions): void {
- if (options.x != undefined) {
+ if (options.x !== undefined) {
doc.SetData(KeyStore.X, options.x, NumberField);
}
- if (options.y != undefined) {
+ if (options.y !== undefined) {
doc.SetData(KeyStore.Y, options.y, NumberField);
}
- if (options.width != undefined) {
+ if (options.width !== undefined) {
doc.SetData(KeyStore.Width, options.width, NumberField);
}
- if (options.height != undefined) {
+ if (options.height !== undefined) {
doc.SetData(KeyStore.Height, options.height, NumberField);
}
- if (options.title != undefined) {
+ if (options.nativeWidth !== undefined) {
+ doc.SetData(KeyStore.NativeWidth, options.nativeWidth, NumberField);
+ }
+ if (options.nativeHeight !== undefined) {
+ doc.SetData(KeyStore.NativeHeight, options.nativeHeight, NumberField);
+ }
+ if (options.title !== undefined) {
doc.SetData(KeyStore.Title, options.title, TextField);
}
doc.SetData(KeyStore.Scale, 1, NumberField);
@@ -129,11 +137,14 @@ 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(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(ImageBox.LayoutString()));
+ 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]));
+ imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data, KeyStore.Annotations]));
return imageProto;
}
return imageProto;
@@ -143,6 +154,9 @@ export namespace Documents {
let doc = GetImagePrototype().MakeDelegate();
setupOptions(doc, options);
doc.Set(KeyStore.Data, new ImageField(new URL(url)));
+
+ let annotation = Documents.TextDocument({ title: "hello" });
+ doc.Set(KeyStore.Annotations, new ListField([annotation]));
return doc;
}
@@ -158,7 +172,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/DragManager.ts b/src/client/util/DragManager.ts
index f4dcce7c8..337ec855a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -78,9 +78,13 @@ export namespace DragManager {
dragElement.style.transformOrigin = "0 0";
dragElement.style.zIndex = "1000";
dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+ dragElement.style.width = `${rect.width / scaleX}px`;
+ dragElement.style.height = `${rect.height / scaleY}px`;
+ // It seems like the above code should be able to just be this:
+ // dragElement.style.transform = `translate(${x}px, ${y}px)`;
+ // dragElement.style.width = `${rect.width}px`;
+ // dragElement.style.height = `${rect.height}px`;
dragDiv.appendChild(dragElement);
- _lastPointerX = dragData["xOffset"] + rect.left;
- _lastPointerY = dragData["yOffset"] + rect.top;
let hideSource = false;
if (typeof options.hideSource === "boolean") {
@@ -96,8 +100,8 @@ export namespace DragManager {
const moveHandler = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- x += e.clientX - _lastPointerX; _lastPointerX = e.clientX;
- y += e.clientY - _lastPointerY; _lastPointerY = e.clientY;
+ x += e.movementX;
+ y += e.movementY;
dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
};
const upHandler = (e: PointerEvent) => {
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 0759ae110..1a711ae64 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,13 +1,13 @@
-import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
import { observable, action } from "mobx";
+import { DocumentView } from "../views/nodes/DocumentView";
export namespace SelectionManager {
class Manager {
@observable
- SelectedDocuments: Array<CollectionFreeFormDocumentView> = [];
+ SelectedDocuments: Array<DocumentView> = [];
@action
- SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void {
+ SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
// if doc is not in SelectedDocuments, add it
if (!ctrlPressed) {
manager.SelectedDocuments = [];
@@ -21,11 +21,11 @@ export namespace SelectionManager {
const manager = new Manager;
- export function SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void {
+ export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
manager.SelectDoc(doc, ctrlPressed)
}
- export function IsSelected(doc: CollectionFreeFormDocumentView): boolean {
+ export function IsSelected(doc: DocumentView): boolean {
return manager.SelectedDocuments.indexOf(doc) !== -1;
}
@@ -33,7 +33,7 @@ export namespace SelectionManager {
manager.SelectedDocuments = []
}
- export function SelectedDocuments(): Array<CollectionFreeFormDocumentView> {
+ export function SelectedDocuments(): Array<DocumentView> {
return manager.SelectedDocuments;
}
} \ No newline at end of file
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
new file mode 100644
index 000000000..9fd4f7bef
--- /dev/null
+++ b/src/client/util/Transform.ts
@@ -0,0 +1,113 @@
+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);
+ }
+
+ get TranslateX(): number { return this._translateX; }
+ get TranslateY(): number { return this._translateY; }
+ get Scale(): number { return this._scale; }
+
+ 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;
+ }
+
+ scale = (scale: number): Transform => {
+ this._scale *= scale;
+ this._translateX *= scale;
+ this._translateY *= scale;
+ return this;
+ }
+
+ scaleAbout = (scale: number, x: number, y: number): Transform => {
+ this._translateX += x * this._scale - x * this._scale * scale;
+ this._translateY += y * this._scale - y * this._scale * scale;
+ this._scale *= scale;
+ return this;
+ }
+
+ transform = (transform: Transform): Transform => {
+ this._translateX = transform._translateX + transform._scale * this._translateX;
+ this._translateY = transform._translateY + transform._scale * this._translateY;
+ this._scale *= transform._scale;
+ return this;
+ }
+
+ preTranslate = (x: number, y: number): Transform => {
+ this._translateX += this._scale * x;
+ this._translateY += this._scale * y;
+ return this;
+ }
+
+ preScale = (scale: number): Transform => {
+ this._scale *= scale;
+ return this;
+ }
+
+ preTransform = (transform: Transform): Transform => {
+ this._translateX += transform._translateX * this._scale;
+ this._translateY += transform._translateY * this._scale;
+ this._scale *= transform._scale;
+ return this;
+ }
+
+ translated = (x: number, y: number): Transform => {
+ return this.copy().translate(x, y);
+ }
+
+ preTranslated = (x: number, y: number): Transform => {
+ return this.copy().preTranslate(x, y);
+ }
+
+ scaled = (scale: number): Transform => {
+ return this.copy().scale(scale);
+ }
+
+ scaledAbout = (scale: number, x: number, y: number): Transform => {
+ return this.copy().scaleAbout(scale, x, y);
+ }
+
+ preScaled = (scale: number): Transform => {
+ return this.copy().preScale(scale);
+ }
+
+ transformed = (transform: Transform): Transform => {
+ return this.copy().transform(transform);
+ }
+
+ 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];
+ }
+
+ transformDirection = (x: number, y: number): [number, number] => {
+ return [x * this._scale, y * this._scale];
+ }
+
+ 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 564cb59f6..62a1899bb 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -4,6 +4,8 @@ import { SelectionManager } from "../util/SelectionManager";
import { observer } from "mobx-react";
import './DocumentDecorations.scss'
import { CollectionFreeFormView } from "./collections/CollectionFreeFormView";
+import { KeyStore } from '../../fields/KeyStore'
+import { NumberField } from "../../fields/NumberField";
@observer
export class DocumentDecorations extends React.Component {
@@ -25,11 +27,15 @@ export class DocumentDecorations extends React.Component {
!(element.props.ContainingCollectionView instanceof CollectionFreeFormView)) {
return bounds;
}
- var spt = element.TransformToScreenPoint(0, 0);
- var bpt = element.TransformToScreenPoint(element.width, element.height);
+ let transform = element.props.ScreenToLocalTransform().inverse();
+ var [sptX, sptY] = transform.transformPoint(0, 0);
+ // var [bptX, bptY] = transform.transformDirection(element.width, element.height);
+ let doc = element.props.Document;
+ let [bptX, bptY] = [doc.GetNumber(KeyStore.Width, 0), doc.GetNumber(KeyStore.Height, 0)];
+ [bptX, bptY] = transform.transformPoint(bptX, bptY);
return {
- x: Math.min(spt.ScreenX, bounds.x), y: Math.min(spt.ScreenY, bounds.y),
- r: Math.max(bpt.ScreenX, bounds.r), b: Math.max(bpt.ScreenY, bounds.b)
+ x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
+ r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
}
}, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
}
@@ -105,14 +111,32 @@ export class DocumentDecorations extends React.Component {
SelectionManager.SelectedDocuments().forEach(element => {
const rect = element.screenRect;
+ // if (rect.width !== 0) {
+ // 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);
+ // if (Math.abs(dW) > Math.abs(dH))
+ // element.width = actualdW;
+ // else
+ // element.height = actualdH;
+ // }
if (rect.width !== 0) {
- 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;
+ let doc = element.props.Document;
+ let width = doc.GetOrCreate(KeyStore.Width, NumberField);
+ let height = doc.GetOrCreate(KeyStore.Height, NumberField);
+ let x = doc.GetOrCreate(KeyStore.X, NumberField);
+ let y = doc.GetOrCreate(KeyStore.X, NumberField);
+ let scale = width.Data / rect.width;
+ let actualdW = Math.max(width.Data + (dW * scale), 20);
+ let actualdH = Math.max(height.Data + (dH * scale), 20);
+ x.Data += dX * (actualdW - width.Data);
+ y.Data += dY * (actualdH - height.Data);
+ if (Math.abs(dW) > Math.abs(dH))
+ width.Data = actualdW;
+ else
+ height.Data = 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 858c02eb4..cc4b03f57 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -6,19 +6,14 @@ import { DocumentDecorations } from './DocumentDecorations';
import { Documents } from '../documents/Documents';
import { Document } from '../../fields/Document';
import { KeyStore } from '../../fields/KeyStore';
-import { ListField } from '../../fields/ListField';
-import { NumberField } from '../../fields/NumberField';
-import { TextField } from '../../fields/TextField';
import "./Main.scss";
import { ContextMenu } from './ContextMenu';
import { DocumentView } from './nodes/DocumentView';
-import { ImageField } from '../../fields/ImageField';
-import { CompileScript } from './../util/Scripting';
import { Server } from '../Server';
import { Utils } from '../../Utils';
import { ServerUtils } from '../../server/ServerUtil';
import { MessageStore, DocumentTransfer } from '../../server/Message';
-import { Database } from '../../server/database';
+import { Transform } from '../util/Transform';
configure({
@@ -67,7 +62,6 @@ Documents.initProtos(() => {
console.log("RESPONSE: " + res)
let mainContainer: Document;
if (res) {
- var lid = KeyStore.Layout.Id;
let obj = ServerUtils.FromJson(res) as Document
mainContainer = obj
}
@@ -100,7 +94,11 @@ Documents.initProtos(() => {
ReactDOM.render((
<div style={{ position: "absolute", width: "100%", height: "100%" }}>
- <DocumentView Document={mainContainer} ContainingCollectionView={undefined} DocumentView={undefined} />
+ <DocumentView Document={mainContainer}
+ AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity}
+ Scaling={1}
+ isTopMost={true}
+ ContainingCollectionView={undefined} />
<DocumentDecorations />
<ContextMenu />
<button style={{
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index d9e261f55..22c2f3172 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,20 +1,24 @@
import { observer } from "mobx-react";
import { KeyStore } from "../../../fields/KeyStore";
-import React = require("react");
import FlexLayout from "flexlayout-react";
-import { action, 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, observable } from "mobx";
import { DragManager } from "../../util/DragManager";
-import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase";
import { FieldView } from "../nodes/FieldView";
+import { Transform } from "../../util/Transform";
+import "./CollectionDockingView.scss";
+import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase";
+import React = require("react");
+import * as ReactDOM from 'react-dom';
+import Measure from "react-measure";
+import { Utils } from "../../../Utils";
@observer
export class CollectionDockingView extends CollectionViewBase {
@@ -24,7 +28,7 @@ export class CollectionDockingView extends CollectionViewBase {
private _containerRef = React.createRef<HTMLDivElement>();
@computed
private get modelForFlexLayout() {
- const { fieldKey: fieldKey, doc: Document } = this.props;
+ const { fieldKey: fieldKey, Document: Document } = this.props;
const value: Document[] = Document.GetData(fieldKey, ListField, []);
var docs = value.map(doc => {
return { type: 'tabset', weight: 50, selected: 0, children: [{ type: "tab", name: doc.Title, component: doc.Id }] };
@@ -39,11 +43,11 @@ export class CollectionDockingView extends CollectionViewBase {
});
}
@computed
- private get modelForGoldenLayout(): any {
- const { fieldKey: fieldKey, doc: Document } = this.props;
+ private get modelForGoldenLayout(): GoldenLayout {
+ const { fieldKey, 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: {
@@ -63,10 +67,9 @@ export class CollectionDockingView extends CollectionViewBase {
}
private nextId = (function () { var _next_id = 0; return function () { return _next_id++; } })();
-
@action
onResize = () => {
- var cur = this.props.DocumentViewForField!.MainContent.current;
+ 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);
@@ -89,11 +92,16 @@ export class CollectionDockingView extends CollectionViewBase {
if (component === "button") {
return <button>{node.getName()}</button>;
}
- const { fieldKey: fieldKey, doc: Document } = this.props;
+ const { fieldKey, Document } = this.props;
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} ContainingCollectionView={this} Document={value[i]} DocumentView={undefined} />);
+ return (<DocumentView key={value[i].Id} Document={value[i]}
+ AddDocument={this.addDocument} RemoveDocument={this.removeDocument}
+ ScreenToLocalTransform={() => Transform.Identity}
+ isTopMost={true}
+ Scaling={1}
+ ContainingCollectionView={this} />);
}
}
if (component === "text") {
@@ -111,7 +119,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;
@@ -152,7 +160,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
//
@@ -232,13 +239,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} Document={state.doc} ContainingCollectionView={me} DocumentView={undefined} />
- ),
- document.getElementById(containingDiv)
- );
- if (CollectionDockingView.myLayout._maxstack != null) {
- CollectionDockingView.myLayout._maxstack.click();
+ let divContainer = document.getElementById(containingDiv) as HTMLDivElement;
+ 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);
});
@@ -248,11 +261,13 @@ export class CollectionDockingView extends CollectionViewBase {
render() {
- const { fieldKey: fieldKey, doc: Document } = this.props;
+ const { fieldKey: fieldKey, Document: Document } = this.props;
+ 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.DocumentViewForField != undefined ? this.props.DocumentViewForField!.ScalingToScreenSpace : 1;
- var w = Document.GetData(KeyStore.Width, NumberField, Number(0)) / s;
- var h = Document.GetData(KeyStore.Height, NumberField, Number(0)) / s;
+ // tfs: we should be able to use this.props.ScreenToLocalTransform to get s right?
+ var s = this.props.ContainingDocumentView != undefined ? this.props.ContainingDocumentView!.ScalingToScreenSpace : 1;
+ var w = Document.GetNumber(KeyStore.Width, 0) / s;
+ var h = Document.GetNumber(KeyStore.Height, 0) / s;
var chooseLayout = () => {
if (!CollectionDockingView.UseGoldenLayout)
@@ -260,18 +275,54 @@ 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}
+ Scaling={this._parentScaling}
+ ScreenToLocalTransform={() => {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(this.props.HtmlElement);
+ return this.props.CollectionDockingView.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale)
+ }}
+ isTopMost={true}
+ ContainingCollectionView={this.props.CollectionDockingView} />
+
+ 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 e9d134e7b..4cf474f77 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,11 +12,4 @@
top: 0;
left: 0;
}
-}
-
-.border {
- border-style: solid;
- box-sizing: border-box;
- width: 100%;
- height: 100%;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index e6b1d103d..1f15069fd 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -1,50 +1,60 @@
import { observer } from "mobx-react";
import React = require("react");
-import { action, observable, computed } from "mobx";
+import { action, computed } from "mobx";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DragManager } from "../../util/DragManager";
import "./CollectionFreeFormView.scss";
-import { Utils } from "../../../Utils";
import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase";
-import { SelectionManager } from "../../util/SelectionManager";
import { KeyStore } from "../../../fields/KeyStore";
import { Document } from "../../../fields/Document";
import { ListField } from "../../../fields/ListField";
import { NumberField } from "../../../fields/NumberField";
import { Documents } from "../../documents/Documents";
import { FieldWaiting } from "../../../fields/Field";
-import { FakeJsxArgs } from "../nodes/DocumentView";
-import { FieldView } from "../nodes/FieldView";
+import { Transform } from "../../util/Transform";
+import { DocumentView } from "../nodes/DocumentView";
@observer
export class CollectionFreeFormView extends CollectionViewBase {
- public static LayoutString() { return FieldView.LayoutString(CollectionFreeFormView); }
+ public static LayoutString(fieldKey: string = "DataKey") { return CollectionViewBase.LayoutString("CollectionFreeFormView", fieldKey); }
private _canvasRef = React.createRef<HTMLDivElement>();
- private _nodeContainerRef = React.createRef<HTMLDivElement>();
private _lastX: number = 0;
private _lastY: number = 0;
+ private _downX: number = 0;
+ private _downY: number = 0;
+
+ @computed
+ get isAnnotationOverlay() { return this.props.fieldKey == KeyStore.Annotations; }
+
+ @computed
+ get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
+ @computed
+ get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+
+ @computed
+ get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
+
+ @computed
+ get resizeScaling() { return this.isAnnotationOverlay ? this.props.Document.GetNumber(KeyStore.Width, 0) / this.nativeWidth : 1; }
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- const doc = de.data["document"];
+ const doc: DocumentView = de.data["document"];
var me = this;
- if (doc instanceof CollectionFreeFormDocumentView) {
- 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;
- const { scale, translateX, translateY } = Utils.GetScreenTransform(this._canvasRef.current!);
- let sscale = this.props.DocumentViewForField!.props.Document.GetData(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;
- this.bringToFront(doc);
+ 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;
+ //this should be able to use translate and scale methods on an Identity transform, no?
+ const transform = me.getTransform();
+ const screenX = de.x - xOffset;
+ const screenY = de.y - yOffset;
+ const [x, y] = transform.transformPoint(screenX, screenY);
+ doc.props.Document.SetNumber(KeyStore.X, x);
+ doc.props.Document.SetNumber(KeyStore.Y, y);
+ this.bringToFront(doc);
e.stopPropagation();
}
@@ -70,8 +80,8 @@ export class CollectionFreeFormView extends CollectionViewBase {
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
- this._lastX = e.pageX;
- this._lastY = e.pageY;
+ this._downX = this._lastX = e.pageX;
+ this._downY = this._lastY = e.pageY;
}
}
@@ -80,20 +90,23 @@ export class CollectionFreeFormView extends CollectionViewBase {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
e.stopPropagation();
- SelectionManager.DeselectAll();
+ if (Math.abs(this._downX - e.clientX) < 3 && Math.abs(this._downY - e.clientY) < 3) {
+ if (!this.props.isSelected()) {
+ this.props.select(false);
+ }
+ }
}
@action
onPointerMove = (e: PointerEvent): void => {
- var me = this;
if (!e.cancelBubble && this.active) {
e.preventDefault();
e.stopPropagation();
- let currScale: number = this.props.DocumentViewForField!.ScalingToScreenSpace;
- let x = this.props.doc.GetData(KeyStore.PanX, NumberField, Number(0));
- let y = this.props.doc.GetData(KeyStore.PanY, NumberField, Number(0));
- this.props.doc.SetData(KeyStore.PanX, x + (e.pageX - this._lastX) / currScale, NumberField);
- this.props.doc.SetData(KeyStore.PanY, y + (e.pageY - this._lastY) / currScale, NumberField);
+ let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
+ let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
+ let [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+
+ this.SetPan(x + dx, y + dy);
}
this._lastX = e.pageX;
this._lastY = e.pageY;
@@ -102,20 +115,28 @@ export class CollectionFreeFormView extends CollectionViewBase {
@action
onPointerWheel = (e: React.WheelEvent): void => {
e.stopPropagation();
+ e.preventDefault();
+ 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 transform = this.getTransform();
- let { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY } = this.props.DocumentViewForField!.TransformToLocalPoint(e.pageX, e.pageY);
+ let deltaScale = (1 - (e.deltaY / coefficient));
+ let [x, y] = transform.transformPoint(e.clientX, e.clientY);
- var deltaScale = (1 - (e.deltaY / 1000)) * Ss;
+ let localTransform = this.getLocalTransform();
+ localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y)
- var newContainerX = LocalX * deltaScale + Panxx + Xx;
- var newContainerY = LocalY * deltaScale + Panyy + Yy;
-
- let dx = ContainerX - newContainerX;
- let dy = ContainerY - newContainerY;
+ this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
+ this.SetPan(localTransform.TranslateX, localTransform.TranslateY);
+ }
- this.props.doc.Set(KeyStore.Scale, new NumberField(deltaScale));
- this.props.doc.SetData(KeyStore.PanX, Panxx + dx, NumberField);
- this.props.doc.SetData(KeyStore.PanY, Panyy + dy, NumberField);
+ @action
+ private SetPan(panX: number, panY: number) {
+ 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.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX);
+ this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY);
}
@action
@@ -125,22 +146,22 @@ export class CollectionFreeFormView extends CollectionViewBase {
let fReader = new FileReader()
let file = e.dataTransfer.items[0].getAsFile();
let that = this;
- const panx: number = this.props.doc.GetData(KeyStore.PanX, NumberField, Number(0));
- const pany: number = this.props.doc.GetData(KeyStore.PanY, NumberField, Number(0));
+ const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0);
+ const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0);
let x = e.pageX - panx
let y = e.pageY - pany
- fReader.addEventListener("load", action("drop", (event) => {
+ fReader.addEventListener("load", action("drop", () => {
if (fReader.result) {
let url = "" + fReader.result;
let doc = Documents.ImageDocument(url, {
x: x, y: y
})
- let docs = that.props.doc.GetT(KeyStore.Data, ListField);
+ let docs = that.props.Document.GetT(KeyStore.Data, ListField);
if (docs != FieldWaiting) {
if (!docs) {
docs = new ListField<Document>();
- that.props.doc.Set(KeyStore.Data, docs)
+ that.props.Document.Set(KeyStore.Data, docs)
}
docs.Data.push(doc);
}
@@ -152,12 +173,12 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
}
- onDragOver = (e: React.DragEvent): void => {
+ onDragOver = (): void => {
}
@action
- bringToFront(doc: CollectionFreeFormDocumentView) {
- const { fieldKey: fieldKey, doc: Document } = this.props;
+ bringToFront(doc: DocumentView) {
+ const { fieldKey: fieldKey, Document: Document } = this.props;
const value: Document[] = Document.GetList<Document>(fieldKey, []);
var topmost = value.reduce((topmost, d) => Math.max(d.GetNumber(KeyStore.ZIndex, 0), topmost), -1000);
@@ -173,8 +194,29 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
}
+ @computed
+ get translate(): [number, number] {
+ const x = this.props.Document.GetNumber(KeyStore.PanX, 0);
+ const y = this.props.Document.GetNumber(KeyStore.PanY, 0);
+ return [x, y];
+ }
+
+ @computed
+ get scale(): number {
+ return this.props.Document.GetNumber(KeyStore.Scale, 1);
+ }
+
+ getTransform = (): Transform => {
+ return this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).transform(this.getLocalTransform())
+ }
+
+ getLocalTransform = (): Transform => {
+ const [x, y] = this.translate;
+ return Transform.Identity.translate(-x, -y).scale(1 / this.scale);
+ }
+
render() {
- const { fieldKey: fieldKey, doc: Document } = this.props;
+ const { fieldKey, Document } = this.props;
// const value: Document[] = Document.GetList<Document>(fieldKey, []);
const lvalue = Document.GetT<ListField<Document>>(fieldKey, ListField);
if (!lvalue || lvalue === "<Waiting>") {
@@ -182,27 +224,32 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
const panx: number = Document.GetNumber(KeyStore.PanX, 0);
const pany: number = Document.GetNumber(KeyStore.PanY, 0);
- const currScale: number = Document.GetNumber(KeyStore.Scale, 1);
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}
- ref={this.createDropTarget}>
- <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px) scale(${currScale}, ${currScale})`, transformOrigin: `left, top` }} ref={this._canvasRef}>
-
- <div className="node-container" ref={this._nodeContainerRef}>
- {lvalue.Data.map(doc => {
- return (<CollectionFreeFormDocumentView 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.createDropTarget}>
+ <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 ? this.props.BackgroundView() : null}
+ {lvalue.Data.map(doc => {
+ return (<CollectionFreeFormDocumentView key={doc.Id} Document={doc}
+ AddDocument={this.addDocument}
+ RemoveDocument={this.removeDocument}
+ ScreenToLocalTransform={this.getTransform}
+ isTopMost={false}
+ Scaling={1}
+ ContainingCollectionView={this} />);
+ })}
</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 9f32ccb72..719783fd7 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 } from "../../../fields/KeyStore";
+import { KeyStore } from "../../../fields/KeyStore";
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,14 @@ export class CollectionSchemaView extends CollectionViewBase {
let props: FieldViewProps = {
doc: rowProps.value[0],
fieldKey: rowProps.value[1],
- DocumentViewForField: undefined
+ isSelected: () => false,
+ isTopMost: false
}
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();
@@ -86,56 +89,74 @@ export class CollectionSchemaView extends CollectionViewBase {
if (target.tagName == "SPAN" && target.className.includes("Resizer")) {
e.stopPropagation();
}
- if (e.button === 2 && this.active) {
- e.stopPropagation();
- e.preventDefault();
- } else {
+ // if (e.button === 2 && this.active) {
+ // e.stopPropagation();
+ // e.preventDefault();
+ // } else
+ {
if (e.buttons === 1 && this.active) {
e.stopPropagation();
}
}
}
+
+ @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 { doc: Document, fieldKey: fieldKey } = this.props;
+ const { Document: Document, fieldKey: fieldKey } = this.props;
const children = Document.GetList<Document>(fieldKey, []);
- const columns = Document.GetList(KS.ColumnsKey,
- [KS.Title, KS.Data, KS.Author])
+ const columns = Document.GetList(KeyStore.ColumnsKey,
+ [KeyStore.Title, KeyStore.Data, KeyStore.Author])
let content;
+ var me = this;
if (this.selectedIndex != -1) {
content = (
- <DocumentView 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}
+ ScreenToLocalTransform={() => Transform.Identity}//TODO This should probably be an actual transform
+ Scaling={this._parentScaling}
+ isTopMost={false}
+ 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"
- }}
- 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 4fcbb1699..0a90bd0f2 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -10,39 +10,58 @@ import React = require("react");
import { DocumentView } from "../nodes/DocumentView";
import { CollectionDockingView } from "./CollectionDockingView";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { FieldViewProps } from "../nodes/FieldView";
+import { Transform } from "../../util/Transform";
+
+
+export interface CollectionViewProps {
+ fieldKey: Key;
+ Document: Document;
+ ContainingDocumentView: Opt<DocumentView>;
+ ScreenToLocalTransform: () => Transform;
+ isSelected: () => boolean;
+ isTopMost: boolean;
+ select: (ctrlPressed: boolean) => void;
+ BackgroundView?: () => JSX.Element;
+}
export const COLLECTION_BORDER_WIDTH = 2;
@observer
-export class CollectionViewBase extends React.Component<FieldViewProps> {
+export class CollectionViewBase extends React.Component<CollectionViewProps> {
+ public static LayoutString(collectionType: string, fieldKey: string = "DataKey") {
+ return `<${collectionType} Document={Document}
+ ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} isSelected={isSelected} select={select}
+ isTopMost={isTopMost}
+ ContainingDocumentView={DocumentView} BackgroundView={BackgroundView} />`;
+ }
@computed
public get active(): boolean {
- var isSelected = (this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField));
+ var isSelected = (this.props.ContainingDocumentView && SelectionManager.IsSelected(this.props.ContainingDocumentView));
var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this);
- var topMost = this.props.DocumentViewForField != undefined && (
- this.props.DocumentViewForField.props.ContainingCollectionView == undefined ||
- this.props.DocumentViewForField.props.ContainingCollectionView instanceof CollectionDockingView);
+ var topMost = this.props.isTopMost;
return isSelected || childSelected || topMost;
}
@action
addDocument = (doc: Document): void => {
//TODO This won't create the field if it doesn't already exist
- const value = this.props.doc.GetData(this.props.fieldKey, ListField, new Array<Document>())
+ const value = this.props.Document.GetData(this.props.fieldKey, ListField, new Array<Document>())
value.push(doc);
}
@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.doc.GetData(this.props.fieldKey, ListField, new Array<Document>())
- if (value.indexOf(doc) !== -1) {
- value.splice(value.indexOf(doc), 1)
+ const value = this.props.Document.GetData(this.props.fieldKey, ListField, new Array<Document>())
+ 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/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index bfd50da81..e04135257 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -7,17 +7,16 @@ 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";
import { Utils } from "../../../Utils";
+import { Transform } from "../../util/Transform";
@observer
-export class CollectionFreeFormDocumentView extends DocumentView {
- private _contextMenuCanOpen = false;
- private _downX: number = 0;
- private _downY: number = 0;
+export class CollectionFreeFormDocumentView extends React.Component<DocumentViewProps> {
+ private _mainCont = React.createRef<HTMLDivElement>();
constructor(props: DocumentViewProps) {
super(props);
@@ -30,198 +29,71 @@ export class CollectionFreeFormDocumentView extends DocumentView {
}
@computed
- get x(): number {
- return this.props.Document.GetData(KeyStore.X, NumberField, Number(0));
- }
-
- @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)
+ get transform(): string {
+ return `scale(${this.props.Scaling}, ${this.props.Scaling}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px)`;
}
@computed
- get transform(): string {
- return `translate(${this.x}px, ${this.y}px)`;
+ get width(): number {
+ return this.props.Document.GetNumber(KeyStore.Width, 0);
}
@computed
- get width(): number {
- return this.props.Document.GetData(KeyStore.Width, NumberField, Number(0));
+ 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) {
this.props.Document.SetData(KeyStore.ZIndex, h, NumberField)
}
- @action
- dragComplete = (e: DragManager.DragCompleteEvent) => {
- }
-
- @computed
- get active(): boolean {
- return SelectionManager.IsSelected(this) || this.props.ContainingCollectionView === undefined ||
- this.props.ContainingCollectionView.active;
- }
-
- @computed
- get topMost(): boolean {
- return this.props.ContainingCollectionView == undefined || this.props.ContainingCollectionView instanceof CollectionDockingView;
- }
-
- 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();
- if (e.buttons === 2) {
- e.preventDefault();
- }
- document.removeEventListener("pointermove", this.onPointerMove)
- document.addEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp)
- document.addEventListener("pointerup", this.onPointerUp);
- }
- }
-
- 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.topMost) {
- this._contextMenuCanOpen = false;
- const rect = this.screenRect;
- let dragData: { [id: string]: any } = {};
- dragData["document"] = this;
- dragData["xOffset"] = e.x - rect.left;
- dragData["yOffset"] = e.y - rect.top;
- DragManager.StartDrag(this._mainCont.current, dragData, {
- handlers: {
- dragComplete: this.dragComplete,
- },
- hideSource: true
- })
- }
- }
- e.stopPropagation();
- e.preventDefault();
- }
-
- onPointerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onPointerMove)
- document.removeEventListener("pointerup", this.onPointerUp)
- e.stopPropagation();
- if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
- SelectionManager.SelectDoc(this, e.ctrlKey);
- }
- }
-
- openRight = (e: React.MouseEvent): void => {
- CollectionDockingView.AddRightSplit(this.props.Document);
- }
-
- deleteClicked = (e: React.MouseEvent): void => {
- if (this.props.ContainingCollectionView instanceof CollectionFreeFormView) {
- this.props.ContainingCollectionView.removeDocument(this.props.Document)
- }
- }
- @action
- fullScreenClicked = (e: React.MouseEvent): void => {
- CollectionDockingView.OpenFullScreen(this.props.Document);
- 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 => {
- if (!SelectionManager.IsSelected(this)) {
- return;
- }
- e.preventDefault()
-
- if (!this._contextMenuCanOpen) {
- return;
- }
-
- if (this.topMost) {
- ContextMenu.Instance.clearItems()
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked })
- }
- 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: "Open Right", event: this.openRight })
- ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
- SelectionManager.SelectDoc(this, e.ctrlKey);
- }
- ContextMenu.Instance.addItem({
- description: "Copy ID", event: () => {
- Utils.CopyText(this.props.Document.Id)
- }
- })
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ getTransform = (): Transform => {
+ return this.props.ScreenToLocalTransform().translate(-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={{
+ <div ref={this._mainCont} style={{
+ transformOrigin: "left top",
transform: this.transform,
width: this.width,
height: this.height,
position: "absolute",
zIndex: this.zIndex,
- }}
- onContextMenu={this.onContextMenu}
- onPointerDown={this.onPointerDown}>
+ backgroundColor: "transparent"
+ }} >
- <DocumentView {...this.props} DocumentView={this} />
+ <DocumentView {...this.props} Scaling={this.width / this.nativeWidth} ScreenToLocalTransform={this.getTransform} />
</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 3767d28c6..aa85fba03 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -5,8 +5,6 @@ import { Opt, FieldWaiting, Field } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
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";
@@ -14,14 +12,25 @@ 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";
+import { SelectionManager } from "../../util/SelectionManager";
+import { DragManager } from "../../util/DragManager";
+import { ContextMenu } from "../ContextMenu";
+import { TextField } from "../../../fields/TextField";
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>;
+
+ Document: Document;
+ AddDocument?: (doc: Document) => void;
+ RemoveDocument?: (doc: Document) => boolean;
+ ScreenToLocalTransform: () => Transform;
+ isTopMost: boolean;
+ //tfs: This shouldn't be necessary I don't think
+ Scaling: number;
}
export interface JsxArgs extends DocumentViewProps {
Keys: { [name: string]: Key }
@@ -68,13 +77,27 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
@observer
export class DocumentView extends React.Component<DocumentViewProps> {
- protected _mainCont = React.createRef<any>();
+ private _mainCont = React.createRef<HTMLDivElement>();
get MainContent() {
return this._mainCont;
}
+ get screenRect(): ClientRect | DOMRect {
+ if (this._mainCont.current) {
+ return this._mainCont.current.getBoundingClientRect();
+ }
+ return new DOMRect();
+ }
@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 backgroundLayout(): string | undefined {
+ let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField);
+ if (field && field !== "<Waiting>") {
+ return field.Data;
+ }
}
@computed
@@ -87,94 +110,164 @@ export class DocumentView extends React.Component<DocumentViewProps> {
return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>());
}
- //
- // returns the cumulative scaling between the document and the screen
- //
@computed
- public get ScalingToScreenSpace(): number {
- if (this.props.ContainingCollectionView != undefined &&
- this.props.ContainingCollectionView.props.DocumentViewForField != undefined) {
- let ss = this.props.ContainingCollectionView.props.doc.GetData(KeyStore.Scale, NumberField, Number(1));
- return this.props.ContainingCollectionView.props.DocumentViewForField.ScalingToScreenSpace * ss;
+ get active(): boolean {
+ return SelectionManager.IsSelected(this) || this.props.ContainingCollectionView === undefined ||
+ this.props.ContainingCollectionView.active;
+ }
+
+ private _contextMenuCanOpen = false;
+ private _downX: number = 0;
+ private _downY: number = 0;
+ 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();
+ if (e.buttons === 2) {
+ e.preventDefault();
+ }
+ document.removeEventListener("pointermove", this.onPointerMove)
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp)
+ document.addEventListener("pointerup", this.onPointerUp);
}
- return 1;
}
- //
- // Converts a coordinate in the screen space of the app into a local document coordinate.
- //
- 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.DocumentViewForField != undefined ?
- this.props.ContainingCollectionView.props.DocumentViewForField.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.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.GetData(KeyStore.Scale, NumberField, Number(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;
-
- return { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY };
+ @action
+ dragComplete = (e: DragManager.DragCompleteEvent) => {
}
- //
- // Converts a point in the coordinate space of a document to a screen space coordinate.
- //
- public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: number, ScreenY: number } {
+ @computed
+ get topMost(): boolean {
+ return this.props.ContainingCollectionView == undefined || this.props.ContainingCollectionView instanceof CollectionDockingView;
+ }
+
+ 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.topMost) {
+ this._contextMenuCanOpen = false;
+ const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ let dragData: { [id: string]: any } = {};
+ dragData["document"] = this;
+ dragData["xOffset"] = e.x - left;
+ dragData["yOffset"] = e.y - top;
+ DragManager.StartDrag(this._mainCont.current, dragData, {
+ handlers: {
+ dragComplete: this.dragComplete,
+ },
+ hideSource: true
+ })
+ }
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
- 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;
+ onPointerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onPointerMove)
+ document.removeEventListener("pointerup", this.onPointerUp)
+ e.stopPropagation();
+ if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
+ SelectionManager.SelectDoc(this, e.ctrlKey);
}
+ }
+
+ openRight = (e: React.MouseEvent): void => {
+ CollectionDockingView.AddRightSplit(this.props.Document);
+ }
+
+ deleteClicked = (e: React.MouseEvent): void => {
+ if (this.props.RemoveDocument) {
+ this.props.RemoveDocument(this.props.Document);
+ }
+ }
+ @action
+ fullScreenClicked = (e: React.MouseEvent): void => {
+ CollectionDockingView.OpenFullScreen(this.props.Document);
+ 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)
+ }
- 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;
+ @action
+ onContextMenu = (e: React.MouseEvent): void => {
+ if (!SelectionManager.IsSelected(this)) {
+ return;
+ }
+ e.preventDefault()
+
+ if (!this._contextMenuCanOpen) {
+ return;
+ }
+
+ if (this.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: "Open Right", event: this.openRight })
+ ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked })
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
+ SelectionManager.SelectDoc(this, e.ctrlKey);
+ }
+ }
- // 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.DocumentViewForField : undefined;
- if (containingDocView != undefined) {
- let ss = containingDocView.props.Document.GetData(KeyStore.Scale, NumberField, Number(1));
- 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);
- parentX = ScreenX;
- parentY = ScreenY;
+ //
+ // returns the cumulative scaling between the document and the screen
+ // tfs: I don't think this should be necessary
+ //
+ @computed
+ public get ScalingToScreenSpace(): number {
+ if (this.props.ContainingCollectionView != undefined &&
+ this.props.ContainingCollectionView.props.ContainingDocumentView != undefined) {
+ let ss = this.props.ContainingCollectionView.props.Document.GetNumber(KeyStore.Scale, 1);
+ return this.props.ContainingCollectionView.props.ContainingDocumentView.ScalingToScreenSpace * ss;
}
- return { ScreenX: parentX, ScreenY: parentY };
+ return 1;
}
+ isSelected = () => {
+ return SelectionManager.IsSelected(this);
+ }
+
+ select = (ctrlPressed: boolean) => {
+ SelectionManager.SelectDoc(this, ctrlPressed)
+ }
render() {
- let bindings = { ...this.props } as any;
let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
if (!lkeys || lkeys === "<Waiting>") {
return <p>Error loading layout keys</p>;
}
+ let bindings = { ...this.props } as any;
+ bindings.isSelected = this.isSelected;
+ bindings.select = this.select;
for (const key of this.layoutKeys) {
bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data
}
@@ -182,13 +275,36 @@ export class DocumentView extends React.Component<DocumentViewProps> {
let field = this.props.Document.Get(key);
bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
}
- 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.
+ /*
+ tfs:
+ Should this be moved to CollectionFreeformView or another component that renders
+ Document backgrounds (or contents based on a layout key, which could be used here as well)
+ that CollectionFreeformView uses? It seems like a lot for it to be here considering only one view currently uses it...
+ */
+ let backgroundLayout = this.backgroundLayout;
+ if (backgroundLayout) {
+ let backgroundView = () => (<JsxParser
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView }}
+ bindings={bindings}
+ jsx={this.backgroundLayout}
+ showWarnings={true}
+ onError={(test: any) => { console.log(test) }}
+ />);
+ bindings.BackgroundView = backgroundView;
}
+
+ bindings.DocumentView = this;
+
+ var width = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
+ var strwidth = width > 0 ? width.toString() + "px" : "100%";
+ var height = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+ var strheight = height > 0 ? height.toString() + "px" : "100%";
return (
- <div className="node" ref={this._mainCont} style={{ width: "100%", height: "100%", }}>
+ <div className="documentView-node" ref={this._mainCont} style={{ width: strwidth, height: strheight, transformOrigin: "left top", transform: `scale(${this.props.Scaling},${this.props.Scaling})` }}
+ onContextMenu={this.onContextMenu}
+ onPointerDown={this.onPointerDown} >
<JsxParser
- components={{ FormattedTextBox: FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView }}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView }}
bindings={bindings}
jsx={this.layout}
showWarnings={true}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index fae124528..918acff4c 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -10,7 +10,6 @@ import { ImageField } from "../../../fields/ImageField";
import { Key } from "../../../fields/Key";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
-import { DocumentView } from "./DocumentView";
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
@@ -20,12 +19,13 @@ import { DocumentView } from "./DocumentView";
export interface FieldViewProps {
fieldKey: Key;
doc: Document;
- DocumentViewForField: Opt<DocumentView>
+ isSelected: () => boolean;
+ isTopMost: boolean;
}
@observer
export class FieldView extends React.Component<FieldViewProps> {
- public static LayoutString(fieldType: { name: string }) { return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} fieldKey={DataKey} />`; }
+ public static LayoutString(fieldType: { name: string }) { return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} fieldKey={DataKey} isSelected={isSelected} isTopMost={isTopMost} />`; }
@computed
get field(): FieldValue<Field> {
const { doc, fieldKey } = this.props;
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 39d7bf4f0..b17650d06 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,5 +1,4 @@
import { action, IReactionDisposer, reaction } from "mobx";
-import { observer } from "mobx-react"
import { baseKeymap } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
@@ -13,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
@@ -31,7 +31,6 @@ import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView
// specified Key and assigns it to an HTML input node. When changes are made tot his node,
// this will edit the document and assign the new value to that field.
//]
-@observer
export class FormattedTextBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString(FormattedTextBox) }
@@ -111,7 +110,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
}
onPointerDown = (e: React.PointerEvent): void => {
let me = this;
- if (e.buttons === 1 && me.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(me.props.DocumentViewForField)) {
+ if (e.buttons === 1 && this.props.isSelected()) {
e.stopPropagation();
}
}
@@ -119,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 136fda1d0..36f5e0fe0 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,6 +1,14 @@
.imageBox-cont {
padding: 0vw;
+ 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 013b8b7d3..b5ce8b28c 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,15 +1,14 @@
import Lightbox from 'react-image-lightbox';
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { SelectionManager } from "../../util/SelectionManager";
import "./ImageBox.scss";
import React = require("react")
import { ImageField } from '../../../fields/ImageField';
import { FieldViewProps, FieldView } from './FieldView';
-import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
import { FieldWaiting } from '../../../fields/Field';
import { observer } from "mobx-react"
import { observable, action } from 'mobx';
+import { KeyStore } from '../../../fields/KeyStore';
@observer
export class ImageBox extends React.Component<FieldViewProps> {
@@ -40,7 +39,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
onPointerDown = (e: React.PointerEvent): void => {
if (Date.now() - this._lastTap < 300) {
- if (e.buttons === 1 && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) {
+ if (e.buttons === 1 && this.props.isSelected()) {
e.stopPropagation();
this._downX = e.clientX;
this._downY = e.clientY;
@@ -62,12 +61,14 @@ export class ImageBox extends React.Component<FieldViewProps> {
lightbox = (path: string) => {
const images = [path, "http://www.cs.brown.edu/~bcz/face.gif"];
- if (this._isOpen && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) {
+ if (this._isOpen && this.props.isSelected()) {
return (<Lightbox
mainSrc={images[this._photoIndex]}
nextSrc={images[(this._photoIndex + 1) % images.length]}
prevSrc={images[(this._photoIndex + images.length - 1) % images.length]}
- onCloseRequest={() => this.setState({ isOpen: false })}
+ onCloseRequest={action(() =>
+ this._isOpen = false
+ )}
onMovePrevRequest={action(() =>
this._photoIndex = (this._photoIndex + images.length - 1) % images.length
)}
@@ -82,10 +83,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 nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 1);
return (
<div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} >
- <img src={path} width="100%" alt="Image not found" />
+ <img src={path} width={nativeWidth} alt="Image not found" />
{this.lightbox(path)}
</div>)
}
diff --git a/src/fields/BasicField.ts b/src/fields/BasicField.ts
index 15eb067a0..8728b7145 100644
--- a/src/fields/BasicField.ts
+++ b/src/fields/BasicField.ts
@@ -1,9 +1,9 @@
-import { Field, FIELD_ID } from "./Field"
+import { Field, FieldId } from "./Field"
import { observable, computed, action } from "mobx";
import { Server } from "../client/Server";
export abstract class BasicField<T> extends Field {
- constructor(data: T, save: boolean, id: FIELD_ID = undefined) {
+ constructor(data: T, save: boolean, id?: FieldId) {
super(id);
this.data = data;
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 3067621be..fcc8adbcf 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -1,8 +1,8 @@
-import { Field, Cast, Opt, FieldWaiting, FIELD_ID, FieldValue } from "./Field"
import { Key } from "./Key"
import { KeyStore } from "./KeyStore";
+import { Field, Cast, FieldWaiting, FieldValue, FieldId } from "./Field"
import { NumberField } from "./NumberField";
-import { ObservableMap, computed, action, observable } from "mobx";
+import { ObservableMap, computed, action } from "mobx";
import { TextField } from "./TextField";
import { ListField } from "./ListField";
import { Server } from "../client/Server";
@@ -10,7 +10,7 @@ import { Types } from "../server/Message";
export class Document extends Field {
public fields: ObservableMap<string, { key: Key, field: Field }> = new ObservableMap();
- public _proxies: ObservableMap<string, FIELD_ID> = new ObservableMap();
+ public _proxies: ObservableMap<string, FieldId> = new ObservableMap();
constructor(id?: string, save: boolean = true) {
super(id)
@@ -40,7 +40,17 @@ export class Document extends Field {
if (this.fields.has(key.Id)) {
field = this.fields.get(key.Id)!.field;
} else if (this._proxies.has(key.Id)) {
- field = Server.GetDocumentField(this, key);
+ Server.GetDocumentField(this, key);
+ /*
+ The field might have been instantly filled from the cache
+ Maybe we want to just switch back to returning the value
+ from Server.GetDocumentField if it's in the cache
+ */
+ if (this.fields.has(key.Id)) {
+ field = this.fields.get(key.Id)!.field;
+ } else {
+ field = "<Waiting>";
+ }
}
} else {
let doc: FieldValue<Document> = this;
@@ -49,7 +59,17 @@ export class Document extends Field {
let curProxy = doc._proxies.get(key.Id);
if (!curField || (curProxy && curField.field.Id !== curProxy)) {
if (curProxy) {
- field = Server.GetDocumentField(doc, key);
+ Server.GetDocumentField(doc, key);
+ /*
+ The field might have been instantly filled from the cache
+ Maybe we want to just switch back to returning the value
+ from Server.GetDocumentField if it's in the cache
+ */
+ if (this.fields.has(key.Id)) {
+ field = this.fields.get(key.Id)!.field;
+ } else {
+ field = "<Waiting>";
+ }
break;
}
if ((doc.fields.has(KeyStore.Prototype.Id) || doc._proxies.has(KeyStore.Prototype.Id))) {
diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts
index 4096cbb92..9d3c209b4 100644
--- a/src/fields/DocumentReference.ts
+++ b/src/fields/DocumentReference.ts
@@ -1,4 +1,4 @@
-import { Field, Opt, FieldValue, FIELD_ID } from "./Field";
+import { Field, Opt, FieldValue, FieldId } from "./Field";
import { Document } from "./Document";
import { Key } from "./Key";
import { Types } from "../server/Message";
@@ -47,7 +47,7 @@ export class DocumentReference extends Field {
return "";
}
- ToJson(): { type: Types, data: FIELD_ID, _id: string } {
+ ToJson(): { type: Types, data: FieldId, _id: string } {
return {
type: Types.DocumentReference,
data: this.document.Id,
diff --git a/src/fields/Field.ts b/src/fields/Field.ts
index 853fb9327..c7e0232af 100644
--- a/src/fields/Field.ts
+++ b/src/fields/Field.ts
@@ -11,9 +11,9 @@ 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;
@@ -24,12 +24,12 @@ export abstract class Field {
callback(this);
}
- private id: string;
- get Id(): string {
+ 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/ImageField.ts b/src/fields/ImageField.ts
index aba011199..b2226d55a 100644
--- a/src/fields/ImageField.ts
+++ b/src/fields/ImageField.ts
@@ -1,10 +1,10 @@
import { BasicField } from "./BasicField";
-import { Field, FIELD_ID } from "./Field";
+import { Field, FieldId } from "./Field";
import { Types } from "../server/Message";
import { ObjectID } from "bson";
export class ImageField extends BasicField<URL> {
- constructor(data: URL | undefined = undefined, id: FIELD_ID = undefined, save: boolean = true) {
+ constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) {
super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data, save, id);
}
diff --git a/src/fields/Key.ts b/src/fields/Key.ts
index d3958df2d..c16a00878 100644
--- a/src/fields/Key.ts
+++ b/src/fields/Key.ts
@@ -1,4 +1,4 @@
-import { Field, FIELD_ID } from "./Field"
+import { Field, FieldId } from "./Field"
import { Utils } from "../Utils";
import { observable } from "mobx";
import { Types } from "../server/Message";
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 1e0b12729..c7c52f0b0 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -9,16 +9,16 @@ export namespace KeyStore {
export const PanX = new Key("PanX");
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 BackgroundLayout = new Key("BackgroundLayout");
export const LayoutKeys = new Key("LayoutKeys");
export const LayoutFields = new Key("LayoutFields");
export const ColumnsKey = new Key("SchemaColumns");
-
- export function Get(name: string): Key {
- return new Key(name)
- }
}
diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts
index 1357dc445..2e192bf90 100644
--- a/src/fields/ListField.ts
+++ b/src/fields/ListField.ts
@@ -1,4 +1,4 @@
-import { Field, FIELD_ID, FieldValue, Opt } from "./Field";
+import { Field, FieldId, FieldValue, Opt } from "./Field";
import { BasicField } from "./BasicField";
import { Types } from "../server/Message";
import { observe, action } from "mobx";
@@ -7,7 +7,7 @@ import { ServerUtils } from "../server/ServerUtil";
export class ListField<T extends Field> extends BasicField<T[]> {
private _proxies: string[] = []
- constructor(data: T[] = [], id: FIELD_ID = undefined, save: boolean = true) {
+ constructor(data: T[] = [], id?: FieldId, save: boolean = true) {
super(data, save, id);
this.updateProxies();
if (save) {
diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts
index 29e285201..47dfc74cb 100644
--- a/src/fields/NumberField.ts
+++ b/src/fields/NumberField.ts
@@ -1,9 +1,9 @@
import { BasicField } from "./BasicField"
import { Types } from "../server/Message";
-import { FIELD_ID } from "./Field";
+import { FieldId } from "./Field";
export class NumberField extends BasicField<number> {
- constructor(data: number = 0, id: FIELD_ID = undefined, save: boolean = true) {
+ constructor(data: number = 0, id?: FieldId, save: boolean = true) {
super(data, save, id);
}
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index 9783107e3..5efb43314 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -1,9 +1,9 @@
import { BasicField } from "./BasicField";
import { Types } from "../server/Message";
-import { FIELD_ID } from "./Field";
+import { FieldId } from "./Field";
export class RichTextField extends BasicField<string> {
- constructor(data: string = "", id: FIELD_ID = undefined, save: boolean = true) {
+ constructor(data: string = "", id?: FieldId, save: boolean = true) {
super(data, save, id);
}
diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts
index efb3c035f..ad96ab6d9 100644
--- a/src/fields/TextField.ts
+++ b/src/fields/TextField.ts
@@ -1,9 +1,9 @@
import { BasicField } from "./BasicField"
-import { FIELD_ID } from "./Field";
+import { FieldId } from "./Field";
import { Types } from "../server/Message";
export class TextField extends BasicField<string> {
- constructor(data: string = "", id: FIELD_ID = undefined, save: boolean = true) {
+ constructor(data: string = "", id?: FieldId, save: boolean = true) {
super(data, save, id);
}
diff --git a/src/server/Message.ts b/src/server/Message.ts
index e78c36ef1..7f3190f7f 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,5 +1,4 @@
import { Utils } from "../Utils";
-import { FIELD_ID, Field } from "../fields/Field";
export class Message<T> {
private name: string;
diff --git a/src/server/index.ts b/src/server/index.ts
index d05e1fca6..f5e66b31b 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -10,7 +10,7 @@ import { Client } from './Client';
import { Socket } from 'socket.io';
import { Utils } from '../Utils';
import { ObservableMap } from 'mobx';
-import { FIELD_ID, Field } from '../fields/Field';
+import { FieldId, Field } from '../fields/Field';
import { Database } from './database';
import { ServerUtils } from './ServerUtil';
import { ObjectID } from 'mongodb';
@@ -68,7 +68,7 @@ app.post("/signup", postSignup);
app.get("/login", getLogin);
app.post("/login", postLogin);
-let FieldStore: ObservableMap<FIELD_ID, Field> = new ObservableMap();
+let FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
// define a route handler for the default home page
app.get("/", (req, res) => {