From 481ca03c47962b301199285feebdb73a66818a9f Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Sun, 20 Jan 2019 19:48:04 -0500
Subject: switching to cffv

---
 src/Main.tsx | 31 +++++++++++++++++++++----------
 1 file changed, 21 insertions(+), 10 deletions(-)

(limited to 'src/Main.tsx')

diff --git a/src/Main.tsx b/src/Main.tsx
index f911b6098..e417c0690 100644
--- a/src/Main.tsx
+++ b/src/Main.tsx
@@ -5,24 +5,31 @@ import { NodeCollectionStore } from './stores/NodeCollectionStore';
 import { StaticTextNodeStore } from './stores/StaticTextNodeStore';
 import { VideoNodeStore } from './stores/VideoNodeStore';
 import { FreeFormCanvas } from './views/freeformcanvas/FreeFormCanvas';
-import { Key, KeyStore as KS } from './fields/Key';
+import { Key, KeyStore as KS, KeyStore } from './fields/Key';
 import { NumberField } from './fields/NumberField';
 import { Document } from './fields/Document';
 import { configure, runInAction } from 'mobx';
 import { NodeStore } from './stores/NodeStore';
 import { Documents } from './documents/Documents';
 import { DocumentDecorations } from './DocumentDecorations';
+import { CollectionFreeFormView } from './views/freeformcanvas/CollectionFreeFormView';
+import { ListField } from './fields/ListField';
+import { DocumentView } from './views/nodes/DocumentView';
+import { DocumentViewModel } from './viewmodels/DocumentViewModel';
 
 configure({
     enforceActions: "observed"
 });
 
-const mainNodeCollection = new NodeCollectionStore();
+const mainNodeCollection = new Array<Document>();
+let mainContainer = Documents.CollectionDocument(mainNodeCollection, {
+    x: 0, y: 0, width: window.screen.width, height: window.screen.height
+})
 ReactDOM.render((
-    <div>
+    <div style={{display: "grid", width: "100vw", height: "100vh"}}>
         <h1>Dash Web</h1>
         <DocumentDecorations />
-        <FreeFormCanvas store={mainNodeCollection} />
+        <DocumentView dvm={new DocumentViewModel(mainContainer)} />
     </div>), document.getElementById('root'));
 
 
@@ -41,7 +48,6 @@ for (let i = 0; i < 20; i++) {
 }
 
 runInAction(() => {
-    mainNodeCollection.AddNodes(nodes);
     let doc1 = Documents.TextDocument("Hello world");
     let doc2 = doc1.MakeDelegate();
     doc2.SetField(KS.X, new NumberField(150));
@@ -56,9 +62,14 @@ runInAction(() => {
     let doc5 = Documents.ImageDocument("https://static.boredpanda.com/blog/wp-content/uploads/2018/04/5acb63d83493f__700-png.jpg", {
         x: 650, y: 500
     });
-    mainNodeCollection.Docs.push(doc1);
-    mainNodeCollection.Docs.push(doc2);
-    mainNodeCollection.Docs.push(doc4);
-    mainNodeCollection.Docs.push(doc3);
-    mainNodeCollection.Docs.push(doc5);
+    let mainNodes = mainContainer.GetFieldT(KeyStore.Data, ListField);
+    if (!mainNodes) {
+        mainNodes = new ListField<Document>();
+        mainContainer.SetField(KeyStore.Data, mainNodes);
+    }
+    mainNodes.Data.push(doc1);
+    mainNodes.Data.push(doc2);
+    mainNodes.Data.push(doc4);
+    mainNodes.Data.push(doc3);
+    mainNodes.Data.push(doc5);
 });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 6a04f60ce76fbd8274dd9daf285042f9cab16656 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Mon, 21 Jan 2019 21:42:03 -0500
Subject: context menu basics working

---
 package-lock.json                                  | 73 +++++++++-------
 package.json                                       |  1 +
 src/DocumentDecorations.scss                       |  1 -
 src/Main.tsx                                       | 21 ++++-
 src/documents/Documents.ts                         |  6 +-
 src/fields/Key.ts                                  |  1 +
 src/viewmodels/DocumentViewModel.ts                | 10 +--
 src/views/ContextMenu.scss                         | 34 ++++++++
 src/views/ContextMenu.tsx                          | 68 +++++++++++++++
 src/views/ContextMenuItem.tsx                      | 17 ++++
 .../freeformcanvas/CollectionFreeFormView.tsx      | 96 ++++++++++++++++++----
 src/views/freeformcanvas/FreeFormCanvas.tsx        |  6 +-
 src/views/nodes/DocumentView.tsx                   | 48 +++++++++--
 src/views/nodes/FieldTextBox.scss                  | 10 +++
 src/views/nodes/FieldTextBox.tsx                   |  5 +-
 15 files changed, 329 insertions(+), 68 deletions(-)
 create mode 100644 src/views/ContextMenu.scss
 create mode 100644 src/views/ContextMenu.tsx
 create mode 100644 src/views/ContextMenuItem.tsx
 create mode 100644 src/views/nodes/FieldTextBox.scss

(limited to 'src/Main.tsx')

diff --git a/package-lock.json b/package-lock.json
index 955f0f3ae..7dfe02494 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,16 +4,29 @@
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
+    "@fortawesome/fontawesome-common-types": {
+      "version": "0.2.12",
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.12.tgz",
+      "integrity": "sha512-ISLNpEx6fhJTYYkvBeo/4DHeL5EIA+VybJoOOnH67m6uXt6V6VFizdEN4qchHagNIeZfzI0LnA22gk0wbVPv/g=="
+    },
+    "@fortawesome/fontawesome-svg-core": {
+      "version": "1.2.12",
+      "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.12.tgz",
+      "integrity": "sha512-cqTfa3vZ+Z9UYQnmLfCOwyLnf0Xcoxkmm/BSaI29Yikzu9zIeD4es7lBZMDqLOXYSEQC+rCr8caxFlGJcJVA+w==",
+      "requires": {
+        "@fortawesome/fontawesome-common-types": "^0.2.12"
+      }
+    },
     "@types/chai": {
       "version": "4.1.7",
       "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz",
-      "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==",
+      "integrity": "sha1-G44zthqMCcvh+FEzBxuqDb+fpxo=",
       "dev": true
     },
     "@types/mocha": {
       "version": "5.2.5",
       "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz",
-      "integrity": "sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww==",
+      "integrity": "sha1-ikrM/EA8EkoLr+ip/GGgXsEDIHM=",
       "dev": true
     },
     "@types/node": {
@@ -117,7 +130,7 @@
     "@types/react-dom": {
       "version": "16.0.11",
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.11.tgz",
-      "integrity": "sha512-x6zUx9/42B5Kl2Vl9HlopV8JF64wLpX3c+Pst9kc1HgzrsH+mkehe/zmHMQTplIrR48H2gpU7ZqurQolYu8XBA==",
+      "integrity": "sha1-vRDMsNkmA0P0uaSdT3qDMKXB8IE=",
       "dev": true,
       "requires": {
         "@types/react": "*"
@@ -126,7 +139,7 @@
     "@types/uuid": {
       "version": "3.4.4",
       "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz",
-      "integrity": "sha512-tPIgT0GUmdJQNSHxp0X2jnpQfBSTfGxUMc/2CXBU2mnyTFVYVa2ojpoQ74w0U2yn2vw3jnC640+77lkFFpdVDw==",
+      "integrity": "sha1-evaTYPpl7w3stB/RUL9MpcDO/fU=",
       "requires": {
         "@types/node": "*"
       }
@@ -579,7 +592,7 @@
     "awesome-typescript-loader": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-5.2.1.tgz",
-      "integrity": "sha512-slv66OAJB8orL+UUaTI3pKlLorwIvS4ARZzYR9iJJyGsEgOqueMfOMdKySWzZ73vIkEe3fcwFgsKMg4d8zyb1g==",
+      "integrity": "sha1-pB2veEdRX0klzbqjB11h8onpE/w=",
       "dev": true,
       "requires": {
         "chalk": "^2.4.1",
@@ -982,7 +995,7 @@
     "chai": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
-      "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
+      "integrity": "sha1-dgqnLPION5XoSxKHfODoNzeqKeU=",
       "dev": true,
       "requires": {
         "assertion-error": "^1.1.0",
@@ -1289,7 +1302,7 @@
     "copy-webpack-plugin": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz",
-      "integrity": "sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA==",
+      "integrity": "sha1-5/QN2KaEd9QF3Rt6hUquMksVi64=",
       "dev": true,
       "requires": {
         "cacache": "^10.0.4",
@@ -1388,7 +1401,7 @@
         "p-limit": {
           "version": "1.3.0",
           "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
-          "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+          "integrity": "sha1-uGvV8MJWkJEcdZD8v8IBDVSzzLg=",
           "dev": true,
           "requires": {
             "p-try": "^1.0.0"
@@ -1534,7 +1547,7 @@
         "schema-utils": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
-          "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+          "integrity": "sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=",
           "dev": true,
           "requires": {
             "ajv": "^6.1.0",
@@ -4165,12 +4178,12 @@
     "mobx": {
       "version": "5.8.0",
       "resolved": "https://registry.npmjs.org/mobx/-/mobx-5.8.0.tgz",
-      "integrity": "sha512-NsZB+9bF5j+nv9Qwk6bNeE3np26a4TbTGkMpOLf6o1zXoM9BtHPQn/00px4uZ2AXJXtQML5P4MEWdMm6icMIfQ=="
+      "integrity": "sha1-z1nq5Lqi/eQ4f/tkYrfM87SXsDw="
     },
     "mobx-react": {
       "version": "5.4.3",
       "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.4.3.tgz",
-      "integrity": "sha512-WC8yFlwvJ91hy8j6CrydAuFteUafcuvdITFQeHl3LRIf5ayfT/4W3M/byhEYD2BcJWejeXr8y4Rh2H26RunCRQ==",
+      "integrity": "sha1-Zwm33YlnDEDpgVkUrCyknMAr+0c=",
       "requires": {
         "hoist-non-react-statics": "^3.0.0",
         "react-lifecycles-compat": "^3.0.2"
@@ -4179,12 +4192,12 @@
     "mobx-react-devtools": {
       "version": "6.0.3",
       "resolved": "https://registry.npmjs.org/mobx-react-devtools/-/mobx-react-devtools-6.0.3.tgz",
-      "integrity": "sha512-PY+lG6XeWaC0DFnDaVC7ImrHJQu7XVXNy4z4fmqHzWc3p+fJisKmaZNwgToO2vJGBghz98Mx2yXUBLw1Ba2mPQ=="
+      "integrity": "sha1-TVNsjN132fS4H16EFde+3MxqKpk="
     },
     "mocha": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
-      "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
+      "integrity": "sha1-bYrlCPWRZ/lA8rWzxKYSrlDJCuY=",
       "dev": true,
       "requires": {
         "browser-stdout": "1.3.1",
@@ -4203,13 +4216,13 @@
         "commander": {
           "version": "2.15.1",
           "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
-          "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
+          "integrity": "sha1-30boZ9D8Kuxmo0ZitAapzK//Ww8=",
           "dev": true
         },
         "debug": {
           "version": "3.1.0",
           "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=",
           "dev": true,
           "requires": {
             "ms": "2.0.0"
@@ -4218,7 +4231,7 @@
         "glob": {
           "version": "7.1.2",
           "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-          "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+          "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
           "dev": true,
           "requires": {
             "fs.realpath": "^1.0.0",
@@ -4232,7 +4245,7 @@
         "supports-color": {
           "version": "5.4.0",
           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
-          "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
+          "integrity": "sha1-HGszdALCE3YF7+GfEP7DkPb6q1Q=",
           "dev": true,
           "requires": {
             "has-flag": "^3.0.0"
@@ -4398,7 +4411,7 @@
     "node-sass": {
       "version": "4.11.0",
       "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz",
-      "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==",
+      "integrity": "sha1-GD+uw5jpy+k7pDNi4naMqYimNpo=",
       "requires": {
         "async-foreach": "^0.1.3",
         "chalk": "^1.1.1",
@@ -5206,7 +5219,7 @@
     "react": {
       "version": "16.7.0",
       "resolved": "https://registry.npmjs.org/react/-/react-16.7.0.tgz",
-      "integrity": "sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==",
+      "integrity": "sha512-ZUj2lkUDLjwJaGu4WD0dYSvsfIyhQt2l/AJDlg4ij+rCDU3fSFKgHWanNovViUoaWHAxgrpft3KGFfvWPZH5LA==",
       "requires": {
         "loose-envify": "^1.1.0",
         "object-assign": "^4.1.1",
@@ -5217,7 +5230,7 @@
     "react-dom": {
       "version": "16.7.0",
       "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.7.0.tgz",
-      "integrity": "sha512-D0Ufv1ExCAmF38P2Uh1lwpminZFRXEINJe53zRAbm4KPwSyd6DY/uDoS0Blj9jvPpn1+wivKpZYc8aAAN/nAkg==",
+      "integrity": "sha512-GfG8Vh/jMcnJKDpv7T6O1pS/WVqiocjPQ9o9cscW8bjR9W36DT3Xb4pDZT70t1xyVvX48/NrTQGz0H7I3fCLhQ==",
       "requires": {
         "loose-envify": "^1.1.0",
         "object-assign": "^4.1.1",
@@ -5233,7 +5246,7 @@
     "react-jsx-parser": {
       "version": "1.13.0",
       "resolved": "https://registry.npmjs.org/react-jsx-parser/-/react-jsx-parser-1.13.0.tgz",
-      "integrity": "sha512-oypIhM30ESZ8UkU0xDmzSV2Mtb2mVvtVnyNzjDxx2h2PCHpYFdDVLx1c15E3ot6nTIVlIh072tWwS3iJ7VVgmg==",
+      "integrity": "sha1-Dstuus1O41S68+h85SXcJuiPJtQ=",
       "requires": {
         "acorn-jsx": "^4.1.1",
         "react": "^16.4.0"
@@ -5618,7 +5631,7 @@
     "sass-loader": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz",
-      "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==",
+      "integrity": "sha1-Fv1ROMuLQkv4p1lSihly1yqtBp0=",
       "dev": true,
       "requires": {
         "clone-deep": "^2.0.1",
@@ -6321,7 +6334,7 @@
     "style-loader": {
       "version": "0.23.1",
       "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz",
-      "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==",
+      "integrity": "sha1-y5FUYG8+dxq2xKtjcCahBJF02SU=",
       "dev": true,
       "requires": {
         "loader-utils": "^1.1.0",
@@ -6331,7 +6344,7 @@
         "schema-utils": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
-          "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+          "integrity": "sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=",
           "dev": true,
           "requires": {
             "ajv": "^6.1.0",
@@ -6525,7 +6538,7 @@
     "ts-node": {
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
-      "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
+      "integrity": "sha1-lWLcLR5tJI0kvFX3c+P2FDN9m68=",
       "dev": true,
       "requires": {
         "arrify": "^1.0.0",
@@ -6775,7 +6788,7 @@
     "uuid": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
-      "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
+      "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE="
     },
     "v8-compile-cache": {
       "version": "2.0.2",
@@ -6925,7 +6938,7 @@
     "webpack-dev-server": {
       "version": "3.1.14",
       "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz",
-      "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==",
+      "integrity": "sha1-YPsim5l/xaCh/GI3QhAwGAlZ1Gk=",
       "dev": true,
       "requires": {
         "ansi-html": "0.0.7",
@@ -6963,7 +6976,7 @@
         "debug": {
           "version": "3.2.6",
           "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
-          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "integrity": "sha1-6D0X3hbYp++3cX7b5fsQE17uYps=",
           "dev": true,
           "requires": {
             "ms": "^2.1.1"
@@ -6978,7 +6991,7 @@
         "schema-utils": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
-          "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+          "integrity": "sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=",
           "dev": true,
           "requires": {
             "ajv": "^6.1.0",
@@ -6989,7 +7002,7 @@
         "webpack-log": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
-          "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==",
+          "integrity": "sha1-W3ko4GN1k/EZ0y9iJ8HgrDHhtH8=",
           "dev": true,
           "requires": {
             "ansi-colors": "^3.0.0",
diff --git a/package.json b/package.json
index 08e999058..71ca3cc8b 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
     "webpack-dev-server": "^3.1.14"
   },
   "dependencies": {
+    "@fortawesome/fontawesome-svg-core": "^1.2.12",
     "@types/prosemirror-commands": "^1.0.1",
     "@types/prosemirror-history": "^1.0.1",
     "@types/prosemirror-keymap": "^1.0.1",
diff --git a/src/DocumentDecorations.scss b/src/DocumentDecorations.scss
index fff4d201a..5a9ff7d01 100644
--- a/src/DocumentDecorations.scss
+++ b/src/DocumentDecorations.scss
@@ -1,6 +1,5 @@
 #documentDecorations-container {
     position: absolute;
-    z-index: 1000;
     display: grid;
     grid-template-rows: 20px 1fr 20px;
     grid-template-columns: 20px 1fr 20px;
diff --git a/src/Main.tsx b/src/Main.tsx
index 0a683c858..cb91c33a3 100644
--- a/src/Main.tsx
+++ b/src/Main.tsx
@@ -9,7 +9,7 @@ import { FreeFormCanvas } from './views/freeformcanvas/FreeFormCanvas';
 import { Key, KeyStore as KS, KeyStore } from './fields/Key';
 import { NumberField } from './fields/NumberField';
 import { Document } from './fields/Document';
-import { configure, runInAction } from 'mobx';
+import { configure, runInAction, action } from 'mobx';
 import { NodeStore } from './stores/NodeStore';
 import { Documents } from './documents/Documents';
 import { DocumentDecorations } from './DocumentDecorations';
@@ -17,6 +17,7 @@ import { CollectionFreeFormView } from './views/freeformcanvas/CollectionFreeFor
 import { ListField } from './fields/ListField';
 import { DocumentView } from './views/nodes/DocumentView';
 import { DocumentViewModel } from './viewmodels/DocumentViewModel';
+import { ContextMenu } from './views/ContextMenu';
 
 configure({
     enforceActions: "observed"
@@ -26,11 +27,27 @@ const mainNodeCollection = new Array<Document>();
 let mainContainer = Documents.CollectionDocument(mainNodeCollection, {
     x: 0, y: 0, width: window.screen.width, height: window.screen.height
 })
+let mainContvm = new DocumentViewModel(mainContainer);
+mainContvm.IsMainDoc = true;
+
+window.addEventListener("drop", function(e) {
+    e.preventDefault();
+}, false)
+window.addEventListener("dragover", function(e) {
+    e.preventDefault();
+}, false)
+document.addEventListener("pointerdown", action(function(e: PointerEvent) {
+    if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
+        ContextMenu.Instance.clearItems()
+    }
+}), true)
+
 ReactDOM.render((
     <div style={{display: "grid", width: "100vw", height: "100vh"}}>
         <h1>Dash Web</h1>
+        <DocumentView dvm={mainContvm} parent={undefined} />
         <DocumentDecorations />
-        <DocumentView dvm={new DocumentViewModel(mainContainer)} />
+        <ContextMenu />
     </div>), document.getElementById('root'));
 
 
diff --git a/src/documents/Documents.ts b/src/documents/Documents.ts
index cc08f4123..75cc3b491 100644
--- a/src/documents/Documents.ts
+++ b/src/documents/Documents.ts
@@ -35,7 +35,7 @@ export namespace Documents {
             textProto.SetField(KeyStore.Y, new NumberField(0));
             textProto.SetField(KeyStore.Width, new NumberField(300));
             textProto.SetField(KeyStore.Height, new NumberField(150));
-            textProto.SetField(KeyStore.Layout, new TextField("<FieldTextBox doc={doc} fieldKey={DataKey} />"));
+            textProto.SetField(KeyStore.Layout, new TextField("<FieldTextBox doc={dvm.Doc} fieldKey={DataKey} />"));
             textProto.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
         }
         return textProto;
@@ -73,11 +73,11 @@ export namespace Documents {
     function GetCollectionPrototype(): Document {
         if(!collectionProto) {
             collectionProto = new Document();
-            collectionProto.SetField(KeyStore.X, new NumberField(150));
+            collectionProto.SetField(KeyStore.X, new NumberField(0));
             collectionProto.SetField(KeyStore.Y, new NumberField(0));
             collectionProto.SetField(KeyStore.Width, new NumberField(300));
             collectionProto.SetField(KeyStore.Height, new NumberField(300));
-            collectionProto.SetField(KeyStore.Layout, new TextField('<CollectionFreeFormView doc={doc} fieldKey={DataKey} isSelected={isSelected}/>'));
+            collectionProto.SetField(KeyStore.Layout, new TextField('<CollectionFreeFormView dvm={dvm} fieldKey={DataKey} isSelected={isSelected}/>'));
             collectionProto.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
         }
         return collectionProto;
diff --git a/src/fields/Key.ts b/src/fields/Key.ts
index db30f545d..1f8ba0be5 100644
--- a/src/fields/Key.ts
+++ b/src/fields/Key.ts
@@ -36,6 +36,7 @@ export namespace KeyStore {
     export let Y = new Key("Y");
     export let PanX = new Key("PanX");
     export let PanY = new Key("PanY");
+    export let Scale = new Key("Scale");
     export let Width = new Key("Width");
     export let Height = new Key("Height");
     export let Data = new Key("Data");
diff --git a/src/viewmodels/DocumentViewModel.ts b/src/viewmodels/DocumentViewModel.ts
index f3154db54..21e88f964 100644
--- a/src/viewmodels/DocumentViewModel.ts
+++ b/src/viewmodels/DocumentViewModel.ts
@@ -9,13 +9,13 @@ export class DocumentViewModel {
         return this.doc;
     }
 
-    private _selected = false;
+    private _isMainDoc = false
 
-    get Selected() : boolean {
-        return this._selected;
+    get IsMainDoc(): boolean {
+        return this._isMainDoc;
     }
 
-    set Selected(isSelected: boolean) {
-        this._selected = isSelected;
+    set IsMainDoc(v: boolean) {
+        this._isMainDoc = v;
     }
 }
\ No newline at end of file
diff --git a/src/views/ContextMenu.scss b/src/views/ContextMenu.scss
new file mode 100644
index 000000000..234f82eb9
--- /dev/null
+++ b/src/views/ContextMenu.scss
@@ -0,0 +1,34 @@
+.contextMenu-cont {
+    position: absolute;
+    display: flex;
+    z-index: 1000;
+    box-shadow: #AAAAAA .2vw .2vw .4vw;
+}
+
+.contextMenu-item {
+    width: 10vw;
+    height: 4vh;
+    background: #DDDDDD;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    transition: all .1s;
+}
+
+.contextMenu-item:hover {
+    transition: all .1s;
+    background: #AAAAAA
+}
+
+.contextMenu-description {
+    font-size: 1.5vw;
+    text-align: left;
+    width: 8vw;
+}
\ No newline at end of file
diff --git a/src/views/ContextMenu.tsx b/src/views/ContextMenu.tsx
new file mode 100644
index 000000000..299ce91c6
--- /dev/null
+++ b/src/views/ContextMenu.tsx
@@ -0,0 +1,68 @@
+import React = require("react");
+import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
+import { observable } from "mobx";
+import { observer } from "mobx-react";
+import "./ContextMenu.scss"
+
+@observer
+export class ContextMenu extends React.Component {
+    static Instance: ContextMenu
+
+    @observable private _items: Array<ContextMenuProps> = [{description:"test", event:(e:React.MouseEvent) => e.preventDefault()}];
+    @observable private _pageX: number = 0;
+    @observable private _pageY: number = 0;
+    @observable private _display: string = "none";
+
+    private ref: React.RefObject<HTMLDivElement>;
+
+    constructor(props: Readonly<{}>) {
+        super(props);
+
+        this.ref = React.createRef()
+
+        ContextMenu.Instance = this;
+    }
+
+    clearItems() {
+        this._items = []
+        this._display = "none"
+    }
+
+    addItem(item: ContextMenuProps) {
+        if (this._items.indexOf(item) === -1) {
+            this._items.push(item);
+        }
+    }
+
+    getItems() {
+        return this._items;
+    }
+
+    displayMenu(x: number, y: number) {
+        this._pageX = x
+        this._pageY = y
+
+        this._display = "flex"
+    }
+
+    intersects = (x: number, y: number): boolean => {
+        if (this.ref.current && this._display !== "none") {
+            if (x >= this._pageX && x <= this._pageX + this.ref.current.getBoundingClientRect().width) {
+                if (y >= this._pageY && y <= this._pageY + this.ref.current.getBoundingClientRect().height) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    render() {
+        return(
+            <div className="contextMenu-cont" style={{left: this._pageX, top: this._pageY, display: this._display }} ref={this.ref}>
+                {this._items.map(prop => {
+                    return <ContextMenuItem {...prop} key={prop.description}/>
+                })}
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/views/ContextMenuItem.tsx b/src/views/ContextMenuItem.tsx
new file mode 100644
index 000000000..beacb44d2
--- /dev/null
+++ b/src/views/ContextMenuItem.tsx
@@ -0,0 +1,17 @@
+import React = require("react");
+import { ContextMenu } from "./ContextMenu";
+
+export interface ContextMenuProps {
+    description: string;
+    event: (e: React.MouseEvent<HTMLDivElement>) => void;
+}
+
+export class ContextMenuItem extends React.Component<ContextMenuProps> {
+    render() {
+        return(
+            <div className="contextMenu-item" onClick={this.props.event}>
+                <div className="contextMenu-description">{this.props.description}</div>
+            </div>
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/views/freeformcanvas/CollectionFreeFormView.tsx b/src/views/freeformcanvas/CollectionFreeFormView.tsx
index 4c8fcec10..84b30ac38 100644
--- a/src/views/freeformcanvas/CollectionFreeFormView.tsx
+++ b/src/views/freeformcanvas/CollectionFreeFormView.tsx
@@ -2,7 +2,7 @@ import { observer } from "mobx-react";
 import { Key, KeyStore } from "../../fields/Key";
 import "./FreeFormCanvas.scss";
 import React = require("react");
-import { action } from "mobx";
+import { action, observable } from "mobx";
 import { Document } from "../../fields/Document";
 import { DocumentViewModel } from "../../viewmodels/DocumentViewModel";
 import { DocumentView } from "../nodes/DocumentView";
@@ -11,10 +11,12 @@ import { NumberField } from "../../fields/NumberField";
 import { SSL_OP_SINGLE_DH_USE } from "constants";
 import { DocumentDecorations } from "../../DocumentDecorations";
 import { SelectionManager } from "../../util/SelectionManager";
+import { Documents } from "../../documents/Documents";
+import { ContextMenu } from "../ContextMenu";
 
 interface IProps {
     fieldKey: Key;
-    doc: Document;
+    dvm: DocumentViewModel;
     isSelected: boolean;
 }
 
@@ -29,10 +31,14 @@ export class CollectionFreeFormView extends React.Component<IProps> {
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        if (!this.props.isSelected) {
+        if (!this.props.isSelected && !this.props.dvm.IsMainDoc) {
             return;
         }
 
+        if (this.props.dvm.IsMainDoc) {
+            SelectionManager.DeselectAll()
+        }
+
         e.stopPropagation();
         if (e.button === 2) {
             this._isPointerDown = true;
@@ -60,14 +66,14 @@ export class CollectionFreeFormView extends React.Component<IProps> {
         if (!this._isPointerDown) {
             return;
         }
-        const { doc } = this.props;
+        const { dvm } = this.props;
+        const doc = dvm.Doc;
 
         let x = doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
         let y = doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
         doc.SetFieldValue(KeyStore.PanX, x + e.movementX, NumberField);
         doc.SetFieldValue(KeyStore.PanY, y + e.movementY, NumberField);
 
-
         DocumentDecorations.Instance.forceUpdate()
     }
 
@@ -76,16 +82,75 @@ export class CollectionFreeFormView extends React.Component<IProps> {
         e.stopPropagation();
 
         let scaleAmount = 1 - (e.deltaY / 1000);
-        //this.props.store.Scale *= scaleAmount;
+        let currScale = this.props.dvm.Doc.GetFieldValue(KeyStore.Scale, NumberField, Number(1));
+        this.props.dvm.Doc.SetField(KeyStore.Scale, new NumberField(currScale * scaleAmount));
+
+        const panx: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
+        const pany: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+        let dx = (e.pageX - window.screen.width / 2) * currScale * (scaleAmount - 1)
+        let dy = (e.pageY - window.screen.height / 2) * currScale * (scaleAmount - 1)
+
+        this.props.dvm.Doc.SetFieldValue(KeyStore.PanX, panx - dx, NumberField);
+        this.props.dvm.Doc.SetFieldValue(KeyStore.PanY, pany - dy, NumberField);
+
+        DocumentDecorations.Instance.forceUpdate()
+    }
+
+    onDrop = (e: React.DragEvent): void => {
+        e.stopPropagation()
+        e.preventDefault()
+        let fReader = new FileReader()
+        let file = e.dataTransfer.items[0].getAsFile();
+        let that = this;
+        const panx: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
+        const pany: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+        let x = e.pageX - panx
+        let y = e.pageY - pany
+
+        fReader.addEventListener("load", action("drop", (event) => {
+            if (fReader.result) {
+                let url = "" + fReader.result;
+                let doc = Documents.ImageDocument(url, {
+                    x: x, y: y
+                })
+                let docs = that.props.dvm.Doc.GetFieldT(KeyStore.Data, ListField);
+                if (!docs) {
+                    docs = new ListField<Document>();
+                    that.props.dvm.Doc.SetField(KeyStore.Data, docs)
+                }
+                docs.Data.push(doc);
+            }
+        }), false)
+
+        if (file) {
+            fReader.readAsDataURL(file)
+        }
+    }
+
+    @action
+    removeDocument = (doc: Document): void => {
+        const value: Document[] = this.props.dvm.Doc.GetFieldValue(this.props.fieldKey, ListField, [])
+        if (value.indexOf(doc) !== -1) {
+            value.splice(value.indexOf(doc), 1)
+
+            SelectionManager.DeselectAll()
+            ContextMenu.Instance.clearItems()
+        }
+    }
+
+    onDragOver = (e: React.DragEvent): void => {
+        // console.log(e.dataTransfer)
     }
 
     render() {
-        const { fieldKey, doc } = this.props;
-        const value: Document[] = doc.GetFieldValue(fieldKey, ListField, []);
-        const panx: number = doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
-        const pany: number = doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+        const { fieldKey, dvm } = this.props;
+        
+        const value: Document[] = dvm.Doc.GetFieldValue(fieldKey, ListField, []);
+        const panx: number = dvm.Doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
+        const pany: number = dvm.Doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+        const currScale: number = dvm.Doc.GetFieldValue(KeyStore.Scale, NumberField, Number(1));
+        // DocumentDecorations.Instance.forceUpdate()
         return (
-
             <div className="border" style={{
                     borderStyle: "solid",
                     borderWidth: "2px"
@@ -94,11 +159,12 @@ export class CollectionFreeFormView extends React.Component<IProps> {
                     width: "100%",
                     height: "calc(100% - 4px)",
                     overflow: "hidden"
-                }}>
-                    <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px)`, transformOrigin: '50% 50%', width: "100%", height: "100%" }}>
-                        <div className="node-container" style={{width: "100%", height: "100%"}}>
+                }} onDrop={this.onDrop} onDragOver={this.onDragOver}>
+                    <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px) scale(${currScale}, ${currScale})`, transformOrigin: `50%, 50%`}}>
+                        
+                        <div className="node-container">
                             {value.map(doc => {
-                                return (<DocumentView key={doc.Id} dvm={new DocumentViewModel(doc)} />);
+                                return (<DocumentView key={doc.Id} parent={this} dvm={new DocumentViewModel(doc)} />);
                             })}
                         </div>
                     </div>
diff --git a/src/views/freeformcanvas/FreeFormCanvas.tsx b/src/views/freeformcanvas/FreeFormCanvas.tsx
index de5e88fa1..d7dc0ecaa 100644
--- a/src/views/freeformcanvas/FreeFormCanvas.tsx
+++ b/src/views/freeformcanvas/FreeFormCanvas.tsx
@@ -79,9 +79,9 @@ export class FreeFormCanvas extends React.Component<IProps> {
             <div className="freeformcanvas-container" onPointerDown={this.onPointerDown} onWheel={this.onPointerWheel} onContextMenu={(e) => e.preventDefault()}>
                 <div className="freeformcanvas" style={{ transform: store.Transform, transformOrigin: '50% 50%' }}>
                     <div className="node-container">
-                        {this.props.store.Docs.map(doc => {
-                            return (<DocumentView key={doc.Id} dvm={new DocumentViewModel(doc)} />);
-                        })}
+                        {/* {this.props.store.Docs.map(doc => {
+                            return (<DocumentView key={doc.Id} parent={null} dvm={new DocumentViewModel(doc)} />);
+                        })} */}
                     </div>
                 </div>
             </div>
diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx
index e37172943..ee6269430 100644
--- a/src/views/nodes/DocumentView.tsx
+++ b/src/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { observer } from "mobx-react";
 import React = require("react");
-import { computed, observable } from "mobx";
+import { computed, observable, action } from "mobx";
 import { KeyStore, Key } from "../../fields/Key";
 import { NumberField } from "../../fields/NumberField";
 import { TextField } from "../../fields/TextField";
@@ -12,15 +12,19 @@ import { CollectionFreeFormView } from "../freeformcanvas/CollectionFreeFormView
 import "./NodeView.scss"
 import { SelectionManager } from "../../util/SelectionManager";
 import { DocumentDecorations } from "../../DocumentDecorations";
+import { ContextMenu } from "../ContextMenu";
+import { Opt } from "../../fields/Field";
 const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
 
 interface IProps {
     dvm: DocumentViewModel;
+    parent: Opt<CollectionFreeFormView>;
 }
 
 @observer
 export class DocumentView extends React.Component<IProps> {
     private _mainCont = React.createRef<HTMLDivElement>();
+    private _contextMenuCanOpen = false;
 
     get mainCont(): React.RefObject<HTMLDivElement> {
         return this._mainCont
@@ -91,8 +95,10 @@ export class DocumentView extends React.Component<IProps> {
 
     onPointerDown = (e: React.PointerEvent): void => {
         e.stopPropagation();
+
         if (e.button === 2) {
             this._isPointerDown = true;
+            this._contextMenuCanOpen = true;
             document.removeEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
@@ -120,6 +126,7 @@ export class DocumentView extends React.Component<IProps> {
         if (!this._isPointerDown) {
             return;
         }
+        this._contextMenuCanOpen = false
         this.x += e.movementX;
         this.y += e.movementY;
         DocumentDecorations.Instance.opacity = 0
@@ -132,10 +139,39 @@ export class DocumentView extends React.Component<IProps> {
         }
     }
 
+    onClick = (e: React.MouseEvent): void => {    }
+
+    deleteClicked = (e: React.MouseEvent): void => {
+        if (this.props.parent) {
+            this.props.parent.removeDocument(this.props.dvm.Doc)
+        }
+    }
+
+    @action
+    onContextMenu = (e: React.MouseEvent): void => {
+        e.preventDefault()
+
+        if (!this._contextMenuCanOpen) {
+            return;
+        }
+
+        if (this.props.dvm.IsMainDoc) {
+            ContextMenu.Instance.clearItems()
+        }
+        else {
+            // DocumentViews should stop propogation of this event
+            e.stopPropagation();
+
+            ContextMenu.Instance.clearItems();
+            ContextMenu.Instance.addItem({description: "Delete", event: this.deleteClicked})
+            ContextMenu.Instance.displayMenu(e.pageX, e.pageY)
+        }
+    }
+
     render() {
         let doc = this.props.dvm.Doc;
         let bindings: any = {
-            doc: doc,
+            dvm: this.props.dvm,
             isSelected: this.selected
         };
         for (const key of this.layoutKeys) {
@@ -154,11 +190,9 @@ export class DocumentView extends React.Component<IProps> {
                     width: this.width,
                     height: this.height,
                 }}
-                onContextMenu={
-                    (e) => {
-                        e.preventDefault()
-                }}
-                onPointerDown={this.onPointerDown}>
+                onContextMenu={this.onContextMenu}
+                onPointerDown={this.onPointerDown}
+                onClick={this.onClick}>
                 <JsxParser
                     components={{ FieldTextBox, FreeFormCanvas, CollectionFreeFormView }}
                     bindings={bindings}
diff --git a/src/views/nodes/FieldTextBox.scss b/src/views/nodes/FieldTextBox.scss
new file mode 100644
index 000000000..5c95fe2b2
--- /dev/null
+++ b/src/views/nodes/FieldTextBox.scss
@@ -0,0 +1,10 @@
+.ProseMirror {
+    margin-top: -1em;
+    width: 100%;
+    height: 100%;
+}
+
+.fieldTextBox-cont {
+    background: white;
+    padding: 1vw;
+}
\ No newline at end of file
diff --git a/src/views/nodes/FieldTextBox.tsx b/src/views/nodes/FieldTextBox.tsx
index dbac3906a..3b8743627 100644
--- a/src/views/nodes/FieldTextBox.tsx
+++ b/src/views/nodes/FieldTextBox.tsx
@@ -13,6 +13,8 @@ import {baseKeymap} from "prosemirror-commands"
 import {undo, redo, history} from "prosemirror-history"
 import { Opt } from "../../fields/Field";
 
+import "./FieldTextBox.scss"
+
 interface IProps {
     fieldKey:Key;
     doc:Document;
@@ -34,7 +36,6 @@ interface IProps {
 //  specified Key and assigns it to an HTML input node.  When changes are made tot his node, 
 //  this will edit the document and assign the new value to that field.
 //
-@observer
 export class FieldTextBox extends React.Component<IProps> {
     private _ref: React.RefObject<HTMLDivElement>;
     private _editorView: Opt<EditorView>;
@@ -112,6 +113,6 @@ export class FieldTextBox extends React.Component<IProps> {
     }
 
     render() {
-        return (<div ref={this._ref} />)
+        return (<div className="fieldTextBox-cont" ref={this._ref} />)
     }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From f98e634ae7070cd841bc523514d147195308696c Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Fri, 25 Jan 2019 18:34:28 -0500
Subject: got rid of documentviewmodel.  fixed panning/zooming.

---
 src/Main.tsx                                       |  11 +-
 src/documents/Documents.ts                         |  10 +-
 src/fields/Document.ts                             |   3 +-
 src/viewmodels/DocumentViewModel.ts                |  10 --
 .../freeformcanvas/CollectionFreeFormView.tsx      | 150 +++++++++++++--------
 src/views/freeformcanvas/FreeFormCanvas.scss       |  15 ---
 src/views/freeformcanvas/FreeFormCanvas.tsx        |  90 -------------
 src/views/nodes/DocumentView.tsx                   | 118 +++++++++-------
 8 files changed, 176 insertions(+), 231 deletions(-)
 delete mode 100644 src/views/freeformcanvas/FreeFormCanvas.scss
 delete mode 100644 src/views/freeformcanvas/FreeFormCanvas.tsx

(limited to 'src/Main.tsx')

diff --git a/src/Main.tsx b/src/Main.tsx
index cb91c33a3..c2275b6a7 100644
--- a/src/Main.tsx
+++ b/src/Main.tsx
@@ -5,7 +5,6 @@ import "normalize.css"
 import { NodeCollectionStore } from './stores/NodeCollectionStore';
 import { StaticTextNodeStore } from './stores/StaticTextNodeStore';
 import { VideoNodeStore } from './stores/VideoNodeStore';
-import { FreeFormCanvas } from './views/freeformcanvas/FreeFormCanvas';
 import { Key, KeyStore as KS, KeyStore } from './fields/Key';
 import { NumberField } from './fields/NumberField';
 import { Document } from './fields/Document';
@@ -27,8 +26,6 @@ const mainNodeCollection = new Array<Document>();
 let mainContainer = Documents.CollectionDocument(mainNodeCollection, {
     x: 0, y: 0, width: window.screen.width, height: window.screen.height
 })
-let mainContvm = new DocumentViewModel(mainContainer);
-mainContvm.IsMainDoc = true;
 
 window.addEventListener("drop", function(e) {
     e.preventDefault();
@@ -43,9 +40,9 @@ document.addEventListener("pointerdown", action(function(e: PointerEvent) {
 }), true)
 
 ReactDOM.render((
-    <div style={{display: "grid", width: "100vw", height: "100vh"}}>
+    <div style={{display: "grid"}}>
         <h1>Dash Web</h1>
-        <DocumentView dvm={mainContvm} parent={undefined} />
+        <DocumentView Document={mainContainer} ContainingCollectionView={undefined} ContainingDocumentView={undefined}/>
         <DocumentDecorations />
         <ContextMenu />
     </div>), document.getElementById('root'));
@@ -75,10 +72,10 @@ runInAction(() => {
     });
     let docset = new Array<Document>(doc1, doc2);
     let doc4 = Documents.CollectionDocument(docset, {
-        x: 100, y: 400
+        x: 0, y: 400
     });
     let doc5 = Documents.ImageDocument("https://static.boredpanda.com/blog/wp-content/uploads/2018/04/5acb63d83493f__700-png.jpg", {
-        x: 650, y: 500
+        x: 1280, y: 500
     });
     let mainNodes = mainContainer.GetFieldT(KeyStore.Data, ListField);
     if (!mainNodes) {
diff --git a/src/documents/Documents.ts b/src/documents/Documents.ts
index 75cc3b491..5fee7343b 100644
--- a/src/documents/Documents.ts
+++ b/src/documents/Documents.ts
@@ -25,6 +25,9 @@ export namespace Documents {
         if(options.height) {
             doc.SetFieldValue(KeyStore.Height, options.height, NumberField);
         }
+        doc.SetFieldValue(KeyStore.Scale, 1, NumberField);
+        doc.SetFieldValue(KeyStore.PanX, 0, NumberField);
+        doc.SetFieldValue(KeyStore.PanY, 0, NumberField);
     }
 
     let textProto:Document;
@@ -35,7 +38,7 @@ export namespace Documents {
             textProto.SetField(KeyStore.Y, new NumberField(0));
             textProto.SetField(KeyStore.Width, new NumberField(300));
             textProto.SetField(KeyStore.Height, new NumberField(150));
-            textProto.SetField(KeyStore.Layout, new TextField("<FieldTextBox doc={dvm.Doc} fieldKey={DataKey} />"));
+            textProto.SetField(KeyStore.Layout, new TextField("<FieldTextBox doc={Document} fieldKey={DataKey} />"));
             textProto.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
         }
         return textProto;
@@ -75,9 +78,12 @@ export namespace Documents {
             collectionProto = new Document();
             collectionProto.SetField(KeyStore.X, new NumberField(0));
             collectionProto.SetField(KeyStore.Y, new NumberField(0));
+            collectionProto.SetField(KeyStore.Scale, new NumberField(1));
+            collectionProto.SetField(KeyStore.PanX, new NumberField(0));
+            collectionProto.SetField(KeyStore.PanY, new NumberField(0));
             collectionProto.SetField(KeyStore.Width, new NumberField(300));
             collectionProto.SetField(KeyStore.Height, new NumberField(300));
-            collectionProto.SetField(KeyStore.Layout, new TextField('<CollectionFreeFormView dvm={dvm} fieldKey={DataKey} isSelected={isSelected}/>'));
+            collectionProto.SetField(KeyStore.Layout, new TextField('<CollectionFreeFormView Document={Document} fieldKey={DataKey} ContainingDocumentView={ContainingDocumentView} isSelected={isSelected}/>'));
             collectionProto.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
         }
         return collectionProto;
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 0bba9c21e..6b1cccaf9 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -31,7 +31,8 @@ export class Document extends Field {
 
     GetFieldValue<T, U extends { Data: T }>(key: Key, ctor: { new(): U }, defaultVal: T): T {
         let val = this.GetField(key);
-        return (val && val instanceof ctor) ? val.Data : defaultVal;
+        let vval = (val && val instanceof ctor) ? val.Data : defaultVal;
+        return vval;
     }
 
     SetField(key: Key, field: Opt<Field>): void {
diff --git a/src/viewmodels/DocumentViewModel.ts b/src/viewmodels/DocumentViewModel.ts
index 21e88f964..008275f3c 100644
--- a/src/viewmodels/DocumentViewModel.ts
+++ b/src/viewmodels/DocumentViewModel.ts
@@ -8,14 +8,4 @@ export class DocumentViewModel {
     get Doc(): Document {
         return this.doc;
     }
-
-    private _isMainDoc = false
-
-    get IsMainDoc(): boolean {
-        return this._isMainDoc;
-    }
-
-    set IsMainDoc(v: boolean) {
-        this._isMainDoc = v;
-    }
 }
\ No newline at end of file
diff --git a/src/views/freeformcanvas/CollectionFreeFormView.tsx b/src/views/freeformcanvas/CollectionFreeFormView.tsx
index 84b30ac38..383d71d83 100644
--- a/src/views/freeformcanvas/CollectionFreeFormView.tsx
+++ b/src/views/freeformcanvas/CollectionFreeFormView.tsx
@@ -1,8 +1,7 @@
 import { observer } from "mobx-react";
 import { Key, KeyStore } from "../../fields/Key";
-import "./FreeFormCanvas.scss";
 import React = require("react");
-import { action, observable } from "mobx";
+import { action, observable, computed } from "mobx";
 import { Document } from "../../fields/Document";
 import { DocumentViewModel } from "../../viewmodels/DocumentViewModel";
 import { DocumentView } from "../nodes/DocumentView";
@@ -13,85 +12,123 @@ import { DocumentDecorations } from "../../DocumentDecorations";
 import { SelectionManager } from "../../util/SelectionManager";
 import { Documents } from "../../documents/Documents";
 import { ContextMenu } from "../ContextMenu";
+import { Opt } from "../../fields/Field";
 
 interface IProps {
     fieldKey: Key;
-    dvm: DocumentViewModel;
-    isSelected: boolean;
+    Document: Document;
+    ContainingDocumentView: Opt<DocumentView>;
 }
 
 @observer
 export class CollectionFreeFormView extends React.Component<IProps> {
 
-    private _isPointerDown: boolean = false;
-
     constructor(props: IProps) {
         super(props);
     }
 
-    @action
-    onPointerDown = (e: React.PointerEvent): void => {
-        if (!this.props.isSelected && !this.props.dvm.IsMainDoc) {
-            return;
-        }
-
-        if (this.props.dvm.IsMainDoc) {
-            SelectionManager.DeselectAll()
-        }
+    @computed
+    public get active():boolean {
+        var isSelected    = (this.props.ContainingDocumentView != undefined &&  SelectionManager.IsSelected(this.props.ContainingDocumentView));
+        var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this );
+        var topMost       = this.props.ContainingDocumentView != undefined && this.props.ContainingDocumentView.props.ContainingCollectionView == undefined;
+        return isSelected || childSelected || topMost;
+    }
 
-        e.stopPropagation();
-        if (e.button === 2) {
-            this._isPointerDown = true;
+    _lastX: number = 0;
+    _lastY:number = 0;
+    @action
+    onPointerDown = (e: React.PointerEvent): void => 
+    {
+        if (this.active && e.button === 2) {
+            e.stopPropagation();
+            e.preventDefault();
             document.removeEventListener("pointermove", this.onPointerMove);
             document.addEventListener("pointermove", this.onPointerMove);
             document.removeEventListener("pointerup", this.onPointerUp);
             document.addEventListener("pointerup", this.onPointerUp);
+            this._lastX = e.pageX;
+            this._lastY = e.pageY;
         }
     }
 
     @action
     onPointerUp = (e: PointerEvent): void => {
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
         e.stopPropagation();
-        if (e.button === 2) {
-            this._isPointerDown = false;
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
+
+        var topMost = this.props.ContainingDocumentView != undefined && this.props.ContainingDocumentView.props.ContainingCollectionView == undefined;
+        if (topMost) {
+            SelectionManager.DeselectAll()
         }
     }
 
     @action
-    onPointerMove = (e: PointerEvent): void => {
-        e.preventDefault();
-        e.stopPropagation();
-        if (!this._isPointerDown) {
-            return;
+    onPointerMove = (e: PointerEvent): void => 
+    {
+        if (!e.cancelBubble) {
+            e.preventDefault();
+            e.stopPropagation();
+            const doc = this.props.Document;
+            let me = this;
+            let currScale:number = this.props.Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1));
+            if (me.props.ContainingDocumentView!.props.ContainingDocumentView != undefined) {
+                let pme = me.props.ContainingDocumentView!.props.ContainingDocumentView!.props.Document;
+                currScale = pme.GetFieldValue(KeyStore.Scale, NumberField, Number(0));
+            } 
+            let x = doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
+            let y = doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+            doc.SetFieldValue(KeyStore.PanX, x + (e.pageX - this._lastX)/currScale, NumberField);
+            doc.SetFieldValue(KeyStore.PanY, y + (e.pageY - this._lastY)/currScale, NumberField);
+            this._lastX = e.pageX;
+            this._lastY = e.pageY;
+
+            DocumentDecorations.Instance.forceUpdate()
         }
-        const { dvm } = this.props;
-        const doc = dvm.Doc;
+    }
 
-        let x = doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
-        let y = doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
-        doc.SetFieldValue(KeyStore.PanX, x + e.movementX, NumberField);
-        doc.SetFieldValue(KeyStore.PanY, y + e.movementY, NumberField);
 
-        DocumentDecorations.Instance.forceUpdate()
+    private getLocalPoint(me:DocumentView, inputX: number, inputY: number) {
+        let ContainerX = inputX;
+        let ContainerY = inputY;
+        if (me.props.ContainingDocumentView != undefined) {
+            let pme = me.props.ContainingDocumentView!;
+            let {LocalX, Ss, W, Panxx, Xx, LocalY, Panyy, Yy} = this.getLocalPoint(pme, ContainerX, ContainerY);
+            ContainerX = LocalX;
+            ContainerY = LocalY;
+        } 
+
+        let W      = me.props.Document.GetFieldValue(KeyStore.Width, NumberField, Number(0));
+        let Xx     = me.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0));
+        let Yy     = me.props.Document.GetFieldValue(KeyStore.Y, NumberField, Number(0));
+        let Ss     = me.props.Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1));
+        let Panxx  = me.props.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
+        let Panyy  = me.props.Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+        let LocalX = W / 2 - (Xx + Panxx) / Ss + (ContainerX - W / 2) / Ss;
+        let LocalY = -(Yy + Panyy) / Ss + ContainerY / Ss;
+
+        return {LocalX, Ss, W, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY};
     }
 
     @action
-    onPointerWheel = (e: React.WheelEvent): void => {
+    onPointerWheel = (e: React.WheelEvent): void => 
+    {
         e.stopPropagation();
 
-        let scaleAmount = 1 - (e.deltaY / 1000);
-        let currScale = this.props.dvm.Doc.GetFieldValue(KeyStore.Scale, NumberField, Number(1));
-        this.props.dvm.Doc.SetField(KeyStore.Scale, new NumberField(currScale * scaleAmount));
+        let {LocalX, Ss, W, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY} = this.getLocalPoint(this.props.ContainingDocumentView!, e.pageX, e.pageY);
+
+        var deltaScale  = (1 - (e.deltaY / 1000)) * Ss;
 
-        const panx: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
-        const pany: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
-        let dx = (e.pageX - window.screen.width / 2) * currScale * (scaleAmount - 1)
-        let dy = (e.pageY - window.screen.height / 2) * currScale * (scaleAmount - 1)
+        var newContainerX = LocalX * deltaScale + W/2-W/2 * deltaScale + Panxx + Xx;
+        var newContainerY = LocalY * deltaScale + Panyy+ Yy;
 
-        this.props.dvm.Doc.SetFieldValue(KeyStore.PanX, panx - dx, NumberField);
-        this.props.dvm.Doc.SetFieldValue(KeyStore.PanY, pany - dy, NumberField);
+        let dx = ContainerX - newContainerX;
+        let dy = ContainerY - newContainerY;
+
+        this.props.Document.SetField(KeyStore.Scale, new NumberField(deltaScale));
+        this.props.Document.SetFieldValue(KeyStore.PanX, Panxx+dx, NumberField);
+        this.props.Document.SetFieldValue(KeyStore.PanY, Panyy+dy, NumberField);
 
         DocumentDecorations.Instance.forceUpdate()
     }
@@ -102,8 +139,8 @@ export class CollectionFreeFormView extends React.Component<IProps> {
         let fReader = new FileReader()
         let file = e.dataTransfer.items[0].getAsFile();
         let that = this;
-        const panx: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
-        const pany: number = this.props.dvm.Doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+        const panx: number = this.props.Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
+        const pany: number = this.props.Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
         let x = e.pageX - panx
         let y = e.pageY - pany
 
@@ -113,10 +150,10 @@ export class CollectionFreeFormView extends React.Component<IProps> {
                 let doc = Documents.ImageDocument(url, {
                     x: x, y: y
                 })
-                let docs = that.props.dvm.Doc.GetFieldT(KeyStore.Data, ListField);
+                let docs = that.props.Document.GetFieldT(KeyStore.Data, ListField);
                 if (!docs) {
                     docs = new ListField<Document>();
-                    that.props.dvm.Doc.SetField(KeyStore.Data, docs)
+                    that.props.Document.SetField(KeyStore.Data, docs)
                 }
                 docs.Data.push(doc);
             }
@@ -129,7 +166,7 @@ export class CollectionFreeFormView extends React.Component<IProps> {
 
     @action
     removeDocument = (doc: Document): void => {
-        const value: Document[] = this.props.dvm.Doc.GetFieldValue(this.props.fieldKey, ListField, [])
+        const value: Document[] = this.props.Document.GetFieldValue(this.props.fieldKey, ListField, [])
         if (value.indexOf(doc) !== -1) {
             value.splice(value.indexOf(doc), 1)
 
@@ -141,14 +178,13 @@ export class CollectionFreeFormView extends React.Component<IProps> {
     onDragOver = (e: React.DragEvent): void => {
         // console.log(e.dataTransfer)
     }
-
     render() {
-        const { fieldKey, dvm } = this.props;
+        const { fieldKey, Document: Document } = this.props;
         
-        const value: Document[] = dvm.Doc.GetFieldValue(fieldKey, ListField, []);
-        const panx: number = dvm.Doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
-        const pany: number = dvm.Doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
-        const currScale: number = dvm.Doc.GetFieldValue(KeyStore.Scale, NumberField, Number(1));
+        const value: Document[] = Document.GetFieldValue(fieldKey, ListField, []);
+        const panx: number = Document.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
+        const pany: number = Document.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
+        const currScale: number = Document.GetFieldValue(KeyStore.Scale, NumberField, Number(1));
         // DocumentDecorations.Instance.forceUpdate()
         return (
             <div className="border" style={{
@@ -160,11 +196,11 @@ export class CollectionFreeFormView extends React.Component<IProps> {
                     height: "calc(100% - 4px)",
                     overflow: "hidden"
                 }} onDrop={this.onDrop} onDragOver={this.onDragOver}>
-                    <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px) scale(${currScale}, ${currScale})`, transformOrigin: `50%, 50%`}}>
+                    <div className="collectionfreeformview" style={{ transform: `translate(${panx}px, ${pany}px) scale(${currScale}, ${currScale})` , transformOrigin: `left, top`}}>
                         
                         <div className="node-container">
                             {value.map(doc => {
-                                return (<DocumentView key={doc.Id} parent={this} dvm={new DocumentViewModel(doc)} />);
+                                return (<DocumentView key={doc.Id} ContainingCollectionView={this} Document={doc} ContainingDocumentView={this.props.ContainingDocumentView}/>);
                             })}
                         </div>
                     </div>
diff --git a/src/views/freeformcanvas/FreeFormCanvas.scss b/src/views/freeformcanvas/FreeFormCanvas.scss
deleted file mode 100644
index 884ef90e6..000000000
--- a/src/views/freeformcanvas/FreeFormCanvas.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-.freeformcanvas-container {
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    overflow: hidden;
-
-    .freeformcanvas {
-        position: absolute;
-        top: 0;
-        left: 0;
-    }
-}
-
diff --git a/src/views/freeformcanvas/FreeFormCanvas.tsx b/src/views/freeformcanvas/FreeFormCanvas.tsx
deleted file mode 100644
index d7dc0ecaa..000000000
--- a/src/views/freeformcanvas/FreeFormCanvas.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { observer } from "mobx-react";
-import { Key } from "../../fields/Key";
-import { NodeCollectionStore } from "../../stores/NodeCollectionStore";
-import "./FreeFormCanvas.scss";
-import React = require("react");
-import { action } from "mobx";
-import { Document } from "../../fields/Document";
-import {DocumentViewModel} from "../../viewmodels/DocumentViewModel";
-import {DocumentView} from "../nodes/DocumentView";
-import {TextField} from "../../fields/TextField";
-import {ListField} from "../../fields/ListField";
-import {Field} from "../../fields/Field";
-import { SelectionManager } from "../../util/SelectionManager";
-
-interface IProps {
-    store: NodeCollectionStore;
-}
-
-@observer
-export class FreeFormCanvas extends React.Component<IProps> {
-
-    private _isPointerDown: boolean = false;
-
-    constructor(props:IProps) {
-        super(props);
-    }
-
-    @action
-    onPointerDown = (e: React.PointerEvent): void => {
-        e.stopPropagation();
-        if (e.button === 2) {
-            this._isPointerDown = true;
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.addEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            document.addEventListener("pointerup", this.onPointerUp);
-        }
-
-        SelectionManager.DeselectAll()
-    }
-
-    @action
-    onPointerUp = (e: PointerEvent): void => {
-        e.stopPropagation();
-        if (e.button === 2) {
-            this._isPointerDown = false;
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
-        }
-
-        // let doc = this.props.store.Docs[0];
-        // let dataField = doc.GetFieldT(KeyStore.Data, TextField);
-        // let data = dataField ? dataField.Data : "";
-        // this.props.store.Docs[0].SetFieldValue(KeyStore.Data, data + " hello", TextField);
-    }
-
-    @action
-    onPointerMove = (e: PointerEvent): void => {
-        e.stopPropagation();
-        if (!this._isPointerDown) {
-            return;
-        }
-
-        this.props.store.X += e.movementX;
-        this.props.store.Y += e.movementY;
-    }
-
-    @action
-    onPointerWheel = (e: React.WheelEvent): void => {
-        e.stopPropagation();
-
-        let scaleAmount = 1 - (e.deltaY / 1000);
-        this.props.store.Scale *= scaleAmount;
-    }
-
-    render() {
-        let store = this.props.store;
-        return (
-            <div className="freeformcanvas-container" onPointerDown={this.onPointerDown} onWheel={this.onPointerWheel} onContextMenu={(e) => e.preventDefault()}>
-                <div className="freeformcanvas" style={{ transform: store.Transform, transformOrigin: '50% 50%' }}>
-                    <div className="node-container">
-                        {/* {this.props.store.Docs.map(doc => {
-                            return (<DocumentView key={doc.Id} parent={null} dvm={new DocumentViewModel(doc)} />);
-                        })} */}
-                    </div>
-                </div>
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx
index ee6269430..f7d4aa838 100644
--- a/src/views/nodes/DocumentView.tsx
+++ b/src/views/nodes/DocumentView.tsx
@@ -7,7 +7,7 @@ import { TextField } from "../../fields/TextField";
 import { DocumentViewModel } from "../../viewmodels/DocumentViewModel";
 import { ListField } from "../../fields/ListField";
 import { FieldTextBox } from "../nodes/FieldTextBox"
-import { FreeFormCanvas } from "../freeformcanvas/FreeFormCanvas"
+import { Document } from "../../fields/Document";
 import { CollectionFreeFormView } from "../freeformcanvas/CollectionFreeFormView"
 import "./NodeView.scss"
 import { SelectionManager } from "../../util/SelectionManager";
@@ -17,14 +17,19 @@ import { Opt } from "../../fields/Field";
 const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
 
 interface IProps {
-    dvm: DocumentViewModel;
-    parent: Opt<CollectionFreeFormView>;
+    Document:    Document;
+    ContainingCollectionView: Opt<object>;
+    ContainingDocumentView:   Opt<DocumentView>
 }
 
 @observer
 export class DocumentView extends React.Component<IProps> {
     private _mainCont = React.createRef<HTMLDivElement>();
     private _contextMenuCanOpen = false;
+    private _downX:number = 0;
+    private _downY:number = 0;
+    private _lastX:number = 0;
+    private _lastY:number = 0;
 
     get mainCont(): React.RefObject<HTMLDivElement> {
         return this._mainCont
@@ -32,20 +37,20 @@ export class DocumentView extends React.Component<IProps> {
 
     @computed
     get x(): number {
-        return this.props.dvm.Doc.GetFieldValue(KeyStore.X, NumberField, Number(0));
+        return this.props.Document.GetFieldValue(KeyStore.X, NumberField, Number(0));
     }
 
     @computed
     get y(): number {
-        return this.props.dvm.Doc.GetFieldValue(KeyStore.Y, NumberField, Number(0));
+        return this.props.Document.GetFieldValue(KeyStore.Y, NumberField, Number(0));
     }
 
     set x(x: number) {
-        this.props.dvm.Doc.SetFieldValue(KeyStore.X, x, NumberField)
+        this.props.Document.SetFieldValue(KeyStore.X, x, NumberField)
     }
 
     set y(y: number) {
-        this.props.dvm.Doc.SetFieldValue(KeyStore.Y, y, NumberField)
+        this.props.Document.SetFieldValue(KeyStore.Y, y, NumberField)
     }
 
     @computed
@@ -55,35 +60,35 @@ export class DocumentView extends React.Component<IProps> {
 
     @computed
     get width(): number {
-        return this.props.dvm.Doc.GetFieldValue(KeyStore.Width, NumberField, Number(0));
+        return this.props.Document.GetFieldValue(KeyStore.Width, NumberField, Number(0));
     }
 
     set width(w: number) {
-        this.props.dvm.Doc.SetFieldValue(KeyStore.Width, w, NumberField)
+        this.props.Document.SetFieldValue(KeyStore.Width, w, NumberField)
     }
 
     @computed
     get height(): number {
-        return this.props.dvm.Doc.GetFieldValue(KeyStore.Height, NumberField, Number(0));
+        return this.props.Document.GetFieldValue(KeyStore.Height, NumberField, Number(0));
     }
 
     set height(h: number) {
-        this.props.dvm.Doc.SetFieldValue(KeyStore.Height, h, NumberField)
+        this.props.Document.SetFieldValue(KeyStore.Height, h, NumberField)
     }
 
     @computed
     get layout(): string {
-        return this.props.dvm.Doc.GetFieldValue(KeyStore.Layout, TextField, String("<p>Error loading layout data</p>"));
+        return this.props.Document.GetFieldValue(KeyStore.Layout, TextField, String("<p>Error loading layout data</p>"));
     }
 
     @computed
     get layoutKeys(): Key[] {
-        return this.props.dvm.Doc.GetFieldValue(KeyStore.LayoutKeys, ListField, new Array<Key>());
+        return this.props.Document.GetFieldValue(KeyStore.LayoutKeys, ListField, new Array<Key>());
     }
 
     @computed
     get layoutFields(): Key[] {
-        return this.props.dvm.Doc.GetFieldValue(KeyStore.LayoutFields, ListField, new Array<Key>());
+        return this.props.Document.GetFieldValue(KeyStore.LayoutFields, ListField, new Array<Key>());
     }
 
     @computed
@@ -91,45 +96,59 @@ export class DocumentView extends React.Component<IProps> {
         return SelectionManager.IsSelected(this)
     }
 
-    private _isPointerDown = false;
+    @computed
+    get active() : boolean {
+        return SelectionManager.IsSelected(this) ||  (this.props.ContainingCollectionView instanceof CollectionFreeFormView && this.props.ContainingCollectionView.active);
+    }
 
     onPointerDown = (e: React.PointerEvent): void => {
-        e.stopPropagation();
-
-        if (e.button === 2) {
-            this._isPointerDown = true;
-            this._contextMenuCanOpen = true;
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.addEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            document.addEventListener("pointerup", this.onPointerUp);
-        }
-        SelectionManager.SelectDoc(this, e.ctrlKey)
+        this._downX = e.pageX;
+        this._downY = e.pageY;
+        this._lastX = e.pageX;
+        this._lastY = e.pageY;
+        this._contextMenuCanOpen = e.button == 2;
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.addEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        document.addEventListener("pointerup", this.onPointerUp);
     }
 
     onPointerUp = (e: PointerEvent): void => {
-        e.stopPropagation();
-        if (e.button === 2) {
+        document.removeEventListener("pointermove", this.onPointerMove);
+        document.removeEventListener("pointerup", this.onPointerUp);
+        if (!e.cancelBubble) {
+            e.stopPropagation();
             e.preventDefault();
-            this._isPointerDown = false;
-            document.removeEventListener("pointermove", this.onPointerMove);
-            document.removeEventListener("pointerup", this.onPointerUp);
-            console.log(this.x);
-            console.log(this.y)
-            DocumentDecorations.Instance.opacity = 1
+            DocumentDecorations.Instance.opacity = 1;
+            if (this._downX == e.pageX && this._downY == e.pageY) {
+                SelectionManager.SelectDoc(this, e.ctrlKey)
+            }
         }
     }
 
     onPointerMove = (e: PointerEvent): void => {
-        e.stopPropagation();
-        e.preventDefault();
-        if (!this._isPointerDown) {
-            return;
+        if (this.active && !e.cancelBubble) {
+            e.stopPropagation();
+            e.preventDefault();
+            this._contextMenuCanOpen = false
+            let me = this;
+            var dx = e.pageX - this._lastX;
+            var dy = e.pageY - this._lastY;
+            this._lastX = e.pageX;
+            this._lastY = e.pageY;
+            let currScale:number = 1;
+            if (me.props.ContainingDocumentView != undefined) {
+                let pme = me.props.ContainingDocumentView!.props.Document;
+                currScale = pme.GetFieldValue(KeyStore.Scale, NumberField, Number(0)); 
+                if (me.props.ContainingDocumentView!.props.ContainingDocumentView != undefined) {
+                    let pme = me.props.ContainingDocumentView!.props.ContainingDocumentView!.props.Document;
+                    currScale *= pme.GetFieldValue(KeyStore.Scale, NumberField, Number(0));
+                } 
+            } 
+            this.x += dx/currScale;
+            this.y += dy/currScale;
+            DocumentDecorations.Instance.opacity = 0;
         }
-        this._contextMenuCanOpen = false
-        this.x += e.movementX;
-        this.y += e.movementY;
-        DocumentDecorations.Instance.opacity = 0
     }
 
     onDragStart = (e: React.DragEvent<HTMLDivElement>): void => {
@@ -142,8 +161,8 @@ export class DocumentView extends React.Component<IProps> {
     onClick = (e: React.MouseEvent): void => {    }
 
     deleteClicked = (e: React.MouseEvent): void => {
-        if (this.props.parent) {
-            this.props.parent.removeDocument(this.props.dvm.Doc)
+        if (this.props.ContainingCollectionView instanceof CollectionFreeFormView) {
+            this.props.ContainingCollectionView.removeDocument(this.props.Document)
         }
     }
 
@@ -155,7 +174,8 @@ export class DocumentView extends React.Component<IProps> {
             return;
         }
 
-        if (this.props.dvm.IsMainDoc) {
+        var topMost = this.props.ContainingCollectionView == undefined;
+        if (topMost) {
             ContextMenu.Instance.clearItems()
         }
         else {
@@ -169,10 +189,10 @@ export class DocumentView extends React.Component<IProps> {
     }
 
     render() {
-        let doc = this.props.dvm.Doc;
+        let doc = this.props.Document;
         let bindings: any = {
-            dvm: this.props.dvm,
-            isSelected: this.selected
+            Document: this.props.Document,
+            ContainingDocumentView: this
         };
         for (const key of this.layoutKeys) {
             bindings[key.Name + "Key"] = key;
@@ -194,7 +214,7 @@ export class DocumentView extends React.Component<IProps> {
                 onPointerDown={this.onPointerDown}
                 onClick={this.onClick}>
                 <JsxParser
-                    components={{ FieldTextBox, FreeFormCanvas, CollectionFreeFormView }}
+                    components={{ FieldTextBox, CollectionFreeFormView }}
                     bindings={bindings}
                     jsx={this.layout}
                 />
-- 
cgit v1.2.3-70-g09d2

