From 65026fdb919132f6046b45fc3b0490df2e5ad2fe Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 14 Sep 2022 11:12:29 -0400
Subject: changed docking view thumbnails to be imagefields, not docs.  allowed
 docking view to be nested to avoid crashes before when they became nested.  
 fixed dropping docs with dropAction of 'alias' to never remove them.  fixed
 clearing startLink to not deselect when link is already started

---
 src/client/views/collections/CollectionView.tsx | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 1ee77d4ce..0a06b6e00 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -116,6 +116,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         switch (type) {
             default:
             case CollectionViewType.Freeform: return <CollectionFreeFormView key="collview" {...props} />;
+            case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />;
             case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />;
             case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />;
             case CollectionViewType.Tree: return <CollectionTreeView key="collview" {...props} />;
-- 
cgit v1.2.3-70-g09d2


From 6290c9147b4aead8b0253aa958f086752b397a4f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 19 Sep 2022 12:47:17 -0400
Subject: if same youtube video is uploaded, reuse the existing file without
 downloading again.  removed failed uploads from parsed_files.  change
 pinWithView to highlight the pinned view region.  Give pinWithView
 collections an option to just pan or zoom to % of screen size.

---
 package-lock.json                                  | 1729 ++++++++++----------
 package.json                                       |    1 +
 src/client/views/DocComponent.tsx                  |    1 -
 src/client/views/collections/CollectionView.tsx    |    1 +
 src/client/views/collections/TabDocView.tsx        |    4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   39 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |    4 +-
 src/client/views/nodes/DocumentView.tsx            |    1 +
 src/client/views/nodes/PDFBox.tsx                  |    3 +
 src/client/views/nodes/trails/PresBox.tsx          |   73 +-
 src/client/views/pdf/PDFViewer.tsx                 |    8 +-
 src/server/DashUploadUtils.ts                      |  128 +-
 12 files changed, 1046 insertions(+), 946 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/package-lock.json b/package-lock.json
index 5c186f43f..6763aec2c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1138,7 +1138,7 @@
         "@types/bcrypt-nodejs": {
             "version": "0.0.30",
             "resolved": "https://registry.npmjs.org/@types/bcrypt-nodejs/-/bcrypt-nodejs-0.0.30.tgz",
-            "integrity": "sha512-gSWCu7EOXhcM0FYM8tanV0deaX1VM07sgBS8dSUmgnAQ5/3Xn6k+mWF+ZfE+c/OCW14IVnNdTOKcHgvL78oDFQ==",
+            "integrity": "sha1-TN2WtJKTs5MhIuS34pVD415rrlg=",
             "dev": true
         },
         "@types/bezier-js": {
@@ -2243,7 +2243,7 @@
         "@types/strip-bom": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
-            "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==",
+            "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=",
             "dev": true
         },
         "@types/strip-json-comments": {
@@ -2279,7 +2279,7 @@
         "@types/typescript": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/@types/typescript/-/typescript-2.0.0.tgz",
-            "integrity": "sha512-WMEWfMISiJ2QKyk5/dSdgL0ZwP//PZj0jmDU0hMh51FmLq4WIYzjlngsUQZXejQL+QtkXJUOGjb3G3UCvgZuSQ==",
+            "integrity": "sha1-xDNTnJi64oaCswfqp6D9IRW4PCg=",
             "dev": true,
             "requires": {
                 "typescript": "*"
@@ -2594,7 +2594,7 @@
                 "textarea-caret": {
                     "version": "3.0.2",
                     "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.0.2.tgz",
-                    "integrity": "sha1-82DEhpmqGr9xhoCkOjGoUGZcLK8="
+                    "integrity": "sha512-gRzeti2YS4did7UJnPQ47wrjD+vp+CJIe9zbsu0bJ987d8QVLvLNG9757rqiQTIy4hGIeFauTTJt5Xkn51UkXg=="
                 }
             }
         },
@@ -2613,7 +2613,7 @@
         "Base64": {
             "version": "0.2.1",
             "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz",
-            "integrity": "sha512-reGEWshDmTDQDsCec/HduOO9Wyj6yMOupMfhIf3ugN1TDlK2NQW4DDJSqNNtp380SNcvRfXtO8HSCQot0d0SMw=="
+            "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg="
         },
         "D": {
             "version": "1.0.0",
@@ -2656,7 +2656,7 @@
         "acorn-globals": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz",
-            "integrity": "sha512-uWttZCk96+7itPxK8xCzY86PnxKTMrReKDqrHzv42VQY0K30PUO8WY13WMOuI+cOdX4EIdzdvQ8k6jkuGRFMYw==",
+            "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=",
             "requires": {
                 "acorn": "^4.0.4"
             },
@@ -2664,7 +2664,7 @@
                 "acorn": {
                     "version": "4.0.13",
                     "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
-                    "integrity": "sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug=="
+                    "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
                 }
             }
         },
@@ -2693,7 +2693,7 @@
         "after": {
             "version": "0.8.2",
             "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
-            "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
+            "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA=="
         },
         "agent-base": {
             "version": "6.0.2",
@@ -2768,7 +2768,7 @@
         "align-text": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
-            "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==",
+            "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
             "requires": {
                 "kind-of": "^3.0.2",
                 "longest": "^1.0.1",
@@ -2783,7 +2783,7 @@
                 "kind-of": {
                     "version": "3.2.2",
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-                    "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+                    "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
                         "is-buffer": "^1.1.5"
                     }
@@ -2793,12 +2793,12 @@
         "amdefine": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
-            "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg=="
+            "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
         },
         "ansi-align": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz",
-            "integrity": "sha512-TdlOggdA/zURfMYa7ABC66j+oqfMew58KpJMbUlH3bcZP1b+cBHIHDDn5uH9INsxrHBPjsqM0tDB4jPTF/vgJA==",
+            "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
             "requires": {
                 "string-width": "^2.0.0"
             }
@@ -2862,7 +2862,7 @@
         "any-promise": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
-            "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="
+            "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
         },
         "anymatch": {
             "version": "2.0.0",
@@ -2876,7 +2876,7 @@
                 "normalize-path": {
                     "version": "2.1.1",
                     "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
-                    "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==",
+                    "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
                     "requires": {
                         "remove-trailing-separator": "^1.0.1"
                     }
@@ -2971,7 +2971,7 @@
         "arr-diff": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
-            "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA=="
+            "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
         },
         "arr-flatten": {
             "version": "1.1.0",
@@ -2981,7 +2981,7 @@
         "arr-union": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
-            "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q=="
+            "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ="
         },
         "array-back": {
             "version": "2.0.0",
@@ -3072,18 +3072,18 @@
         "array-equal": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
-            "integrity": "sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA==",
+            "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
             "dev": true
         },
         "array-find-index": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
-            "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw=="
+            "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E="
         },
         "array-flatten": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
-            "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+            "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
         },
         "array-includes": {
             "version": "3.1.5",
@@ -3101,7 +3101,7 @@
         "array-union": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
-            "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==",
+            "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
             "dev": true,
             "requires": {
                 "array-uniq": "^1.0.1"
@@ -3110,13 +3110,13 @@
         "array-uniq": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
-            "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==",
+            "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
             "dev": true
         },
         "array-unique": {
             "version": "0.3.2",
             "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
-            "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ=="
+            "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
         },
         "array.prototype.flat": {
             "version": "1.3.0",
@@ -3167,7 +3167,7 @@
         "asap": {
             "version": "2.0.6",
             "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
-            "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
+            "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
         },
         "asn1": {
             "version": "0.2.6",
@@ -3180,7 +3180,7 @@
         "assert-plus": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
-            "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="
+            "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
         },
         "assertion-error": {
             "version": "1.1.0",
@@ -3190,7 +3190,7 @@
         "assign-symbols": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
-            "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw=="
+            "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
         },
         "ast-types-flow": {
             "version": "0.0.7",
@@ -3220,7 +3220,7 @@
         "async-foreach": {
             "version": "0.1.3",
             "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
-            "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA=="
+            "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI="
         },
         "async-limiter": {
             "version": "1.0.1",
@@ -3231,7 +3231,7 @@
         "asynckit": {
             "version": "0.4.0",
             "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-            "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+            "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
         },
         "atob": {
             "version": "2.1.2",
@@ -3281,7 +3281,7 @@
         "aws-sign2": {
             "version": "0.7.0",
             "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
-            "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="
+            "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
         },
         "aws4": {
             "version": "1.11.0",
@@ -3311,12 +3311,12 @@
         "babel": {
             "version": "6.23.0",
             "resolved": "https://registry.npmjs.org/babel/-/babel-6.23.0.tgz",
-            "integrity": "sha512-ZDcCaI8Vlct8PJ3DvmyqUz+5X2Ylz3ZuuItBe/74yXosk2dwyVo/aN7MCJ8HJzhnnJ+6yP4o+lDgG9MBe91DLA=="
+            "integrity": "sha1-0NHn2APpdHZb7qMjLU4VPA77kPQ="
         },
         "babel-code-frame": {
             "version": "6.26.0",
             "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
-            "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==",
+            "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
             "dev": true,
             "requires": {
                 "chalk": "^1.1.3",
@@ -3327,19 +3327,19 @@
                 "ansi-regex": {
                     "version": "2.1.1",
                     "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-                    "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
+                    "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
                     "dev": true
                 },
                 "ansi-styles": {
                     "version": "2.2.1",
                     "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-                    "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
+                    "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
                     "dev": true
                 },
                 "chalk": {
                     "version": "1.1.3",
                     "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-                    "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
+                    "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
                     "dev": true,
                     "requires": {
                         "ansi-styles": "^2.2.1",
@@ -3352,7 +3352,7 @@
                 "js-tokens": {
                     "version": "3.0.2",
                     "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
-                    "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==",
+                    "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
                     "dev": true
                 },
                 "strip-ansi": {
@@ -3436,12 +3436,12 @@
         "babel-plugin-syntax-jsx": {
             "version": "6.18.0",
             "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
-            "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw=="
+            "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY="
         },
         "babel-runtime": {
             "version": "6.26.0",
             "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
-            "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
+            "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
             "requires": {
                 "core-js": "^2.4.0",
                 "regenerator-runtime": "^0.11.0"
@@ -3462,7 +3462,7 @@
         "babel-types": {
             "version": "6.26.0",
             "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
-            "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==",
+            "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
             "requires": {
                 "babel-runtime": "^6.26.0",
                 "esutils": "^2.0.2",
@@ -3485,7 +3485,7 @@
         "backo2": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
-            "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
+            "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA=="
         },
         "bail": {
             "version": "2.0.2",
@@ -3514,7 +3514,7 @@
                 "define-property": {
                     "version": "1.0.0",
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
-                    "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==",
+                    "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
                     "requires": {
                         "is-descriptor": "^1.0.0"
                     }
@@ -3550,12 +3550,12 @@
         "base16": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
-            "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ=="
+            "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA="
         },
         "base64-arraybuffer": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
-            "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
+            "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg=="
         },
         "base64-js": {
             "version": "1.5.1",
@@ -3575,18 +3575,18 @@
         "batch": {
             "version": "0.6.1",
             "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
-            "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
+            "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
             "dev": true
         },
         "bcrypt-nodejs": {
             "version": "0.0.3",
             "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz",
-            "integrity": "sha512-NmTbLm867btBHCBZ222FQXkQKzecB0KG6pTXFa6NeTVZaSnLfCsx7EK2PL3J+kX8xJThUquEBbhimRCKKZX9zA=="
+            "integrity": "sha1-xgkX8m3CNWYVZsaBBhwwPCsohCs="
         },
         "bcrypt-pbkdf": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
-            "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
+            "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
             "requires": {
                 "tweetnacl": "^0.14.3"
             }
@@ -3599,7 +3599,7 @@
         "bezier-curve": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/bezier-curve/-/bezier-curve-1.0.0.tgz",
-            "integrity": "sha512-h6uZJ6qdFfswS1rIRericgouhTeiVi/MnH10OKtCu2IZzXa+ZcjaxRLHY4u/evRsJcxYbbiNkgWQj2Z4UIcpEQ=="
+            "integrity": "sha1-o9+v6rEqlMRicw1QeYxSqEBdc3k="
         },
         "bezier-js": {
             "version": "4.1.1",
@@ -3648,7 +3648,7 @@
         "block-stream": {
             "version": "0.0.9",
             "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
-            "integrity": "sha512-OorbnJVPII4DuUKbjARAe8u8EfqOmkEEaSFIyoQ7OjTHn6kafxWl0wLgoZ2rXaYd7MyLcDaU4TmhfxtwgcccMQ==",
+            "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
             "requires": {
                 "inherits": "~2.0.0"
             }
@@ -3711,7 +3711,7 @@
         "bonjour": {
             "version": "3.5.0",
             "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
-            "integrity": "sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg==",
+            "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
             "dev": true,
             "requires": {
                 "array-flatten": "^2.1.0",
@@ -3733,7 +3733,7 @@
         "boolbase": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
-            "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
+            "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
         },
         "bootstrap": {
             "version": "4.6.1",
@@ -3762,7 +3762,7 @@
                 "camelcase": {
                     "version": "4.1.0",
                     "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
-                    "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw=="
+                    "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
                 }
             }
         },
@@ -3795,7 +3795,7 @@
                 "extend-shallow": {
                     "version": "2.0.1",
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-                    "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+                    "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
                         "is-extendable": "^0.1.0"
                     }
@@ -3805,7 +3805,7 @@
         "browser-assert": {
             "version": "1.2.1",
             "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz",
-            "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ=="
+            "integrity": "sha1-mqpaKox0aFwq4Fv+Ru/WBvBowgA="
         },
         "browser-process-hrtime": {
             "version": "1.0.0",
@@ -3850,12 +3850,12 @@
         "buffer-crc32": {
             "version": "0.2.13",
             "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
-            "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="
+            "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
         },
         "buffer-equal-constant-time": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
-            "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+            "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
         },
         "buffer-from": {
             "version": "1.1.2",
@@ -3871,12 +3871,12 @@
         "buffer-shims": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
-            "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g=="
+            "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E="
         },
         "built-in-math-eval": {
             "version": "0.3.0",
             "resolved": "https://registry.npmjs.org/built-in-math-eval/-/built-in-math-eval-0.3.0.tgz",
-            "integrity": "sha512-5XD5cujru60ooKJ4sGZqoH5v2Xvgw7ezV54gJX/OnPkgDKoH3BnlMEi8xW6hl8xaEjxKHebgrsawroeZnGwIMA==",
+            "integrity": "sha1-JA3CHLOJQ5WIxhxGDrAHZJfvxBw=",
             "requires": {
                 "math-codegen": "^0.3.5"
             }
@@ -4007,7 +4007,7 @@
         "caller-callsite": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
-            "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==",
+            "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
             "requires": {
                 "callsites": "^2.0.0"
             },
@@ -4015,14 +4015,14 @@
                 "callsites": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
-                    "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ=="
+                    "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA="
                 }
             }
         },
         "caller-path": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
-            "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==",
+            "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
             "requires": {
                 "caller-callsite": "^2.0.0"
             }
@@ -4049,7 +4049,7 @@
         "camelcase-keys": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
-            "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==",
+            "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
             "requires": {
                 "camelcase": "^2.0.0",
                 "map-obj": "^1.0.0"
@@ -4058,14 +4058,14 @@
                 "camelcase": {
                     "version": "2.1.1",
                     "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
-                    "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw=="
+                    "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
                 }
             }
         },
         "camelize": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
-            "integrity": "sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg=="
+            "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
         },
         "caniuse-lite": {
             "version": "1.0.30001351",
@@ -4090,7 +4090,7 @@
         "caseless": {
             "version": "0.12.0",
             "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
-            "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
+            "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
         },
         "ccount": {
             "version": "2.0.1",
@@ -4100,7 +4100,7 @@
         "center-align": {
             "version": "0.1.3",
             "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
-            "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==",
+            "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
             "requires": {
                 "align-text": "^0.1.3",
                 "lazy-cache": "^1.0.3"
@@ -4138,7 +4138,7 @@
         "change-emitter": {
             "version": "0.1.6",
             "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
-            "integrity": "sha512-YXzt1cQ4a2jqazhcuSWEOc1K2q8g9H6eWNsyZgi640LDzRWVQ2eDe+Y/kVdftH+vYdPF2rgDb3dLdpxE1jvAxw=="
+            "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
         },
         "character-entities": {
             "version": "2.0.2",
@@ -4148,7 +4148,7 @@
         "character-parser": {
             "version": "2.2.0",
             "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
-            "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==",
+            "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=",
             "requires": {
                 "is-regex": "^1.0.3"
             }
@@ -4162,12 +4162,12 @@
         "check-error": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
-            "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA=="
+            "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII="
         },
         "child_process": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz",
-            "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g=="
+            "integrity": "sha1-sffn/HPSXn/R1FWtyU4UODAYK1o="
         },
         "chokidar": {
             "version": "2.1.8",
@@ -4196,7 +4196,7 @@
         "chrome": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/chrome/-/chrome-0.1.0.tgz",
-            "integrity": "sha512-6KYl20U4Taj6YipylsWr2etUvp9AElJKfGNSBmyGTymYmancnOb041ZNadolEZi2nboLXH7jMSqUmm4kpuTzfg==",
+            "integrity": "sha1-9h2beS/v6MGUxwVt3BAscmqGQyk=",
             "requires": {
                 "exeq": "^2.2.0",
                 "plist": "^1.1.0"
@@ -4216,7 +4216,7 @@
         "clamp": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz",
-            "integrity": "sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA=="
+            "integrity": "sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ="
         },
         "class-transformer": {
             "version": "0.2.3",
@@ -4237,7 +4237,7 @@
                 "define-property": {
                     "version": "0.2.5",
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-                    "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==",
+                    "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
                     "requires": {
                         "is-descriptor": "^0.1.0"
                     }
@@ -4267,7 +4267,7 @@
         "cli-boxes": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
-            "integrity": "sha512-3Fo5wu8Ytle8q9iCzS4D2MWVL2X7JVWRiS1BnXbTFDhS9c/REkM9vd1AmabsoZoY5/dGi5TT9iKL8Kb6DeBRQg=="
+            "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM="
         },
         "cli-cursor": {
             "version": "3.1.0",
@@ -4287,7 +4287,7 @@
         "clipboard": {
             "version": "1.7.1",
             "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz",
-            "integrity": "sha512-smkaRaIQsrnKN1F3wd1/vY9Q+DeR4L8ZCXKeHCFC2j8RZuSBbuImcLdnIO4GTxmzJxQuDGNKkyfpGoPW7Ua5bQ==",
+            "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=",
             "requires": {
                 "good-listener": "^1.2.2",
                 "select": "^1.1.2",
@@ -4312,7 +4312,7 @@
                 "camelcase": {
                     "version": "4.1.0",
                     "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
-                    "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw=="
+                    "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
                 },
                 "yargs-parser": {
                     "version": "7.0.0",
@@ -4362,7 +4362,7 @@
         "clj-fuzzy": {
             "version": "0.3.3",
             "resolved": "https://registry.npmjs.org/clj-fuzzy/-/clj-fuzzy-0.3.3.tgz",
-            "integrity": "sha512-9cyh9A8+OphDZeKIG21MgyDHWDkWxTvagwvFLVjtdi6eToFENF7iDLlKwhHrnBQRSQwprKNhazG053nE/UgwfQ=="
+            "integrity": "sha1-seU0MJHFIC28UlMoY+HEp4RX8D0="
         },
         "clone-deep": {
             "version": "4.0.1",
@@ -4377,7 +4377,7 @@
         "clone-response": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
-            "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==",
+            "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
             "requires": {
                 "mimic-response": "^1.0.0"
             },
@@ -4397,12 +4397,12 @@
         "code-point-at": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
-            "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA=="
+            "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
         },
         "collection-visit": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
-            "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==",
+            "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
             "requires": {
                 "map-visit": "^1.0.0",
                 "object-visit": "^1.0.0"
@@ -4428,7 +4428,7 @@
         "color-name": {
             "version": "1.1.3",
             "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-            "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+            "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
         },
         "color-string": {
             "version": "1.9.1",
@@ -4491,13 +4491,13 @@
         "commondir": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
-            "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
+            "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
             "dev": true
         },
         "component-bind": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
-            "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
+            "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw=="
         },
         "component-emitter": {
             "version": "1.3.0",
@@ -4507,7 +4507,7 @@
         "component-inherit": {
             "version": "0.0.3",
             "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
-            "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
+            "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA=="
         },
         "compress-brotli": {
             "version": "1.3.8",
@@ -4572,7 +4572,7 @@
                 "bytes": {
                     "version": "3.0.0",
                     "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
-                    "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
+                    "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
                     "dev": true
                 },
                 "debug": {
@@ -4587,7 +4587,7 @@
                 "ms": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-                    "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+                    "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
                     "dev": true
                 }
             }
@@ -4595,7 +4595,7 @@
         "concat-map": {
             "version": "0.0.1",
             "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-            "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+            "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
         },
         "concat-stream": {
             "version": "1.6.2",
@@ -4661,7 +4661,7 @@
         "connect-flash": {
             "version": "0.1.1",
             "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz",
-            "integrity": "sha512-2rcfELQt/ZMP+SM/pG8PyhJRaLKp+6Hk2IUBNkEit09X+vwn3QsAL3ZbYtxUn7NVPzbMTSLRDhqe0B/eh30RYA=="
+            "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA="
         },
         "connect-history-api-fallback": {
             "version": "1.6.0",
@@ -4690,12 +4690,12 @@
                 "process-nextick-args": {
                     "version": "1.0.7",
                     "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
-                    "integrity": "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw=="
+                    "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
                 },
                 "readable-stream": {
                     "version": "2.2.7",
                     "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz",
-                    "integrity": "sha512-a6ibcfWFhgihuTw/chl+u3fB5ykBZFmnvpyZHebY0MCQE4vvYcsCLpCeaQ1BkH7HdJYavNSqF0WDLeo4IPHQaQ==",
+                    "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=",
                     "requires": {
                         "buffer-shims": "~1.0.0",
                         "core-util-is": "~1.0.0",
@@ -4719,7 +4719,7 @@
         "console-control-strings": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
-            "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
+            "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
         },
         "constantinople": {
             "version": "3.1.2",
@@ -4803,7 +4803,7 @@
         "cookie-signature": {
             "version": "1.0.6",
             "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
-            "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+            "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
         },
         "cookies": {
             "version": "0.8.0",
@@ -4848,7 +4848,7 @@
         "copy-descriptor": {
             "version": "0.1.1",
             "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
-            "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw=="
+            "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
         },
         "copy-webpack-plugin": {
             "version": "4.6.0",
@@ -4878,7 +4878,7 @@
                 "p-try": {
                     "version": "1.0.0",
                     "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
-                    "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==",
+                    "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
                     "dev": true
                 },
                 "serialize-javascript": {
@@ -4892,7 +4892,7 @@
         "core-js": {
             "version": "1.2.7",
             "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
-            "integrity": "sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA=="
+            "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
         },
         "core-js-pure": {
             "version": "3.22.8",
@@ -4956,7 +4956,7 @@
         "create-error-class": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
-            "integrity": "sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==",
+            "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
             "requires": {
                 "capture-stack-trace": "^1.0.0"
             }
@@ -5021,7 +5021,7 @@
         "cross-spawn": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
-            "integrity": "sha512-eZ+m1WNhSZutOa/uRblAc9Ut5MQfukFrFMtPSm3bZCA888NmMd5AWXWdgRZ80zd+pTk1P2JrGjg9pUPTvl2PWQ==",
+            "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
             "requires": {
                 "lru-cache": "^4.0.1",
                 "which": "^1.2.9"
@@ -5051,7 +5051,7 @@
         "crypto-random-string": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
-            "integrity": "sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg=="
+            "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
         },
         "css-box-model": {
             "version": "1.2.1",
@@ -5064,7 +5064,7 @@
         "css-color-keywords": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
-            "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="
+            "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU="
         },
         "css-in-js-utils": {
             "version": "2.0.1",
@@ -5207,7 +5207,7 @@
         "currently-unhandled": {
             "version": "0.4.1",
             "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
-            "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==",
+            "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
             "requires": {
                 "array-find-index": "^1.0.1"
             }
@@ -5215,12 +5215,12 @@
         "custom-event": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
-            "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU="
+            "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg=="
         },
         "cyclist": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
-            "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==",
+            "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
             "dev": true
         },
         "d": {
@@ -5367,7 +5367,7 @@
         "dashdash": {
             "version": "1.14.1",
             "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
-            "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
+            "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
             "requires": {
                 "assert-plus": "^1.0.0"
             }
@@ -5419,7 +5419,7 @@
         "de-indent": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
-            "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="
+            "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0="
         },
         "debounce": {
             "version": "1.2.1",
@@ -5437,7 +5437,7 @@
         "decamelize": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-            "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="
+            "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
         },
         "decode-named-character-reference": {
             "version": "1.0.2",
@@ -5450,7 +5450,7 @@
         "decode-uri-component": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
-            "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og=="
+            "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
         },
         "decompress-response": {
             "version": "4.2.1",
@@ -5615,7 +5615,7 @@
                 "globby": {
                     "version": "6.1.0",
                     "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
-                    "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==",
+                    "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
                     "dev": true,
                     "requires": {
                         "array-union": "^1.0.1",
@@ -5628,7 +5628,7 @@
                         "pify": {
                             "version": "2.3.0",
                             "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-                            "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+                            "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
                             "dev": true
                         }
                     }
@@ -5653,7 +5653,7 @@
         "delayed-stream": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-            "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
+            "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
         },
         "delegate": {
             "version": "3.2.0",
@@ -5663,7 +5663,7 @@
         "delegates": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-            "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
+            "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
         },
         "denque": {
             "version": "1.5.1",
@@ -5766,7 +5766,7 @@
                 "import-fresh": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
-                    "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==",
+                    "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
                     "requires": {
                         "caller-path": "^2.0.0",
                         "resolve-from": "^3.0.0"
@@ -5801,7 +5801,7 @@
                 "parse-json": {
                     "version": "4.0.0",
                     "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
-                    "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==",
+                    "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
                     "requires": {
                         "error-ex": "^1.3.1",
                         "json-parse-better-errors": "^1.0.1"
@@ -5815,7 +5815,7 @@
                 "resolve-from": {
                     "version": "3.0.0",
                     "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
-                    "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw=="
+                    "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
                 },
                 "string-width": {
                     "version": "4.2.3",
@@ -5887,7 +5887,7 @@
         "deps-regex": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/deps-regex/-/deps-regex-0.1.4.tgz",
-            "integrity": "sha512-3tzwGYogSJi8HoG93R5x9NrdefZQOXgHgGih/7eivloOq6yC6O+yoFxZnkgP661twvfILONfoKRdF9GQOGx2RA=="
+            "integrity": "sha1-UYZnt2kUYKXn4KNBvnbrfOgJAYQ="
         },
         "dequal": {
             "version": "2.0.3",
@@ -5941,7 +5941,7 @@
                 "pify": {
                     "version": "3.0.0",
                     "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
-                    "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
+                    "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
                     "dev": true
                 }
             }
@@ -5949,7 +5949,7 @@
         "dns-equal": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
-            "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==",
+            "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
             "dev": true
         },
         "dns-packet": {
@@ -5965,7 +5965,7 @@
         "dns-txt": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
-            "integrity": "sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ==",
+            "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
             "dev": true,
             "requires": {
                 "buffer-indexof": "^1.0.0"
@@ -5983,7 +5983,7 @@
         "doctypes": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
-            "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ=="
+            "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk="
         },
         "dom-converter": {
             "version": "0.2.0",
@@ -6092,12 +6092,12 @@
         "double-bits": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/double-bits/-/double-bits-1.1.1.tgz",
-            "integrity": "sha512-BCLEIBq0O/DWoA7BsCu/R+RP0ZXiowP8BhtJT3qeuuQEBpnS8LK/Wo6UTJQv6v8mK1fj8n90YziHLwGdM5whSg=="
+            "integrity": "sha1-WKu6RUlNpND6Nrc60RoobJGEscY="
         },
         "duplexer3": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
-            "integrity": "sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA=="
+            "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
         },
         "duplexify": {
             "version": "3.7.1",
@@ -6131,7 +6131,7 @@
         "dynamic-dedupe": {
             "version": "0.3.0",
             "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
-            "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==",
+            "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=",
             "dev": true,
             "requires": {
                 "xtend": "^4.0.0"
@@ -6140,7 +6140,7 @@
         "ecc-jsbn": {
             "version": "0.1.2",
             "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
-            "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
+            "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
             "requires": {
                 "jsbn": "~0.1.0",
                 "safer-buffer": "^2.1.0"
@@ -6157,7 +6157,7 @@
         "ee-first": {
             "version": "1.1.1",
             "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
-            "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+            "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
         },
         "electron-to-chromium": {
             "version": "1.4.149",
@@ -6186,7 +6186,7 @@
         "encodeurl": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
-            "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
+            "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
         },
         "encoding": {
             "version": "0.1.13",
@@ -6261,7 +6261,7 @@
                 "ms": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-                    "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+                    "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
                 },
                 "ws": {
                     "version": "7.4.6",
@@ -6425,7 +6425,7 @@
         "es6-promise": {
             "version": "3.2.1",
             "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
-            "integrity": "sha512-oj4jOSXvWglTsc3wrw86iom3LDPOx1nbipQk+jaG3dy+sMRM6ReSgVr/VlmBuF6lXUrflN9DCcQHeSbAwGUl4g=="
+            "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q="
         },
         "es6-symbol": {
             "version": "3.1.3",
@@ -6445,12 +6445,12 @@
         "escape-html": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
-            "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+            "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
         },
         "escape-string-regexp": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="
+            "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
         },
         "escodegen": {
             "version": "1.14.3",
@@ -7676,7 +7676,7 @@
         "etag": {
             "version": "1.8.1",
             "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
-            "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
+            "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
         },
         "event-target-shim": {
             "version": "5.0.1",
@@ -7704,7 +7704,7 @@
         "execa": {
             "version": "0.7.0",
             "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
-            "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==",
+            "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
             "requires": {
                 "cross-spawn": "^5.0.1",
                 "get-stream": "^3.0.0",
@@ -7718,7 +7718,7 @@
                 "cross-spawn": {
                     "version": "5.1.0",
                     "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
-                    "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==",
+                    "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
                     "requires": {
                         "lru-cache": "^4.0.1",
                         "shebang-command": "^1.2.0",
@@ -7749,7 +7749,7 @@
         "exeq": {
             "version": "2.4.0",
             "resolved": "https://registry.npmjs.org/exeq/-/exeq-2.4.0.tgz",
-            "integrity": "sha512-B648qbDS00nQZv9UQGLT5RbZm/5dNBX10F8oWeXcgpFHSLm1249u95t/3sn2wXdQjLhlF+edAECdshFtSr1K0Q==",
+            "integrity": "sha1-Td8qaEZIxCeteZNJzzO9dTWPiEo=",
             "requires": {
                 "bluebird": "^3.0.3",
                 "native-or-bluebird": "^1.2.0"
@@ -7758,7 +7758,7 @@
         "exif": {
             "version": "0.6.0",
             "resolved": "https://registry.npmjs.org/exif/-/exif-0.6.0.tgz",
-            "integrity": "sha512-gEwM4uanNMfLnDNKclZ7jPEA99E3rpy4ntoS6QW8u6murZjl1o8qRaPdMoC46Syg3d9/QaET0bYKhWlTwJCPgg==",
+            "integrity": "sha1-YKYmaAdlQst+T1cZnUrG830sX0o=",
             "requires": {
                 "debug": "^2.2"
             },
@@ -7774,7 +7774,7 @@
                 "ms": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-                    "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+                    "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
                 }
             }
         },
@@ -7786,7 +7786,7 @@
         "expand-brackets": {
             "version": "2.1.4",
             "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
-            "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==",
+            "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
             "requires": {
                 "debug": "^2.3.3",
                 "define-property": "^0.2.5",
@@ -7808,7 +7808,7 @@
                 "define-property": {
                     "version": "0.2.5",
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-                    "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==",
+                    "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
                     "requires": {
                         "is-descriptor": "^0.1.0"
                     }
@@ -7816,7 +7816,7 @@
                 "extend-shallow": {
                     "version": "2.0.1",
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-                    "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+                    "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
                         "is-extendable": "^0.1.0"
                     }
@@ -7824,7 +7824,7 @@
                 "ms": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-                    "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+                    "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
                 }
             }
         },
@@ -7907,7 +7907,7 @@
         "express-flash": {
             "version": "0.0.2",
             "resolved": "https://registry.npmjs.org/express-flash/-/express-flash-0.0.2.tgz",
-            "integrity": "sha512-QVUR0ZZRCaa8+iPHoUQaQJrQWcQuK/Q+19M7IUIdIEtvwhrA/ifHT7y1CVJI41YfGiOQnbGtn3uvd2vOdgu58A==",
+            "integrity": "sha1-I9GovPP5DXB5KOSJ+Whp7K0KzaI=",
             "requires": {
                 "connect-flash": "0.1.x"
             }
@@ -7964,7 +7964,7 @@
         "expressjs": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/expressjs/-/expressjs-1.0.1.tgz",
-            "integrity": "sha512-eFnQ5bMJxTZ29XwRJPV8ee/OURBBMS6Fm+b5rvMMEyz6u2IxPEh2SRzMZt9WvgnV+SMLmnzkALE1DnGG1HxJCw=="
+            "integrity": "sha1-IgMoRpoY31rWFeK3oM6ZXxf7ru8="
         },
         "ext": {
             "version": "1.6.0",
@@ -7991,7 +7991,7 @@
         "extend-shallow": {
             "version": "3.0.2",
             "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
-            "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==",
+            "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
             "requires": {
                 "assign-symbols": "^1.0.0",
                 "is-extendable": "^1.0.1"
@@ -8047,7 +8047,7 @@
                 "define-property": {
                     "version": "1.0.0",
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
-                    "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==",
+                    "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
                     "requires": {
                         "is-descriptor": "^1.0.0"
                     }
@@ -8055,7 +8055,7 @@
                 "extend-shallow": {
                     "version": "2.0.1",
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-                    "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+                    "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
                         "is-extendable": "^0.1.0"
                     }
@@ -8125,7 +8125,7 @@
         "extsprintf": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
-            "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="
+            "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
         },
         "fast-deep-equal": {
             "version": "3.1.3",
@@ -8146,7 +8146,7 @@
         "fast-levenshtein": {
             "version": "2.0.6",
             "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-            "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+            "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
             "dev": true
         },
         "fast-text-encoding": {
@@ -8185,7 +8185,7 @@
         "fd-slicer": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
-            "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+            "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
             "requires": {
                 "pend": "~1.2.0"
             }
@@ -8193,7 +8193,7 @@
         "ffmpeg": {
             "version": "0.0.4",
             "resolved": "https://registry.npmjs.org/ffmpeg/-/ffmpeg-0.0.4.tgz",
-            "integrity": "sha512-3TgWUJJlZGQn+crJFyhsO/oNeRRnGTy6GhgS98oUCIfZrOW5haPPV7DUfOm3xJcHr5q3TJpjk2GudPutrNisRA==",
+            "integrity": "sha1-HEYN+OfaUSf2LO70v6BsWciWMMs=",
             "requires": {
                 "when": ">= 0.0.1"
             }
@@ -8240,7 +8240,7 @@
         "fill-range": {
             "version": "4.0.0",
             "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
-            "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==",
+            "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
             "requires": {
                 "extend-shallow": "^2.0.1",
                 "is-number": "^3.0.0",
@@ -8251,7 +8251,7 @@
                 "extend-shallow": {
                     "version": "2.0.1",
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-                    "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+                    "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
                         "is-extendable": "^0.1.0"
                     }
@@ -8261,7 +8261,7 @@
         "filter-obj": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
-            "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
+            "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs="
         },
         "finalhandler": {
             "version": "1.2.0",
@@ -8295,7 +8295,7 @@
         "find": {
             "version": "0.1.7",
             "resolved": "https://registry.npmjs.org/find/-/find-0.1.7.tgz",
-            "integrity": "sha512-jPrupTOe/pO//3a9Ty2o4NqQCp0L46UG+swUnfFtdmtQVN8pEltKpAqR7Nuf6vWn0GBXx5w+R1MyZzqwjEIqdA==",
+            "integrity": "sha1-yGyHrxqxjyIrvjjeyGy8dg0Wpvs=",
             "requires": {
                 "traverse-chain": "~0.1.0"
             }
@@ -8303,7 +8303,7 @@
         "find-cache-dir": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz",
-            "integrity": "sha512-46TFiBOzX7xq/PcSWfFwkyjpemdRnMe31UQF+os0y+1W3k95f6R4SEt02Hj4p3X0Mir9gfrkmOtshFidS0VPUg==",
+            "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=",
             "dev": true,
             "requires": {
                 "commondir": "^1.0.1",
@@ -8444,7 +8444,7 @@
         "fluent-ffmpeg": {
             "version": "2.1.2",
             "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz",
-            "integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==",
+            "integrity": "sha1-yVLeIkD4EuvaCqgAbXd27irPfXQ=",
             "requires": {
                 "async": ">=0.2.9",
                 "which": "^1.1.1"
@@ -8511,7 +8511,7 @@
         "for-each-property": {
             "version": "0.0.4",
             "resolved": "https://registry.npmjs.org/for-each-property/-/for-each-property-0.0.4.tgz",
-            "integrity": "sha512-xYs28PM0CKXETFzuGC6ZooH0voZlsSDZwidJcy92flQJi3PK7i3gZx23xHXCPOaD4zmet3bDo+wS7E7SujrlCw==",
+            "integrity": "sha1-z6hXrsFCLh0Sb/CHhPz2Jim8g/Y=",
             "requires": {
                 "get-prototype-chain": "^1.0.1"
             }
@@ -8519,7 +8519,7 @@
         "for-each-property-deep": {
             "version": "0.0.3",
             "resolved": "https://registry.npmjs.org/for-each-property-deep/-/for-each-property-deep-0.0.3.tgz",
-            "integrity": "sha512-qzP8QkODWVVRPpWiBZacSbBl67cTTWoBfxMG0wE46AsS1yl7qv05sGN+dHvD4s4tnvl/goe6Sp4qBI+rlVBgNg==",
+            "integrity": "sha1-MTCaSvw4qcygbxsiP1PWSm0IP60=",
             "requires": {
                 "for-each-property": "0.0.4"
             }
@@ -8527,12 +8527,12 @@
         "for-in": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
-            "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ=="
+            "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
         },
         "forever-agent": {
             "version": "0.6.1",
             "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
-            "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="
+            "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
         },
         "fork-ts-checker-webpack-plugin": {
             "version": "1.6.0",
@@ -8586,7 +8586,7 @@
         "fragment-cache": {
             "version": "0.2.1",
             "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
-            "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==",
+            "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
             "requires": {
                 "map-cache": "^0.2.2"
             }
@@ -8594,12 +8594,12 @@
         "fresh": {
             "version": "0.5.2",
             "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
-            "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
+            "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
         },
         "from2": {
             "version": "2.3.0",
             "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
-            "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==",
+            "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
             "dev": true,
             "requires": {
                 "inherits": "^2.0.1",
@@ -8631,7 +8631,7 @@
         "fs-extra": {
             "version": "0.26.7",
             "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz",
-            "integrity": "sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==",
+            "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=",
             "requires": {
                 "graceful-fs": "^4.1.2",
                 "jsonfile": "^2.1.0",
@@ -8666,7 +8666,7 @@
         "fs-write-stream-atomic": {
             "version": "1.0.10",
             "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
-            "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==",
+            "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
             "dev": true,
             "requires": {
                 "graceful-fs": "^4.1.2",
@@ -8695,7 +8695,7 @@
         "fs.realpath": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-            "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+            "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
         },
         "fsevents": {
             "version": "1.2.13",
@@ -8876,7 +8876,7 @@
         "get-func-name": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
-            "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig=="
+            "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE="
         },
         "get-intrinsic": {
             "version": "1.1.2",
@@ -8896,12 +8896,12 @@
         "get-prototype-chain": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/get-prototype-chain/-/get-prototype-chain-1.0.1.tgz",
-            "integrity": "sha512-2m7WZ0jveIg/dAbCbpUxEToaJ8Dmti5EkgDP8YM3UpHUT6SAORjE2odP8XQGNVGXMHi8q8cCCoy3HTByTaTVTw=="
+            "integrity": "sha1-oXGhFeoeSQbG7ThDofABwYUQQW8="
         },
         "get-stdin": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz",
-            "integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA=="
+            "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g="
         },
         "get-stream": {
             "version": "6.0.1",
@@ -8920,12 +8920,12 @@
         "get-value": {
             "version": "2.0.6",
             "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
-            "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA=="
+            "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
         },
         "getpass": {
             "version": "0.1.7",
             "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
-            "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
+            "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
             "requires": {
                 "assert-plus": "^1.0.0"
             }
@@ -8933,7 +8933,7 @@
         "github-from-package": {
             "version": "0.0.0",
             "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
-            "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
+            "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
         },
         "glob": {
             "version": "7.2.3",
@@ -8951,7 +8951,7 @@
         "glob-parent": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
-            "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==",
+            "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
             "requires": {
                 "is-glob": "^3.1.0",
                 "path-dirname": "^1.0.0"
@@ -8960,7 +8960,7 @@
                 "is-glob": {
                     "version": "3.1.0",
                     "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
-                    "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==",
+                    "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
                     "requires": {
                         "is-extglob": "^2.1.0"
                     }
@@ -8976,7 +8976,7 @@
         "global-dirs": {
             "version": "0.1.1",
             "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
-            "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==",
+            "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
             "requires": {
                 "ini": "^1.3.4"
             }
@@ -8989,7 +8989,7 @@
         "globby": {
             "version": "7.1.1",
             "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz",
-            "integrity": "sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g==",
+            "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=",
             "dev": true,
             "requires": {
                 "array-union": "^1.0.1",
@@ -9003,7 +9003,7 @@
                 "pify": {
                     "version": "3.0.0",
                     "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
-                    "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
+                    "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
                     "dev": true
                 }
             }
@@ -9044,7 +9044,7 @@
         "golden-layout": {
             "version": "1.5.9",
             "resolved": "https://registry.npmjs.org/golden-layout/-/golden-layout-1.5.9.tgz",
-            "integrity": "sha512-iBXDQCXOTgUEQJo96zPbjDoy5bRIk9XW5l+q+pDgLnIyReqaa1aiQctNud4epsskyLt952BG521dew5Z1liSxA==",
+            "integrity": "sha1-o5vB9qZ+b4hreXwBbdkk6UJrp38=",
             "requires": {
                 "jquery": "*"
             }
@@ -9052,7 +9052,7 @@
         "good-listener": {
             "version": "1.2.2",
             "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
-            "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
+            "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
             "requires": {
                 "delegate": "^3.1.2"
             }
@@ -9219,7 +9219,7 @@
         "har-schema": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
-            "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q=="
+            "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
         },
         "har-validator": {
             "version": "5.1.5",
@@ -9241,7 +9241,7 @@
         "has-ansi": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
-            "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
+            "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
             "requires": {
                 "ansi-regex": "^2.0.0"
             },
@@ -9249,7 +9249,7 @@
                 "ansi-regex": {
                     "version": "2.1.1",
                     "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-                    "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="
+                    "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
                 }
             }
         },
@@ -9269,19 +9269,19 @@
                 "isarray": {
                     "version": "2.0.1",
                     "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
-                    "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
+                    "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
                 }
             }
         },
         "has-cors": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
-            "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
+            "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA=="
         },
         "has-flag": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-            "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
+            "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
         },
         "has-property-descriptors": {
             "version": "1.0.0",
@@ -9307,12 +9307,12 @@
         "has-unicode": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
-            "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
+            "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
         },
         "has-value": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
-            "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==",
+            "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
             "requires": {
                 "get-value": "^2.0.6",
                 "has-values": "^1.0.0",
@@ -9322,7 +9322,7 @@
         "has-values": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
-            "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==",
+            "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
             "requires": {
                 "is-number": "^3.0.0",
                 "kind-of": "^4.0.0"
@@ -9336,7 +9336,7 @@
                 "kind-of": {
                     "version": "4.0.0",
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
-                    "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==",
+                    "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
                     "requires": {
                         "is-buffer": "^1.1.5"
                     }
@@ -9461,7 +9461,7 @@
         "hpack.js": {
             "version": "2.1.6",
             "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
-            "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==",
+            "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
             "dev": true,
             "requires": {
                 "inherits": "^2.0.1",
@@ -9490,7 +9490,7 @@
         "html": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/html/-/html-1.0.0.tgz",
-            "integrity": "sha512-lw/7YsdKiP3kk5PnR1INY17iJuzdAtJewxr14ozKJWbbR97znovZ0mh+WEMZ8rjc3lgTK+ID/htTjuyGKB52Kw==",
+            "integrity": "sha1-pUT6nqVJK/s6LMqCEKEL57WvH2E=",
             "requires": {
                 "concat-stream": "^1.4.7"
             }
@@ -9573,7 +9573,7 @@
         "http-browserify": {
             "version": "1.7.0",
             "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz",
-            "integrity": "sha512-Irf/LJXmE3cBzU1eaR4+NEX6bmVLqt1wkmDiA7kBwH7zmb0D8kBAXsDmQ88hhj/qv9iEZKlyGx/hrMcFi8sOHw==",
+            "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=",
             "requires": {
                 "Base64": "~0.2.0",
                 "inherits": "~2.0.1"
@@ -9587,7 +9587,7 @@
         "http-deceiver": {
             "version": "1.2.7",
             "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
-            "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==",
+            "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
             "dev": true
         },
         "http-errors": {
@@ -9634,7 +9634,7 @@
         "http-signature": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
-            "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
+            "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
             "requires": {
                 "assert-plus": "^1.0.0",
                 "jsprim": "^1.2.2",
@@ -9653,12 +9653,12 @@
         "https": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
-            "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg=="
+            "integrity": "sha1-PDfHrhqO65ZpBKKtHpdaGUt+06Q="
         },
         "https-browserify": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
-            "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg=="
+            "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
         },
         "https-proxy-agent": {
             "version": "5.0.1",
@@ -9710,7 +9710,7 @@
         "icss-replace-symbols": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
-            "integrity": "sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==",
+            "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
             "dev": true
         },
         "icss-utils": {
@@ -9730,7 +9730,7 @@
         "iferr": {
             "version": "0.1.5",
             "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
-            "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==",
+            "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
             "dev": true
         },
         "ignore": {
@@ -9742,7 +9742,7 @@
         "ignore-by-default": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
-            "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
+            "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk="
         },
         "iink-js": {
             "version": "1.5.4",
@@ -9786,7 +9786,7 @@
         "image-size-stream": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/image-size-stream/-/image-size-stream-1.1.0.tgz",
-            "integrity": "sha512-N505B5FSy2Xf5l/Haef+99TwfJqTu40hnU560+rC0Cm6cxtwVz2yRFh9WpOk1YEjfv3dI0PgVYAH0hmXQmjDcw==",
+            "integrity": "sha1-Ivou2mbG31AQh0bacUkmSy0l+Gs=",
             "requires": {
                 "image-size": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1",
                 "readable-stream": "^1.0.33",
@@ -9800,12 +9800,12 @@
                 "isarray": {
                     "version": "0.0.1",
                     "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-                    "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
+                    "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
                 },
                 "readable-stream": {
                     "version": "1.1.14",
                     "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
-                    "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
+                    "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
                     "requires": {
                         "core-util-is": "~1.0.0",
                         "inherits": "~2.0.1",
@@ -9823,7 +9823,7 @@
         "immediate": {
             "version": "3.0.6",
             "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
-            "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+            "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
         },
         "import-fresh": {
             "version": "3.3.0",
@@ -9837,7 +9837,7 @@
         "import-lazy": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
-            "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A=="
+            "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM="
         },
         "import-local": {
             "version": "3.1.0",
@@ -9851,7 +9851,7 @@
         "imurmurhash": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
-            "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="
+            "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
         },
         "in-publish": {
             "version": "2.0.1",
@@ -9861,7 +9861,7 @@
         "indent-string": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
-            "integrity": "sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==",
+            "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
             "requires": {
                 "repeating": "^2.0.0"
             }
@@ -9869,12 +9869,12 @@
         "indexof": {
             "version": "0.0.1",
             "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
-            "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
+            "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg=="
         },
         "inflight": {
             "version": "1.0.6",
             "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-            "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+            "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
             "requires": {
                 "once": "^1.3.0",
                 "wrappy": "1"
@@ -9891,7 +9891,7 @@
                 "camelcase": {
                     "version": "4.1.0",
                     "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
-                    "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw=="
+                    "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
                 }
             }
         },
@@ -9913,7 +9913,7 @@
         "inline-style-prefixer": {
             "version": "3.0.8",
             "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz",
-            "integrity": "sha512-ne8XIyyqkRaNJ1JfL1NYzNdCNxq+MCBQhC8NgOQlzNm2vv3XxlP0VSLQUbSRCF6KPEoveCVEpayHoHzcMyZsMQ==",
+            "integrity": "sha1-hVG45bTVcyROZqNLBPfTIHaitTQ=",
             "requires": {
                 "bowser": "^1.7.3",
                 "css-in-js-utils": "^2.0.0"
@@ -10032,7 +10032,7 @@
         "inspect-function": {
             "version": "0.2.2",
             "resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz",
-            "integrity": "sha512-becs5gzcHwPrlHawscYkyQ/ShiOiosrXPhA5RVZ3qyWH4aWdD52RnMfXq/dwQXciHwiieD8aIPwdIWYv6eL+sQ==",
+            "integrity": "sha1-hdoMUli8TDMK4yg7Z0fgdZ2QpjU=",
             "requires": {
                 "split-skip": "0.0.1",
                 "unpack-string": "0.0.2"
@@ -10041,7 +10041,7 @@
                 "split-skip": {
                     "version": "0.0.1",
                     "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.1.tgz",
-                    "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ=="
+                    "integrity": "sha1-gK2ONumOV2RUzDtmfB3SXYZejwA="
                 }
             }
         },
@@ -10064,7 +10064,7 @@
                 "magicli": {
                     "version": "0.0.5",
                     "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz",
-                    "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==",
+                    "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=",
                     "requires": {
                         "commander": "^2.9.0",
                         "get-stdin": "^5.0.1",
@@ -10115,7 +10115,7 @@
                         "inspect-function": {
                             "version": "0.2.2",
                             "resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz",
-                            "integrity": "sha512-becs5gzcHwPrlHawscYkyQ/ShiOiosrXPhA5RVZ3qyWH4aWdD52RnMfXq/dwQXciHwiieD8aIPwdIWYv6eL+sQ==",
+                            "integrity": "sha1-hdoMUli8TDMK4yg7Z0fgdZ2QpjU=",
                             "requires": {
                                 "split-skip": "0.0.1",
                                 "unpack-string": "0.0.2"
@@ -10124,14 +10124,14 @@
                                 "split-skip": {
                                     "version": "0.0.1",
                                     "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.1.tgz",
-                                    "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ=="
+                                    "integrity": "sha1-gK2ONumOV2RUzDtmfB3SXYZejwA="
                                 }
                             }
                         },
                         "magicli": {
                             "version": "0.0.5",
                             "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz",
-                            "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==",
+                            "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=",
                             "requires": {
                                 "commander": "^2.9.0",
                                 "get-stdin": "^5.0.1",
@@ -10142,14 +10142,14 @@
                         "split-skip": {
                             "version": "0.0.2",
                             "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.2.tgz",
-                            "integrity": "sha512-weHOi8BolsDnGIwhhWHbA+wKSuSpvWwjRrdj8SdbIIis2vSwOE37CQP8x3EleuzxanUr3AK8BdUy4MkiOULPZg=="
+                            "integrity": "sha1-2J2Iu9L3Pka1FYqjcKVhIk6A1GE="
                         }
                     }
                 },
                 "split-skip": {
                     "version": "0.0.1",
                     "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.1.tgz",
-                    "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ=="
+                    "integrity": "sha1-gK2ONumOV2RUzDtmfB3SXYZejwA="
                 }
             }
         },
@@ -10220,7 +10220,7 @@
         "ip-regex": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
-            "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==",
+            "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
             "dev": true
         },
         "ipaddr.js": {
@@ -10237,7 +10237,7 @@
         "is-accessor-descriptor": {
             "version": "0.1.6",
             "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
-            "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==",
+            "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
             "requires": {
                 "kind-of": "^3.0.2"
             },
@@ -10250,7 +10250,7 @@
                 "kind-of": {
                     "version": "3.2.2",
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-                    "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+                    "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
                         "is-buffer": "^1.1.5"
                     }
@@ -10269,7 +10269,7 @@
         "is-arrayish": {
             "version": "0.2.1",
             "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
-            "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
+            "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
         },
         "is-bigint": {
             "version": "1.0.4",
@@ -10282,7 +10282,7 @@
         "is-binary-path": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
-            "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==",
+            "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
             "requires": {
                 "binary-extensions": "^1.0.0"
             }
@@ -10325,7 +10325,7 @@
         "is-data-descriptor": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
-            "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==",
+            "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
             "requires": {
                 "kind-of": "^3.0.2"
             },
@@ -10338,7 +10338,7 @@
                 "kind-of": {
                     "version": "3.2.2",
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-                    "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+                    "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
                         "is-buffer": "^1.1.5"
                     }
@@ -10373,12 +10373,12 @@
         "is-directory": {
             "version": "0.3.1",
             "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
-            "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw=="
+            "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE="
         },
         "is-expression": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz",
-            "integrity": "sha512-vyMeQMq+AiH5uUnoBfMTwf18tO3bM6k1QXBE9D6ueAAquEfCZe3AJPtud9g6qS0+4X8xA7ndpZiDyeb2l2qOBw==",
+            "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=",
             "requires": {
                 "acorn": "~4.0.2",
                 "object-assign": "^4.0.1"
@@ -10387,7 +10387,7 @@
                 "acorn": {
                     "version": "4.0.13",
                     "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
-                    "integrity": "sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug=="
+                    "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
                 }
             }
         },
@@ -10399,7 +10399,7 @@
         "is-extglob": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-            "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
+            "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
         },
         "is-finite": {
             "version": "1.1.0",
@@ -10409,7 +10409,7 @@
         "is-fullwidth-code-point": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-            "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w=="
+            "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
         },
         "is-generator-function": {
             "version": "1.0.10",
@@ -10430,12 +10430,12 @@
         "is-in-browser": {
             "version": "1.1.3",
             "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
-            "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g=="
+            "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
         },
         "is-installed-globally": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
-            "integrity": "sha512-ERNhMg+i/XgDwPIPF3u24qpajVreaiSuvpb1Uu0jugw7KKcxGyCX8cgp8P5fwTmAuXku6beDHHECdKArjlg7tw==",
+            "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
             "requires": {
                 "global-dirs": "^0.1.0",
                 "is-path-inside": "^1.0.0"
@@ -10449,12 +10449,12 @@
         "is-npm": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
-            "integrity": "sha512-9r39FIr3d+KD9SbX0sfMsHzb5PP3uimOiwr3YupUaUFG4W0l1U57Rx3utpttV7qz5U3jmrO5auUa04LU9pyHsg=="
+            "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ="
         },
         "is-number": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
-            "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==",
+            "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
             "requires": {
                 "kind-of": "^3.0.2"
             },
@@ -10467,7 +10467,7 @@
                 "kind-of": {
                     "version": "3.2.2",
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-                    "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+                    "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
                         "is-buffer": "^1.1.5"
                     }
@@ -10485,7 +10485,7 @@
         "is-obj": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
-            "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg=="
+            "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
         },
         "is-path-cwd": {
             "version": "2.2.0",
@@ -10516,7 +10516,7 @@
         "is-path-inside": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
-            "integrity": "sha512-qhsCR/Esx4U4hg/9I19OVUAJkGWtjRYHMRgUMZE2TDdj+Ag+kttZanLupfddNyglzz50cUlmWzUaI37GDfNx/g==",
+            "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
             "requires": {
                 "path-is-inside": "^1.0.1"
             }
@@ -10542,7 +10542,7 @@
         "is-redirect": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
-            "integrity": "sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw=="
+            "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ="
         },
         "is-regex": {
             "version": "1.1.4",
@@ -10577,7 +10577,7 @@
         "is-stream": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
-            "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ=="
+            "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
         },
         "is-string": {
             "version": "1.0.7",
@@ -10610,7 +10610,7 @@
         "is-typedarray": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-            "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
+            "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
         },
         "is-url": {
             "version": "1.2.4",
@@ -10620,7 +10620,7 @@
         "is-utf8": {
             "version": "0.2.1",
             "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
-            "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q=="
+            "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
         },
         "is-weakref": {
             "version": "1.0.2",
@@ -10643,28 +10643,28 @@
         "is-wsl": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
-            "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==",
+            "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
             "dev": true
         },
         "isarray": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-            "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
+            "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
         },
         "isexe": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-            "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+            "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
         },
         "isobject": {
             "version": "3.0.1",
             "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
-            "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
+            "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
         },
         "isomorphic-fetch": {
             "version": "2.2.1",
             "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
-            "integrity": "sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==",
+            "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
             "requires": {
                 "node-fetch": "^1.0.1",
                 "whatwg-fetch": ">=0.10.0"
@@ -10673,7 +10673,7 @@
         "isstream": {
             "version": "0.1.2",
             "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
-            "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="
+            "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
         },
         "its-set": {
             "version": "1.2.3",
@@ -10730,7 +10730,7 @@
         "js-stringify": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
-            "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g=="
+            "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds="
         },
         "js-tokens": {
             "version": "4.0.0",
@@ -10749,7 +10749,7 @@
         "jsbn": {
             "version": "0.1.1",
             "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
-            "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
+            "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
         },
         "jsdom": {
             "version": "15.2.1",
@@ -10869,7 +10869,7 @@
         "json-css": {
             "version": "1.5.6",
             "resolved": "https://registry.npmjs.org/json-css/-/json-css-1.5.6.tgz",
-            "integrity": "sha512-B/0T0OxZH9tSb93tXV6VOYtXqrPz/Vgz2QrCT/4NXen8HGElYkYr9V+8IrSVTMj/ftxa8cG1kcu7f3iAMlaFlQ=="
+            "integrity": "sha1-65ZPg0ouTqobwvaY/12wB6JsfAA="
         },
         "json-parse-better-errors": {
             "version": "1.0.2",
@@ -10900,7 +10900,7 @@
         "json-stringify-safe": {
             "version": "5.0.1",
             "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
-            "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
+            "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
         },
         "json5": {
             "version": "1.0.1",
@@ -10922,7 +10922,7 @@
         "jsonfile": {
             "version": "2.4.0",
             "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
-            "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==",
+            "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
             "requires": {
                 "graceful-fs": "^4.1.6"
             }
@@ -11031,7 +11031,7 @@
         "jstransformer": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
-            "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==",
+            "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=",
             "requires": {
                 "is-promise": "^2.0.0",
                 "promise": "^7.0.1"
@@ -11153,7 +11153,7 @@
         "klaw": {
             "version": "1.3.1",
             "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
-            "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==",
+            "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
             "requires": {
                 "graceful-fs": "^4.1.9"
             }
@@ -11181,7 +11181,7 @@
         "latest-version": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
-            "integrity": "sha512-Be1YRHWWlZaSsrz2U+VInk+tO0EwLIyV+23RhWLINJYwg/UIikxjlj3MhH37/6/EDCAusjajvMkMMUXRaMWl/w==",
+            "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=",
             "requires": {
                 "package-json": "^4.0.0"
             }
@@ -11189,7 +11189,7 @@
         "lazy-cache": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
-            "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ=="
+            "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4="
         },
         "lazystream": {
             "version": "1.0.1",
@@ -11218,7 +11218,7 @@
         "levn": {
             "version": "0.3.0",
             "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
-            "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
+            "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
             "dev": true,
             "requires": {
                 "prelude-ls": "~1.1.2",
@@ -11241,7 +11241,7 @@
         "load-json-file": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
-            "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==",
+            "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
             "requires": {
                 "graceful-fs": "^4.1.2",
                 "parse-json": "^2.2.0",
@@ -11253,7 +11253,7 @@
                 "parse-json": {
                     "version": "2.2.0",
                     "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
-                    "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==",
+                    "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
                     "requires": {
                         "error-ex": "^1.2.0"
                     }
@@ -11298,22 +11298,22 @@
         "lodash._getnative": {
             "version": "3.9.1",
             "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
-            "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA=="
+            "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U="
         },
         "lodash.chunk": {
             "version": "4.2.0",
             "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz",
-            "integrity": "sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w=="
+            "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw="
         },
         "lodash.curry": {
             "version": "4.1.1",
             "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
-            "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA=="
+            "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA="
         },
         "lodash.debounce": {
             "version": "3.1.1",
             "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-3.1.1.tgz",
-            "integrity": "sha512-lcmJwMpdPAtChA4hfiwxTtgFeNAaow701wWUgVUqeD0XJF7vMXIN+bu/2FJSGxT0NUbZy9g9VFrlOFfPjl+0Ew==",
+            "integrity": "sha1-gSIRw3ipTMKdWqTjNGzwv846ffU=",
             "requires": {
                 "lodash._getnative": "^3.0.0"
             }
@@ -11321,42 +11321,42 @@
         "lodash.defaults": {
             "version": "4.2.0",
             "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
-            "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
+            "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
         },
         "lodash.difference": {
             "version": "4.5.0",
             "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
-            "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="
+            "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw="
         },
         "lodash.flatten": {
             "version": "4.4.0",
             "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
-            "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="
+            "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
         },
         "lodash.flow": {
             "version": "3.5.0",
             "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz",
-            "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw=="
+            "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o="
         },
         "lodash.get": {
             "version": "4.4.2",
             "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
-            "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="
+            "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
         },
         "lodash.isequal": {
             "version": "4.5.0",
             "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
-            "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
+            "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
         },
         "lodash.isplainobject": {
             "version": "4.0.6",
             "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
-            "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+            "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
         },
         "lodash.memoize": {
             "version": "4.1.2",
             "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
-            "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="
+            "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
         },
         "lodash.merge": {
             "version": "4.6.2",
@@ -11366,23 +11366,23 @@
         "lodash.padend": {
             "version": "4.6.1",
             "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz",
-            "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw=="
+            "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4="
         },
         "lodash.sortby": {
             "version": "4.7.0",
             "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
-            "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==",
+            "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
             "dev": true
         },
         "lodash.throttle": {
             "version": "4.1.1",
             "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
-            "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
+            "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
         },
         "lodash.union": {
             "version": "4.6.0",
             "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
-            "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="
+            "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg="
         },
         "log-symbols": {
             "version": "2.2.0",
@@ -11410,7 +11410,7 @@
         "longest": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
-            "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg=="
+            "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
         },
         "longest-streak": {
             "version": "3.0.1",
@@ -11428,7 +11428,7 @@
         "loud-rejection": {
             "version": "1.6.0",
             "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
-            "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==",
+            "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
             "requires": {
                 "currently-unhandled": "^0.4.1",
                 "signal-exit": "^3.0.0"
@@ -11477,7 +11477,7 @@
                 "find-up": {
                     "version": "2.1.0",
                     "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
-                    "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==",
+                    "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
                     "requires": {
                         "locate-path": "^2.0.0"
                     }
@@ -11485,7 +11485,7 @@
                 "locate-path": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
-                    "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==",
+                    "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
                     "requires": {
                         "p-locate": "^2.0.0",
                         "path-exists": "^3.0.0"
@@ -11502,7 +11502,7 @@
                 "p-locate": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
-                    "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==",
+                    "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
                     "requires": {
                         "p-limit": "^1.1.0"
                     }
@@ -11510,7 +11510,7 @@
                 "p-try": {
                     "version": "1.0.0",
                     "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
-                    "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww=="
+                    "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M="
                 }
             }
         },
@@ -11538,17 +11538,17 @@
         "map-cache": {
             "version": "0.2.2",
             "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
-            "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg=="
+            "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8="
         },
         "map-obj": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
-            "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg=="
+            "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
         },
         "map-visit": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
-            "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==",
+            "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
             "requires": {
                 "object-visit": "^1.0.0"
             }
@@ -11598,7 +11598,7 @@
         "math-codegen": {
             "version": "0.3.5",
             "resolved": "https://registry.npmjs.org/math-codegen/-/math-codegen-0.3.5.tgz",
-            "integrity": "sha512-SsFYMv33FxMKYxI1PBiaZT+8AeDITK+k/PKhbHNlOPHIz5FIPF4wy78yWqanN6luXdsXENUZgCIC6xH6bfUq1g==",
+            "integrity": "sha1-R5nuRnfe0Ud2bQA8ykt4ee3UDMo=",
             "requires": {
                 "extend": "^3.0.0",
                 "mr-parser": "^0.2.1"
@@ -11607,7 +11607,7 @@
         "mathquill": {
             "version": "0.10.1-a",
             "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz",
-            "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=",
+            "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==",
             "requires": {
                 "jquery": "^1.12.3"
             },
@@ -11615,14 +11615,19 @@
                 "jquery": {
                     "version": "1.12.4",
                     "resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz",
-                    "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw="
+                    "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg=="
                 }
             }
         },
         "max-safe-integer": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/max-safe-integer/-/max-safe-integer-1.0.1.tgz",
-            "integrity": "sha512-CHZ/Nopqh46UtA0YvLclxj9F95qExLmTnMS5fnYlXvX4zj1RUOC3cPaGEOMhdPE8SSudSQ6wkQUJLA5E/WeL4A=="
+            "integrity": "sha1-84BgvixWPYwC5tSK85Ei/YO29BA="
+        },
+        "md5-file": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz",
+            "integrity": "sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw=="
         },
         "mdast-util-definitions": {
             "version": "5.1.1",
@@ -11778,7 +11783,7 @@
         "media-typer": {
             "version": "0.3.0",
             "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
-            "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
+            "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
         },
         "memfs": {
             "version": "3.4.4",
@@ -11829,12 +11834,12 @@
         "memorystream": {
             "version": "0.3.1",
             "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
-            "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="
+            "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI="
         },
         "meow": {
             "version": "3.7.0",
             "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
-            "integrity": "sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==",
+            "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
             "requires": {
                 "camelcase-keys": "^2.0.0",
                 "decamelize": "^1.1.2",
@@ -11859,7 +11864,7 @@
         "merge-descriptors": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
-            "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+            "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
         },
         "merge-stream": {
             "version": "2.0.0",
@@ -11870,7 +11875,7 @@
         "methods": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
-            "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
+            "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
         },
         "microevent.ts": {
             "version": "0.1.1",
@@ -12419,7 +12424,7 @@
                 "he": {
                     "version": "1.1.1",
                     "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
-                    "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==",
+                    "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
                     "dev": true
                 },
                 "minimatch": {
@@ -12434,13 +12439,13 @@
                 "minimist": {
                     "version": "0.0.8",
                     "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
-                    "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==",
+                    "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
                     "dev": true
                 },
                 "mkdirp": {
                     "version": "0.5.1",
                     "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-                    "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==",
+                    "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
                     "dev": true,
                     "requires": {
                         "minimist": "0.0.8"
@@ -12449,7 +12454,7 @@
                 "ms": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-                    "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+                    "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
                     "dev": true
                 },
                 "supports-color": {
@@ -12573,7 +12578,7 @@
         "move-concurrently": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
-            "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==",
+            "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
             "dev": true,
             "requires": {
                 "aproba": "^1.1.1",
@@ -12634,14 +12639,14 @@
                 "ms": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-                    "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+                    "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
                 }
             }
         },
         "mr-parser": {
             "version": "0.2.1",
             "resolved": "https://registry.npmjs.org/mr-parser/-/mr-parser-0.2.1.tgz",
-            "integrity": "sha512-hug+mpbSSKnH13rFqy3zm+XiG+QTStiDAgMTHK355TIstQE0qBkBtSJsa5YHP94AuarVX9b/4dcebdTRZ9YiEw=="
+            "integrity": "sha1-hhi5ukF+KOn0OaQcaVtVTq/u2Sc="
         },
         "mri": {
             "version": "1.2.0",
@@ -12666,7 +12671,7 @@
         "multicast-dns-service-types": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
-            "integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==",
+            "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
             "dev": true
         },
         "mute-stream": {
@@ -12711,7 +12716,7 @@
         "native-or-bluebird": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz",
-            "integrity": "sha512-0SH8UubxDfe382eYiwmd12qxAbiWGzlGZv6CkMA+DPojWa/Y0oH4hE0lRtFfFgJmPQFyKXeB8XxPbZz6TvvKaQ=="
+            "integrity": "sha1-OcR7/Xgl0fuf+tMiEK4l2q3xAck="
         },
         "natural-compare": {
             "version": "1.4.0",
@@ -12738,7 +12743,7 @@
         "nextafter": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/nextafter/-/nextafter-1.0.0.tgz",
-            "integrity": "sha512-7PO+A89Tll2rSEfyrjtqO0MaI37+nnxBdnQcPypfbEYYuGaJxWGCqaOwQX4a3GHNTS08l1kazuiLEWZniZjMUQ==",
+            "integrity": "sha1-t9d7U1MQ4+CX5gJauwqQNHfsGjo=",
             "requires": {
                 "double-bits": "^1.1.0"
             }
@@ -12769,7 +12774,7 @@
         "node-ensure": {
             "version": "0.0.0",
             "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
-            "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw=="
+            "integrity": "sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc="
         },
         "node-environment-flags": {
             "version": "1.0.5",
@@ -12858,7 +12863,7 @@
                 "nopt": {
                     "version": "3.0.6",
                     "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
-                    "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==",
+                    "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
                     "requires": {
                         "abbrev": "1"
                     }
@@ -12899,7 +12904,7 @@
                 "semver": {
                     "version": "5.3.0",
                     "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
-                    "integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw=="
+                    "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8="
                 },
                 "string-width": {
                     "version": "1.0.2",
@@ -12963,12 +12968,12 @@
                 "ansi-regex": {
                     "version": "2.1.1",
                     "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-                    "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="
+                    "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
                 },
                 "ansi-styles": {
                     "version": "2.2.1",
                     "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
-                    "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="
+                    "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
                 },
                 "aproba": {
                     "version": "1.2.0",
@@ -12987,7 +12992,7 @@
                 "chalk": {
                     "version": "1.1.3",
                     "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
-                    "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
+                    "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
                     "requires": {
                         "ansi-styles": "^2.2.1",
                         "escape-string-regexp": "^1.0.2",
@@ -13014,7 +13019,7 @@
                 "get-stdin": {
                     "version": "4.0.1",
                     "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
-                    "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw=="
+                    "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
                 },
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
@@ -13112,7 +13117,7 @@
         "noop-logger": {
             "version": "0.1.1",
             "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
-            "integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ=="
+            "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI="
         },
         "nopt": {
             "version": "5.0.0",
@@ -13288,12 +13293,12 @@
                 },
                 "abbrev": {
                     "version": "1.1.1",
-                    "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
                 },
                 "agent-base": {
                     "version": "4.3.0",
-                    "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
                     "requires": {
                         "es6-promisify": "^5.0.0"
@@ -13301,7 +13306,7 @@
                 },
                 "agentkeepalive": {
                     "version": "3.5.2",
-                    "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==",
                     "requires": {
                         "humanize-ms": "^1.2.1"
@@ -13309,7 +13314,7 @@
                 },
                 "ansi-align": {
                     "version": "2.0.0",
-                    "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
                     "requires": {
                         "string-width": "^2.0.0"
@@ -13317,12 +13322,12 @@
                 },
                 "ansi-regex": {
                     "version": "2.1.1",
-                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
                 },
                 "ansi-styles": {
                     "version": "3.2.1",
-                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
                     "requires": {
                         "color-convert": "^1.9.0"
@@ -13330,27 +13335,27 @@
                 },
                 "ansicolors": {
                     "version": "0.3.2",
-                    "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk="
                 },
                 "ansistyles": {
                     "version": "0.1.3",
-                    "resolved": "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk="
                 },
                 "aproba": {
                     "version": "2.0.0",
-                    "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
                 },
                 "archy": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA="
                 },
                 "are-we-there-yet": {
                     "version": "1.1.4",
-                    "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
+                    "resolved": false,
                     "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
                     "requires": {
                         "delegates": "^1.0.0",
@@ -13359,7 +13364,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+                            "resolved": false,
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -13373,7 +13378,7 @@
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -13383,12 +13388,12 @@
                 },
                 "asap": {
                     "version": "2.0.6",
-                    "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+                    "resolved": false,
                     "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
                 },
                 "asn1": {
                     "version": "0.2.4",
-                    "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
                     "requires": {
                         "safer-buffer": "~2.1.0"
@@ -13396,32 +13401,32 @@
                 },
                 "assert-plus": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
                 },
                 "asynckit": {
                     "version": "0.4.0",
-                    "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
                 },
                 "aws-sign2": {
                     "version": "0.7.0",
-                    "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
                 },
                 "aws4": {
                     "version": "1.8.0",
-                    "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
                 },
                 "balanced-match": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
                 },
                 "bcrypt-pbkdf": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
                     "optional": true,
                     "requires": {
@@ -13430,7 +13435,7 @@
                 },
                 "bin-links": {
                     "version": "1.1.8",
-                    "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-1.1.8.tgz",
+                    "resolved": false,
                     "integrity": "sha512-KgmVfx+QqggqP9dA3iIc5pA4T1qEEEL+hOhOhNPaUm77OTrJoOXE/C05SJLNJe6m/2wUK7F1tDSou7n5TfCDzQ==",
                     "requires": {
                         "bluebird": "^3.5.3",
@@ -13443,12 +13448,12 @@
                 },
                 "bluebird": {
                     "version": "3.5.5",
-                    "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
+                    "resolved": false,
                     "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w=="
                 },
                 "boxen": {
                     "version": "1.3.0",
-                    "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==",
                     "requires": {
                         "ansi-align": "^2.0.0",
@@ -13462,7 +13467,7 @@
                 },
                 "brace-expansion": {
                     "version": "1.1.11",
-                    "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+                    "resolved": false,
                     "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
                     "requires": {
                         "balanced-match": "^1.0.0",
@@ -13471,27 +13476,27 @@
                 },
                 "buffer-from": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA=="
                 },
                 "builtins": {
                     "version": "1.0.3",
-                    "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og="
                 },
                 "byline": {
                     "version": "5.0.0",
-                    "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE="
                 },
                 "byte-size": {
                     "version": "5.0.1",
-                    "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-5.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw=="
                 },
                 "cacache": {
                     "version": "12.0.3",
-                    "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==",
                     "requires": {
                         "bluebird": "^3.5.5",
@@ -13513,27 +13518,27 @@
                 },
                 "call-limit": {
                     "version": "1.1.1",
-                    "resolved": "https://registry.npmjs.org/call-limit/-/call-limit-1.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ=="
                 },
                 "camelcase": {
                     "version": "4.1.0",
-                    "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
                 },
                 "capture-stack-trace": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0="
                 },
                 "caseless": {
                     "version": "0.12.0",
-                    "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
                 },
                 "chalk": {
                     "version": "2.4.1",
-                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
                     "requires": {
                         "ansi-styles": "^3.2.1",
@@ -13543,17 +13548,17 @@
                 },
                 "chownr": {
                     "version": "1.1.4",
-                    "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
                 },
                 "ci-info": {
                     "version": "2.0.0",
-                    "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="
                 },
                 "cidr-regex": {
                     "version": "2.0.10",
-                    "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-2.0.10.tgz",
+                    "resolved": false,
                     "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==",
                     "requires": {
                         "ip-regex": "^2.1.0"
@@ -13561,12 +13566,12 @@
                 },
                 "cli-boxes": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM="
                 },
                 "cli-columns": {
                     "version": "3.1.2",
-                    "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-3.1.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=",
                     "requires": {
                         "string-width": "^2.0.0",
@@ -13575,7 +13580,7 @@
                 },
                 "cli-table3": {
                     "version": "0.5.1",
-                    "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==",
                     "requires": {
                         "colors": "^1.1.2",
@@ -13585,7 +13590,7 @@
                 },
                 "cliui": {
                     "version": "5.0.0",
-                    "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
                     "requires": {
                         "string-width": "^3.1.0",
@@ -13595,17 +13600,17 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "4.1.1",
-                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
                         },
                         "is-fullwidth-code-point": {
                             "version": "2.0.0",
-                            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
                         },
                         "string-width": {
                             "version": "3.1.0",
-                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
                             "requires": {
                                 "emoji-regex": "^7.0.1",
@@ -13615,7 +13620,7 @@
                         },
                         "strip-ansi": {
                             "version": "5.2.0",
-                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                             "requires": {
                                 "ansi-regex": "^4.1.0"
@@ -13625,12 +13630,12 @@
                 },
                 "clone": {
                     "version": "1.0.4",
-                    "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+                    "resolved": false,
                     "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
                 },
                 "cmd-shim": {
                     "version": "3.0.3",
-                    "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-3.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==",
                     "requires": {
                         "graceful-fs": "^4.1.2",
@@ -13639,12 +13644,12 @@
                 },
                 "code-point-at": {
                     "version": "1.1.0",
-                    "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
                 },
                 "color-convert": {
                     "version": "1.9.1",
-                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
                     "requires": {
                         "color-name": "^1.1.1"
@@ -13652,18 +13657,18 @@
                 },
                 "color-name": {
                     "version": "1.1.3",
-                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
                 },
                 "colors": {
                     "version": "1.3.3",
-                    "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==",
                     "optional": true
                 },
                 "columnify": {
                     "version": "1.5.4",
-                    "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz",
+                    "resolved": false,
                     "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=",
                     "requires": {
                         "strip-ansi": "^3.0.0",
@@ -13672,7 +13677,7 @@
                 },
                 "combined-stream": {
                     "version": "1.0.6",
-                    "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
+                    "resolved": false,
                     "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
                     "requires": {
                         "delayed-stream": "~1.0.0"
@@ -13680,12 +13685,12 @@
                 },
                 "concat-map": {
                     "version": "0.0.1",
-                    "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
                 },
                 "concat-stream": {
                     "version": "1.6.2",
-                    "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
                     "requires": {
                         "buffer-from": "^1.0.0",
@@ -13696,7 +13701,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+                            "resolved": false,
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -13710,7 +13715,7 @@
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -13720,7 +13725,7 @@
                 },
                 "config-chain": {
                     "version": "1.1.12",
-                    "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
+                    "resolved": false,
                     "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
                     "requires": {
                         "ini": "^1.3.4",
@@ -13729,7 +13734,7 @@
                 },
                 "configstore": {
                     "version": "3.1.5",
-                    "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.5.tgz",
+                    "resolved": false,
                     "integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==",
                     "requires": {
                         "dot-prop": "^4.2.1",
@@ -13742,12 +13747,12 @@
                 },
                 "console-control-strings": {
                     "version": "1.1.0",
-                    "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
                 },
                 "copy-concurrently": {
                     "version": "1.0.5",
-                    "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+                    "resolved": false,
                     "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
                     "requires": {
                         "aproba": "^1.1.1",
@@ -13760,24 +13765,24 @@
                     "dependencies": {
                         "aproba": {
                             "version": "1.2.0",
-                            "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
                         },
                         "iferr": {
                             "version": "0.1.5",
-                            "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+                            "resolved": false,
                             "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
                         }
                     }
                 },
                 "core-util-is": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
                 },
                 "create-error-class": {
                     "version": "3.0.2",
-                    "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
                     "requires": {
                         "capture-stack-trace": "^1.0.0"
@@ -13785,7 +13790,7 @@
                 },
                 "cross-spawn": {
                     "version": "5.1.0",
-                    "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
                     "requires": {
                         "lru-cache": "^4.0.1",
@@ -13795,7 +13800,7 @@
                     "dependencies": {
                         "lru-cache": {
                             "version": "4.1.5",
-                            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+                            "resolved": false,
                             "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
                             "requires": {
                                 "pseudomap": "^1.0.2",
@@ -13804,24 +13809,24 @@
                         },
                         "yallist": {
                             "version": "2.1.2",
-                            "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+                            "resolved": false,
                             "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
                         }
                     }
                 },
                 "crypto-random-string": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
                 },
                 "cyclist": {
                     "version": "0.2.2",
-                    "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA="
                 },
                 "dashdash": {
                     "version": "1.14.1",
-                    "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
                     "requires": {
                         "assert-plus": "^1.0.0"
@@ -13829,7 +13834,7 @@
                 },
                 "debug": {
                     "version": "3.1.0",
-                    "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
                     "requires": {
                         "ms": "2.0.0"
@@ -13837,34 +13842,34 @@
                     "dependencies": {
                         "ms": {
                             "version": "2.0.0",
-                            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
                         }
                     }
                 },
                 "debuglog": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI="
                 },
                 "decamelize": {
                     "version": "1.2.0",
-                    "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
                 },
                 "decode-uri-component": {
                     "version": "0.2.0",
-                    "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
                 },
                 "deep-extend": {
                     "version": "0.6.0",
-                    "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
                 },
                 "defaults": {
                     "version": "1.0.3",
-                    "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
                     "requires": {
                         "clone": "^1.0.2"
@@ -13872,7 +13877,7 @@
                 },
                 "define-properties": {
                     "version": "1.1.3",
-                    "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
                     "requires": {
                         "object-keys": "^1.0.12"
@@ -13880,27 +13885,27 @@
                 },
                 "delayed-stream": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
                 },
                 "delegates": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
                 },
                 "detect-indent": {
                     "version": "5.0.0",
-                    "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50="
                 },
                 "detect-newline": {
                     "version": "2.1.0",
-                    "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I="
                 },
                 "dezalgo": {
                     "version": "1.0.3",
-                    "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=",
                     "requires": {
                         "asap": "^2.0.0",
@@ -13909,7 +13914,7 @@
                 },
                 "dot-prop": {
                     "version": "4.2.1",
-                    "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==",
                     "requires": {
                         "is-obj": "^1.0.0"
@@ -13917,17 +13922,17 @@
                 },
                 "dotenv": {
                     "version": "5.0.1",
-                    "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow=="
                 },
                 "duplexer3": {
                     "version": "0.1.4",
-                    "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
+                    "resolved": false,
                     "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
                 },
                 "duplexify": {
                     "version": "3.6.0",
-                    "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==",
                     "requires": {
                         "end-of-stream": "^1.0.0",
@@ -13938,7 +13943,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+                            "resolved": false,
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -13952,7 +13957,7 @@
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -13962,7 +13967,7 @@
                 },
                 "ecc-jsbn": {
                     "version": "0.1.2",
-                    "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
                     "optional": true,
                     "requires": {
@@ -13972,17 +13977,17 @@
                 },
                 "editor": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I="
                 },
                 "emoji-regex": {
                     "version": "7.0.3",
-                    "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
                 },
                 "encoding": {
                     "version": "0.1.12",
-                    "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+                    "resolved": false,
                     "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
                     "requires": {
                         "iconv-lite": "~0.4.13"
@@ -13990,7 +13995,7 @@
                 },
                 "end-of-stream": {
                     "version": "1.4.1",
-                    "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
                     "requires": {
                         "once": "^1.4.0"
@@ -13998,17 +14003,17 @@
                 },
                 "env-paths": {
                     "version": "2.2.0",
-                    "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA=="
                 },
                 "err-code": {
                     "version": "1.1.2",
-                    "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA="
                 },
                 "errno": {
                     "version": "0.1.7",
-                    "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+                    "resolved": false,
                     "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
                     "requires": {
                         "prr": "~1.0.1"
@@ -14016,7 +14021,7 @@
                 },
                 "es-abstract": {
                     "version": "1.12.0",
-                    "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==",
                     "requires": {
                         "es-to-primitive": "^1.1.1",
@@ -14028,7 +14033,7 @@
                 },
                 "es-to-primitive": {
                     "version": "1.2.0",
-                    "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
                     "requires": {
                         "is-callable": "^1.1.4",
@@ -14038,12 +14043,12 @@
                 },
                 "es6-promise": {
                     "version": "4.2.8",
-                    "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+                    "resolved": false,
                     "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
                 },
                 "es6-promisify": {
                     "version": "5.0.0",
-                    "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
                     "requires": {
                         "es6-promise": "^4.0.3"
@@ -14051,12 +14056,12 @@
                 },
                 "escape-string-regexp": {
                     "version": "1.0.5",
-                    "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+                    "resolved": false,
                     "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
                 },
                 "execa": {
                     "version": "0.7.0",
-                    "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
                     "requires": {
                         "cross-spawn": "^5.0.1",
@@ -14070,39 +14075,39 @@
                     "dependencies": {
                         "get-stream": {
                             "version": "3.0.0",
-                            "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
                         }
                     }
                 },
                 "extend": {
                     "version": "3.0.2",
-                    "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
                 },
                 "extsprintf": {
                     "version": "1.3.0",
-                    "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
                 },
                 "fast-json-stable-stringify": {
                     "version": "2.0.0",
-                    "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
                 },
                 "figgy-pudding": {
                     "version": "3.5.1",
-                    "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w=="
                 },
                 "find-npm-prefix": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA=="
                 },
                 "flush-write-stream": {
                     "version": "1.0.3",
-                    "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==",
                     "requires": {
                         "inherits": "^2.0.1",
@@ -14111,7 +14116,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+                            "resolved": false,
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -14125,7 +14130,7 @@
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -14135,12 +14140,12 @@
                 },
                 "forever-agent": {
                     "version": "0.6.1",
-                    "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
                 },
                 "form-data": {
                     "version": "2.3.2",
-                    "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
                     "requires": {
                         "asynckit": "^0.4.0",
@@ -14150,7 +14155,7 @@
                 },
                 "from2": {
                     "version": "2.3.0",
-                    "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
                     "requires": {
                         "inherits": "^2.0.1",
@@ -14159,7 +14164,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+                            "resolved": false,
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -14173,7 +14178,7 @@
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -14183,7 +14188,7 @@
                 },
                 "fs-minipass": {
                     "version": "1.2.7",
-                    "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
+                    "resolved": false,
                     "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
                     "requires": {
                         "minipass": "^2.6.0"
@@ -14191,7 +14196,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
-                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
                             "requires": {
                                 "safe-buffer": "^5.1.2",
@@ -14202,7 +14207,7 @@
                 },
                 "fs-vacuum": {
                     "version": "1.2.10",
-                    "resolved": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.10.tgz",
+                    "resolved": false,
                     "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=",
                     "requires": {
                         "graceful-fs": "^4.1.2",
@@ -14212,7 +14217,7 @@
                 },
                 "fs-write-stream-atomic": {
                     "version": "1.0.10",
-                    "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+                    "resolved": false,
                     "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
                     "requires": {
                         "graceful-fs": "^4.1.2",
@@ -14223,12 +14228,12 @@
                     "dependencies": {
                         "iferr": {
                             "version": "0.1.5",
-                            "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+                            "resolved": false,
                             "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
                         },
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+                            "resolved": false,
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -14242,7 +14247,7 @@
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -14252,17 +14257,17 @@
                 },
                 "fs.realpath": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
                 },
                 "function-bind": {
                     "version": "1.1.1",
-                    "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
                 },
                 "gauge": {
                     "version": "2.7.4",
-                    "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+                    "resolved": false,
                     "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
                     "requires": {
                         "aproba": "^1.0.3",
@@ -14277,12 +14282,12 @@
                     "dependencies": {
                         "aproba": {
                             "version": "1.2.0",
-                            "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
                         },
                         "string-width": {
                             "version": "1.0.2",
-                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+                            "resolved": false,
                             "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
                             "requires": {
                                 "code-point-at": "^1.0.0",
@@ -14294,12 +14299,12 @@
                 },
                 "genfun": {
                     "version": "5.0.0",
-                    "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA=="
                 },
                 "gentle-fs": {
                     "version": "2.3.1",
-                    "resolved": "https://registry.npmjs.org/gentle-fs/-/gentle-fs-2.3.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-OlwBBwqCFPcjm33rF2BjW+Pr6/ll2741l+xooiwTCeaX2CA1ZuclavyMBe0/KlR21/XGsgY6hzEQZ15BdNa13Q==",
                     "requires": {
                         "aproba": "^1.1.2",
@@ -14317,24 +14322,24 @@
                     "dependencies": {
                         "aproba": {
                             "version": "1.2.0",
-                            "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
                         },
                         "iferr": {
                             "version": "0.1.5",
-                            "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+                            "resolved": false,
                             "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE="
                         }
                     }
                 },
                 "get-caller-file": {
                     "version": "2.0.5",
-                    "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+                    "resolved": false,
                     "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
                 },
                 "get-stream": {
                     "version": "4.1.0",
-                    "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
                     "requires": {
                         "pump": "^3.0.0"
@@ -14342,7 +14347,7 @@
                 },
                 "getpass": {
                     "version": "0.1.7",
-                    "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+                    "resolved": false,
                     "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
                     "requires": {
                         "assert-plus": "^1.0.0"
@@ -14350,7 +14355,7 @@
                 },
                 "glob": {
                     "version": "7.1.6",
-                    "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+                    "resolved": false,
                     "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
                     "requires": {
                         "fs.realpath": "^1.0.0",
@@ -14363,7 +14368,7 @@
                 },
                 "global-dirs": {
                     "version": "0.1.1",
-                    "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
                     "requires": {
                         "ini": "^1.3.4"
@@ -14371,7 +14376,7 @@
                 },
                 "got": {
                     "version": "6.7.1",
-                    "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
                     "requires": {
                         "create-error-class": "^3.0.0",
@@ -14389,24 +14394,24 @@
                     "dependencies": {
                         "get-stream": {
                             "version": "3.0.0",
-                            "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
                         }
                     }
                 },
                 "graceful-fs": {
                     "version": "4.2.4",
-                    "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
                 },
                 "har-schema": {
                     "version": "2.0.0",
-                    "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
                 },
                 "har-validator": {
                     "version": "5.1.5",
-                    "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+                    "resolved": false,
                     "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
                     "requires": {
                         "ajv": "^6.12.3",
@@ -14415,7 +14420,7 @@
                     "dependencies": {
                         "ajv": {
                             "version": "6.12.6",
-                            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+                            "resolved": false,
                             "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
                             "requires": {
                                 "fast-deep-equal": "^3.1.1",
@@ -14426,19 +14431,19 @@
                         },
                         "fast-deep-equal": {
                             "version": "3.1.3",
-                            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+                            "resolved": false,
                             "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
                         },
                         "json-schema-traverse": {
                             "version": "0.4.1",
-                            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
                         }
                     }
                 },
                 "has": {
                     "version": "1.0.3",
-                    "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
                     "requires": {
                         "function-bind": "^1.1.1"
@@ -14446,32 +14451,32 @@
                 },
                 "has-flag": {
                     "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
                 },
                 "has-symbols": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
                 },
                 "has-unicode": {
                     "version": "2.0.1",
-                    "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
                 },
                 "hosted-git-info": {
                     "version": "2.8.9",
-                    "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+                    "resolved": false,
                     "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="
                 },
                 "http-cache-semantics": {
                     "version": "3.8.1",
-                    "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w=="
                 },
                 "http-proxy-agent": {
                     "version": "2.1.0",
-                    "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
                     "requires": {
                         "agent-base": "4",
@@ -14480,7 +14485,7 @@
                 },
                 "http-signature": {
                     "version": "1.2.0",
-                    "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
                     "requires": {
                         "assert-plus": "^1.0.0",
@@ -14490,7 +14495,7 @@
                 },
                 "https-proxy-agent": {
                     "version": "2.2.4",
-                    "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
                     "requires": {
                         "agent-base": "^4.3.0",
@@ -14499,7 +14504,7 @@
                 },
                 "humanize-ms": {
                     "version": "1.2.1",
-                    "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
                     "requires": {
                         "ms": "^2.0.0"
@@ -14507,7 +14512,7 @@
                 },
                 "iconv-lite": {
                     "version": "0.4.23",
-                    "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+                    "resolved": false,
                     "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
                     "requires": {
                         "safer-buffer": ">= 2.1.2 < 3"
@@ -14515,12 +14520,12 @@
                 },
                 "iferr": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/iferr/-/iferr-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg=="
                 },
                 "ignore-walk": {
                     "version": "3.0.3",
-                    "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
                     "requires": {
                         "minimatch": "^3.0.4"
@@ -14528,22 +14533,22 @@
                 },
                 "import-lazy": {
                     "version": "2.1.0",
-                    "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM="
                 },
                 "imurmurhash": {
                     "version": "0.1.4",
-                    "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+                    "resolved": false,
                     "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
                 },
                 "infer-owner": {
                     "version": "1.0.4",
-                    "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="
                 },
                 "inflight": {
                     "version": "1.0.6",
-                    "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+                    "resolved": false,
                     "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
                     "requires": {
                         "once": "^1.3.0",
@@ -14552,17 +14557,17 @@
                 },
                 "inherits": {
                     "version": "2.0.4",
-                    "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
                 },
                 "ini": {
                     "version": "1.3.8",
-                    "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+                    "resolved": false,
                     "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
                 },
                 "init-package-json": {
                     "version": "1.10.3",
-                    "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==",
                     "requires": {
                         "glob": "^7.1.1",
@@ -14577,22 +14582,22 @@
                 },
                 "ip": {
                     "version": "1.1.5",
-                    "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+                    "resolved": false,
                     "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
                 },
                 "ip-regex": {
                     "version": "2.1.0",
-                    "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk="
                 },
                 "is-callable": {
                     "version": "1.1.4",
-                    "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
                 },
                 "is-ci": {
                     "version": "1.2.1",
-                    "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
                     "requires": {
                         "ci-info": "^1.5.0"
@@ -14600,14 +14605,14 @@
                     "dependencies": {
                         "ci-info": {
                             "version": "1.6.0",
-                            "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A=="
                         }
                     }
                 },
                 "is-cidr": {
                     "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-3.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-8Xnnbjsb0x462VoYiGlhEi+drY8SFwrHiSYuzc/CEwco55vkehTaxAyIjEdpi3EMvLPPJAJi9FlzP+h+03gp0Q==",
                     "requires": {
                         "cidr-regex": "^2.0.10"
@@ -14615,12 +14620,12 @@
                 },
                 "is-date-object": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
                 },
                 "is-fullwidth-code-point": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
                     "requires": {
                         "number-is-nan": "^1.0.0"
@@ -14628,7 +14633,7 @@
                 },
                 "is-installed-globally": {
                     "version": "0.1.0",
-                    "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
                     "requires": {
                         "global-dirs": "^0.1.0",
@@ -14637,17 +14642,17 @@
                 },
                 "is-npm": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ="
                 },
                 "is-obj": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
                 },
                 "is-path-inside": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
                     "requires": {
                         "path-is-inside": "^1.0.1"
@@ -14655,12 +14660,12 @@
                 },
                 "is-redirect": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ="
                 },
                 "is-regex": {
                     "version": "1.0.4",
-                    "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+                    "resolved": false,
                     "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
                     "requires": {
                         "has": "^1.0.1"
@@ -14668,17 +14673,17 @@
                 },
                 "is-retry-allowed": {
                     "version": "1.2.0",
-                    "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg=="
                 },
                 "is-stream": {
                     "version": "1.1.0",
-                    "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
                 },
                 "is-symbol": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
                     "requires": {
                         "has-symbols": "^1.0.0"
@@ -14686,43 +14691,43 @@
                 },
                 "is-typedarray": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
                 },
                 "isarray": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
                 },
                 "isexe": {
                     "version": "2.0.0",
-                    "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
                 },
                 "isstream": {
                     "version": "0.1.2",
-                    "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
                 },
                 "jsbn": {
                     "version": "0.1.1",
-                    "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
                     "optional": true
                 },
                 "json-parse-better-errors": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
                 },
                 "json-schema": {
                     "version": "0.4.0",
-                    "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
                 },
                 "json-stringify-safe": {
                     "version": "5.0.1",
-                    "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
                 },
                 "jsonparse": {
@@ -14732,7 +14737,7 @@
                 },
                 "jsprim": {
                     "version": "1.4.2",
-                    "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
                     "requires": {
                         "assert-plus": "1.0.0",
@@ -14743,7 +14748,7 @@
                 },
                 "latest-version": {
                     "version": "3.1.0",
-                    "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=",
                     "requires": {
                         "package-json": "^4.0.0"
@@ -14751,12 +14756,12 @@
                 },
                 "lazy-property": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/lazy-property/-/lazy-property-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc="
                 },
                 "libcipm": {
                     "version": "4.0.8",
-                    "resolved": "https://registry.npmjs.org/libcipm/-/libcipm-4.0.8.tgz",
+                    "resolved": false,
                     "integrity": "sha512-IN3hh2yDJQtZZ5paSV4fbvJg4aHxCCg5tcZID/dSVlTuUiWktsgaldVljJv6Z5OUlYspx6xQkbR0efNodnIrOA==",
                     "requires": {
                         "bin-links": "^1.1.2",
@@ -14778,7 +14783,7 @@
                 },
                 "libnpm": {
                     "version": "3.0.1",
-                    "resolved": "https://registry.npmjs.org/libnpm/-/libnpm-3.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==",
                     "requires": {
                         "bin-links": "^1.1.2",
@@ -14805,7 +14810,7 @@
                 },
                 "libnpmaccess": {
                     "version": "3.0.2",
-                    "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==",
                     "requires": {
                         "aproba": "^2.0.0",
@@ -14816,7 +14821,7 @@
                 },
                 "libnpmconfig": {
                     "version": "1.2.1",
-                    "resolved": "https://registry.npmjs.org/libnpmconfig/-/libnpmconfig-1.2.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==",
                     "requires": {
                         "figgy-pudding": "^3.5.1",
@@ -14826,7 +14831,7 @@
                     "dependencies": {
                         "find-up": {
                             "version": "3.0.0",
-                            "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
                             "requires": {
                                 "locate-path": "^3.0.0"
@@ -14834,7 +14839,7 @@
                         },
                         "locate-path": {
                             "version": "3.0.0",
-                            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
                             "requires": {
                                 "p-locate": "^3.0.0",
@@ -14843,7 +14848,7 @@
                         },
                         "p-limit": {
                             "version": "2.2.0",
-                            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
                             "requires": {
                                 "p-try": "^2.0.0"
@@ -14851,7 +14856,7 @@
                         },
                         "p-locate": {
                             "version": "3.0.0",
-                            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
                             "requires": {
                                 "p-limit": "^2.0.0"
@@ -14859,14 +14864,14 @@
                         },
                         "p-try": {
                             "version": "2.2.0",
-                            "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
                         }
                     }
                 },
                 "libnpmhook": {
                     "version": "5.0.3",
-                    "resolved": "https://registry.npmjs.org/libnpmhook/-/libnpmhook-5.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==",
                     "requires": {
                         "aproba": "^2.0.0",
@@ -14877,7 +14882,7 @@
                 },
                 "libnpmorg": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/libnpmorg/-/libnpmorg-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==",
                     "requires": {
                         "aproba": "^2.0.0",
@@ -14888,7 +14893,7 @@
                 },
                 "libnpmpublish": {
                     "version": "1.1.2",
-                    "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-1.1.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==",
                     "requires": {
                         "aproba": "^2.0.0",
@@ -14904,7 +14909,7 @@
                 },
                 "libnpmsearch": {
                     "version": "2.0.2",
-                    "resolved": "https://registry.npmjs.org/libnpmsearch/-/libnpmsearch-2.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==",
                     "requires": {
                         "figgy-pudding": "^3.5.1",
@@ -14914,7 +14919,7 @@
                 },
                 "libnpmteam": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/libnpmteam/-/libnpmteam-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==",
                     "requires": {
                         "aproba": "^2.0.0",
@@ -14925,7 +14930,7 @@
                 },
                 "libnpx": {
                     "version": "10.2.4",
-                    "resolved": "https://registry.npmjs.org/libnpx/-/libnpx-10.2.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-BPc0D1cOjBeS8VIBKUu5F80s6njm0wbVt7CsGMrIcJ+SI7pi7V0uVPGpEMH9H5L8csOcclTxAXFE2VAsJXUhfA==",
                     "requires": {
                         "dotenv": "^5.0.1",
@@ -14940,7 +14945,7 @@
                 },
                 "lock-verify": {
                     "version": "2.1.0",
-                    "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==",
                     "requires": {
                         "npm-package-arg": "^6.1.0",
@@ -14949,7 +14954,7 @@
                 },
                 "lockfile": {
                     "version": "1.0.4",
-                    "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==",
                     "requires": {
                         "signal-exit": "^3.0.2"
@@ -14957,12 +14962,12 @@
                 },
                 "lodash._baseindexof": {
                     "version": "3.1.0",
-                    "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw="
                 },
                 "lodash._baseuniq": {
                     "version": "4.6.0",
-                    "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=",
                     "requires": {
                         "lodash._createset": "~4.0.0",
@@ -14971,17 +14976,17 @@
                 },
                 "lodash._bindcallback": {
                     "version": "3.0.1",
-                    "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4="
                 },
                 "lodash._cacheindexof": {
                     "version": "3.0.2",
-                    "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI="
                 },
                 "lodash._createcache": {
                     "version": "3.1.2",
-                    "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=",
                     "requires": {
                         "lodash._getnative": "^3.0.0"
@@ -14989,52 +14994,52 @@
                 },
                 "lodash._createset": {
                     "version": "4.0.3",
-                    "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY="
                 },
                 "lodash._getnative": {
                     "version": "3.9.1",
-                    "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U="
                 },
                 "lodash._root": {
                     "version": "3.0.1",
-                    "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI="
                 },
                 "lodash.clonedeep": {
                     "version": "4.5.0",
-                    "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
                 },
                 "lodash.restparam": {
                     "version": "3.6.1",
-                    "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU="
                 },
                 "lodash.union": {
                     "version": "4.6.0",
-                    "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg="
                 },
                 "lodash.uniq": {
                     "version": "4.5.0",
-                    "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M="
                 },
                 "lodash.without": {
                     "version": "4.4.0",
-                    "resolved": "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw="
                 },
                 "lowercase-keys": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="
                 },
                 "lru-cache": {
                     "version": "5.1.1",
-                    "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
                     "requires": {
                         "yallist": "^3.0.2"
@@ -15042,7 +15047,7 @@
                 },
                 "make-dir": {
                     "version": "1.3.0",
-                    "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
                     "requires": {
                         "pify": "^3.0.0"
@@ -15050,7 +15055,7 @@
                 },
                 "make-fetch-happen": {
                     "version": "5.0.2",
-                    "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==",
                     "requires": {
                         "agentkeepalive": "^3.4.1",
@@ -15068,17 +15073,17 @@
                 },
                 "meant": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/meant/-/meant-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-KN+1uowN/NK+sT/Lzx7WSGIj2u+3xe5n2LbwObfjOhPZiA+cCfCm6idVl0RkEfjThkw5XJ96CyRcanq6GmKtUg=="
                 },
                 "mime-db": {
                     "version": "1.35.0",
-                    "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg=="
                 },
                 "mime-types": {
                     "version": "2.1.19",
-                    "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz",
+                    "resolved": false,
                     "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==",
                     "requires": {
                         "mime-db": "~1.35.0"
@@ -15086,7 +15091,7 @@
                 },
                 "minimatch": {
                     "version": "3.0.4",
-                    "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
                     "requires": {
                         "brace-expansion": "^1.1.7"
@@ -15094,12 +15099,12 @@
                 },
                 "minimist": {
                     "version": "1.2.6",
-                    "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+                    "resolved": false,
                     "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
                 },
                 "minizlib": {
                     "version": "1.3.3",
-                    "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
                     "requires": {
                         "minipass": "^2.9.0"
@@ -15107,7 +15112,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
-                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
                             "requires": {
                                 "safe-buffer": "^5.1.2",
@@ -15118,7 +15123,7 @@
                 },
                 "mississippi": {
                     "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
                     "requires": {
                         "concat-stream": "^1.5.0",
@@ -15135,7 +15140,7 @@
                 },
                 "mkdirp": {
                     "version": "0.5.5",
-                    "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+                    "resolved": false,
                     "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
                     "requires": {
                         "minimist": "^1.2.5"
@@ -15143,14 +15148,14 @@
                     "dependencies": {
                         "minimist": {
                             "version": "1.2.6",
-                            "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+                            "resolved": false,
                             "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
                         }
                     }
                 },
                 "move-concurrently": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
                     "requires": {
                         "aproba": "^1.1.1",
@@ -15163,24 +15168,24 @@
                     "dependencies": {
                         "aproba": {
                             "version": "1.2.0",
-                            "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
                         }
                     }
                 },
                 "ms": {
                     "version": "2.1.1",
-                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
                 },
                 "mute-stream": {
                     "version": "0.0.7",
-                    "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+                    "resolved": false,
                     "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
                 },
                 "node-fetch-npm": {
                     "version": "2.0.2",
-                    "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==",
                     "requires": {
                         "encoding": "^0.1.11",
@@ -15190,7 +15195,7 @@
                 },
                 "node-gyp": {
                     "version": "5.1.0",
-                    "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==",
                     "requires": {
                         "env-paths": "^2.2.0",
@@ -15208,7 +15213,7 @@
                 },
                 "nopt": {
                     "version": "4.0.3",
-                    "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
                     "requires": {
                         "abbrev": "1",
@@ -15217,7 +15222,7 @@
                 },
                 "normalize-package-data": {
                     "version": "2.5.0",
-                    "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
                     "requires": {
                         "hosted-git-info": "^2.1.4",
@@ -15228,7 +15233,7 @@
                     "dependencies": {
                         "resolve": {
                             "version": "1.10.0",
-                            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
                             "requires": {
                                 "path-parse": "^1.0.6"
@@ -15238,7 +15243,7 @@
                 },
                 "npm-audit-report": {
                     "version": "1.3.3",
-                    "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-1.3.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-8nH/JjsFfAWMvn474HB9mpmMjrnKb1Hx/oTAdjv4PT9iZBvBxiZ+wtDUapHCJwLqYGQVPaAfs+vL5+5k9QndXw==",
                     "requires": {
                         "cli-table3": "^0.5.0",
@@ -15247,7 +15252,7 @@
                 },
                 "npm-bundled": {
                     "version": "1.1.1",
-                    "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
                     "requires": {
                         "npm-normalize-package-bin": "^1.0.1"
@@ -15255,12 +15260,12 @@
                 },
                 "npm-cache-filename": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE="
                 },
                 "npm-install-checks": {
                     "version": "3.0.2",
-                    "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-3.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==",
                     "requires": {
                         "semver": "^2.3.0 || 3.x || 4 || 5"
@@ -15268,7 +15273,7 @@
                 },
                 "npm-lifecycle": {
                     "version": "3.1.5",
-                    "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz",
+                    "resolved": false,
                     "integrity": "sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==",
                     "requires": {
                         "byline": "^5.0.0",
@@ -15283,17 +15288,17 @@
                 },
                 "npm-logical-tree": {
                     "version": "1.2.1",
-                    "resolved": "https://registry.npmjs.org/npm-logical-tree/-/npm-logical-tree-1.2.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg=="
                 },
                 "npm-normalize-package-bin": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
                 },
                 "npm-package-arg": {
                     "version": "6.1.1",
-                    "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==",
                     "requires": {
                         "hosted-git-info": "^2.7.1",
@@ -15304,7 +15309,7 @@
                 },
                 "npm-packlist": {
                     "version": "1.4.8",
-                    "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
+                    "resolved": false,
                     "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
                     "requires": {
                         "ignore-walk": "^3.0.1",
@@ -15314,7 +15319,7 @@
                 },
                 "npm-pick-manifest": {
                     "version": "3.0.2",
-                    "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==",
                     "requires": {
                         "figgy-pudding": "^3.5.1",
@@ -15324,7 +15329,7 @@
                 },
                 "npm-profile": {
                     "version": "4.0.4",
-                    "resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-4.0.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==",
                     "requires": {
                         "aproba": "^1.1.2 || 2",
@@ -15334,7 +15339,7 @@
                 },
                 "npm-registry-fetch": {
                     "version": "4.0.7",
-                    "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.7.tgz",
+                    "resolved": false,
                     "integrity": "sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==",
                     "requires": {
                         "JSONStream": "^1.3.4",
@@ -15348,14 +15353,14 @@
                     "dependencies": {
                         "safe-buffer": {
                             "version": "5.2.1",
-                            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
                         }
                     }
                 },
                 "npm-run-path": {
                     "version": "2.0.2",
-                    "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
                     "requires": {
                         "path-key": "^2.0.0"
@@ -15363,12 +15368,12 @@
                 },
                 "npm-user-validate": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw=="
                 },
                 "npmlog": {
                     "version": "4.1.2",
-                    "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
                     "requires": {
                         "are-we-there-yet": "~1.1.2",
@@ -15379,27 +15384,27 @@
                 },
                 "number-is-nan": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
                 },
                 "oauth-sign": {
                     "version": "0.9.0",
-                    "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
                 },
                 "object-assign": {
                     "version": "4.1.1",
-                    "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
                 },
                 "object-keys": {
                     "version": "1.0.12",
-                    "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz",
+                    "resolved": false,
                     "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag=="
                 },
                 "object.getownpropertydescriptors": {
                     "version": "2.0.3",
-                    "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
                     "requires": {
                         "define-properties": "^1.1.2",
@@ -15408,7 +15413,7 @@
                 },
                 "once": {
                     "version": "1.4.0",
-                    "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
                     "requires": {
                         "wrappy": "1"
@@ -15416,22 +15421,22 @@
                 },
                 "opener": {
                     "version": "1.5.2",
-                    "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="
                 },
                 "os-homedir": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
                 },
                 "os-tmpdir": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
                 },
                 "osenv": {
                     "version": "0.1.5",
-                    "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+                    "resolved": false,
                     "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
                     "requires": {
                         "os-homedir": "^1.0.0",
@@ -15440,12 +15445,12 @@
                 },
                 "p-finally": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
                 },
                 "package-json": {
                     "version": "4.0.1",
-                    "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
                     "requires": {
                         "got": "^6.7.1",
@@ -15456,7 +15461,7 @@
                 },
                 "pacote": {
                     "version": "9.5.12",
-                    "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.12.tgz",
+                    "resolved": false,
                     "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==",
                     "requires": {
                         "bluebird": "^3.5.3",
@@ -15493,7 +15498,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
-                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
                             "requires": {
                                 "safe-buffer": "^5.1.2",
@@ -15504,7 +15509,7 @@
                 },
                 "parallel-transform": {
                     "version": "1.1.0",
-                    "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
                     "requires": {
                         "cyclist": "~0.2.2",
@@ -15514,7 +15519,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+                            "resolved": false,
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -15528,7 +15533,7 @@
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -15538,57 +15543,57 @@
                 },
                 "path-exists": {
                     "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
                 },
                 "path-is-absolute": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
                 },
                 "path-is-inside": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM="
                 },
                 "path-key": {
                     "version": "2.0.1",
-                    "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
                 },
                 "path-parse": {
                     "version": "1.0.7",
-                    "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+                    "resolved": false,
                     "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
                 },
                 "performance-now": {
                     "version": "2.1.0",
-                    "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
                 },
                 "pify": {
                     "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
                 },
                 "prepend-http": {
                     "version": "1.0.4",
-                    "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+                    "resolved": false,
                     "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
                 },
                 "process-nextick-args": {
                     "version": "2.0.0",
-                    "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
                 },
                 "promise-inflight": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
                 },
                 "promise-retry": {
                     "version": "1.1.1",
-                    "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=",
                     "requires": {
                         "err-code": "^1.0.0",
@@ -15597,14 +15602,14 @@
                     "dependencies": {
                         "retry": {
                             "version": "0.10.1",
-                            "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
+                            "resolved": false,
                             "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q="
                         }
                     }
                 },
                 "promzard": {
                     "version": "0.3.0",
-                    "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=",
                     "requires": {
                         "read": "1"
@@ -15612,12 +15617,12 @@
                 },
                 "proto-list": {
                     "version": "1.2.4",
-                    "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+                    "resolved": false,
                     "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk="
                 },
                 "protoduck": {
                     "version": "5.0.1",
-                    "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==",
                     "requires": {
                         "genfun": "^5.0.0"
@@ -15625,22 +15630,22 @@
                 },
                 "prr": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
                 },
                 "pseudomap": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
                 },
                 "psl": {
                     "version": "1.1.29",
-                    "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
+                    "resolved": false,
                     "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
                 },
                 "pump": {
                     "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
                     "requires": {
                         "end-of-stream": "^1.1.0",
@@ -15649,7 +15654,7 @@
                 },
                 "pumpify": {
                     "version": "1.5.1",
-                    "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
                     "requires": {
                         "duplexify": "^3.6.0",
@@ -15659,7 +15664,7 @@
                     "dependencies": {
                         "pump": {
                             "version": "2.0.1",
-                            "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
                             "requires": {
                                 "end-of-stream": "^1.1.0",
@@ -15670,22 +15675,22 @@
                 },
                 "punycode": {
                     "version": "1.4.1",
-                    "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
                 },
                 "qrcode-terminal": {
                     "version": "0.12.0",
-                    "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ=="
                 },
                 "qs": {
                     "version": "6.5.2",
-                    "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
                 },
                 "query-string": {
                     "version": "6.8.2",
-                    "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==",
                     "requires": {
                         "decode-uri-component": "^0.2.0",
@@ -15695,12 +15700,12 @@
                 },
                 "qw": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/qw/-/qw-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-77/cdA+a0FQwRCassYNBLMi5ltQ="
                 },
                 "rc": {
                     "version": "1.2.8",
-                    "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+                    "resolved": false,
                     "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
                     "requires": {
                         "deep-extend": "^0.6.0",
@@ -15711,7 +15716,7 @@
                 },
                 "read": {
                     "version": "1.0.7",
-                    "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
+                    "resolved": false,
                     "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=",
                     "requires": {
                         "mute-stream": "~0.0.4"
@@ -15719,7 +15724,7 @@
                 },
                 "read-cmd-shim": {
                     "version": "1.0.5",
-                    "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz",
+                    "resolved": false,
                     "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==",
                     "requires": {
                         "graceful-fs": "^4.1.2"
@@ -15727,7 +15732,7 @@
                 },
                 "read-installed": {
                     "version": "4.0.3",
-                    "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=",
                     "requires": {
                         "debuglog": "^1.0.1",
@@ -15741,7 +15746,7 @@
                 },
                 "read-package-json": {
                     "version": "2.1.1",
-                    "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==",
                     "requires": {
                         "glob": "^7.1.1",
@@ -15753,7 +15758,7 @@
                 },
                 "read-package-tree": {
                     "version": "5.3.1",
-                    "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==",
                     "requires": {
                         "read-package-json": "^2.0.0",
@@ -15763,7 +15768,7 @@
                 },
                 "readable-stream": {
                     "version": "3.6.0",
-                    "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
                     "requires": {
                         "inherits": "^2.0.3",
@@ -15773,7 +15778,7 @@
                 },
                 "readdir-scoped-modules": {
                     "version": "1.1.0",
-                    "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
                     "requires": {
                         "debuglog": "^1.0.1",
@@ -15784,7 +15789,7 @@
                 },
                 "registry-auth-token": {
                     "version": "3.4.0",
-                    "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
                     "requires": {
                         "rc": "^1.1.6",
@@ -15793,7 +15798,7 @@
                 },
                 "registry-url": {
                     "version": "3.1.0",
-                    "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
                     "requires": {
                         "rc": "^1.0.1"
@@ -15801,7 +15806,7 @@
                 },
                 "request": {
                     "version": "2.88.0",
-                    "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
                     "requires": {
                         "aws-sign2": "~0.7.0",
@@ -15828,27 +15833,27 @@
                 },
                 "require-directory": {
                     "version": "2.1.1",
-                    "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
                 },
                 "require-main-filename": {
                     "version": "2.0.0",
-                    "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
                 },
                 "resolve-from": {
                     "version": "4.0.0",
-                    "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
                 },
                 "retry": {
                     "version": "0.12.0",
-                    "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
                 },
                 "rimraf": {
                     "version": "2.7.1",
-                    "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
                     "requires": {
                         "glob": "^7.1.3"
@@ -15856,7 +15861,7 @@
                 },
                 "run-queue": {
                     "version": "1.0.3",
-                    "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
                     "requires": {
                         "aproba": "^1.1.1"
@@ -15864,29 +15869,29 @@
                     "dependencies": {
                         "aproba": {
                             "version": "1.2.0",
-                            "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
                         }
                     }
                 },
                 "safe-buffer": {
                     "version": "5.1.2",
-                    "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
                 },
                 "safer-buffer": {
                     "version": "2.1.2",
-                    "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
                 },
                 "semver": {
                     "version": "5.7.1",
-                    "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
                 },
                 "semver-diff": {
                     "version": "2.1.0",
-                    "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
                     "requires": {
                         "semver": "^5.0.3"
@@ -15894,12 +15899,12 @@
                 },
                 "set-blocking": {
                     "version": "2.0.0",
-                    "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
                 },
                 "sha": {
                     "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/sha/-/sha-3.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==",
                     "requires": {
                         "graceful-fs": "^4.1.2"
@@ -15907,7 +15912,7 @@
                 },
                 "shebang-command": {
                     "version": "1.2.0",
-                    "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
                     "requires": {
                         "shebang-regex": "^1.0.0"
@@ -15915,27 +15920,27 @@
                 },
                 "shebang-regex": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
                 },
                 "signal-exit": {
                     "version": "3.0.2",
-                    "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
                 },
                 "slide": {
                     "version": "1.1.6",
-                    "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
+                    "resolved": false,
                     "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc="
                 },
                 "smart-buffer": {
                     "version": "4.1.0",
-                    "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw=="
                 },
                 "socks": {
                     "version": "2.3.3",
-                    "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==",
                     "requires": {
                         "ip": "1.1.5",
@@ -15944,7 +15949,7 @@
                 },
                 "socks-proxy-agent": {
                     "version": "4.0.2",
-                    "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==",
                     "requires": {
                         "agent-base": "~4.2.1",
@@ -15953,7 +15958,7 @@
                     "dependencies": {
                         "agent-base": {
                             "version": "4.2.1",
-                            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
                             "requires": {
                                 "es6-promisify": "^5.0.0"
@@ -15963,12 +15968,12 @@
                 },
                 "sorted-object": {
                     "version": "2.0.1",
-                    "resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw="
                 },
                 "sorted-union-stream": {
                     "version": "2.1.3",
-                    "resolved": "https://registry.npmjs.org/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=",
                     "requires": {
                         "from2": "^1.3.0",
@@ -15977,7 +15982,7 @@
                     "dependencies": {
                         "from2": {
                             "version": "1.3.0",
-                            "resolved": "https://registry.npmjs.org/from2/-/from2-1.3.0.tgz",
+                            "resolved": false,
                             "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=",
                             "requires": {
                                 "inherits": "~2.0.1",
@@ -15986,12 +15991,12 @@
                         },
                         "isarray": {
                             "version": "0.0.1",
-                            "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+                            "resolved": false,
                             "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
                         },
                         "readable-stream": {
                             "version": "1.1.14",
-                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+                            "resolved": false,
                             "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -16002,14 +16007,14 @@
                         },
                         "string_decoder": {
                             "version": "0.10.31",
-                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+                            "resolved": false,
                             "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
                         }
                     }
                 },
                 "spdx-correct": {
                     "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==",
                     "requires": {
                         "spdx-expression-parse": "^3.0.0",
@@ -16018,12 +16023,12 @@
                 },
                 "spdx-exceptions": {
                     "version": "2.1.0",
-                    "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg=="
                 },
                 "spdx-expression-parse": {
                     "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
                     "requires": {
                         "spdx-exceptions": "^2.1.0",
@@ -16032,17 +16037,17 @@
                 },
                 "spdx-license-ids": {
                     "version": "3.0.5",
-                    "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+                    "resolved": false,
                     "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q=="
                 },
                 "split-on-first": {
                     "version": "1.1.0",
-                    "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
                 },
                 "sshpk": {
                     "version": "1.14.2",
-                    "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=",
                     "requires": {
                         "asn1": "~0.2.3",
@@ -16058,7 +16063,7 @@
                 },
                 "ssri": {
                     "version": "6.0.2",
-                    "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
                     "requires": {
                         "figgy-pudding": "^3.5.1"
@@ -16066,7 +16071,7 @@
                 },
                 "stream-each": {
                     "version": "1.2.2",
-                    "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==",
                     "requires": {
                         "end-of-stream": "^1.1.0",
@@ -16075,7 +16080,7 @@
                 },
                 "stream-iterate": {
                     "version": "1.2.0",
-                    "resolved": "https://registry.npmjs.org/stream-iterate/-/stream-iterate-1.2.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=",
                     "requires": {
                         "readable-stream": "^2.1.5",
@@ -16084,7 +16089,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+                            "resolved": false,
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -16098,7 +16103,7 @@
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -16108,7 +16113,7 @@
                 },
                 "stream-shift": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
                 },
                 "strict-uri-encode": {
@@ -16118,7 +16123,7 @@
                 },
                 "string-width": {
                     "version": "2.1.1",
-                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
                     "requires": {
                         "is-fullwidth-code-point": "^2.0.0",
@@ -16127,17 +16132,17 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "3.0.0",
-                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
                         },
                         "is-fullwidth-code-point": {
                             "version": "2.0.0",
-                            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
                         },
                         "strip-ansi": {
                             "version": "4.0.0",
-                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
                             "requires": {
                                 "ansi-regex": "^3.0.0"
@@ -16160,12 +16165,12 @@
                 },
                 "stringify-package": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg=="
                 },
                 "strip-ansi": {
                     "version": "3.0.1",
-                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
                     "requires": {
                         "ansi-regex": "^2.0.0"
@@ -16173,17 +16178,17 @@
                 },
                 "strip-eof": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
                 },
                 "strip-json-comments": {
                     "version": "2.0.1",
-                    "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
                 },
                 "supports-color": {
                     "version": "5.4.0",
-                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
                     "requires": {
                         "has-flag": "^3.0.0"
@@ -16191,7 +16196,7 @@
                 },
                 "tar": {
                     "version": "4.4.19",
-                    "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz",
+                    "resolved": false,
                     "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==",
                     "requires": {
                         "chownr": "^1.1.4",
@@ -16205,7 +16210,7 @@
                     "dependencies": {
                         "minipass": {
                             "version": "2.9.0",
-                            "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
                             "requires": {
                                 "safe-buffer": "^5.1.2",
@@ -16214,19 +16219,19 @@
                         },
                         "safe-buffer": {
                             "version": "5.2.1",
-                            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
                         },
                         "yallist": {
                             "version": "3.1.1",
-                            "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
                         }
                     }
                 },
                 "term-size": {
                     "version": "1.2.0",
-                    "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=",
                     "requires": {
                         "execa": "^0.7.0"
@@ -16234,17 +16239,17 @@
                 },
                 "text-table": {
                     "version": "0.2.0",
-                    "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ="
                 },
                 "through": {
                     "version": "2.3.8",
-                    "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+                    "resolved": false,
                     "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
                 },
                 "through2": {
                     "version": "2.0.3",
-                    "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
                     "requires": {
                         "readable-stream": "^2.1.5",
@@ -16253,7 +16258,7 @@
                     "dependencies": {
                         "readable-stream": {
                             "version": "2.3.6",
-                            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+                            "resolved": false,
                             "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
                             "requires": {
                                 "core-util-is": "~1.0.0",
@@ -16267,7 +16272,7 @@
                         },
                         "string_decoder": {
                             "version": "1.1.1",
-                            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
                             "requires": {
                                 "safe-buffer": "~5.1.0"
@@ -16277,17 +16282,17 @@
                 },
                 "timed-out": {
                     "version": "4.0.1",
-                    "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8="
                 },
                 "tiny-relative-date": {
                     "version": "1.3.0",
-                    "resolved": "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A=="
                 },
                 "tough-cookie": {
                     "version": "2.4.3",
-                    "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
                     "requires": {
                         "psl": "^1.1.24",
@@ -16296,7 +16301,7 @@
                 },
                 "tunnel-agent": {
                     "version": "0.6.0",
-                    "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
                     "requires": {
                         "safe-buffer": "^5.0.1"
@@ -16304,28 +16309,28 @@
                 },
                 "tweetnacl": {
                     "version": "0.14.5",
-                    "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+                    "resolved": false,
                     "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
                     "optional": true
                 },
                 "typedarray": {
                     "version": "0.0.6",
-                    "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+                    "resolved": false,
                     "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
                 },
                 "uid-number": {
                     "version": "0.0.6",
-                    "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
+                    "resolved": false,
                     "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE="
                 },
                 "umask": {
                     "version": "1.1.0",
-                    "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0="
                 },
                 "unique-filename": {
                     "version": "1.1.1",
-                    "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
                     "requires": {
                         "unique-slug": "^2.0.0"
@@ -16333,7 +16338,7 @@
                 },
                 "unique-slug": {
                     "version": "2.0.0",
-                    "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=",
                     "requires": {
                         "imurmurhash": "^0.1.4"
@@ -16341,7 +16346,7 @@
                 },
                 "unique-string": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
                     "requires": {
                         "crypto-random-string": "^1.0.0"
@@ -16349,17 +16354,17 @@
                 },
                 "unpipe": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
                 },
                 "unzip-response": {
                     "version": "2.0.1",
-                    "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c="
                 },
                 "update-notifier": {
                     "version": "2.5.0",
-                    "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==",
                     "requires": {
                         "boxen": "^1.2.1",
@@ -16376,7 +16381,7 @@
                 },
                 "uri-js": {
                     "version": "4.4.0",
-                    "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
                     "requires": {
                         "punycode": "^2.1.0"
@@ -16384,14 +16389,14 @@
                     "dependencies": {
                         "punycode": {
                             "version": "2.1.1",
-                            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
                         }
                     }
                 },
                 "url-parse-lax": {
                     "version": "1.0.0",
-                    "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
                     "requires": {
                         "prepend-http": "^1.0.1"
@@ -16399,17 +16404,17 @@
                 },
                 "util-deprecate": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
                 },
                 "util-extend": {
                     "version": "1.0.3",
-                    "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8="
                 },
                 "util-promisify": {
                     "version": "2.1.0",
-                    "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=",
                     "requires": {
                         "object.getownpropertydescriptors": "^2.0.3"
@@ -16417,12 +16422,12 @@
                 },
                 "uuid": {
                     "version": "3.3.3",
-                    "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
                 },
                 "validate-npm-package-license": {
                     "version": "3.0.4",
-                    "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+                    "resolved": false,
                     "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
                     "requires": {
                         "spdx-correct": "^3.0.0",
@@ -16431,7 +16436,7 @@
                 },
                 "validate-npm-package-name": {
                     "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
                     "requires": {
                         "builtins": "^1.0.3"
@@ -16439,7 +16444,7 @@
                 },
                 "verror": {
                     "version": "1.10.0",
-                    "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
                     "requires": {
                         "assert-plus": "^1.0.0",
@@ -16449,7 +16454,7 @@
                 },
                 "wcwidth": {
                     "version": "1.0.1",
-                    "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
                     "requires": {
                         "defaults": "^1.0.3"
@@ -16457,7 +16462,7 @@
                 },
                 "which": {
                     "version": "1.3.1",
-                    "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
                     "requires": {
                         "isexe": "^2.0.0"
@@ -16465,12 +16470,12 @@
                 },
                 "which-module": {
                     "version": "2.0.0",
-                    "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
                 },
                 "wide-align": {
                     "version": "1.1.2",
-                    "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
+                    "resolved": false,
                     "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
                     "requires": {
                         "string-width": "^1.0.2"
@@ -16478,7 +16483,7 @@
                     "dependencies": {
                         "string-width": {
                             "version": "1.0.2",
-                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+                            "resolved": false,
                             "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
                             "requires": {
                                 "code-point-at": "^1.0.0",
@@ -16490,7 +16495,7 @@
                 },
                 "widest-line": {
                     "version": "2.0.1",
-                    "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
                     "requires": {
                         "string-width": "^2.1.1"
@@ -16498,7 +16503,7 @@
                 },
                 "worker-farm": {
                     "version": "1.7.0",
-                    "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
                     "requires": {
                         "errno": "~0.1.7"
@@ -16506,7 +16511,7 @@
                 },
                 "wrap-ansi": {
                     "version": "5.1.0",
-                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+                    "resolved": false,
                     "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
                     "requires": {
                         "ansi-styles": "^3.2.0",
@@ -16516,17 +16521,17 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "4.1.1",
-                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
                         },
                         "is-fullwidth-code-point": {
                             "version": "2.0.0",
-                            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
                         },
                         "string-width": {
                             "version": "3.1.0",
-                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
                             "requires": {
                                 "emoji-regex": "^7.0.1",
@@ -16536,7 +16541,7 @@
                         },
                         "strip-ansi": {
                             "version": "5.2.0",
-                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                             "requires": {
                                 "ansi-regex": "^4.1.0"
@@ -16546,12 +16551,12 @@
                 },
                 "wrappy": {
                     "version": "1.0.2",
-                    "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+                    "resolved": false,
                     "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
                 },
                 "write-file-atomic": {
                     "version": "2.4.3",
-                    "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
                     "requires": {
                         "graceful-fs": "^4.1.11",
@@ -16561,27 +16566,27 @@
                 },
                 "xdg-basedir": {
                     "version": "3.0.0",
-                    "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
+                    "resolved": false,
                     "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ="
                 },
                 "xtend": {
                     "version": "4.0.1",
-                    "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
                 },
                 "y18n": {
                     "version": "4.0.1",
-                    "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ=="
                 },
                 "yallist": {
                     "version": "3.0.3",
-                    "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
                 },
                 "yargs": {
                     "version": "14.2.3",
-                    "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",
+                    "resolved": false,
                     "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==",
                     "requires": {
                         "cliui": "^5.0.0",
@@ -16599,12 +16604,12 @@
                     "dependencies": {
                         "ansi-regex": {
                             "version": "4.1.0",
-                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
                         },
                         "find-up": {
                             "version": "3.0.0",
-                            "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
                             "requires": {
                                 "locate-path": "^3.0.0"
@@ -16612,12 +16617,12 @@
                         },
                         "is-fullwidth-code-point": {
                             "version": "2.0.0",
-                            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
                         },
                         "locate-path": {
                             "version": "3.0.0",
-                            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
                             "requires": {
                                 "p-locate": "^3.0.0",
@@ -16626,7 +16631,7 @@
                         },
                         "p-limit": {
                             "version": "2.3.0",
-                            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
                             "requires": {
                                 "p-try": "^2.0.0"
@@ -16634,7 +16639,7 @@
                         },
                         "p-locate": {
                             "version": "3.0.0",
-                            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
                             "requires": {
                                 "p-limit": "^2.0.0"
@@ -16642,12 +16647,12 @@
                         },
                         "p-try": {
                             "version": "2.2.0",
-                            "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
                         },
                         "string-width": {
                             "version": "3.1.0",
-                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
                             "requires": {
                                 "emoji-regex": "^7.0.1",
@@ -16657,7 +16662,7 @@
                         },
                         "strip-ansi": {
                             "version": "5.2.0",
-                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+                            "resolved": false,
                             "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
                             "requires": {
                                 "ansi-regex": "^4.1.0"
@@ -16667,7 +16672,7 @@
                 },
                 "yargs-parser": {
                     "version": "15.0.1",
-                    "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz",
+                    "resolved": false,
                     "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
                     "requires": {
                         "camelcase": "^5.0.0",
@@ -16676,7 +16681,7 @@
                     "dependencies": {
                         "camelcase": {
                             "version": "5.3.1",
-                            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+                            "resolved": false,
                             "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
                         }
                     }
@@ -16686,7 +16691,7 @@
         "npm-run-path": {
             "version": "2.0.2",
             "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
-            "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==",
+            "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
             "requires": {
                 "path-key": "^2.0.0"
             }
@@ -16713,7 +16718,7 @@
         "number-is-nan": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
-            "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ=="
+            "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
         },
         "nwsapi": {
             "version": "2.2.0",
@@ -16724,7 +16729,7 @@
         "oauth": {
             "version": "0.9.15",
             "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
-            "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA=="
+            "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE="
         },
         "oauth-sign": {
             "version": "0.9.0",
@@ -16734,12 +16739,12 @@
         "object-assign": {
             "version": "4.1.1",
             "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-            "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
+            "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
         },
         "object-copy": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
-            "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==",
+            "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
             "requires": {
                 "copy-descriptor": "^0.1.0",
                 "define-property": "^0.2.5",
@@ -16749,7 +16754,7 @@
                 "define-property": {
                     "version": "0.2.5",
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-                    "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==",
+                    "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
                     "requires": {
                         "is-descriptor": "^0.1.0"
                     }
@@ -16762,7 +16767,7 @@
                 "kind-of": {
                     "version": "3.2.2",
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-                    "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+                    "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
                         "is-buffer": "^1.1.5"
                     }
@@ -16819,7 +16824,7 @@
                 "magicli": {
                     "version": "0.0.5",
                     "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz",
-                    "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==",
+                    "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=",
                     "requires": {
                         "commander": "^2.9.0",
                         "get-stdin": "^5.0.1",
@@ -16832,7 +16837,7 @@
         "object-visit": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
-            "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==",
+            "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
             "requires": {
                 "isobject": "^3.0.0"
             }
@@ -16894,7 +16899,7 @@
         "object.pick": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
-            "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==",
+            "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
             "requires": {
                 "isobject": "^3.0.1"
             }
@@ -16932,7 +16937,7 @@
         "once": {
             "version": "1.4.0",
             "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-            "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+            "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
             "requires": {
                 "wrappy": "1"
             }
@@ -16994,12 +16999,12 @@
         "os-homedir": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
-            "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ=="
+            "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
         },
         "os-tmpdir": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
-            "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="
+            "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
         },
         "osenv": {
             "version": "0.1.5",
@@ -17018,12 +17023,12 @@
         "p-debounce": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/p-debounce/-/p-debounce-1.0.0.tgz",
-            "integrity": "sha512-ttOxn4Yt0hzIsLLqKi/Ry9QRxW+UQKdoWHz7g99Ci57zPkqUU3kbWKAeHuv+HfRLe109acYLUY6kuVCOOqnt4g=="
+            "integrity": "sha1-y38svu/YegnrqGHhErZ1J+Yh4v0="
         },
         "p-finally": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
-            "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="
+            "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
         },
         "p-limit": {
             "version": "2.3.0",
@@ -17064,7 +17069,7 @@
         "package-json": {
             "version": "4.0.1",
             "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",
-            "integrity": "sha512-q/R5GrMek0vzgoomq6rm9OX+3PQve8sLwTirmK30YB3Cu0Bbt9OX9M/SIUnroN5BGJkzwGsFwDaRGD9EwBOlCA==",
+            "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
             "requires": {
                 "got": "^6.7.1",
                 "registry-auth-token": "^3.0.1",
@@ -17080,7 +17085,7 @@
                 "got": {
                     "version": "6.7.1",
                     "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
-                    "integrity": "sha512-Y/K3EDuiQN9rTZhBvPRWMLXIKdeD1Rj0nzunfoi0Yyn5WBEbzxXKU9Ub2X41oZBagVWOBU3MuDonFMgPWQFnwg==",
+                    "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
                     "requires": {
                         "create-error-class": "^3.0.0",
                         "duplexer3": "^0.1.4",
@@ -17196,7 +17201,7 @@
         "pascalcase": {
             "version": "0.1.1",
             "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
-            "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw=="
+            "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
         },
         "passport": {
             "version": "0.4.1",
@@ -17218,7 +17223,7 @@
         "passport-local": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
-            "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==",
+            "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=",
             "requires": {
                 "passport-strategy": "1.x.x"
             }
@@ -17238,7 +17243,7 @@
         "passport-strategy": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
-            "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA=="
+            "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ="
         },
         "path-browserify": {
             "version": "1.0.1",
@@ -17248,27 +17253,27 @@
         "path-dirname": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
-            "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q=="
+            "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA="
         },
         "path-exists": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-            "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="
+            "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
         },
         "path-is-absolute": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-            "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
+            "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
         },
         "path-is-inside": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
-            "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w=="
+            "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM="
         },
         "path-key": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
-            "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw=="
+            "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
         },
         "path-parse": {
             "version": "1.0.7",
@@ -17278,7 +17283,7 @@
         "path-to-regexp": {
             "version": "0.1.7",
             "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
-            "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+            "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
         },
         "path-type": {
             "version": "4.0.0",
@@ -17293,7 +17298,7 @@
         "pause": {
             "version": "0.0.1",
             "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
-            "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
+            "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10="
         },
         "pdf-parse": {
             "version": "1.1.1",
@@ -17341,7 +17346,7 @@
         "pend": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
-            "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="
+            "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
         },
         "perfect-scrollbar": {
             "version": "1.5.5",
@@ -17351,7 +17356,7 @@
         "performance-now": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
-            "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="
+            "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
         },
         "picocolors": {
             "version": "1.0.0",
@@ -17371,12 +17376,12 @@
         "pinkie": {
             "version": "2.0.4",
             "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
-            "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg=="
+            "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
         },
         "pinkie-promise": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
-            "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==",
+            "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
             "requires": {
                 "pinkie": "^2.0.0"
             }
@@ -17437,7 +17442,7 @@
         "plist": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz",
-            "integrity": "sha512-dL9Xc2Aj3YyBnwvCNuHmFl2LWvQacm/HEAsoVwLiuu0POboMChETt5wexpU1P6F6MnibIucXlVsMFFgNUT2IyA==",
+            "integrity": "sha1-CEtQk93JJQbiWfh0uNmxr7jHlZM=",
             "requires": {
                 "base64-js": "0.0.8",
                 "util-deprecate": "1.0.2",
@@ -17448,7 +17453,7 @@
                 "base64-js": {
                     "version": "0.0.8",
                     "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
-                    "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="
+                    "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg="
                 }
             }
         },
@@ -17488,7 +17493,7 @@
         "posix-character-classes": {
             "version": "0.1.1",
             "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
-            "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg=="
+            "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
         },
         "postcss": {
             "version": "7.0.39",
@@ -17686,13 +17691,13 @@
         "prelude-ls": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
-            "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
+            "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
             "dev": true
         },
         "prepend-http": {
             "version": "1.0.4",
             "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
-            "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg=="
+            "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="
         },
         "prettier": {
             "version": "2.7.1",
@@ -17741,7 +17746,7 @@
         "process": {
             "version": "0.11.10",
             "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
-            "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="
+            "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
         },
         "process-nextick-args": {
             "version": "2.0.1",
@@ -17764,7 +17769,7 @@
         "promise-inflight": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
-            "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==",
+            "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
             "dev": true
         },
         "prop-types": {
@@ -17815,7 +17820,7 @@
         "prosemirror-find-replace": {
             "version": "0.9.0",
             "resolved": "https://registry.npmjs.org/prosemirror-find-replace/-/prosemirror-find-replace-0.9.0.tgz",
-            "integrity": "sha512-LfhQ/Zr0PkkJpCsr9vTJ5ZPYh49mSVVG+hHJ6djT+chlCW+t2ilSxBpBG+2IeE/I5nlbcvuLLAbxeI1g3pTCpA=="
+            "integrity": "sha1-QgsENNF5xdBJD44hSNhVGpVJY4I="
         },
         "prosemirror-history": {
             "version": "1.3.0",
@@ -17907,13 +17912,13 @@
         "prr": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
-            "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
+            "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
             "dev": true
         },
         "pseudomap": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
-            "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="
+            "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
         },
         "psl": {
             "version": "1.8.0",
@@ -18145,12 +18150,12 @@
         "pure-color": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz",
-            "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA=="
+            "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4="
         },
         "q": {
             "version": "1.5.1",
             "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
-            "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw=="
+            "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
         },
         "qs": {
             "version": "6.5.3",
@@ -18171,7 +18176,7 @@
         "querystring": {
             "version": "0.2.0",
             "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
-            "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==",
+            "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
             "dev": true
         },
         "querystringify": {
@@ -18193,7 +18198,7 @@
         "random-bytes": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
-            "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ=="
+            "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
         },
         "randombytes": {
             "version": "2.1.0",
@@ -18296,7 +18301,7 @@
         "react-base16-styling": {
             "version": "0.5.3",
             "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz",
-            "integrity": "sha512-EPuchwVvYPSFFIjGpH0k6wM0HQsmJ0vCk7BSl5ryxMVFIWW4hX4Kksu4PNtxfgOxDebTLkJQ8iC7zwAql0eusg==",
+            "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=",
             "requires": {
                 "base16": "^1.0.0",
                 "lodash.curry": "^4.0.1",
@@ -18365,7 +18370,7 @@
         "react-dock": {
             "version": "0.2.4",
             "resolved": "https://registry.npmjs.org/react-dock/-/react-dock-0.2.4.tgz",
-            "integrity": "sha512-ywUJPC/TIM9PO700skka0fH4aqbrH8RojUXejZFvjtqlc5KZ+xjHqFdo4A3j+dp+0NLFZ3Nai4xzcf3FUJ9BsQ==",
+            "integrity": "sha1-5yfcdVCztzEWY13LnA4E0Lev4Xw=",
             "requires": {
                 "lodash.debounce": "^3.1.1",
                 "prop-types": "^15.5.8"
@@ -18738,7 +18743,7 @@
         "react-themeable": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz",
-            "integrity": "sha512-kl5tQ8K+r9IdQXZd8WLa+xxYN04lLnJXRVhHfdgwsUJr/SlKJxIejoc9z9obEkx1mdqbTw1ry43fxEUwyD9u7w==",
+            "integrity": "sha1-fURm3ZsrX6dQWHJ4JenxUro3mg4=",
             "requires": {
                 "object-assign": "^3.0.0"
             },
@@ -18746,7 +18751,7 @@
                 "object-assign": {
                     "version": "3.0.0",
                     "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz",
-                    "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ=="
+                    "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I="
                 }
             }
         },
@@ -18801,7 +18806,7 @@
         "read-pkg": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
-            "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==",
+            "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
             "requires": {
                 "load-json-file": "^1.0.0",
                 "normalize-package-data": "^2.3.2",
@@ -18811,7 +18816,7 @@
                 "path-type": {
                     "version": "1.1.0",
                     "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
-                    "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==",
+                    "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
                     "requires": {
                         "graceful-fs": "^4.1.2",
                         "pify": "^2.0.0",
@@ -18823,7 +18828,7 @@
         "read-pkg-up": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
-            "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==",
+            "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
             "requires": {
                 "find-up": "^1.0.0",
                 "read-pkg": "^1.0.0"
@@ -18832,7 +18837,7 @@
                 "find-up": {
                     "version": "1.1.2",
                     "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
-                    "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==",
+                    "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
                     "requires": {
                         "path-exists": "^2.0.0",
                         "pinkie-promise": "^2.0.0"
@@ -18841,7 +18846,7 @@
                 "path-exists": {
                     "version": "2.1.0",
                     "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
-                    "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==",
+                    "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
                     "requires": {
                         "pinkie-promise": "^2.0.0"
                     }
@@ -18887,12 +18892,12 @@
         "readline": {
             "version": "1.3.0",
             "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz",
-            "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg=="
+            "integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw="
         },
         "rechoir": {
             "version": "0.6.2",
             "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
-            "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==",
+            "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
             "requires": {
                 "resolve": "^1.1.6"
             }
@@ -18918,7 +18923,7 @@
         "redent": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
-            "integrity": "sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==",
+            "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
             "requires": {
                 "indent-string": "^2.1.0",
                 "strip-indent": "^1.0.1"
@@ -18927,7 +18932,7 @@
         "reduce-flatten": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz",
-            "integrity": "sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ=="
+            "integrity": "sha1-JYx479FT3fk8tWEjf2EYTzaW4yc="
         },
         "redux": {
             "version": "4.2.0",
@@ -18984,7 +18989,7 @@
         "registry-url": {
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
-            "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==",
+            "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
             "requires": {
                 "rc": "^1.0.1"
             }
@@ -19002,7 +19007,7 @@
         "relateurl": {
             "version": "0.2.7",
             "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
-            "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog=="
+            "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
         },
         "remark-gfm": {
             "version": "3.0.1",
@@ -19039,7 +19044,7 @@
         "remove-trailing-separator": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
-            "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw=="
+            "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
         },
         "renderkid": {
             "version": "3.0.0",
@@ -19125,12 +19130,12 @@
         "repeat-string": {
             "version": "1.6.1",
             "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
-            "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w=="
+            "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
         },
         "repeating": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
-            "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==",
+            "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
             "requires": {
                 "is-finite": "^1.0.0"
             }
@@ -19234,7 +19239,7 @@
         "require-directory": {
             "version": "2.1.1",
             "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
-            "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
+            "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
         },
         "require-from-string": {
             "version": "2.0.2",
@@ -19249,7 +19254,7 @@
         "require-package-name": {
             "version": "2.0.1",
             "resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz",
-            "integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q=="
+            "integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk="
         },
         "require_optional": {
             "version": "1.0.1",
@@ -19263,14 +19268,14 @@
                 "resolve-from": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
-                    "integrity": "sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ=="
+                    "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
                 }
             }
         },
         "requires-port": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
-            "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+            "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
             "dev": true
         },
         "resize-observer-polyfill": {
@@ -19316,7 +19321,7 @@
         "resolve-url": {
             "version": "0.2.1",
             "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
-            "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg=="
+            "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
         },
         "responselike": {
             "version": "2.0.0",
@@ -19351,7 +19356,7 @@
         "retry": {
             "version": "0.12.0",
             "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
-            "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+            "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
             "dev": true
         },
         "reveal.js": {
@@ -19362,7 +19367,7 @@
         "right-align": {
             "version": "0.1.3",
             "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
-            "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==",
+            "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
             "requires": {
                 "align-text": "^0.1.1"
             }
@@ -19397,7 +19402,7 @@
         "run-queue": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
-            "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==",
+            "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
             "dev": true,
             "requires": {
                 "aproba": "^1.1.1"
@@ -19444,7 +19449,7 @@
         "safe-regex": {
             "version": "1.1.0",
             "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
-            "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==",
+            "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
             "requires": {
                 "ret": "~0.1.10"
             }
@@ -19532,13 +19537,13 @@
         "scss-loader": {
             "version": "0.0.1",
             "resolved": "https://registry.npmjs.org/scss-loader/-/scss-loader-0.0.1.tgz",
-            "integrity": "sha512-SbT/smRJjkvvdHSEdAYAplosVkrtaSwwgUlnQCOuDS5sOKNjrS/eYCMvKeV6+YxK5cCOCsOJZd3vltrXatFp+g==",
+            "integrity": "sha1-6uAXueDzjBKlMtslwiC5Avs05nE=",
             "dev": true
         },
         "scss-tokenizer": {
             "version": "0.2.3",
             "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
-            "integrity": "sha512-dYE8LhncfBUar6POCxMTm0Ln+erjeczqEvCJib5/7XNkdw1FkUGgwMPY360FY0FgPWQxHWCx29Jl3oejyGLM9Q==",
+            "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
             "requires": {
                 "js-base64": "^2.1.8",
                 "source-map": "^0.4.2"
@@ -19547,7 +19552,7 @@
                 "source-map": {
                     "version": "0.4.4",
                     "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
-                    "integrity": "sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==",
+                    "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
                     "requires": {
                         "amdefine": ">=0.0.4"
                     }
@@ -19562,17 +19567,17 @@
         "section-iterator": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz",
-            "integrity": "sha512-xvTNwcbeDayXotnV32zLb3duQsP+4XosHpb/F+tu6VzEZFmIjzPdNk6/O+QOOx5XTh08KL2ufdXeCO33p380pQ=="
+            "integrity": "sha1-v0RNev7rlK1Dw5rS+yYVFifMuio="
         },
         "select": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
-            "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
+            "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
         },
         "select-hose": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
-            "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==",
+            "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
             "dev": true
         },
         "selfsigned": {
@@ -19592,12 +19597,12 @@
         "semver-compare": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
-            "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="
+            "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w="
         },
         "semver-diff": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
-            "integrity": "sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==",
+            "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
             "requires": {
                 "semver": "^5.0.3"
             }
@@ -19661,7 +19666,7 @@
         "serve-index": {
             "version": "1.9.1",
             "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
-            "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==",
+            "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
             "dev": true,
             "requires": {
                 "accepts": "~1.3.4",
@@ -19691,7 +19696,7 @@
                 "http-errors": {
                     "version": "1.6.3",
                     "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
-                    "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==",
+                    "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
                     "dev": true,
                     "requires": {
                         "depd": "~1.1.2",
@@ -19703,13 +19708,13 @@
                 "inherits": {
                     "version": "2.0.3",
                     "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-                    "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
+                    "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
                     "dev": true
                 },
                 "ms": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-                    "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+                    "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
                     "dev": true
                 },
                 "setprototypeof": {
@@ -19740,7 +19745,7 @@
         "set-blocking": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-            "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
+            "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
         },
         "set-value": {
             "version": "2.0.1",
@@ -19756,7 +19761,7 @@
                 "extend-shallow": {
                     "version": "2.0.1",
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-                    "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+                    "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
                         "is-extendable": "^0.1.0"
                     }
@@ -19766,7 +19771,7 @@
         "setimmediate": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
-            "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
+            "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
         },
         "setprototypeof": {
             "version": "1.2.0",
@@ -19928,7 +19933,7 @@
         "shebang-command": {
             "version": "1.2.0",
             "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
-            "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
+            "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
             "requires": {
                 "shebang-regex": "^1.0.0"
             }
@@ -19936,7 +19941,7 @@
         "shebang-regex": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
-            "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ=="
+            "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
         },
         "shelljs": {
             "version": "0.8.5",
@@ -19971,7 +19976,7 @@
         "simple-assign": {
             "version": "0.1.0",
             "resolved": "https://registry.npmjs.org/simple-assign/-/simple-assign-0.1.0.tgz",
-            "integrity": "sha512-otdSSQzuVsmDoe5MnSm4ZgHd5sl0ak6A1CTjW1R/DUHQ8xoZuU1NUzf9x6n9Dvp3nxpvW51WNMQ/7rQ9432xDg=="
+            "integrity": "sha1-F/0wZqXz13OPUDIbsPFMooHMS6o="
         },
         "simple-concat": {
             "version": "1.0.1",
@@ -19991,7 +19996,7 @@
         "simple-swizzle": {
             "version": "0.2.2",
             "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
-            "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+            "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
             "requires": {
                 "is-arrayish": "^0.3.1"
             },
@@ -20006,7 +20011,7 @@
         "slash": {
             "version": "1.0.0",
             "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
-            "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==",
+            "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
             "dev": true
         },
         "slice-ansi": {
@@ -20023,7 +20028,7 @@
         "sliced": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
-            "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA=="
+            "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
         },
         "snapdragon": {
             "version": "0.8.2",
@@ -20051,7 +20056,7 @@
                 "define-property": {
                     "version": "0.2.5",
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-                    "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==",
+                    "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
                     "requires": {
                         "is-descriptor": "^0.1.0"
                     }
@@ -20059,7 +20064,7 @@
                 "extend-shallow": {
                     "version": "2.0.1",
                     "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-                    "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+                    "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
                     "requires": {
                         "is-extendable": "^0.1.0"
                     }
@@ -20067,7 +20072,7 @@
                 "ms": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-                    "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+                    "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
                 }
             }
         },
@@ -20084,7 +20089,7 @@
                 "define-property": {
                     "version": "1.0.0",
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
-                    "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==",
+                    "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
                     "requires": {
                         "is-descriptor": "^1.0.0"
                     }
@@ -20133,7 +20138,7 @@
                 "kind-of": {
                     "version": "3.2.2",
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-                    "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+                    "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
                         "is-buffer": "^1.1.5"
                     }
@@ -20229,7 +20234,7 @@
                 "component-emitter": {
                     "version": "1.2.1",
                     "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
-                    "integrity": "sha512-jPatnhd33viNplKjqXKRkGU345p263OIWzDL2wH3LGIGp5Kojo+uXizHmOADRvhGFFTnJqX3jBAKP6vvmSDKcA=="
+                    "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
                 },
                 "debug": {
                     "version": "4.1.1",
@@ -20242,7 +20247,7 @@
                 "isarray": {
                     "version": "2.0.1",
                     "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
-                    "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
+                    "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
                 }
             }
         },
@@ -20312,7 +20317,7 @@
         "source-map": {
             "version": "0.5.7",
             "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-            "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="
+            "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
         },
         "source-map-resolve": {
             "version": "0.5.3",
@@ -20355,7 +20360,7 @@
         "sparse-bitfield": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
-            "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+            "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
             "optional": true,
             "requires": {
                 "memory-pager": "^1.0.2"
@@ -20458,7 +20463,7 @@
         "split-skip": {
             "version": "0.0.2",
             "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.2.tgz",
-            "integrity": "sha512-weHOi8BolsDnGIwhhWHbA+wKSuSpvWwjRrdj8SdbIIis2vSwOE37CQP8x3EleuzxanUr3AK8BdUy4MkiOULPZg=="
+            "integrity": "sha1-2J2Iu9L3Pka1FYqjcKVhIk6A1GE="
         },
         "split-string": {
             "version": "3.1.0",
@@ -20471,7 +20476,7 @@
         "sprintf-js": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
-            "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
+            "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
         },
         "sshpk": {
             "version": "1.17.0",
@@ -20523,7 +20528,7 @@
                 "define-property": {
                     "version": "0.2.5",
                     "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
-                    "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==",
+                    "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
                     "requires": {
                         "is-descriptor": "^0.1.0"
                     }
@@ -20602,7 +20607,7 @@
                 "ms": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-                    "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+                    "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
                 }
             }
         },
@@ -20692,7 +20697,7 @@
                 "magicli": {
                     "version": "0.0.5",
                     "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz",
-                    "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==",
+                    "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=",
                     "requires": {
                         "commander": "^2.9.0",
                         "get-stdin": "^5.0.1",
@@ -20734,7 +20739,7 @@
                 "get-stdin": {
                     "version": "4.0.1",
                     "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
-                    "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw=="
+                    "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4="
                 }
             }
         },
@@ -21107,7 +21112,7 @@
         "to-array": {
             "version": "0.1.4",
             "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
-            "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
+            "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A=="
         },
         "to-fast-properties": {
             "version": "2.0.0",
@@ -21130,7 +21135,7 @@
                 "kind-of": {
                     "version": "3.2.2",
                     "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
-                    "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+                    "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
                     "requires": {
                         "is-buffer": "^1.1.5"
                     }
@@ -21178,7 +21183,7 @@
                 "nopt": {
                     "version": "1.0.10",
                     "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
-                    "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
+                    "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
                     "requires": {
                         "abbrev": "1"
                     }
@@ -21313,7 +21318,7 @@
                 "arrify": {
                     "version": "1.0.1",
                     "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
-                    "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
+                    "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
                     "dev": true
                 }
             }
@@ -21717,7 +21722,7 @@
                 "jsonfile": {
                     "version": "4.0.0",
                     "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
-                    "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+                    "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
                     "requires": {
                         "graceful-fs": "^4.1.6"
                     }
@@ -21747,12 +21752,12 @@
                 "camelcase": {
                     "version": "1.2.1",
                     "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
-                    "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g=="
+                    "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk="
                 },
                 "cliui": {
                     "version": "2.1.0",
                     "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
-                    "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==",
+                    "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
                     "requires": {
                         "center-align": "^0.1.1",
                         "right-align": "^0.1.1",
@@ -21833,7 +21838,7 @@
                 "pako": {
                     "version": "0.2.9",
                     "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
-                    "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="
+                    "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU="
                 }
             }
         },
@@ -21978,7 +21983,7 @@
                 "has-value": {
                     "version": "0.3.1",
                     "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
-                    "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==",
+                    "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
                     "requires": {
                         "get-value": "^2.0.3",
                         "has-values": "^0.1.4",
@@ -21988,7 +21993,7 @@
                         "isobject": {
                             "version": "2.1.0",
                             "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
-                            "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==",
+                            "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
                             "requires": {
                                 "isarray": "1.0.0"
                             }
@@ -21998,7 +22003,7 @@
                 "has-values": {
                     "version": "0.1.4",
                     "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
-                    "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ=="
+                    "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E="
                 }
             }
         },
@@ -22070,7 +22075,7 @@
                 "punycode": {
                     "version": "1.3.2",
                     "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
-                    "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==",
+                    "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
                     "dev": true
                 }
             }
@@ -22623,7 +22628,7 @@
                 "ansi-regex": {
                     "version": "2.1.1",
                     "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
-                    "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
+                    "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
                     "dev": true
                 },
                 "debug": {
@@ -22648,7 +22653,7 @@
                 "memory-fs": {
                     "version": "0.4.1",
                     "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
-                    "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==",
+                    "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
                     "dev": true,
                     "requires": {
                         "errno": "^0.1.3",
@@ -22694,7 +22699,7 @@
                 "resolve-cwd": {
                     "version": "2.0.0",
                     "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
-                    "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==",
+                    "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
                     "dev": true,
                     "requires": {
                         "resolve-from": "^3.0.0"
@@ -22703,7 +22708,7 @@
                 "resolve-from": {
                     "version": "3.0.0",
                     "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
-                    "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==",
+                    "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
                     "dev": true
                 },
                 "semver": {
@@ -23117,7 +23122,7 @@
                 "lodash": {
                     "version": "3.10.1",
                     "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
-                    "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ=="
+                    "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y="
                 }
             }
         },
@@ -23244,7 +23249,7 @@
         "yeast": {
             "version": "0.1.2",
             "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
-            "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
+            "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg=="
         },
         "yn": {
             "version": "2.0.0",
diff --git a/package.json b/package.json
index ccbc73a79..c1a3c5fa4 100644
--- a/package.json
+++ b/package.json
@@ -222,6 +222,7 @@
         "jszip": "^3.7.1",
         "lodash": "^4.17.21",
         "material-ui": "^0.20.2",
+        "md5-file": "^5.0.0",
         "memorystream": "^0.3.1",
         "mobile-detect": "^1.4.5",
         "mobx": "^5.15.7",
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 886dd974b..465bb40f0 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -3,7 +3,6 @@ import { DateField } from '../../fields/DateField';
 import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, Opt } from '../../fields/Doc';
 import { InkTool } from '../../fields/InkField';
 import { List } from '../../fields/List';
-import { ScriptField } from '../../fields/ScriptField';
 import { Cast, ScriptCast } from '../../fields/Types';
 import { denormalizeEmail, distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util';
 import { returnFalse } from '../../Utils';
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 0a06b6e00..ee3f46818 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -42,6 +42,7 @@ interface CollectionViewProps_ extends FieldViewProps {
     isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently)
     layoutEngine?: () => string;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void;
+    setBrushViewer?: (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => void;
 
     // property overrides for child documents
     childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 7a075a7ff..a82bd2dc8 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -231,7 +231,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             pinDoc.presentationTargetDoc = doc;
             pinDoc.title = doc.title + ' - Slide';
             pinDoc.data = new List<Doc>(); // the children of the alias' layout are the presentation slide children.  the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data
-            pinDoc.presMovement = pinProps?.pinDocView ? PresMovement.None : PresMovement.Zoom;
+            pinDoc.presMovement = pinProps?.pinDocView && !pinProps?.pinWithView ? PresMovement.None : PresMovement.Zoom;
             pinDoc.groupWithUp = false;
             pinDoc.context = curPres;
             // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements.  That way the PresBox could configure all of its children at render time
@@ -367,7 +367,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     @action
     focusFunc = (doc: Doc, options?: DocFocusOptions) => {
         const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap;
-        if (shrinkwrap && this._document) {
+        if (options?.willZoom !== false && shrinkwrap && this._document) {
             const focusSpeed = NumCast(this._document.focusSpeed, 500);
             shrinkwrap();
             this._document._viewTransition = `transform ${focusSpeed}ms`;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6927d429d..3cc425745 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -88,6 +88,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     private _cachedPool: Map<string, PoolData> = new Map();
     private _lastTap = 0;
     private _batch: UndoManager.Batch | undefined = undefined;
+    private _brushtimer: any;
+    private _brushtimer1: any;
 
     // private isWritingMode: boolean = true;
     // private writingModeDocs: Doc[] = [];
@@ -114,6 +116,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     @observable _marqueeRef = React.createRef<HTMLDivElement>();
     @observable _marqueeViewRef = React.createRef<MarqueeView>();
     @observable ChildDrag: DocumentView | undefined; // child document view being dragged.  needed to update drop areas of groups when a group item is dragged.
+    @observable _brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 }; // highlighted region of freeform canvas used by presentations to indicate a region
 
     @computed get views() {
         const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele);
@@ -1520,6 +1523,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     componentDidMount() {
         super.componentDidMount?.();
         this.props.setContentView?.(this);
+        this.props.setBrushViewer?.(this.brushView);
         setTimeout(
             action(() => {
                 this._firstRender = false;
@@ -1881,6 +1885,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         </div>
                     ) : null}
                     <CollectionFreeFormViewPannableContents
+                        brushView={this._brushedView}
                         isAnnotationOverlay={this.isAnnotationOverlay}
                         isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable}
                         transform={this.contentTransform}
@@ -1916,9 +1921,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         }
     };
 
+    @action
+    brushView = (viewport: { width: number; height: number; panX: number; panY: number }) => {
+        this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2, opacity: 1 };
+        this._brushtimer1 && clearTimeout(this._brushtimer1);
+        this._brushtimer && clearTimeout(this._brushtimer);
+        this._brushtimer1 = setTimeout(
+            action(() => {
+                this._brushedView.opacity = 0;
+                this._brushtimer = setTimeout(
+                    action(() => (this._brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 })),
+                    500
+                );
+            }),
+            1000
+        );
+    };
+
     render() {
         TraceMobx();
-        const clientRect = this._mainCont?.getBoundingClientRect();
         return (
             <div
                 className={'collectionfreeformview-container'}
@@ -2004,6 +2025,7 @@ interface CollectionFreeFormViewPannableContentsProps {
     presPinView?: boolean;
     isAnnotationOverlay: boolean | undefined;
     isAnnotationOverlayScrollable: boolean | undefined;
+    brushView: { panX: number; panY: number; width: number; height: number; opacity: number };
 }
 
 @observer
@@ -2123,6 +2145,21 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
                     //willChange: "transform"
                 }}>
                 {this.props.children()}
+                {!this.props.brushView.width ? null : (
+                    <div
+                        className="collectionFreeFormView-brushView"
+                        style={{
+                            zIndex: 1000,
+                            opacity: this.props.brushView.opacity,
+                            border: 'orange solid 2px',
+                            position: 'absolute',
+                            transform: `translate(${this.props.brushView.panX}px, ${this.props.brushView.panY}px)`,
+                            width: this.props.brushView.width,
+                            height: this.props.brushView.height,
+                            transition: 'opacity 2s',
+                        }}
+                    />
+                )}
                 {this.presPaths}
                 {this.progressivize}
                 {this.zoomProgressivize}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 58a00bbac..584c9690f 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -425,13 +425,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
     @undoBatch
     @action
     pinWithView = async () => {
-        const scale = Math.min(this.props.PanelWidth() / this.Bounds.width, this.props.PanelHeight() / this.Bounds.height);
         const doc = this.props.Document;
         const viewOptions: PinViewProps = {
             bounds: this.Bounds,
-            scale: scale,
         };
-        TabDocView.PinDoc(doc, { pinWithView: viewOptions });
+        TabDocView.PinDoc(doc, { pinWithView: viewOptions, pinDocView: true });
         MarqueeOptionsMenu.Instance.fadeOut(true);
         this.hideMarquee();
     };
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2966c2a22..f24ceb5ae 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -86,6 +86,7 @@ export interface DocComponentView {
     updateIcon?: () => void; // updates the icon representation of the document
     getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
     scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
+    brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void;
     setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
     reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height.  However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
     shrinkWrap?: () => void; // requests a document to display all of its contents with no white space.  currently only implemented (needed?) for freeform views
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 345407c2f..001d9a5a6 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -195,6 +195,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
     }
 
+    brushView = (view: { width: number; height: number; panX: number; panY: number }) => {
+        this._pdfViewer?.brushView(view);
+    };
     scrollFocus = (doc: Doc, smooth: boolean) => {
         let didToggle = false;
         if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 6a929ef80..ab59e6112 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -44,7 +44,6 @@ export interface PinProps {
 
 export interface PinViewProps {
     bounds: MarqueeViewBounds;
-    scale: number;
 }
 
 @observer
@@ -362,17 +361,32 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         bestTarget._viewTransition = presTransitionTime;
         if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth;
         if (temporal) bestTarget._currentTimecode = activeItem.presStartTime;
-        if (scrollable) bestTarget._scrollTop = activeItem.presPinViewScroll;
+        if (scrollable) {
+            bestTarget._scrollTop = activeItem.presPinViewScroll;
+            const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number'));
+            if (contentBounds) {
+                const dv = DocumentManager.Instance.getDocumentView(bestTarget)?.ComponentView;
+                dv?.brushView?.({ panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] });
+            }
+        }
         if (dataview) Doc.GetProto(bestTarget).data = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
         if (textview) Doc.GetProto(bestTarget).text = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
         if (pannable) {
-            bestTarget._panX = activeItem.presPinViewX;
-            bestTarget._panY = activeItem.presPinViewY;
-            bestTarget._viewScale = activeItem.presPinViewScale;
             const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number'));
             if (contentBounds) {
+                const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] };
+                bestTarget._panX = viewport.panX;
+                bestTarget._panY = viewport.panY;
                 const dv = DocumentManager.Instance.getDocumentView(bestTarget);
-                dv && (bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), dv.props.PanelWidth() / (contentBounds[2] - contentBounds[0])));
+                if (dv) {
+                    const computedScale = NumCast(activeItem.presZoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height);
+                    activeItem.presMovement === 'zoom' && (bestTarget._viewScale = activeItem.presZoom !== undefined ? computedScale : Math.min(computedScale, NumCast(bestTarget._viewScale)));
+                    dv.ComponentView?.brushView?.(viewport);
+                }
+            } else {
+                bestTarget._panX = activeItem.presPinViewX;
+                bestTarget._panY = activeItem.presPinViewY;
+                activeItem.presMovement === 'zoom' && (bestTarget._viewScale = activeItem.presPinViewScale);
             }
         }
         return setTimeout(() => (bestTarget._viewTransition = undefined), transTime + 10);
@@ -389,7 +403,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             pinDoc.presPinView = true;
             pinDoc.presPinViewX = bounds.left + bounds.width / 2;
             pinDoc.presPinViewY = bounds.top + bounds.height / 2;
-            pinDoc.presPinViewScale = pinProps.pinWithView.scale;
             pinDoc.presPinViewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
         }
         if (pinProps?.pinDocView) {
@@ -401,12 +414,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             pinDoc.presWidth = NumCast(targetDoc.width);
             pinDoc.presHeight = NumCast(targetDoc.height);
 
-            if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop;
+            if (scrollable) {
+                pinDoc.presPinViewScroll = pinDoc._scrollTop;
+            }
             if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth;
             if (temporal) pinDoc.presEndTime = NumCast((pinDoc.presStartTime = pinDoc._currentTimecode)) + 0.1;
             if (textview) pinDoc.presData = targetDoc.text instanceof ObjectField ? targetDoc.text[Copy]() : targetDoc.text;
             if (dataview) pinDoc.presData = targetDoc.data instanceof ObjectField ? targetDoc.data[Copy]() : targetDoc.data;
-            if (pannable) {
+            if (pannable || scrollable) {
                 const panX = NumCast(pinDoc._panX);
                 const panY = NumCast(pinDoc._panY);
                 const pw = NumCast(pinProps.panelWidth);
@@ -1131,7 +1146,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         let scale = Number(number) / 100;
         if (change) scale += change;
         if (scale < 0.01) scale = 0.01;
-        if (scale > 1.5) scale = 1.5;
+        if (scale > 1) scale = 1;
         this.selectedArray.forEach(doc => (doc.presZoom = scale));
     };
 
@@ -1246,28 +1261,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     })}>
                     <div className="ribbon-box">
                         Movement
-                        {isPresCollection || (isPresCollection && isPinWithView) ? (
-                            <div className="ribbon-property" style={{ marginLeft: 0, height: 25, textAlign: 'left', paddingLeft: 5, paddingRight: 5, fontSize: 10 }}>
-                                {this.scrollable ? 'Scroll to pinned view' : !isPinWithView ? 'No movement' : 'Pan & Zoom to pinned view'}
-                            </div>
-                        ) : (
-                            <div
-                                className="presBox-dropdown"
-                                onClick={action(e => {
-                                    e.stopPropagation();
-                                    this._openMovementDropdown = !this._openMovementDropdown;
-                                })}
-                                style={{ borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5, border: this._openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
-                                {this.movementName(activeItem)}
-                                <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
-                                <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={StopEvent} style={{ display: this._openMovementDropdown ? 'grid' : 'none' }}>
-                                    {presMovement(PresMovement.None)}
-                                    {presMovement(PresMovement.Zoom)}
-                                    {presMovement(PresMovement.Pan)}
-                                    {presMovement(PresMovement.Jump)}
-                                </div>
+                        <div
+                            className="presBox-dropdown"
+                            onClick={action(e => {
+                                e.stopPropagation();
+                                this._openMovementDropdown = !this._openMovementDropdown;
+                            })}
+                            style={{ borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5, border: this._openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
+                            {this.movementName(activeItem)}
+                            <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+                            <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={StopEvent} style={{ display: this._openMovementDropdown ? 'grid' : 'none' }}>
+                                {isPresCollection || (isPresCollection && isPinWithView) ? null : presMovement(PresMovement.None)}
+                                {presMovement(PresMovement.Zoom)}
+                                {presMovement(PresMovement.Pan)}
+                                {isPresCollection || (isPresCollection && isPinWithView) ? null : presMovement(PresMovement.Jump)}
                             </div>
-                        )}
+                        </div>
                         <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
                             <div className="presBox-subheading">Zoom (% screen filled)</div>
                             <div className="ribbon-property">
@@ -1282,7 +1291,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 </div>
                             </div>
                         </div>
-                        {inputter('0', '1', '150', zoom, activeItem.presMovement === PresMovement.Zoom, this.setZoom)}
+                        {inputter('0', '1', '100', zoom, activeItem.presMovement === PresMovement.Zoom, this.setZoom)}
                         <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
                             <div className="presBox-subheading">Movement Speed</div>
                             <div className="ribbon-property">
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 53d969c0a..5925c0392 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -64,6 +64,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     private _styleRule: any; // stylesheet rule for making hyperlinks clickable
     private _retries = 0; // number of times tried to create the PDF viewer
     private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void);
+    private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }) => void);
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -174,9 +175,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
         return focusSpeed;
     };
-    crop = (region: Doc | undefined, addCrop?: boolean) => {
-        return this.props.crop(region, addCrop);
-    };
+    crop = (region: Doc | undefined, addCrop?: boolean) => this.props.crop(region, addCrop);
+    brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view);
 
     @action
     setupPdfJsViewer = async () => {
@@ -446,6 +446,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
     };
 
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
+    setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => (this._setBrushViewer = func);
 
     @action
     onZoomWheel = (e: React.WheelEvent) => {
@@ -512,6 +513,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 fieldKey={this.props.fieldKey + '-annotations'}
                 CollectionView={undefined}
                 setPreviewCursor={this.setPreviewCursor}
+                setBrushViewer={this.setBrushViewer}
                 PanelHeight={this.panelHeight}
                 PanelWidth={this.panelWidth}
                 ScreenToLocalTransform={this.overlayTransform}
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index c38f603a4..c28ae686b 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -2,7 +2,7 @@ import { green, red } from 'colors';
 import { ExifImage } from 'exif';
 import * as exifr from 'exifr';
 import { File } from 'formidable';
-import { createWriteStream, existsSync, readFileSync, rename, unlinkSync, writeFile } from 'fs';
+import { createReadStream, createWriteStream, exists, existsSync, readFileSync, rename, unlinkSync, writeFile } from 'fs';
 import * as path from 'path';
 import { basename } from 'path';
 import * as sharp from 'sharp';
@@ -22,6 +22,7 @@ const parse = require('pdf-parse');
 const ffmpeg = require('fluent-ffmpeg');
 const fs = require('fs');
 const requestImageSize = require('../client/util/request-image-size');
+const md5File = require('md5-file');
 
 export enum SizeSuffix {
     Small = '_s',
@@ -99,34 +100,58 @@ export namespace DashUploadUtils {
         };
     }
 
+    function resolveExistingFile(name: string, path: string, directory: Directory, type?: string, duration?: number, rawText?: string) {
+        const data = { size: 0, path, name, type: type ?? '' };
+        const file = { ...data, toJSON: () => ({ ...data, filename: data.path.replace(/.*\//, ''), mtime: duration?.toString(), mime: '', toJson: () => undefined as any }) };
+        return {
+            source: file,
+            result: {
+                accessPaths: {
+                    agnostic: getAccessPaths(directory, path),
+                },
+                rawText,
+                duration,
+            },
+        };
+    }
+
     export function uploadYoutube(videoId: string): Promise<Upload.FileResponse> {
         return new Promise<Upload.FileResponse<Upload.FileInformation>>((res, rej) => {
             console.log('Uploading YouTube video: ' + videoId);
             const name = videoId;
             const path = name.replace(/^-/, '__') + '.mp4';
-            exec(`youtube-dl -o ${path} "https://www.youtube.com/watch?v=${videoId}" -f "mp4[filesize<5M]/bestvideo[filesize<5M]+bestaudio/bestvideo+bestaudio"`, (error: any, stdout: any, stderr: any) => {
-                if (error) {
-                    console.log(`error: Error: ${error.message}`);
-                    res({
-                        source: {
-                            size: 0,
-                            path,
-                            name,
-                            type: '',
-                            toJSON: () => ({ name, path }),
-                        },
-                        result: { name: 'failed youtube query', message: `Could not upload YouTube video (${videoId}).  Error: ${error.message}` },
-                    });
-                } else {
-                    exec(`youtube-dl -o ${path} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => {
-                        const time = Array.from(stdout.trim().split(':')).reverse();
-                        const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
-                        const data = { size: 0, path, name, type: 'video/mp4' };
-                        const file = { ...data, toJSON: () => ({ ...data, filename: data.path.replace(/.*\//, ''), mtime: duration.toString(), mime: '', toJson: () => undefined as any }) };
-                        res(MoveParsedFile(file, Directory.videos));
-                    });
-                }
-            });
+            const finalPath = serverPathToFile(Directory.videos, path);
+            if (existsSync(finalPath)) {
+                exec(`youtube-dl -o ${finalPath} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => {
+                    const time = Array.from(stdout.trim().split(':')).reverse();
+                    const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
+                    res(resolveExistingFile(name, finalPath, Directory.videos, 'video/mp4', duration, undefined));
+                });
+            } else {
+                exec(`youtube-dl -o ${path} "https://www.youtube.com/watch?v=${videoId}" -f "mp4[filesize<5M]/bestvideo[filesize<5M]+bestaudio/bestvideo+bestaudio"`, (error: any, stdout: any, stderr: any) => {
+                    if (error) {
+                        console.log(`error: Error: ${error.message}`);
+                        res({
+                            source: {
+                                size: 0,
+                                path,
+                                name,
+                                type: '',
+                                toJSON: () => ({ name, path }),
+                            },
+                            result: { name: 'failed youtube query', message: `Could not upload YouTube video (${videoId}).  Error: ${error.message}` },
+                        });
+                    } else {
+                        exec(`youtube-dl -o ${path} "https://www.youtube.com/watch?v=${videoId}" --get-duration`, (error: any, stdout: any, stderr: any) => {
+                            const time = Array.from(stdout.trim().split(':')).reverse();
+                            const duration = (time.length > 2 ? Number(time[2]) * 1000 * 60 : 0) + (time.length > 1 ? Number(time[1]) * 60 : 0) + (time.length > 0 ? Number(time[0]) : 0);
+                            const data = { size: 0, path, name, type: 'video/mp4' };
+                            const file = { ...data, toJSON: () => ({ ...data, filename: data.path.replace(/.*\//, ''), mtime: duration.toString(), mime: '', toJson: () => undefined as any }) };
+                            res(MoveParsedFile(file, Directory.videos));
+                        });
+                    }
+                });
+            }
         });
     }
 
@@ -144,6 +169,7 @@ export namespace DashUploadUtils {
                     const result = await UploadImage(path, basename(path));
                     return { source: file, result };
                 }
+                fs.unlink(path, () => {});
                 return { source: file, result: { name: 'Unsupported image format', message: `Could not upload unsupported file (${name}). Please convert to an .jpg` } };
             case 'video':
                 if (format.includes('x-matroska')) {
@@ -167,21 +193,25 @@ export namespace DashUploadUtils {
                             res();
                         })
                     );
-                    if (abort) return { source: file, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${name}). Please convert to an .mp4` } };
-                    // bcz: instead of aborting, we could convert the file using the code below to an mp4.  Problem is that this takes a long time and will clog up the server.
-                    // await new Promise(res =>
-                    //     ffmpeg(file.path)
-                    //         .videoCodec('libx264') // this will copy the data instead of reencode it
-                    //         .audioCodec('mp2')
-                    //         .save(file.path.replace('.MOV', '.mp4').replace('.mov', '.mp4'))
-                    //         .on('end', res)
-                    // );
-                    // file.path = file.path.replace('.mov', '.mp4').replace('.MOV', '.mp4');
-                    // format = '.mp4';
+                    if (abort) {
+                        // bcz: instead of aborting, we could convert the file using the code below to an mp4.  Problem is that this takes a long time and will clog up the server.
+                        // await new Promise(res =>
+                        //     ffmpeg(file.path)
+                        //         .videoCodec('libx264') // this will copy the data instead of reencode it
+                        //         .audioCodec('mp2')
+                        //         .save(file.path.replace('.MOV', '.mp4').replace('.mov', '.mp4'))
+                        //         .on('end', res)
+                        // );
+                        // file.path = file.path.replace('.mov', '.mp4').replace('.MOV', '.mp4');
+                        // format = '.mp4';
+                        fs.unlink(path, () => {});
+                        return { source: file, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${name}). Please convert to an .mp4` } };
+                    }
                 }
                 if (videoFormats.includes(format)) {
                     return MoveParsedFile(file, Directory.videos);
                 }
+                fs.unlink(path, () => {});
                 return { source: file, result: { name: 'Unsupported video format', message: `Could not upload unsupported file (${name}). Please convert to an .mp4` } };
             case 'application':
                 if (applicationFormats.includes(format)) {
@@ -195,6 +225,7 @@ export namespace DashUploadUtils {
                 if (audioFormats.includes(format)) {
                     return UploadAudio(file, format);
                 }
+                fs.unlink(path, () => {});
                 return { source: file, result: { name: 'Unsupported audio format', message: `Could not upload unsupported file (${name}). Please convert to an .mp3` } };
             case 'text':
                 if (types[1] == 'csv') {
@@ -203,20 +234,28 @@ export namespace DashUploadUtils {
         }
 
         console.log(red(`Ignoring unsupported file (${name}) with upload type (${type}).`));
+        fs.unlink(path, () => {});
         return { source: file, result: new Error(`Could not upload unsupported file (${name}) with upload type (${type}).`) };
     }
 
     async function UploadPdf(file: File) {
-        const { path: sourcePath } = file;
-        const dataBuffer = readFileSync(sourcePath);
+        const fileKey = (await md5File(file.path)) + '.pdf';
+        const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
+        if (fExists(fileKey, Directory.pdfs) && fExists(textFilename, Directory.text)) {
+            return new Promise<Upload.FileResponse>(res => {
+                const textFilename = `${fileKey.substring(0, fileKey.length - 4)}.txt`;
+                const readStream = createReadStream(serverPathToFile(Directory.text, textFilename));
+                var rawText = '';
+                readStream.on('data', chunk => (rawText += chunk.toString())).on('end', () => res(resolveExistingFile(file.name, fileKey, Directory.pdfs, file.type, undefined, rawText)));
+            });
+        }
+        const dataBuffer = readFileSync(file.path);
         const result: ParsedPDF = await parse(dataBuffer);
         await new Promise<void>((resolve, reject) => {
-            const name = path.basename(sourcePath);
-            const textFilename = `${name.substring(0, name.length - 4)}.txt`;
             const writeStream = createWriteStream(serverPathToFile(Directory.text, textFilename));
             writeStream.write(result.text, error => (error ? reject(error) : resolve()));
         });
-        return MoveParsedFile(file, Directory.pdfs, undefined, result.text);
+        return MoveParsedFile(file, Directory.pdfs, undefined, result.text, undefined, fileKey);
     }
 
     async function UploadCsv(file: File) {
@@ -368,9 +407,9 @@ export namespace DashUploadUtils {
      * @param suffix If the file doesn't have a suffix and you want to provide it one
      * to appear in the new location
      */
-    export async function MoveParsedFile(file: formidable.File, destination: Directory, suffix: string | undefined = undefined, text?: string, duration?: number): Promise<Upload.FileResponse> {
+    export async function MoveParsedFile(file: formidable.File, destination: Directory, suffix: string | undefined = undefined, text?: string, duration?: number, targetName?: string): Promise<Upload.FileResponse> {
         const { path: sourcePath } = file;
-        let name = path.basename(sourcePath);
+        let name = targetName ?? path.basename(sourcePath);
         suffix && (name += suffix);
         return new Promise(resolve => {
             const destinationPath = serverPathToFile(destination, name);
@@ -391,6 +430,11 @@ export namespace DashUploadUtils {
         });
     }
 
+    export function fExists(name: string, destination: Directory) {
+        const destinationPath = serverPathToFile(destination, name);
+        return existsSync(destinationPath);
+    }
+
     export function getAccessPaths(directory: Directory, fileName: string) {
         return {
             client: clientPathToFile(directory, fileName),
-- 
cgit v1.2.3-70-g09d2


From 6e22c212e288eb3e69740a78732f81485c97a577 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 4 Oct 2022 16:53:01 -0400
Subject: removed gitlike options from collection context menu.  removed
 createDashboard from context menus since it has a UI button

---
 src/client/util/CurrentUserUtils.ts             | 21 ++++++++++-------
 src/client/views/collections/CollectionView.tsx |  7 ++----
 src/client/views/collections/TabDocView.tsx     | 30 ++++++++++++++++++-------
 src/client/views/collections/TreeView.tsx       |  6 ++---
 4 files changed, 40 insertions(+), 24 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index ad14ab141..aa230aa44 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -462,28 +462,33 @@ export class CurrentUserUtils {
     static setupDashboards(doc: Doc, field:string) {
         var myDashboards = DocCast(doc[field]);
 
+        const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`;
         const newDashboard = `createNewDashboard()`;
+
         const reqdBtnOpts:DocumentOptions = { _forceActive: true,  _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true,
              title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", system: true };
         const reqdBtnScript = {onClick: newDashboard,}
         const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
        
+        const contextMenuScripts = [/*newDashboard*/] as string[];
+        const contextMenuLabels = [/*"Create New Dashboard"*/]  as string[];
+        const contextMenuIcons = [/*"plus"*/]  as string[];
+        const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)', 'resetDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters
+        const childContextMenuFilters =  ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, undefined as any];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts
+        const childContextMenuLabels =  ["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters
+        const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
         const reqdOpts:DocumentOptions = {
             title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true,
             targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true,  treeViewTruncateTitleWidth: 150, ignoreClick: true, 
             buttonMenu: true, buttonMenuDoc: newDashboardButton, childDropAction: "alias",
             _showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
-            contextMenuLabels: new List<string>(["Create New Dashboard"]),
-            contextMenuIcons: new List<string>(["plus"]),
-            childContextMenuLabels: new List<string>(["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"]),// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters
-            childContextMenuIcons: new List<string>(["chalkboard", "tv", "camera", "users", "times", "trash"]), // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
+            contextMenuLabels:new List<string>(contextMenuLabels),
+            contextMenuIcons:new List<string>(contextMenuIcons),
+            childContextMenuLabels:new List<string>(childContextMenuLabels),
+            childContextMenuIcons:new List<string>(childContextMenuIcons),
             explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files."
         };
         myDashboards = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts);
-        const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`;
-        const contextMenuScripts = [newDashboard];
-        const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)', 'resetDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters
-        const childContextMenuFilters =  ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, undefined as any];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts
         if (Cast(myDashboards.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) {
             myDashboards.contextMenuScripts = new List<ScriptField>(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!));
         } 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ee3f46818..d88d59205 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -155,7 +155,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         !Doc.noviceMode && subItems.push({ description: 'Map', event: () => func(CollectionViewType.Map), icon: 'globe-americas' });
         subItems.push({ description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' });
 
-        if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) {
+        if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) {
             const existingVm = ContextMenu.Instance.findByDescription(category);
             const catItems = existingVm && 'subitems' in existingVm ? existingVm.subitems : [];
             catItems.push({ description: 'Add a Perspective...', addDivider: true, noexpand: true, subitems: subItems, icon: 'eye' });
@@ -189,7 +189,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             }
             !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' });
 
-            if (!Doc.noviceMode) {
+            if (!Doc.noviceMode && false) {
                 optionItems.push({
                     description: 'Create Branch',
                     event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), 'add:right'),
@@ -206,9 +206,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
                     icon: 'project-diagram',
                 });
             }
-            if (this.Document._viewType === CollectionViewType.Docking) {
-                optionItems.push({ description: 'Create Dashboard', event: () => DashboardView.createNewDashboard(), icon: 'project-diagram' });
-            }
 
             !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' });
 
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index b1f57f3fd..9938245ea 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -96,6 +96,18 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             const iconWrap = document.createElement('div');
             const closeWrap = document.createElement('div');
 
+            const getChild = () => {
+                let child = this.view?.ContentDiv?.children[0];
+                while (child?.children.length) {
+                    const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
+                    if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
+                    if (next?.className?.toString().includes(DashFieldView.name)) break;
+                    if (next) child = next;
+                    else break;
+                }
+                return child;
+            };
+
             titleEle.size = StrCast(doc.title).length + 3;
             titleEle.value = doc.title;
             titleEle.onkeydown = (e: KeyboardEvent) => {
@@ -119,14 +131,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                         action(e => {
                             if (this.view) {
                                 SelectionManager.SelectView(this.view, false);
-                                let child = this.view.ContentDiv!.children[0];
-                                while (child.children.length) {
-                                    const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string');
-                                    if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break;
-                                    if (next?.className?.toString().includes(DashFieldView.name)) break;
-                                    if (next) child = next;
-                                    else break;
-                                }
+                                const child = getChild();
                                 simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
                             } else {
                                 this._activated = true;
@@ -165,6 +170,15 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 }
             };
 
+            tab.element[0].oncontextmenu = (e: MouseEvent) => {
+                let child = getChild();
+                if (child) {
+                    simulateMouseClick(child, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30);
+                    e.stopPropagation();
+                    e.preventDefault();
+                }
+            };
+
             // select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected
             titleEle.onpointerdown = action((e: any) => {
                 if (e.target.className !== 'lm_iconWrap') {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index c34a6faaa..25f57b045 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -782,7 +782,7 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null);
 
-    refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document);
+    refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document, {});
     ignoreEvent = (e: any) => {
         if (this.props.isContentActive(true)) {
             e.stopPropagation();
@@ -1037,7 +1037,7 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     @computed get renderBorder() {
         const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None);
-        const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } };
+        const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) ?? {}) as { [key: string]: { color: string; label: string } };
         return (
             <div className={`treeView-border${this.props.treeView.outlineMode ? TreeViewType.outline : ''}`} style={{ borderColor: sortings[sorting]?.color }}>
                 {!this.treeViewOpen ? null : this.renderContent}
@@ -1057,7 +1057,7 @@ export class TreeView extends React.Component<TreeViewProps> {
     render() {
         TraceMobx();
         const hideTitle = this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode;
-        return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? (
+        return this.props.renderedIds?.indexOf(this.doc[Id]) !== -1 ? (
             '<' + this.doc.title + '>' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
         ) : (
             <div
-- 
cgit v1.2.3-70-g09d2


From eb5f75785fd28acb50f1b30434e89223fff00185 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 7 Oct 2022 00:33:04 -0400
Subject: improvements to sizing webpages

---
 src/client/views/SidebarAnnos.tsx               |  2 +-
 src/client/views/collections/CollectionView.tsx |  1 -
 src/client/views/nodes/LinkDocPreview.tsx       |  2 +-
 src/client/views/nodes/WebBox.tsx               | 28 ++++++++++++++++++-------
 4 files changed, 22 insertions(+), 11 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 90d9c3c43..12f41394d 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -73,7 +73,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
         this.allMetadata.map(tag => (target[tag] = tag));
         DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on');
         this.addDocument(target);
-        this._stackRef.current?.focusDocument(target);
+        this._stackRef.current?.focusDocument(target, {});
         return target;
     };
     makeDocUnfiltered = (doc: Doc) => {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index d88d59205..dcaad5632 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -15,7 +15,6 @@ import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
-import { DashboardView } from '../DashboardView';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index f50524e4e..27e79a83b 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -228,7 +228,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
                         <DocumentView
                             ref={r => {
                                 const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
-                                targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor);
+                                targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor, {});
                             }}
                             Document={this._targetDoc!}
                             moveDocument={returnFalse}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 8288810b1..460edb7c2 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -243,6 +243,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
     }
     @action componentWillUnmount() {
+        this._iframetimeout && clearTimeout(this._iframetimeout);
+        this._iframetimeout = undefined;
         Object.values(this._disposers).forEach(disposer => disposer?.());
         // this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
         // this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp);
@@ -382,6 +384,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return sheet.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length);
     }
 
+    _iframetimeout: any = undefined;
     @action
     iframeLoaded = (e: any) => {
         const iframe = this._iframe;
@@ -409,16 +412,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             }
             this.submitURL(requrlraw, undefined, true);
         }
-        if (iframe?.contentDocument) {
-            iframe.contentDocument.addEventListener('pointerup', this.iframeUp);
-            iframe.contentDocument.addEventListener('pointerdown', this.iframeDown);
-            this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument.body.scrollHeight);
-            setTimeout(
-                action(() => (this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0))),
+        const iframeContent = iframe?.contentDocument;
+        if (iframeContent) {
+            iframeContent.addEventListener('pointerup', this.iframeUp);
+            iframeContent.addEventListener('pointerdown', this.iframeDown);
+            const initHeights = () => {
+                this._scrollHeight = Math.max(this._scrollHeight, (iframeContent.body.children[0] as any)?.scrollHeight || 0);
+                if (this._scrollHeight) {
+                    this.rootDoc.nativeHeight = Math.min(NumCast(this.rootDoc.nativeHeight), this._scrollHeight);
+                    this.layoutDoc.height = Math.min(this.layoutDoc[HeightSym](), (this.layoutDoc[WidthSym]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
+                }
+            };
+            initHeights();
+            this._iframetimeout && clearTimeout(this._iframetimeout);
+            this._iframetimeout = setTimeout(
+                action(() => initHeights),
                 5000
             );
             iframe.setAttribute('enable-annotation', 'true');
-            iframe.contentDocument.addEventListener(
+            iframeContent.addEventListener(
                 'click',
                 undoBatch(
                     action((e: MouseEvent) => {
@@ -882,7 +894,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 onWheel={StopEvent} // block wheel events from propagating since they're handled by the iframe
                 onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
                 onPointerDown={this.onMarqueeDown}>
-                <div className={'webBox-innerContent'} style={{ height: this._webPageHasBeenRendered ? NumCast(this.scrollHeight, 50) : '100%', pointerEvents }}>
+                <div className={'webBox-innerContent'} style={{ height: this._webPageHasBeenRendered && this._scrollHeight ? this.scrollHeight : '100%', pointerEvents }}>
                     {this.content}
                     {<div style={{ display: DragManager.docsBeingDragged.length ? 'none' : undefined, mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>}
                     {renderAnnotations(this.opaqueFilter)}
-- 
cgit v1.2.3-70-g09d2


From 2fc88a931cb2fc3408297b000208990633445585 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 27 Oct 2022 15:57:09 -0400
Subject: cleaned up treeViews to work across PresBox/ppt slides/collections/ -
 modified DocumentView rendering to remove pres box refs.  fixed mainView
 width.

---
 src/client/documents/Documents.ts                  |   4 +-
 src/client/util/CurrentUserUtils.ts                |   8 +-
 src/client/views/DocumentDecorations.tsx           |  18 +-
 src/client/views/LightboxView.tsx                  |  26 +--
 src/client/views/MainView.tsx                      |   1 +
 src/client/views/StyleProvider.tsx                 |   5 +-
 .../views/collections/CollectionStackingView.tsx   |   2 +
 .../views/collections/CollectionTreeView.scss      |   2 -
 .../views/collections/CollectionTreeView.tsx       |  62 ++++---
 src/client/views/collections/CollectionView.tsx    |   2 +
 src/client/views/collections/TabDocView.tsx        |   1 -
 src/client/views/collections/TreeView.scss         |   3 +-
 src/client/views/collections/TreeView.tsx          | 179 +++++++++----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   6 +-
 src/client/views/nodes/DocumentView.tsx            |  37 ++--
 src/client/views/nodes/FieldView.tsx               |   2 -
 src/client/views/nodes/VideoBox.tsx                |   2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   8 +-
 src/client/views/nodes/trails/PresBox.tsx          |   7 +-
 src/client/views/nodes/trails/PresElementBox.scss  |  14 +-
 src/client/views/nodes/trails/PresElementBox.tsx   | 197 ++++++++++++---------
 src/fields/Doc.ts                                  |   3 +
 23 files changed, 301 insertions(+), 290 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2d82ffdf9..bbb896d6e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -335,7 +335,6 @@ export class DocumentOptions {
     treeViewHideHeaderIfTemplate?: boolean; // whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)
     treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view
     treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
-    treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically
     treeViewChildDoubleClick?: ScriptField; //
     // Action Button
     buttonMenu?: boolean; // whether a action button should be displayed
@@ -395,7 +394,6 @@ export namespace Docs {
                         _xMargin: 10,
                         _yMargin: 10,
                         nativeDimModifiable: true,
-                        treeViewGrowsHorizontally: true,
                         nativeHeightUnfrozen: true,
                         forceReflow: true,
                         links: '@links(self)',
@@ -570,7 +568,7 @@ export namespace Docs {
                 DocumentType.SLIDER,
                 {
                     layout: { view: SliderBox, dataField: defaultDataKey },
-                    options: { links: '@links(self)', treeViewGrowsHorizontally: true },
+                    options: { links: '@links(self)' },
                 },
             ],
             [
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 2bc464127..a2974177e 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -480,7 +480,7 @@ export class CurrentUserUtils {
         const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
         const reqdOpts:DocumentOptions = {
             title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true,
-            targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true,  treeViewTruncateTitleWidth: 150, ignoreClick: true, 
+            targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true,  treeViewTruncateTitleWidth: 350, ignoreClick: true, 
             buttonMenu: true, buttonMenuDoc: newDashboardButton, childDropAction: "alias",
             _showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
             contextMenuLabels:new List<string>(contextMenuLabels),
@@ -518,7 +518,7 @@ export class CurrentUserUtils {
         const reqdOpts:DocumentOptions = { _showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true, 
             title: "My Documents", buttonMenu: true, buttonMenuDoc: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", system: true,
             isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, boxShadow: "0 0", childDontRegisterViews: true, 
-            treeViewTruncateTitleWidth: 150, ignoreClick: true, childDropAction: "alias",
+            treeViewTruncateTitleWidth: 350, ignoreClick: true, childDropAction: "alias",
             childContextMenuLabels: new List<string>(["Create new folder"]),
             childContextMenuIcons: new List<string>(["plus"]),
             explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
@@ -535,7 +535,7 @@ export class CurrentUserUtils {
     static setupRecentlyClosed(doc: Doc, field:string) {
         const reqdOpts:DocumentOptions = { _showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, 
             title: "My Recently Closed", buttonMenu: true, childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "alias", system: true,
-            treeViewTruncateTitleWidth: 150, ignoreClick: true,  boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same",
+            treeViewTruncateTitleWidth: 350, ignoreClick: true,  boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same",
             contextMenuLabels: new List<string>(["Empty recently closed"]),
             contextMenuIcons:new List<string>(["trash"]),
             explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
@@ -561,7 +561,7 @@ export class CurrentUserUtils {
         const reqdOpts:DocumentOptions = {
             _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
             boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true,
-            treeViewHideTitle: true, treeViewTruncateTitleWidth: 150
+            treeViewHideTitle: true, treeViewTruncateTitleWidth: 350
         };
         if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" });
         return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 10517d829..06eb6c6d7 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -112,7 +112,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             }
                         }
                         //@ts-ignore
-                        const titleField = +this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
+                        const titleField = +this._accumulatedTitle == this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
                         Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
 
                         if (d.rootDoc.syncLayoutFieldWithTitle) {
@@ -345,7 +345,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
     setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => {
         const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]);
         const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2];
-        const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.rootDoc._jitterRotation) / 180) * Math.PI);
+        const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.rootDoc._rotation) / 180) * Math.PI);
         seldocview.rootDoc.rotateCenterX = final.x / NumCast(seldocview.layoutDoc._width);
         seldocview.rootDoc.rotateCenterY = final.y / NumCast(seldocview.layoutDoc._height);
     };
@@ -381,7 +381,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         const infos = new Map<Doc, { unrotatedDocPos: { x: number; y: number }; startRotCtr: { x: number; y: number }; accumRot: number }>();
         const seldocview = SelectionManager.Views()[0];
         SelectionManager.Views().forEach(dv => {
-            const accumRot = (NumCast(dv.rootDoc._jitterRotation) / 180) * Math.PI;
+            const accumRot = (NumCast(dv.rootDoc._rotation) / 180) * Math.PI;
             const localRotCtr = dv.props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y);
             const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.rootDoc.width) / 2, localRotCtr[1] - NumCast(dv.rootDoc.height) / 2];
             const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot);
@@ -396,7 +396,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     infos.set(dv.rootDoc, { unrotatedDocPos, startRotCtr, accumRot: isAbs ? angle : accumRot + angle });
                     dv.rootDoc.x = infos.get(dv.rootDoc)!.unrotatedDocPos.x - (endRotCtr.x - startRotCtr.x);
                     dv.rootDoc.y = infos.get(dv.rootDoc)!.unrotatedDocPos.y - (endRotCtr.y - startRotCtr.y);
-                    dv.rootDoc._jitterRotation = ((isAbs ? 0 : NumCast(dv.rootDoc._jitterRotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360
+                    dv.rootDoc._rotation = ((isAbs ? 0 : NumCast(dv.rootDoc._rotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360
                 })
             );
         };
@@ -416,7 +416,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                 return false;
             }, // moveEvent
             action(() => {
-                const oldRotation = NumCast(seldocview.rootDoc._jitterRotation);
+                const oldRotation = NumCast(seldocview.rootDoc._rotation);
                 const diff = oldRotation - Math.round(oldRotation / 45) * 45;
                 if (Math.abs(diff) < 5) {
                     if (selectedInk.length) {
@@ -611,7 +611,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         }
                     } else {
                         const rotCtr = [NumCast(doc._width) / 2, NumCast(doc._height) / 2];
-                        const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._jitterRotation) / 180) * Math.PI);
+                        const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._rotation) / 180) * Math.PI);
 
                         const maxHeight = doc.nativHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
                         dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH);
@@ -619,7 +619,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         dH && (doc._autoHeight = false);
 
                         const rotCtr2 = [NumCast(doc._width) / 2, NumCast(doc._height) / 2];
-                        const tlRotated2 = Utils.rotPt(-rotCtr2[0], -rotCtr2[1], (NumCast(doc._jitterRotation) / 180) * Math.PI);
+                        const tlRotated2 = Utils.rotPt(-rotCtr2[0], -rotCtr2[1], (NumCast(doc._rotation) / 180) * Math.PI);
                         doc.x = NumCast(doc.x) + tlRotated.x + rotCtr[0] - (tlRotated2.x + rotCtr2[0]); // doc shifts by amount topleft moves because rotation is about center of doc
                         doc.y = NumCast(doc.y) + tlRotated.y + rotCtr[1] - (tlRotated2.y + rotCtr2[1]);
                     }
@@ -695,7 +695,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
             const loccenter = Utils.rotPt(
                 NumCast(seldocview.rootDoc.rotateCenterX) * NumCast(seldocview.layoutDoc._width),
                 NumCast(seldocview.rootDoc.rotateCenterY) * NumCast(seldocview.layoutDoc._height),
-                (NumCast(seldocview.rootDoc._jitterRotation) / 180) * Math.PI
+                (NumCast(seldocview.rootDoc._rotation) / 180) * Math.PI
             );
             return seldocview.props
                 .ScreenToLocalTransform()
@@ -783,7 +783,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight));
 
         const useRotation = seldocview.rootDoc.type !== DocumentType.EQUATION; // when do we want an object to not rotate?
-        const rotation = NumCast(seldocview.rootDoc._jitterRotation);
+        const rotation = NumCast(seldocview.rootDoc._rotation);
 
         const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : '';
 
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index bd6cea28a..22b0380a2 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -289,29 +289,29 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                             })}
                             Document={LightboxView.LightboxDoc}
                             DataDoc={undefined}
+                            PanelWidth={this.lightboxWidth}
+                            PanelHeight={this.lightboxHeight}
                             LayoutTemplate={LightboxView.LightboxDocTemplate}
-                            addDocument={undefined}
                             isDocumentActive={returnFalse}
                             isContentActive={returnTrue}
-                            addDocTab={this.addDocTab}
-                            pinToPres={TabDocView.PinDoc}
-                            onBrowseClick={MainView.Instance.exploreMode}
+                            styleProvider={DefaultStyleProvider}
+                            ScreenToLocalTransform={this.lightboxScreenToLocal}
+                            renderDepth={0}
                             rootSelected={returnTrue}
                             docViewPath={returnEmptyDoclist}
                             docFilters={this.docFilters}
-                            removeDocument={undefined}
-                            styleProvider={DefaultStyleProvider}
-                            ScreenToLocalTransform={this.lightboxScreenToLocal}
-                            PanelWidth={this.lightboxWidth}
-                            PanelHeight={this.lightboxHeight}
-                            focus={DocUtils.DefaultFocus}
-                            whenChildContentsActiveChanged={emptyFunction}
-                            bringToFront={emptyFunction}
                             docRangeFilters={returnEmptyFilter}
                             searchFilterDocs={returnEmptyDoclist}
                             ContainingCollectionView={undefined}
                             ContainingCollectionDoc={undefined}
-                            renderDepth={0}
+                            addDocument={undefined}
+                            removeDocument={undefined}
+                            whenChildContentsActiveChanged={emptyFunction}
+                            addDocTab={this.addDocTab}
+                            pinToPres={TabDocView.PinDoc}
+                            bringToFront={emptyFunction}
+                            onBrowseClick={MainView.Instance.exploreMode}
+                            focus={DocUtils.DefaultFocus}
                         />
                     </GestureOverlay>
                 </div>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 03c0642ed..4dc1ebd99 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -647,6 +647,7 @@ export class MainView extends React.Component {
                         e.preventDefault();
                     }}
                     style={{
+                        width: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
                         minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
                         transform: LightboxView.LightboxDoc ? 'scale(0.0001)' : undefined,
                     }}>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 2eb3dd532..bc8bd7b7f 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -85,7 +85,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
     const isAnnotated = property.includes(':annotated');
     const isOpen = property.includes(':open');
     const fieldKey = props?.fieldKey ? props.fieldKey + '-' : isCaption ? 'caption-' : '';
-    const comicStyle = () => doc && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic';
     const isBackground = () => doc && BoolCast(doc._lockedPosition);
     const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
     const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
@@ -167,11 +166,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
         case StyleProp.TitleHeight:
             return 15;
         case StyleProp.BorderPath:
-            return comicStyle() && props?.renderDepth && doc?.type !== DocumentType.INK
+            return Doc.IsComicStyle(doc) && props?.renderDepth && doc?.type !== DocumentType.INK
                 ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 }
                 : { path: undefined, width: 0 };
         case StyleProp.JitterRotation:
-            return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
+            return Doc.IsComicStyle(doc) ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0;
         case StyleProp.HeaderMargin:
             return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) ||
                 (doc?.type === DocumentType.RTF && !showTitle()?.includes('noMargin')) ||
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index ba29b1d6f..77b47ed82 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -350,6 +350,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 searchFilterDocs={this.searchFilterDocs}
                 ContainingCollectionDoc={this.props.CollectionView?.props.Document}
                 ContainingCollectionView={this.props.CollectionView}
+                xPadding={NumCast(this.layoutDoc._childXPadding, this.props.childXPadding)}
+                yPadding={NumCast(this.layoutDoc._childYPadding, this.props.childYPadding)}
                 addDocument={this.props.addDocument}
                 moveDocument={this.props.moveDocument}
                 removeDocument={this.props.removeDocument}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 1aef29b2f..3785b7d61 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -13,8 +13,6 @@
     width: 100%;
     position: relative;
     top: 0;
-    padding-left: 10px;
-    padding-right: 10px;
     background: $light-gray;
     font-size: 13px;
     overflow: auto;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 1169f692a..0ff89c5a7 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -58,7 +58,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     private _isDisposing = false; // notes that instance is in process of being disposed
     private refList: Set<any> = new Set(); // list of tree view items to monitor for height changes
     private observer: any; // observer for monitoring tree view items.
-    private static expandViewLabelSize = 20;
 
     @computed get doc() {
         return this.props.Document;
@@ -100,7 +99,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         Object.values(this._disposers).forEach(disposer => disposer?.());
     }
 
+    shrinkWrap = () => {}; // placeholder to allow setContentView to work
+
     componentDidMount() {
+        //this.props.setContentView?.(this);
         this._disposers.autoheight = reaction(
             () => this.rootDoc.autoHeight,
             auto => auto && this.computeHeight(),
@@ -197,9 +199,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     onTreeDrop = (e: React.DragEvent, addDocs?: (docs: Doc[]) => void) => this.onExternalDrop(e, {}, addDocs);
 
     @undoBatch
-    makeTextCollection = (childDocs: Doc[]) => {
-        this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true);
-    };
+    makeTextCollection = (childDocs: Doc[]) => this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true);
 
     get editableTitle() {
         return (
@@ -258,6 +258,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         const icons = StrListCast(this.doc.childContextMenuIcons);
         return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
     };
+    headerFields = () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields);
     @computed get treeViewElements() {
         TraceMobx();
         const dropAction = StrCast(this.doc.childDropAction) as dropActionType;
@@ -281,7 +282,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             this.isContentActive,
             this.panelWidth,
             this.props.renderDepth,
-            () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields),
+            this.headerFields,
             [],
             this.props.onCheckedClick,
             this.onChildClick,
@@ -304,7 +305,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                 className="collectionTreeView-titleBar"
                 ref={action((r: any) => (this._titleRef = r) && (this._titleHeight = r.getBoundingClientRect().height * this.props.ScreenToLocalTransform().Scale))}
                 key={this.doc[Id]}
-                style={!this.outlineMode ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
+                style={!this.outlineMode ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}}>
                 {this.outlineMode ? this.documentTitle : this.editableTitle}
             </div>
         );
@@ -372,7 +373,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     documentTitleHeight = () => (this.layoutDoc?.[HeightSym]() || 0) - NumCast(this.layoutDoc.autoHeightMargins);
     truncateTitleWidth = () => this.treeViewtruncateTitleWidth;
     onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
-    panelWidth = () => Math.max(0, this.props.PanelWidth() - this.marginX() - CollectionTreeView.expandViewLabelSize) * (this.props.NativeDimScaling?.() || 1);
+    panelWidth = () => Math.max(0, this.props.PanelWidth() - 2 * this.marginX() * (this.props.NativeDimScaling?.() || 1));
 
     addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false;
     remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false;
@@ -396,7 +397,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     className="collectionTreeView-contents"
                     key="tree"
                     style={{
-                        ...(!titleBar ? { paddingLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
+                        ...(!titleBar ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}),
                         overflow: 'auto',
                         width: '100%',
                         height: '100%',
@@ -405,9 +406,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                     <div
                         className="collectionTreeView-container"
                         style={{
-                            transform: this.outlineMode ? `scale(${this.nativeDimScaling})` : '',
-                            paddingLeft: `${this.marginX()}px`,
-                            width: this.outlineMode ? `calc(${100 / this.nativeDimScaling}%)` : '',
+                            marginLeft: `${this.marginX()}px`,
                             minHeight: `calc(100% - ${this._titleHeight}px)`,
                         }}
                         onContextMenu={this.onContextMenu}>
@@ -432,24 +431,29 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
     render() {
         TraceMobx();
 
-        return !(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? (
-            <CollectionFreeFormView
-                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
-                isAnnotationOverlay={true}
-                isAnnotationOverlayScrollable={true}
-                childDocumentsActive={this.props.isDocumentActive}
-                fieldKey={this.props.fieldKey + '-annotations'}
-                dropAction={'move'}
-                select={emptyFunction}
-                addDocument={this.addAnnotationDocument}
-                removeDocument={this.remAnnotationDocument}
-                moveDocument={this.moveAnnotationDocument}
-                bringToFront={emptyFunction}
-                renderDepth={this.props.renderDepth + 1}>
-                {this.contentFunc}
-            </CollectionFreeFormView>
-        ) : (
-            this.contentFunc()
+        const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1) || 1;
+        return (
+            <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
+                {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? (
+                    <CollectionFreeFormView
+                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                        isAnnotationOverlay={true}
+                        isAnnotationOverlayScrollable={true}
+                        childDocumentsActive={this.props.isDocumentActive}
+                        fieldKey={this.props.fieldKey + '-annotations'}
+                        dropAction={'move'}
+                        select={emptyFunction}
+                        addDocument={this.addAnnotationDocument}
+                        removeDocument={this.remAnnotationDocument}
+                        moveDocument={this.moveAnnotationDocument}
+                        bringToFront={emptyFunction}
+                        renderDepth={this.props.renderDepth + 1}>
+                        {this.contentFunc}
+                    </CollectionFreeFormView>
+                ) : (
+                    this.contentFunc()
+                )}
+            </div>
         );
     }
 }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index dcaad5632..9f63a11aa 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -54,6 +54,8 @@ interface CollectionViewProps_ extends FieldViewProps {
     childHideDecorationTitle?: () => boolean;
     childHideResizeHandles?: () => boolean;
     childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
+    childXPadding?: number;
+    childYPadding?: number;
     childLayoutString?: string;
     childIgnoreNativeSize?: boolean;
     childClickScript?: ScriptField;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index a6cfbd6b2..cde5132a3 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -256,7 +256,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             pinDoc.treeViewChildrenOnRoot = true; // tree view will look for hierarchical children on the root doc, not the data doc.
             pinDoc.treeViewFieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field
             pinDoc.treeViewExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view
-            pinDoc.treeViewGrowsHorizontally = true; // the document expands horizontally when displayed as a tree view header
             pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
             const presArray: Doc[] = PresBox.Instance?.sortArray();
             const size: number = PresBox.Instance?.selectedArray.size;
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 57bb5274d..83fee013a 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -24,6 +24,7 @@
         // width: $TREE_BULLET_WIDTH;
         width: 100%;
         height: 100%;
+        position: absolute;
 
         .treeView-expandIcon {
             display: none;
@@ -118,7 +119,7 @@
     display: flex; // needed for PresBox's treeView
     border: transparent 1px solid;
     align-items: center;
-    width: 100%;
+    width: max-content;
     border-radius: 5px;
 
     &:hover {
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index b9f86fa6b..1e97eee37 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -10,7 +10,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -321,6 +321,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             _viewType: CollectionViewType.Tree,
             hideLinkButton: true,
             _showSidebar: true,
+            _fitWidth: true,
             treeViewType: TreeViewType.outline,
             x: 0,
             y: 0,
@@ -398,29 +399,22 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
     docTransform = () => this.refTransform(this._dref?.ContentRef?.current);
     getTransform = () => this.refTransform(this._tref.current);
-    docWidth = () => {
-        const layoutDoc = this.layoutDoc;
-        const aspect = Doc.NativeAspect(layoutDoc);
-        if (layoutDoc._fitWidth) return Math.min(this.props.panelWidth() - treeBulletWidth(), layoutDoc[WidthSym]());
-        if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.panelWidth() - treeBulletWidth()));
-        return Math.min((this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.NativeDimScaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]());
-    };
-    docHeight = () => {
-        const layoutDoc = this.layoutDoc;
-        return Math.max(
-            70,
-            Math.min(
-                this.MAX_EMBED_HEIGHT,
-                (() => {
-                    const aspect = Doc.NativeAspect(layoutDoc);
-                    if (aspect) return this.docWidth() / (aspect || 1);
-                    return layoutDoc._fitWidth
-                        ? !Doc.NativeHeight(this.doc)
-                            ? NumCast(this.props.containerCollection._height)
-                            : Math.min((this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)))
-                        : layoutDoc[HeightSym]() || 50;
-                })()
-            )
+    embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1);
+    embeddedPanelHeight = () => {
+        console.log(this.props.treeView.rootDoc.title);
+        const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
+        return Math.min(
+            layoutDoc[HeightSym](),
+            this.MAX_EMBED_HEIGHT,
+            (() => {
+                const aspect = Doc.NativeAspect(layoutDoc);
+                if (aspect) return this.embeddedPanelWidth() / (aspect || 1);
+                return layoutDoc._fitWidth
+                    ? !Doc.NativeHeight(layoutDoc)
+                        ? NumCast(layoutDoc._height) //this.props.containerCollection._height)
+                        : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)))
+                    : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym]();
+            })()
         );
     };
 
@@ -512,28 +506,6 @@ export class TreeView extends React.Component<TreeViewProps> {
         return rows;
     }
 
-    rtfWidth = () => {
-        const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
-        return Math.min(layout[WidthSym](), this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.NativeDimScaling?.() || 1);
-    };
-    rtfHeight = () => {
-        const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc;
-        return this.rtfWidth() <= layout[WidthSym]() ? Math.min(layout[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
-    };
-    rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), treeBulletWidth());
-    expandPanelHeight = () => {
-        if (this.layoutDoc._fitWidth) return this.docHeight();
-        const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym]();
-        const docAspect = this.docWidth() / this.docHeight();
-        return docAspect < aspect ? this.docWidth() / aspect : this.docHeight();
-    };
-    expandPanelWidth = () => {
-        if (this.layoutDoc._fitWidth) return this.docWidth();
-        const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym]();
-        const docAspect = this.docWidth() / this.docHeight();
-        return docAspect > aspect ? this.docHeight() * aspect : this.docWidth();
-    };
-
     @computed get renderContent() {
         TraceMobx();
         const expandKey = this.treeViewExpandedView;
@@ -792,15 +764,18 @@ export class TreeView extends React.Component<TreeViewProps> {
     titleStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
 
+        const treeView = this.props.treeView;
         // prettier-ignore
         switch (property.split(':')[0]) {
             case StyleProp.Opacity:         return this.props.treeView.outlineMode ? undefined : 1;
             case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
             case StyleProp.Highlighting:    if (this.props.treeView.outlineMode) return undefined;
+            case StyleProp.Hidden:          return false;
+            case StyleProp.BoxShadow:       return undefined;
             case StyleProp.DocContents:
                 const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc);
                 const highlightColor = ['transparent', 'rgb(68, 118, 247)', 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
-                return this.props.treeView.outlineMode ? null : (
+                return treeView.outlineMode ? null : (
                     <div
                         className="treeView-label"
                         style={{
@@ -808,15 +783,20 @@ export class TreeView extends React.Component<TreeViewProps> {
                             maxWidth: props?.PanelWidth() || undefined,
                             background: props?.styleProvider?.(doc, props, StyleProp.BackgroundColor),
                             outline: `solid ${highlightColor} ${highlightIndex}px`,
+                            paddingLeft: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
+                            paddingRight: NumCast(treeView.rootDoc.childXPadding, NumCast(treeView.props.childXPadding, Doc.IsComicStyle(doc)?20:0)),
+                            paddingTop: treeView.props.childYPadding,
+                            paddingBottom: treeView.props.childYPadding,
                         }}>
                         {StrCast(doc?.title)}
                     </div>
                 );
         }
-        return this.props?.treeView?.props.styleProvider?.(doc, props, property);
+        return treeView.props.styleProvider?.(doc, props, property);
     };
     embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (property.startsWith(StyleProp.Decorations)) return null;
+        if (property.startsWith(StyleProp.Hidden)) return false;
         return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
     };
     onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
@@ -843,7 +823,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         }
         return false;
     };
-    titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - 2 * treeBulletWidth()));
+    titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth())) / (this.props.treeView.props.NativeDimScaling?.() || 1) - 3 * treeBulletWidth();
 
     return18 = () => 18;
     /**
@@ -851,6 +831,7 @@ export class TreeView extends React.Component<TreeViewProps> {
      */
     @computed
     get renderTitle() {
+        //
         TraceMobx();
         const view = this._editTitle ? (
             <EditableView
@@ -887,6 +868,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     }
                 })}
                 Document={this.doc}
+                fitWidth={(doc: Doc) => true}
                 DataDoc={undefined}
                 scriptContext={this}
                 hideDecorationTitle={this.props.treeView.outlineMode}
@@ -905,7 +887,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 removeDocument={this.props.removeDoc}
                 ScreenToLocalTransform={this.getTransform}
                 NativeHeight={this.return18}
-                NativeWidth={this.titleWidth}
+                NativeWidth={returnZero}
                 PanelWidth={this.titleWidth}
                 PanelHeight={this.return18}
                 contextMenuItems={this.contextMenuItems}
@@ -918,10 +900,12 @@ export class TreeView extends React.Component<TreeViewProps> {
                 disableDocBrushing={this.props.treeView.props.disableDocBrushing}
                 hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
                 dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
+                xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)}
+                yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
                 docFilters={returnEmptyFilter}
                 docRangeFilters={returnEmptyFilter}
                 searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionView={undefined}
+                ContainingCollectionView={this.props.treeView.props.CollectionView}
                 ContainingCollectionDoc={this.props.treeView.props.Document}
             />
         );
@@ -968,53 +952,52 @@ export class TreeView extends React.Component<TreeViewProps> {
     };
 
     renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => {
-        const isExpandable = this.doc._treeViewGrowsHorizontally;
-        const panelWidth = asText || isExpandable ? this.rtfWidth : this.expandPanelWidth;
-        const panelHeight = asText ? this.rtfOutlineHeight : isExpandable ? this.rtfHeight : this.expandPanelHeight;
         return (
-            <DocumentView
-                key={this.doc[Id]}
-                ref={action((r: DocumentView | null) => (this._dref = r))}
-                Document={this.doc}
-                DataDoc={undefined}
-                PanelWidth={panelWidth}
-                PanelHeight={panelHeight}
-                NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined}
-                NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined}
-                LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
-                LayoutTemplate={this.props.treeView.props.childLayoutTemplate}
-                isContentActive={isActive}
-                isDocumentActive={isActive}
-                styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
-                hideTitle={asText}
-                fitContentsToBox={returnTrue}
-                hideDecorationTitle={this.props.treeView.outlineMode}
-                hideResizeHandles={this.props.treeView.outlineMode}
-                onClick={this.onChildClick}
-                focus={this.refocus}
-                onKey={this.onKeyDown}
-                hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
-                dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
-                ScreenToLocalTransform={this.docTransform}
-                renderDepth={this.props.renderDepth + 1}
-                treeViewDoc={this.props.treeView?.props.Document}
-                rootSelected={returnTrue}
-                docViewPath={this.props.treeView.props.docViewPath}
-                docFilters={returnEmptyFilter}
-                docRangeFilters={returnEmptyFilter}
-                searchFilterDocs={returnEmptyDoclist}
-                ContainingCollectionDoc={this.props.containerCollection}
-                ContainingCollectionView={undefined}
-                addDocument={this.props.addDocument}
-                moveDocument={this.move}
-                removeDocument={this.props.removeDoc}
-                whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-                addDocTab={this.props.addDocTab}
-                pinToPres={this.props.treeView.props.pinToPres}
-                disableDocBrushing={this.props.treeView.props.disableDocBrushing}
-                bringToFront={returnFalse}
-                scriptContext={this}
-            />
+            <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
+                <DocumentView
+                    key={this.doc[Id]}
+                    ref={action((r: DocumentView | null) => (this._dref = r))}
+                    Document={this.doc}
+                    DataDoc={undefined}
+                    PanelWidth={this.embeddedPanelWidth}
+                    PanelHeight={this.embeddedPanelHeight}
+                    LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
+                    LayoutTemplate={this.props.treeView.props.childLayoutTemplate}
+                    isContentActive={isActive}
+                    isDocumentActive={isActive}
+                    styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider}
+                    hideTitle={asText}
+                    //fitContentsToBox={returnTrue}
+                    hideDecorationTitle={this.props.treeView.outlineMode}
+                    hideResizeHandles={this.props.treeView.outlineMode}
+                    onClick={this.onChildClick}
+                    focus={this.refocus}
+                    onKey={this.onKeyDown}
+                    hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)}
+                    dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)}
+                    ScreenToLocalTransform={this.docTransform}
+                    renderDepth={this.props.renderDepth + 1}
+                    treeViewDoc={this.props.treeView?.props.Document}
+                    rootSelected={returnTrue}
+                    docViewPath={this.props.treeView.props.docViewPath}
+                    docFilters={returnEmptyFilter}
+                    docRangeFilters={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                    ContainingCollectionDoc={this.props.containerCollection}
+                    ContainingCollectionView={undefined}
+                    addDocument={this.props.addDocument}
+                    moveDocument={this.move}
+                    removeDocument={this.props.removeDoc}
+                    whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+                    xPadding={NumCast(this.props.treeView.props.Document.childXPadding, this.props.treeView.props.childXPadding)}
+                    yPadding={NumCast(this.props.treeView.props.Document.childYPadding, this.props.treeView.props.childYPadding)}
+                    addDocTab={this.props.addDocTab}
+                    pinToPres={this.props.treeView.props.pinToPres}
+                    disableDocBrushing={this.props.treeView.props.disableDocBrushing}
+                    bringToFront={returnFalse}
+                    scriptContext={this}
+                />
+            </div>
         );
     };
 
@@ -1148,7 +1131,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         }
 
         const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None));
-        const rowWidth = () => panelWidth() - treeBulletWidth();
+        const rowWidth = () => panelWidth() - treeBulletWidth() * (treeView.props.NativeDimScaling?.() || 1);
         const treeViewRefs = new Map<Doc, TreeView | undefined>();
         return docs
             .filter(child => child instanceof Doc)
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 559951efc..2f246e74f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1317,7 +1317,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 dontScaleFilter={this.props.dontScaleFilter}
                 dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
                 pointerEvents={this.pointerEvents}
-                jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
+                rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
                 //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
             />
         );
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 04c7b96e3..260c98816 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -25,7 +25,7 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     renderCutoffProvider: (doc: Doc) => boolean;
     zIndex?: number;
     highlight?: boolean;
-    jitterRotation: number;
+    rotation: number;
     dataTransition?: string;
     replica: string;
     CollectionFreeFormView: CollectionFreeFormView;
@@ -38,7 +38,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         { key: '_width' },
         { key: 'x' },
         { key: 'y' },
-        { key: '_jitterRotation', val: 0 },
+        { key: '_rotation', val: 0 },
         { key: '_scrollTop' },
         { key: 'opacity', val: 1 },
         { key: 'viewScale', val: 1 },
@@ -55,7 +55,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     get transform() {
-        return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.jitterRotation, this.props.jitterRotation)}deg)`;
+        return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Document.rotation, this.props.rotation)}deg)`;
     }
     get X() {
         return this.dataProvider?.x ?? NumCast(this.Document.x);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fecbc2de2..5b26469ed 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -53,6 +53,7 @@ import { RadialMenu } from './RadialMenu';
 import { ScriptingBox } from './ScriptingBox';
 import { PresBox } from './trails/PresBox';
 import React = require('react');
+import { CollectionTreeView } from '../collections/CollectionTreeView';
 const { Howl } = require('howler');
 
 interface Window {
@@ -147,6 +148,8 @@ export interface DocumentViewSharedProps {
     pinToPres: (document: Doc) => void;
     ScreenToLocalTransform: () => Transform;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
+    xPadding?: number;
+    yPadding?: number;
     dropAction?: dropActionType;
     dontRegisterView?: boolean;
     hideLinkButton?: boolean;
@@ -1349,17 +1352,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             this.contents
         ) : (
             <div className="documentView-styleWrapper">
-                {!this.headerMargin ? (
-                    <>
-                        {' '}
-                        {this.contents} {titleView}{' '}
-                    </>
-                ) : (
-                    <>
-                        {' '}
-                        {titleView} {this.contents}{' '}
-                    </>
-                )}
+                {' '}
+                {!this.headerMargin ? this.contents : titleView}
+                {!this.headerMargin ? titleView : this.contents}
+                {' ' /* */}
                 {captionView}
             </div>
         );
@@ -1372,7 +1368,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         TraceMobx();
         const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
         const isButton = this.props.Document.type === DocumentType.FONTICON;
-        if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || (this.hidden && !this.props.treeViewDoc)) return null;
+        if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || this.hidden) return null;
         return (
             this.docContents ?? (
                 <div
@@ -1448,7 +1444,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     <>
                         {animRenderDoc}
                         <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
-                            <svg style={{ overflow: 'visible' }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}>
+                            <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}>
                                 <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
                             </svg>
                         </div>
@@ -1552,7 +1548,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.fitWidth));
     }
     @computed get shouldNotScale() {
-        return (this.fitWidth && !this.nativeWidth) || this.props.dontScaleFilter?.(this.Document) || this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
+        return (this.fitWidth && !this.nativeWidth) || this.props.dontScaleFilter?.(this.Document) || [CollectionViewType.Docking].includes(this.Document._viewType as any);
     }
     @computed get effectiveNativeWidth() {
         return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
@@ -1597,7 +1593,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight());
     focus = (doc: Doc, options: DocFocusOptions) => this.docView?.focus(doc, options);
     getBounds = () => {
-        if (!this.docView || !this.docView.ContentDiv || this.props.Document.presBox || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
+        if (!this.docView || !this.docView.ContentDiv || this.props.Document.presBox || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
             return undefined;
         }
         const xf = this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling).inverse();
@@ -1687,10 +1683,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = () => (Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
-        const yshift = () => (Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
-        const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
-        const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
+        const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined;
+        const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined;
+        const isButton = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
         return (
             <div className="contentFittingDocumentView">
                 {!this.props.Document || !this.props.PanelWidth() ? null : (
@@ -1700,11 +1695,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         style={{
                             transition: this.props.dataTransition,
                             transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
-                            width: isButton || isPresTreeElement ? '100%' : xshift() ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
+                            width: isButton ? '100%' : xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
                             height:
                                 isButton || this.props.forceAutoHeight
                                     ? undefined
-                                    : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`),
+                                    : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`),
                         }}>
                         <DocumentViewInternal
                             {...this.props}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 77aaa4441..e53422ab7 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -36,8 +36,6 @@ export interface FieldViewProps extends DocumentViewSharedProps {
     fontSize?: number;
     height?: number;
     width?: number;
-    xPadding?: number;
-    yPadding?: number;
     noSidebar?: boolean;
     dontScale?: boolean;
     dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field)
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index f7f558bb4..fb47bfc07 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -935,7 +935,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     className="videoBox-ui"
                     style={{
                         transformOrigin: 'top left',
-                        transform: `rotate(${NumCast(this.rootDoc.jitterRotation)}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
+                        transform: `rotate(${NumCast(this.rootDoc.rotation)}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`,
                         left: xPos,
                         top: yMid,
                         height: uiHeight,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 5470d6ba9..096f9a92c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1004,11 +1004,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             () => this.autoHeight,
             autoHeight => autoHeight && this.tryUpdateScrollHeight()
         );
+        this._disposers.width = reaction(
+            () => this.props.PanelWidth(),
+            width => this.tryUpdateScrollHeight()
+        );
         this._disposers.scrollHeight = reaction(
             () => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }),
-            ({ width, scrollHeight, autoHeight }) => {
-                width && autoHeight && this.resetNativeHeight(scrollHeight);
-            },
+            ({ width, scrollHeight, autoHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight),
             { fireImmediately: true }
         );
         this._disposers.componentHeights = reaction(
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 258dad39c..4e8aed8a6 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -429,7 +429,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             pinDoc.presPinLayout = true;
             pinDoc.presX = NumCast(targetDoc.x);
             pinDoc.presY = NumCast(targetDoc.y);
-            pinDoc.presRot = NumCast(targetDoc.jitterRotation);
+            pinDoc.presRot = NumCast(targetDoc.rotation);
             pinDoc.presWidth = NumCast(targetDoc.width);
             pinDoc.presHeight = NumCast(targetDoc.height);
         }
@@ -521,7 +521,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             targetDoc._dataTransition = presTransitionTime;
             targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x));
             targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y));
-            targetDoc.jitterRotation = NumCast(activeItem.presRot, NumCast(targetDoc.jitterRotation));
+            targetDoc.rotation = NumCast(activeItem.presRot, NumCast(targetDoc.rotation));
             targetDoc.width = NumCast(activeItem.presWidth, NumCast(targetDoc.width));
             targetDoc.height = NumCast(activeItem.presHeight, NumCast(targetDoc.height));
             setTimeout(() => (targetDoc._dataTransition = undefined), transTime + 10);
@@ -2652,9 +2652,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 PanelHeight={this.panelHeight}
                                 childIgnoreNativeSize={true}
                                 moveDocument={returnFalse}
-                                childFitWidth={returnTrue}
+                                //childFitWidth={returnTrue}
                                 childOpacity={returnOne}
                                 childLayoutTemplate={this.childLayoutTemplate}
+                                childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined}
                                 filterAddDocument={this.addDocumentFilter}
                                 removeDocument={returnFalse}
                                 dontRegisterView={true}
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
index 8a6c2a6dc..415253af1 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -42,10 +42,7 @@ $slide-active: #5b9fdd;
     width: 100%;
     border-bottom: 0.5px solid grey;
     display: flex;
-    justify-content: space-between;
     align-items: center;
-    grid-template-rows: 16px 10px auto;
-    grid-template-columns: max-content max-content max-content max-content auto;
 
     .presItem-name {
         display: flex;
@@ -102,13 +99,7 @@ $slide-active: #5b9fdd;
         grid-row: 3;
         grid-column: 1/8;
         position: relative;
-        display: flex;
-        width: auto;
-        justify-content: center;
-        margin: auto;
-        margin-bottom: 2px;
-        border-bottom-left-radius: 5px;
-        border-bottom-right-radius: 5px;
+        display: inline-block;
     }
 
     .presItem-embeddedMask {
@@ -124,8 +115,7 @@ $slide-active: #5b9fdd;
 
     .presItem-slideButtons {
         display: flex;
-        grid-column: 7;
-        grid-row: 1/3;
+        position: absolute;
         width: max-content;
         justify-self: right;
         justify-content: flex-end;
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index f4ab845f3..5d14a0e9a 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -4,7 +4,9 @@ import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'
 import { observer } from 'mobx-react';
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
+import { InkField } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
+import { RichTextField } from '../../../../fields/RichTextField';
 import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -14,6 +16,7 @@ import { DragManager } from '../../../util/DragManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
+import { MarqueeView } from '../../collections/collectionFreeForm';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { EditableView } from '../../EditableView';
 import { Colors } from '../../global/globalEnums';
@@ -24,9 +27,6 @@ import { PresBox } from './PresBox';
 import './PresElementBox.scss';
 import { PresMovement } from './PresEnums';
 import React = require('react');
-import { InkField } from '../../../../fields/InkField';
-import { RichTextField } from '../../../../fields/RichTextField';
-import { MarqueeView } from '../../collections/collectionFreeForm';
 /**
  * This class models the view a document added to presentation will have in the presentation.
  * It involves some functionality for its buttons and options.
@@ -44,8 +44,11 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get indexInPres() {
         return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, 'data')]).indexOf(this.rootDoc);
     } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
+    @computed get expandViewHeight() {
+        return 100;
+    }
     @computed get collapsedHeight() {
-        return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(this.presBox._viewType as any) ? 35 : 31;
+        return 35;
     } // the collapsed height changes depending on the state of the presBox.  We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
     @computed get presStatus() {
         return this.presBox.presStatus;
@@ -58,7 +61,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as PresBox) : undefined;
     }
     @computed get presBox() {
-        return (this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc)!;
+        return this.props.ContainingCollectionDoc!;
     }
     @computed get targetDoc() {
         return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc;
@@ -67,8 +70,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     componentDidMount() {
         this.layoutDoc.hideLinkButton = true;
         this._heightDisposer = reaction(
-            () => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
-            params => (this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0)),
+            () => ({ expand: this.rootDoc.presExpandInlineButton, height: this.collapsedHeight }),
+            ({ expand, height }) => (this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0)),
             { fireImmediately: true }
         );
     }
@@ -84,10 +87,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     presExpandDocumentClick = () => (this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton);
 
-    embedHeight = (): number => 97;
+    embedHeight = (): number => this.collapsedHeight + this.expandViewHeight;
     // embedWidth = () => this.props.PanelWidth();
     // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
-    embedWidth = (): number => this.props.PanelWidth() - 35;
+    embedWidth = (): number => this.props.PanelWidth() / 2;
     styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
         if (property === StyleProp.Opacity) return 1;
         return this.props.styleProvider?.(doc, props, property);
@@ -98,35 +101,35 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @computed get renderEmbeddedInline() {
         return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? null : (
-            <div className="presItem-embedded" style={{ height: this.embedHeight(), width: this.embedWidth() }}>
+            <div className="presItem-embedded" style={{ height: this.embedHeight(), width: '50%' }}>
                 <DocumentView
                     Document={this.rootDoc}
                     DataDoc={undefined} //this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
+                    PanelWidth={this.embedWidth}
+                    PanelHeight={this.embedHeight}
+                    isContentActive={this.props.isContentActive}
                     styleProvider={this.styleProvider}
+                    hideLinkButton={true}
+                    ScreenToLocalTransform={Transform.Identity}
+                    renderDepth={this.props.renderDepth + 1}
                     docViewPath={returnEmptyDoclist}
+                    docFilters={this.props.docFilters}
+                    docRangeFilters={this.props.docRangeFilters}
+                    searchFilterDocs={this.props.searchFilterDocs}
+                    ContainingCollectionView={undefined}
+                    ContainingCollectionDoc={undefined}
                     rootSelected={returnTrue}
                     addDocument={returnFalse}
                     removeDocument={returnFalse}
-                    isContentActive={this.props.isContentActive}
-                    addDocTab={returnFalse}
-                    pinToPres={returnFalse}
                     fitContentsToBox={returnTrue}
-                    PanelWidth={this.embedWidth}
-                    PanelHeight={this.embedHeight}
-                    ScreenToLocalTransform={Transform.Identity}
                     moveDocument={this.props.moveDocument!}
-                    renderDepth={this.props.renderDepth + 1}
                     focus={DocUtils.DefaultFocus}
                     whenChildContentsActiveChanged={returnFalse}
+                    addDocTab={returnFalse}
+                    pinToPres={returnFalse}
                     bringToFront={returnFalse}
-                    docFilters={this.props.docFilters}
-                    docRangeFilters={this.props.docRangeFilters}
-                    searchFilterDocs={this.props.searchFilterDocs}
-                    ContainingCollectionView={undefined}
-                    ContainingCollectionDoc={undefined}
-                    hideLinkButton={true}
                 />
-                <div className="presItem-embeddedMask" />
+                {/* <div className="presItem-embeddedMask" /> */}
             </div>
         );
     }
@@ -206,8 +209,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
         if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
         dragData.dropAction = 'move';
-        dragData.treeViewDoc = this.props.docViewPath().lastElement()?.props.treeViewDoc;
-        dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument;
+        dragData.treeViewDoc = this.presBox._viewType === CollectionViewType.Tree ? this.props.ContainingCollectionDoc : undefined; // this.props.DocumentView?.()?.props.treeViewDoc;
+        dragData.moveDocument = this.props.moveDocument;
         const dragItem: HTMLElement[] = [];
         if (dragArray.length === 1) {
             const doc = this._itemRef.current || dragArray[0];
@@ -307,7 +310,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
     updateCapturedContainerLayout = (targetDoc: Doc, activeItem: Doc) => {
         activeItem.presX = NumCast(targetDoc.x);
         activeItem.presY = NumCast(targetDoc.y);
-        activeItem.presRot = NumCast(targetDoc.jitterRotation);
+        activeItem.presRot = NumCast(targetDoc.rotation);
         activeItem.presWidth = NumCast(targetDoc.width);
         activeItem.presHeight = NumCast(targetDoc.height);
     };
@@ -464,16 +467,90 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return width;
     }
 
+    @computed get presButtons() {
+        const presBox: Doc = this.presBox; //presBox
+        const presBoxColor: string = StrCast(presBox._backgroundColor);
+        const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
+        const targetDoc: Doc = this.targetDoc;
+        const activeItem: Doc = this.rootDoc;
+
+        const items: JSX.Element[] = [];
+        if (activeItem.presPinLayout) {
+            items.push(
+                <Tooltip key="slide" title={<div className="dash-tooltip">Update captured doc layout</div>}>
+                    <div className="slideButton" onClick={() => this.updateCapturedContainerLayout(targetDoc, activeItem)} style={{ fontWeight: 700, display: 'flex' }}>
+                        L
+                    </div>
+                </Tooltip>
+            );
+        }
+        if (activeItem.presPinData || activeItem.presPinView) {
+            items.push(
+                <Tooltip key="flex" title={<div className="dash-tooltip">Update captured doc content</div>}>
+                    <div className="slideButton" onClick={() => this.updateCapturedViewContents(targetDoc, activeItem)} style={{ fontWeight: 700, display: 'flex' }}>
+                        C
+                    </div>
+                </Tooltip>
+            );
+        }
+        if (!Doc.noviceMode) {
+            items.push(
+                <Tooltip key="slash" title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
+                    <div className="slideButton" onClick={e => (this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
+                        <FontAwesomeIcon icon={`video${this.recordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
+                    </div>
+                </Tooltip>
+            );
+        }
+        if (this.indexInPres === 0) {
+            items.push(
+                <Tooltip key="arrow" title={<div className="dash-tooltip">{activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}</div>}>
+                    <div
+                        className="slideButton"
+                        onClick={() => (activeItem.groupWithUp = !activeItem.groupWithUp)}
+                        style={{
+                            zIndex: 1000 - this.indexInPres,
+                            fontWeight: 700,
+                            backgroundColor: activeItem.groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined,
+                            height: activeItem.groupWithUp ? 53 : 18,
+                            transform: activeItem.groupWithUp ? 'translate(0, -17px)' : undefined,
+                        }}>
+                        <div style={{ transform: activeItem.groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
+                            <FontAwesomeIcon icon={'arrow-up'} onPointerDown={e => e.stopPropagation()} />
+                        </div>
+                    </div>
+                </Tooltip>
+            );
+        }
+        items.push(
+            <Tooltip key="eye" title={<div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? 'Minimize' : 'Expand'}</div>}>
+                <div
+                    className="slideButton"
+                    onClick={e => {
+                        e.stopPropagation();
+                        this.presExpandDocumentClick();
+                    }}>
+                    <FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
+                </div>
+            </Tooltip>
+        );
+        items.push(
+            <Tooltip key="trash" title={<div className="dash-tooltip">Remove from presentation</div>}>
+                <div className={'slideButton'} onClick={this.removeItem}>
+                    <FontAwesomeIcon icon={'trash'} onPointerDown={e => e.stopPropagation()} />
+                </div>
+            </Tooltip>
+        );
+        return items;
+    }
+
     @computed get mainItem() {
         const isSelected: boolean = this.selectedArray?.has(this.rootDoc) ? true : false;
         const isCurrent: boolean = this.presBox._itemIndex === this.indexInPres;
-        const toolbarWidth: number = this.toolbarWidth;
-        const showMore: boolean = this.toolbarWidth >= 300;
         const miniView: boolean = this.toolbarWidth <= 110;
         const presBox: Doc = this.presBox; //presBox
         const presBoxColor: string = StrCast(presBox._backgroundColor);
         const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false;
-        const targetDoc: Doc = this.targetDoc;
         const activeItem: Doc = this.rootDoc;
 
         return (
@@ -484,6 +561,10 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 style={{
                     backgroundColor: presColorBool ? (isSelected ? 'rgba(250,250,250,0.3)' : 'transparent') : isSelected ? Colors.LIGHT_BLUE : 'transparent',
                     opacity: this._dragging ? 0.3 : 1,
+                    paddingLeft: NumCast(this.layoutDoc._xPadding, this.props.xPadding),
+                    paddingRight: NumCast(this.layoutDoc._xPadding, this.props.xPadding),
+                    paddingTop: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
+                    paddingBottom: NumCast(this.layoutDoc._yPadding, this.props.yPadding),
                 }}
                 onClick={e => {
                     e.stopPropagation();
@@ -507,6 +588,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         ref={this._dragRef}
                         className={`presItem-slide ${isCurrent ? 'active' : ''}`}
                         style={{
+                            display: 'infline-block',
                             backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
                             //boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined,
                             border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined,
@@ -514,8 +596,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         <div
                             className="presItem-name"
                             style={{
+                                display: 'inline-flex',
                                 pointerEvents: isSelected ? undefined : 'none',
-                                maxWidth: showMore ? toolbarWidth - 195 : toolbarWidth - 105,
+                                width: `calc(100% ${this.rootDoc.presExpandInlineButton ? '- 50%' : ''} - ${this.presButtons.length * 22}px`,
                                 cursor: isSelected ? 'text' : 'grab',
                             }}>
                             <div>{`${this.indexInPres + 1}.  `}</div>
@@ -523,56 +606,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         </div>
                         {/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */}
                         {/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */}
-                        <div className="presItem-slideButtons">
-                            <Tooltip title={<div className="dash-tooltip">Update captured doc layout</div>}>
-                                <div className="slideButton" onClick={() => this.updateCapturedContainerLayout(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinLayout ? 'flex' : 'none' }}>
-                                    L
-                                </div>
-                            </Tooltip>
-                            <Tooltip title={<div className="dash-tooltip">Update captured doc content</div>}>
-                                <div className="slideButton" onClick={() => this.updateCapturedViewContents(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinData || activeItem.presPinView ? 'flex' : 'none' }}>
-                                    C
-                                </div>
-                            </Tooltip>
-                            {!Doc.noviceMode && (
-                                <Tooltip title={<div className="dash-tooltip">{this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}</div>}>
-                                    <div className="slideButton" onClick={e => (this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}>
-                                        <FontAwesomeIcon icon={`video${this.recordingIsInOverlay ? '-slash' : ''}`} onPointerDown={e => e.stopPropagation()} />
-                                    </div>
-                                </Tooltip>
-                            )}
-                            <Tooltip title={<div className="dash-tooltip">{activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}</div>}>
-                                <div
-                                    className="slideButton"
-                                    onClick={() => (activeItem.groupWithUp = !activeItem.groupWithUp)}
-                                    style={{
-                                        display: this.indexInPres === 0 ? 'none' : '',
-                                        zIndex: 1000 - this.indexInPres,
-                                        fontWeight: 700,
-                                        backgroundColor: activeItem.groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined,
-                                        height: activeItem.groupWithUp ? 53 : 18,
-                                        transform: activeItem.groupWithUp ? 'translate(0, -17px)' : undefined,
-                                    }}>
-                                    <div style={{ transform: activeItem.groupWithUp ? 'rotate(180deg) translate(0, -17.5px)' : 'rotate(0deg)' }}>
-                                        <FontAwesomeIcon icon={'arrow-up'} onPointerDown={e => e.stopPropagation()} />
-                                    </div>
-                                </div>
-                            </Tooltip>
-                            <Tooltip title={<div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? 'Minimize' : 'Expand'}</div>}>
-                                <div
-                                    className="slideButton"
-                                    onClick={e => {
-                                        e.stopPropagation();
-                                        this.presExpandDocumentClick();
-                                    }}>
-                                    <FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? 'eye-slash' : 'eye'} onPointerDown={e => e.stopPropagation()} />
-                                </div>
-                            </Tooltip>
-                            <Tooltip title={<div className="dash-tooltip">Remove from presentation</div>}>
-                                <div className={'slideButton'} onClick={this.removeItem}>
-                                    <FontAwesomeIcon icon={'trash'} onPointerDown={e => e.stopPropagation()} />
-                                </div>
-                            </Tooltip>
+                        <div className="presItem-slideButtons" style={{ position: 'absolute', right: 0 }}>
+                            {...this.presButtons}
                         </div>
                         {this.renderEmbeddedInline}
                     </div>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 466b43287..fc43325fe 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -238,6 +238,9 @@ export class Doc extends RefField {
         Doc.UserDoc().activePage = val;
         DocServer.UPDATE_SERVER_CACHE();
     }
+    public static IsComicStyle(doc?: Doc) {
+        return doc && Doc.ActiveDashboard && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === 'comic';
+    }
     public static get ActiveDashboard() {
         return DocCast(Doc.UserDoc().activeDashboard);
     }
-- 
cgit v1.2.3-70-g09d2


From 2827ad04901e076ffa399f8b069eb64e8be64b6f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 15 Nov 2022 15:47:24 -0500
Subject: added canEmbed prop so collection can allow its items to be dragged
 out (eg., pileView).   made imageBox viewSpec transition time customizable

---
 src/client/util/DocumentManager.ts                 |   4 +-
 .../views/collections/CollectionPileView.tsx       | 135 ++++++++++++---------
 src/client/views/collections/CollectionView.tsx    |   1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 src/client/views/nodes/DocumentView.tsx            |  12 +-
 src/client/views/nodes/ImageBox.scss               |  18 +--
 src/client/views/nodes/ImageBox.tsx                |   4 +-
 7 files changed, 91 insertions(+), 84 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 519f75dbd..9336717c0 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -190,7 +190,7 @@ export class DocumentManager {
                 finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined);
                 !noSelect && docView?.select(false);
                 if (originatingDoc?.followLinkAudio) {
-                    const anno = Cast(finalTargetDoc[Doc.LayoutFieldKey(finalTargetDoc) + '-audioAnnotations'], listSpec(AudioField), null).lastElement();
+                    const anno = Cast(finalTargetDoc[Doc.LayoutFieldKey(finalTargetDoc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement();
                     if (anno) {
                         if (anno instanceof AudioField) {
                             new Howl({
@@ -326,7 +326,7 @@ export class DocumentManager {
                                 docContext,
                                 linkDoc,
                                 true /* if target not found, get rid of context just created */,
-                                undefined,
+                                originatingDoc,
                                 finished,
                                 originalTarget
                             )
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 4489601db..38e240ac6 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,33 +1,35 @@
-import { action, computed, IReactionDisposer, reaction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, HeightSym, WidthSym } from "../../../fields/Doc";
-import { NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils";
-import { DocUtils } from "../../documents/Documents";
-import { SelectionManager } from "../../util/SelectionManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
-import "./CollectionPileView.scss";
-import { CollectionSubView } from "./CollectionSubView";
-import React = require("react");
-import { ScriptField } from "../../../fields/ScriptField";
+import { action, computed, IReactionDisposer, reaction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
+import { NumCast, StrCast } from '../../../fields/Types';
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
+import { DocUtils } from '../../documents/Documents';
+import { SelectionManager } from '../../util/SelectionManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import './CollectionPileView.scss';
+import { CollectionSubView } from './CollectionSubView';
+import React = require('react');
+import { ScriptField } from '../../../fields/ScriptField';
 
 @observer
 export class CollectionPileView extends CollectionSubView() {
-    _originalChrome: any = "";
+    _originalChrome: any = '';
     _disposers: { [name: string]: IReactionDisposer } = {};
 
     componentDidMount() {
-        if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") {
-            this.Document._pileLayoutEngine = "pass";
+        if (this.layoutEngine() !== 'pass' && this.layoutEngine() !== 'starburst') {
+            this.Document._pileLayoutEngine = 'pass';
         }
         this._originalChrome = this.layoutDoc._chromeHidden;
         this.layoutDoc._chromeHidden = true;
 
-        // pileups are designed to go away when they are empty.  
-        this._disposers.selected = reaction(() => this.childDocs.length,
-            (num) => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document));
+        // pileups are designed to go away when they are empty.
+        this._disposers.selected = reaction(
+            () => this.childDocs.length,
+            num => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document)
+        );
     }
     componentWillUnmount() {
         this.layoutDoc._chromeHidden = this._originalChrome;
@@ -38,32 +40,36 @@ export class CollectionPileView extends CollectionSubView() {
 
     @undoBatch
     addPileDoc = (doc: Doc | Doc[]) => {
-        (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d));
+        (doc instanceof Doc ? [doc] : doc).map(d => DocUtils.iconify(d));
         return this.props.addDocument?.(doc) || false;
-    }
+    };
 
     @undoBatch
     removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
-        (doc instanceof Doc ? [doc] : doc).map(undoBatch((d) => Doc.deiconifyView(d)));
+        (doc instanceof Doc ? [doc] : doc).map(undoBatch(d => Doc.deiconifyView(d)));
         return this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
-    }
+    };
 
     toggleIcon = () => {
-        return ScriptField.MakeScript("documentView.iconify()", { documentView: "any" });
-    }
+        return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' });
+    };
 
     // returns the contents of the pileup in a CollectionFreeFormView
     @computed get contents() {
-        const isStarburst = this.layoutEngine() === "starburst";
-        return <div className="collectionPileView-innards"
-            style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : "none" }} >
-            <CollectionFreeFormView {...this.props}
-                layoutEngine={this.layoutEngine}
-                childDocumentsActive={isStarburst ? returnTrue : undefined}
-                addDocument={this.addPileDoc}
-                childClickScript={this.toggleIcon()}
-                moveDocument={this.removePileDoc} />
-        </div>;
+        const isStarburst = this.layoutEngine() === 'starburst';
+        return (
+            <div className="collectionPileView-innards" style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : 'none' }}>
+                <CollectionFreeFormView
+                    {...this.props}
+                    layoutEngine={this.layoutEngine}
+                    childDocumentsActive={isStarburst ? returnTrue : undefined}
+                    addDocument={this.addPileDoc}
+                    childCanEmbedOnDrag={true}
+                    childClickScript={this.toggleIcon()}
+                    moveDocument={this.removePileDoc}
+                />
+            </div>
+        );
     }
 
     // toggles the pileup between starburst to compact
@@ -99,27 +105,35 @@ export class CollectionPileView extends CollectionSubView() {
     pointerDown = (e: React.PointerEvent) => {
         let dist = 0;
         SnappingManager.SetIsDragging(true);
-        setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
-            if (this.layoutEngine() === "pass" && this.childDocs.length && e.shiftKey) {
-                dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
-                if (dist > 100) {
-                    if (!this._undoBatch) {
-                        this._undoBatch = UndoManager.StartBatch("layout pile");
+        setupMoveUpEvents(
+            this,
+            e,
+            (e: PointerEvent, down: number[], delta: number[]) => {
+                if (this.layoutEngine() === 'pass' && this.childDocs.length && e.shiftKey) {
+                    dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
+                    if (dist > 100) {
+                        if (!this._undoBatch) {
+                            this._undoBatch = UndoManager.StartBatch('layout pile');
+                        }
+                        const doc = this.childDocs[0];
+                        doc.x = e.clientX;
+                        doc.y = e.clientY;
+                        this.props.addDocTab(doc, 'inParent') && (this.props.removeDocument?.(doc) || false);
+                        dist = 0;
                     }
-                    const doc = this.childDocs[0];
-                    doc.x = e.clientX;
-                    doc.y = e.clientY;
-                    this.props.addDocTab(doc, "inParent") && (this.props.removeDocument?.(doc) || false);
-                    dist = 0;
                 }
-            }
-            return false;
-        }, () => {
-            this._undoBatch?.end();
-            this._undoBatch = undefined;
-            SnappingManager.SetIsDragging(false);
-        }, emptyFunction, e.shiftKey && this.layoutEngine() === "pass", this.layoutEngine() === "pass" && e.shiftKey); // this sets _doubleTap
-    }
+                return false;
+            },
+            () => {
+                this._undoBatch?.end();
+                this._undoBatch = undefined;
+                SnappingManager.SetIsDragging(false);
+            },
+            emptyFunction,
+            e.shiftKey && this.layoutEngine() === 'pass',
+            this.layoutEngine() === 'pass' && e.shiftKey
+        ); // this sets _doubleTap
+    };
 
     // onClick for toggling the pileup view
     @undoBatch
@@ -130,12 +144,13 @@ export class CollectionPileView extends CollectionSubView() {
             this.toggleStarburst();
             e.stopPropagation();
         }
-    }
+    };
 
     render() {
-        return <div className={`collectionPileView`} onClick={this.onClick} onPointerDown={this.pointerDown}
-            style={{ width: this.props.PanelWidth(), height: "100%" }}>
-            {this.contents}
-        </div>;
+        return (
+            <div className={`collectionPileView`} onClick={this.onClick} onPointerDown={this.pointerDown} style={{ width: this.props.PanelWidth(), height: '100%' }}>
+                {this.contents}
+            </div>
+        );
     }
 }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 9f63a11aa..625d4e9e5 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -54,6 +54,7 @@ interface CollectionViewProps_ extends FieldViewProps {
     childHideDecorationTitle?: () => boolean;
     childHideResizeHandles?: () => boolean;
     childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
+    childCanEmbedOnDrag?: boolean;
     childXPadding?: number;
     childYPadding?: number;
     childLayoutString?: string;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8fe5ad63f..932bd789d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1314,6 +1314,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                 docViewPath={this.props.docViewPath}
                 styleProvider={this.getClusterColor}
+                canEmbedOnDrag={this.props.childCanEmbedOnDrag}
                 dataProvider={this.childDataProvider}
                 sizeProvider={this.childSizeProvider}
                 dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 76d6d3532..84cacd919 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -147,6 +147,7 @@ export interface DocumentViewSharedProps {
     pinToPres: (document: Doc) => void;
     ScreenToLocalTransform: () => Transform;
     bringToFront: (doc: Doc, sendToBack?: boolean) => void;
+    canEmbedOnDrag?: boolean;
     xPadding?: number;
     yPadding?: number;
     dropAction?: dropActionType;
@@ -505,6 +506,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             dragData.treeViewDoc = this.props.treeViewDoc;
             dragData.removeDocument = this.props.removeDocument;
             dragData.moveDocument = this.props.moveDocument;
+            dragData.canEmbed = this.props.canEmbedOnDrag;
             //dragData.dimSource :
             // dragEffects field, set dim
             // add kv pairs to a doc, swap properties with the node while dragging, and then swap when dropping
@@ -525,7 +527,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             e.preventDefault();
             if (e.key === '†' || e.key === 't') {
                 if (!StrCast(this.layoutDoc._showTitle)) this.layoutDoc._showTitle = 'title';
-                if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0);
+                if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true));
                 else if (!this._titleRef.current.setIsFocused(true)) {
                     // if focus didn't change, focus on interior text...
                     this._titleRef.current?.setIsFocused(false);
@@ -540,12 +542,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         // copying over VIEW fields immediately allows the view type to switch to create the right _componentView
         Array.from(Object.keys(Doc.GetProto(anchor)))
             .filter(key => key.startsWith(ViewSpecPrefix))
-            .forEach(spec => {
-                this.layoutDoc[spec.replace(ViewSpecPrefix, '')] = (field => (field instanceof ObjectField ? ObjectField.MakeCopy(field) : field))(anchor[spec]);
-            });
-        // after  a timeout, the right _componentView should have been created, so call it to update its view spec values
+            .forEach(spec => (this.layoutDoc[spec.replace(ViewSpecPrefix, '')] = (field => (field instanceof ObjectField ? ObjectField.MakeCopy(field) : field))(anchor[spec])));
+        // after a render the general viewSpec should have created the right _componentView, so after a timeout, call the componentview to update its specific view specs
         setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
-        const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+        const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo);
         const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing;
         this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
             ...options,
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index cd2b23f02..6359a9491 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -5,7 +5,6 @@
     position: relative;
     transform-origin: top left;
 
-
     .imageBox-annotationLayer {
         position: absolute;
         transform-origin: left top;
@@ -95,7 +94,6 @@
     height: 100%;
 }
 
-
 .imageBox-fader {
     position: relative;
     width: 100%;
@@ -103,7 +101,8 @@
     display: flex;
     height: 100%;
 
-    .imageBox-fadeBlocker, .imageBox-fadeBlocker-hover{
+    .imageBox-fadeBlocker,
+    .imageBox-fadeBlocker-hover {
         width: 100%;
         height: 100%;
         position: absolute;
@@ -126,17 +125,6 @@
     left: 0;
 }
 
-.imageBox-fadeBlocker {
-    -webkit-transition: opacity 1s ease-in-out;
-    -moz-transition: opacity 1s ease-in-out;
-    -o-transition: opacity 1s ease-in-out;
-    transition: opacity 1s ease-in-out;
-}
-
 .imageBox-fadeBlocker-hover {
-    -webkit-transition: opacity 1s ease-in-out;
-    -moz-transition: opacity 1s ease-in-out;
-    -o-transition: opacity 1s ease-in-out;
-    transition: opacity 1s ease-in-out;
     opacity: 0;
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 8ecd8104a..76ba7765c 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -377,7 +377,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 <div className="imageBox-fader" style={{ overflow: Array.from(this.props.docViewPath?.()).slice(-1)[0].fitWidth ? 'auto' : undefined }}>
                     <img key="paths" src={srcpath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
                     {fadepath === srcpath ? null : (
-                        <div className={`imageBox-fadeBlocker${(this.props.isHovering?.() && this.layoutDoc[this.fieldKey + '-useAlt'] === undefined) || BoolCast(this.layoutDoc[this.fieldKey + '-useAlt']) ? '-hover' : ''}`}>
+                        <div
+                            className={`imageBox-fadeBlocker${(this.props.isHovering?.() && this.layoutDoc[this.fieldKey + '-useAlt'] === undefined) || BoolCast(this.layoutDoc[this.fieldKey + '-useAlt']) ? '-hover' : ''}`}
+                            style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000 ms') }}>
                             <img className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
                         </div>
                     )}
-- 
cgit v1.2.3-70-g09d2


From b7d4f932d826d48aca4c7c058e05ceaea9c43057 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 17 Nov 2022 14:35:13 -0500
Subject: mostly changing strings to enums

---
 src/client/util/DictationManager.ts                |   6 +-
 src/client/util/LinkFollower.ts                    |   4 +-
 src/client/views/LightboxView.tsx                  |  16 +-
 src/client/views/MainView.tsx                      |  28 +-
 .../views/PropertiesDocBacklinksSelector.tsx       |  45 ++-
 src/client/views/PropertiesDocContextSelector.tsx  |   6 +-
 src/client/views/PropertiesView.tsx                |   4 +-
 .../views/collections/CollectionDockingView.tsx    |  26 +-
 .../views/collections/CollectionNoteTakingView.tsx |  12 +-
 .../views/collections/CollectionPileView.tsx       |  22 +-
 .../views/collections/CollectionStackingView.tsx   |   4 +-
 .../views/collections/CollectionTimeView.tsx       | 351 +++++++++-------
 src/client/views/collections/CollectionView.tsx    |  11 +-
 src/client/views/collections/TabDocView.tsx        |  34 +-
 src/client/views/collections/TreeView.tsx          |   8 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  23 +-
 .../CollectionMulticolumnView.tsx                  |   9 +-
 .../collectionSchema/CollectionSchemaCells.tsx     | 447 +++++++++++----------
 .../CollectionSchemaMovableRow.tsx                 |   3 +-
 .../collections/collectionSchema/SchemaTable.tsx   |   6 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   4 +-
 src/client/views/nodes/DocumentView.tsx            |  35 +-
 src/client/views/nodes/KeyValueBox.tsx             |   5 +-
 src/client/views/nodes/KeyValuePair.tsx            |  65 +--
 src/client/views/nodes/LinkAnchorBox.tsx           |   5 +-
 src/client/views/nodes/LinkDocPreview.tsx          |   4 +-
 src/client/views/nodes/VideoBox.tsx                |   3 +-
 src/client/views/nodes/button/FontIconBox.tsx      |   3 +-
 .../views/nodes/formattedText/DashFieldView.tsx    |   3 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +-
 .../formattedText/ProsemirrorExampleTransfer.ts    |   3 +-
 src/client/views/nodes/trails/PresBox.tsx          |  13 +-
 src/mobile/MobileInterface.tsx                     |   2 +-
 34 files changed, 668 insertions(+), 550 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 0a61f3478..203d4ad62 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -11,7 +11,7 @@ import { Utils } from '../../Utils';
 import { Docs } from '../documents/Documents';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DictationOverlay } from '../views/DictationOverlay';
-import { DocumentView } from '../views/nodes/DocumentView';
+import { DocumentView, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
 import { SelectionManager } from './SelectionManager';
 import { UndoManager } from './UndoManager';
 
@@ -328,7 +328,7 @@ export namespace DictationManager {
                 {
                     action: (target: DocumentView) => {
                         const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 });
-                        target.props.addDocTab(kvp, 'add:right');
+                        target.props.addDocTab(kvp, OpenWhere.addRight);
                     },
                 },
             ],
@@ -345,7 +345,7 @@ export namespace DictationManager {
                         const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
                         proto.data = new RichTextField(proseMirrorState);
                         proto.backgroundColor = '#eeffff';
-                        target.props.addDocTab(newBox, 'add:right');
+                        target.props.addDocTab(newBox, OpenWhere.addRight);
                     },
                 },
             ],
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 68716a207..a3eb7ed7a 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -4,7 +4,7 @@ import { BoolCast, Cast, DocCast, StrCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 import { LightboxView } from '../views/LightboxView';
-import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView';
+import { DocumentViewSharedProps, OpenWhere, ViewAdjustment } from '../views/nodes/DocumentView';
 import { DocumentManager } from './DocumentManager';
 import { LinkManager } from './LinkManager';
 import { UndoManager } from './UndoManager';
@@ -32,7 +32,7 @@ export class LinkFollower {
         const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => {
             const createTabForTarget = (didFocus: boolean) =>
                 new Promise<ViewAdjustment>(res => {
-                    const where = LightboxView.LightboxDoc ? 'inPlace' : StrCast(sourceDoc.followLinkLocation, followLoc);
+                    const where = LightboxView.LightboxDoc ? OpenWhere.inPlace : (StrCast(sourceDoc.followLinkLocation, followLoc) as OpenWhere);
                     docViewProps.addDocTab(doc, where);
                     setTimeout(() => {
                         const targDocView = DocumentManager.Instance.getFirstDocumentView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise.
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 5660a34e9..1f58763d1 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -18,7 +18,7 @@ import { TabDocView } from './collections/TabDocView';
 import { GestureOverlay } from './GestureOverlay';
 import './LightboxView.scss';
 import { MainView } from './MainView';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
 import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider';
 
 interface LightboxViewProps {
@@ -141,11 +141,13 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`);
         }
     }
-    public static AddDocTab = (doc: Doc, location: string, layoutTemplate?: Doc, openInTabFunc?: any) => {
-        const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined;
-        if (inPlaceView) {
-            inPlaceView.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List<Doc>([doc]);
-            return true;
+    public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc, openInTabFunc?: any) => {
+        if (location !== OpenWhere.lightbox) {
+            const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined;
+            if (inPlaceView) {
+                inPlaceView.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List<Doc>([doc]);
+                return true;
+            }
         }
         LightboxView.openInTabFunc = openInTabFunc;
         SelectionManager.DeselectAll();
@@ -360,7 +362,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     title={'open in tab'}
                     onClick={e => {
                         e.stopPropagation();
-                        CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, '');
+                        CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, OpenWhereMod.none);
                         //LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace");
                         SelectionManager.DeselectAll();
                         LightboxView.SetLightboxDoc(undefined);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 392b4eeeb..c151aebcd 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -49,7 +49,7 @@ import { LinkMenu } from './linking/LinkMenu';
 import './MainView.scss';
 import { AudioBox } from './nodes/AudioBox';
 import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
 import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
@@ -538,7 +538,7 @@ export class MainView extends React.Component {
     @action
     createNewPresentation = () => {
         const pres = Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
-        CollectionDockingView.AddSplit(pres, 'right');
+        CollectionDockingView.AddSplit(pres, OpenWhereMod.right);
         Doc.MyTrails && Doc.AddDocToList(Doc.MyTrails, 'data', pres); // Doc.MyTrails should be created in createDashboard
         Doc.ActivePresentation = pres;
     };
@@ -546,7 +546,7 @@ export class MainView extends React.Component {
     @action
     openPresentation = (pres: Doc) => {
         if (pres.type === DocumentType.PRES) {
-            CollectionDockingView.AddSplit(pres, 'right');
+            CollectionDockingView.AddSplit(pres, OpenWhereMod.right);
             Doc.MyTrails && (Doc.ActivePresentation = pres);
             Doc.AddDocToList(Doc.MyTrails, 'data', pres);
             this.closeFlyout();
@@ -683,20 +683,20 @@ export class MainView extends React.Component {
 
     sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1);
     mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
-    addDocTabFunc = (doc: Doc, location: string): boolean => {
-        const locationFields = doc._viewType === CollectionViewType.Docking ? ['dashboard'] : location.split(':');
-        const locationParams = locationFields.length > 1 ? locationFields[1] : '';
+    addDocTabFunc = (doc: Doc, location: OpenWhere): boolean => {
+        const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':');
+        const whereMods = whereFields.length > 1 ? whereFields[1] : '';
         if (doc.dockingConfig) return DashboardView.openDashboard(doc);
         // prettier-ignore
-        switch (locationFields[0]) {
+        switch (whereFields[0]) {
             default:
-            case 'inPlace':
-            case 'lightbox':   return LightboxView.AddDocTab(doc, location);
-            case 'add':        return CollectionDockingView.AddSplit(doc, locationParams);
-            case 'dashboard':  return DashboardView.openDashboard(doc);
-            case 'close':      return CollectionDockingView.CloseSplit(doc, locationParams);
-            case 'fullScreen': return CollectionDockingView.OpenFullScreen(doc);
-            case 'toggle':     return CollectionDockingView.ToggleSplit(doc, locationParams);
+            case OpenWhere.inPlace:
+            case OpenWhere.lightbox:   return LightboxView.AddDocTab(doc, location);
+            case OpenWhere.add:        return CollectionDockingView.AddSplit(doc, whereMods as OpenWhereMod);
+            case OpenWhere.dashboard:  return DashboardView.openDashboard(doc);
+            case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
+            case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc);
+            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods as OpenWhereMod);
         }
     };
 
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 4ead8eaf0..25ac44078 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -1,20 +1,21 @@
-import { computed } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc, DocListCast } from "../../fields/Doc";
-import { Cast } from "../../fields/Types";
-import { emptyFunction } from "../../Utils";
-import { DocumentType } from "../documents/DocumentTypes";
-import { LinkManager } from "../util/LinkManager";
-import { SelectionManager } from "../util/SelectionManager";
-import { LinkMenu } from "./linking/LinkMenu";
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast } from '../../fields/Doc';
+import { Cast } from '../../fields/Types';
+import { emptyFunction } from '../../Utils';
+import { DocumentType } from '../documents/DocumentTypes';
+import { LinkManager } from '../util/LinkManager';
+import { SelectionManager } from '../util/SelectionManager';
+import { LinkMenu } from './linking/LinkMenu';
+import { OpenWhere, OpenWhereMod } from './nodes/DocumentView';
 import './PropertiesDocBacklinksSelector.scss';
 
 type PropertiesDocBacklinksSelectorProps = {
-    Document: Doc,
-    Stack?: any,
-    hideTitle?: boolean,
-    addDocTab(doc: Doc, location: string): void
+    Document: Doc;
+    Stack?: any;
+    hideTitle?: boolean;
+    addDocTab(doc: Doc, location: OpenWhere): void;
 };
 
 @observer
@@ -40,14 +41,16 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
 
         if (otherdoc) {
             otherdoc.hidden = false;
-            this.props.addDocTab(Doc.IsPrototype(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, "toggle:right");
+            this.props.addDocTab(Doc.IsPrototype(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere);
         }
-    }
+    };
 
     render() {
-        return !SelectionManager.Views().length ? (null) : <div>
-            {this.props.hideTitle ? (null) : <p key="contexts">Contexts:</p>}
-            <LinkMenu docView={SelectionManager.Views().lastElement()} clearLinkEditor={emptyFunction} itemHandler={this.getOnClick} position={{ x: 0 }} />
-        </div>;
+        return !SelectionManager.Views().length ? null : (
+            <div>
+                {this.props.hideTitle ? null : <p key="contexts">Contexts:</p>}
+                <LinkMenu docView={SelectionManager.Views().lastElement()} clearLinkEditor={emptyFunction} itemHandler={this.getOnClick} position={{ x: 0 }} />
+            </div>
+        );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 9d89ee036..2c7da5931 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -7,14 +7,14 @@ import { Cast, NumCast, StrCast } from '../../fields/Types';
 import { CollectionViewType } from '../documents/DocumentTypes';
 import { DocFocusOrOpen } from '../util/DocumentManager';
 import { CollectionDockingView } from './collections/CollectionDockingView';
-import { DocumentView } from './nodes/DocumentView';
+import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
 import './PropertiesDocContextSelector.scss';
 
 type PropertiesDocContextSelectorProps = {
     DocView?: DocumentView;
     Stack?: any;
     hideTitle?: boolean;
-    addDocTab(doc: Doc, location: string): void;
+    addDocTab(doc: Doc, location: OpenWhere): void;
 };
 
 @observer
@@ -53,7 +53,7 @@ export class PropertiesDocContextSelector extends React.Component<PropertiesDocC
             col._panY = NumCast(target.y) + NumCast(target._height) / 2;
         }
         col.hidden = false;
-        this.props.addDocTab(col, 'toggle:right');
+        this.props.addDocTab(col, (OpenWhere.toggle + ':' + OpenWhereMod.right) as OpenWhere);
         setTimeout(() => DocFocusOrOpen(Doc.GetProto(this.props.DocView!.props.Document), col), 100);
     };
 
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index e5ff9e267..93a3fd253 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -24,7 +24,7 @@ import { Transform } from '../util/Transform';
 import { undoBatch, UndoManager } from '../util/UndoManager';
 import { EditableView } from './EditableView';
 import { InkStrokeProperties } from './InkStrokeProperties';
-import { DocumentView, StyleProviderFunc } from './nodes/DocumentView';
+import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
 import { FilterBox } from './nodes/FilterBox';
 import { KeyValueBox } from './nodes/KeyValueBox';
 import { PresBox } from './nodes/trails';
@@ -42,7 +42,7 @@ interface PropertiesViewProps {
     width: number;
     height: number;
     styleProvider?: StyleProviderFunc;
-    addDocTab: (doc: Doc, where: string) => boolean;
+    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
 }
 
 @observer
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 434466505..8cbe548c7 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -25,6 +25,7 @@ import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import { TabDocView } from './TabDocView';
 import React = require('react');
+import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -142,7 +143,7 @@ export class CollectionDockingView extends CollectionSubView() {
 
     @undoBatch
     @action
-    public static ReplaceTab(document: Doc, panelName: string, stack: any, addToSplit?: boolean): boolean {
+    public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean): boolean {
         const instance = CollectionDockingView.Instance;
         if (!instance) return false;
         const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
@@ -164,7 +165,7 @@ export class CollectionDockingView extends CollectionSubView() {
     }
 
     @undoBatch
-    public static ToggleSplit(doc: Doc, location: string, stack?: any, panelName?: string) {
+    public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string) {
         return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1
             ? CollectionDockingView.CloseSplit(doc)
             : CollectionDockingView.AddSplit(doc, location, stack, panelName);
@@ -175,7 +176,7 @@ export class CollectionDockingView extends CollectionSubView() {
     //
     @undoBatch
     @action
-    public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) {
+    public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string) {
         if (document?._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document);
         if (!CollectionDockingView.Instance) return false;
         const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document);
@@ -208,14 +209,15 @@ export class CollectionDockingView extends CollectionSubView() {
                 // if row
                 switch (pullSide) {
                     default:
-                    case 'right':
+                    case OpenWhereMod.none:
+                    case OpenWhereMod.right:
                         glayRoot.contentItems[0].addChild(newContentItem());
                         break;
-                    case 'left':
+                    case OpenWhereMod.left:
                         glayRoot.contentItems[0].addChild(newContentItem(), 0);
                         break;
-                    case 'top':
-                    case 'bottom':
+                    case OpenWhereMod.top:
+                    case OpenWhereMod.bottom:
                         // if not going in a row layout, must add already existing content into column
                         const rowlayout = glayRoot.contentItems[0];
                         const newColumn = rowlayout.layoutManager.createContentItem({ type: 'column' }, instance._goldenLayout);
@@ -496,7 +498,7 @@ export class CollectionDockingView extends CollectionSubView() {
                     title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
                 });
                 this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
-                CollectionDockingView.AddSplit(docToAdd, '', stack);
+                CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
             }
         });
 
@@ -539,7 +541,7 @@ export class CollectionDockingView extends CollectionSubView() {
                             title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`,
                         });
                         this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
-                        CollectionDockingView.AddSplit(docToAdd, '', stack);
+                        CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack);
                     }
                 })
             );
@@ -568,14 +570,14 @@ export class CollectionDockingView extends CollectionSubView() {
 
 ScriptingGlobals.add(
     function openInLightbox(doc: any) {
-        LightboxView.AddDocTab(doc, 'lightbox');
+        LightboxView.AddDocTab(doc, OpenWhere.lightbox);
     },
     'opens up document in a lightbox',
     '(doc: any)'
 );
 ScriptingGlobals.add(
     function openOnRight(doc: any) {
-        return CollectionDockingView.AddSplit(doc, 'right');
+        return CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
     },
     'opens up document in tab on right side of the screen',
     '(doc: any)'
@@ -588,5 +590,5 @@ ScriptingGlobals.add(
     '(doc: any)'
 );
 ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) {
-    CollectionDockingView.ReplaceTab(doc, 'right', undefined, shiftKey);
+    CollectionDockingView.ReplaceTab(doc, OpenWhereMod.right, undefined, shiftKey);
 });
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index b0f64ed60..29670a1a7 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -19,7 +19,7 @@ import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { LightboxView } from '../LightboxView';
-import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
@@ -180,14 +180,6 @@ export class CollectionNoteTakingView extends CollectionSubView() {
         return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     }
 
-    addDocTab = (doc: Doc, where: string) => {
-        if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
-            this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
-            return true;
-        }
-        return this.props.addDocTab(doc, where);
-    };
-
     scrollToBottom = () => {
         smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight);
     };
@@ -274,7 +266,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
                 removeDocument={this.props.removeDocument}
                 contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)}
                 whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-                addDocTab={this.addDocTab}
+                addDocTab={this.props.addDocTab}
                 bringToFront={returnFalse}
                 scriptContext={this.props.scriptContext}
                 pinToPres={this.props.pinToPres}
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 38e240ac6..e95622630 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -12,6 +12,8 @@ import './CollectionPileView.scss';
 import { CollectionSubView } from './CollectionSubView';
 import React = require('react');
 import { ScriptField } from '../../../fields/ScriptField';
+import { OpenWhere } from '../nodes/DocumentView';
+import { computePassLayout, computeStarburstLayout } from './collectionFreeForm';
 
 @observer
 export class CollectionPileView extends CollectionSubView() {
@@ -19,8 +21,8 @@ export class CollectionPileView extends CollectionSubView() {
     _disposers: { [name: string]: IReactionDisposer } = {};
 
     componentDidMount() {
-        if (this.layoutEngine() !== 'pass' && this.layoutEngine() !== 'starburst') {
-            this.Document._pileLayoutEngine = 'pass';
+        if (this.layoutEngine() !== computePassLayout.name && this.layoutEngine() !== computeStarburstLayout.name) {
+            this.Document._pileLayoutEngine = computePassLayout.name;
         }
         this._originalChrome = this.layoutDoc._chromeHidden;
         this.layoutDoc._chromeHidden = true;
@@ -56,7 +58,7 @@ export class CollectionPileView extends CollectionSubView() {
 
     // returns the contents of the pileup in a CollectionFreeFormView
     @computed get contents() {
-        const isStarburst = this.layoutEngine() === 'starburst';
+        const isStarburst = this.layoutEngine() === computeStarburstLayout.name;
         return (
             <div className="collectionPileView-innards" style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : 'none' }}>
                 <CollectionFreeFormView
@@ -74,7 +76,7 @@ export class CollectionPileView extends CollectionSubView() {
 
     // toggles the pileup between starburst to compact
     toggleStarburst = action(() => {
-        if (this.layoutEngine() === 'starburst') {
+        if (this.layoutEngine() === computeStarburstLayout.name) {
             const defaultSize = 110;
             this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
             this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
@@ -83,12 +85,12 @@ export class CollectionPileView extends CollectionSubView() {
             DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
             this.layoutDoc._panX = 0;
             this.layoutDoc._panY = -10;
-            this.props.Document._pileLayoutEngine = 'pass';
+            this.props.Document._pileLayoutEngine = computePassLayout.name;
         } else {
             const defaultSize = 25;
             !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250);
             !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
-            if (this.layoutEngine() === 'pass') {
+            if (this.layoutEngine() === computePassLayout.name) {
                 this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
                 this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
                 this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
@@ -96,7 +98,7 @@ export class CollectionPileView extends CollectionSubView() {
             }
             this.layoutDoc._panX = this.layoutDoc._panY = 0;
             this.layoutDoc._width = this.layoutDoc._height = defaultSize;
-            this.props.Document._pileLayoutEngine = 'starburst';
+            this.props.Document._pileLayoutEngine = computeStarburstLayout.name;
         }
     });
 
@@ -118,7 +120,7 @@ export class CollectionPileView extends CollectionSubView() {
                         const doc = this.childDocs[0];
                         doc.x = e.clientX;
                         doc.y = e.clientY;
-                        this.props.addDocTab(doc, 'inParent') && (this.props.removeDocument?.(doc) || false);
+                        this.props.addDocTab(doc, OpenWhere.inParent) && (this.props.removeDocument?.(doc) || false);
                         dist = 0;
                     }
                 }
@@ -130,8 +132,8 @@ export class CollectionPileView extends CollectionSubView() {
                 SnappingManager.SetIsDragging(false);
             },
             emptyFunction,
-            e.shiftKey && this.layoutEngine() === 'pass',
-            this.layoutEngine() === 'pass' && e.shiftKey
+            e.shiftKey && this.layoutEngine() === computePassLayout.name,
+            this.layoutEngine() === computePassLayout.name && e.shiftKey
         ); // this sets _doubleTap
     };
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 175051d5c..aa4583af6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -22,7 +22,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { EditableView } from '../EditableView';
 import { LightboxView } from '../LightboxView';
 import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
-import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { StyleProp } from '../StyleProvider';
@@ -241,7 +241,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     }
 
-    addDocTab = (doc: Doc, where: string) => {
+    addDocTab = (doc: Doc, where: OpenWhere) => {
         if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
             this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
             return true;
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 3dd9d2d84..ac896a8fd 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,32 +1,32 @@
-import { toUpper } from "lodash";
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, Opt, StrListCast } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { ObjectField } from "../../../fields/ObjectField";
-import { RichTextField } from "../../../fields/RichTextField";
-import { listSpec } from "../../../fields/Schema";
-import { ComputedField, ScriptField } from "../../../fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils";
-import { Docs } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
-import { DocumentManager } from "../../util/DocumentManager";
-import { ScriptingGlobals } from "../../util/ScriptingGlobals";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import { EditableView } from "../EditableView";
-import { ViewSpecPrefix } from "../nodes/DocumentView";
-import { ViewDefBounds } from "./collectionFreeForm/CollectionFreeFormLayoutEngines";
-import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
-import { CollectionSubView } from "./CollectionSubView";
-import "./CollectionTimeView.scss";
-import React = require("react");
+import { toUpper } from 'lodash';
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc, Opt, StrListCast } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { ObjectField } from '../../../fields/ObjectField';
+import { RichTextField } from '../../../fields/RichTextField';
+import { listSpec } from '../../../fields/Schema';
+import { ComputedField, ScriptField } from '../../../fields/ScriptField';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DocumentManager } from '../../util/DocumentManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { ContextMenu } from '../ContextMenu';
+import { ContextMenuProps } from '../ContextMenuItem';
+import { EditableView } from '../EditableView';
+import { ViewSpecPrefix } from '../nodes/DocumentView';
+import { computePivotLayout, computeTimelineLayout, ViewDefBounds } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionSubView } from './CollectionSubView';
+import './CollectionTimeView.scss';
+import React = require('react');
 
 @observer
 export class CollectionTimeView extends CollectionSubView() {
     _changing = false;
-    @observable _layoutEngine = "pivot";
+    @observable _layoutEngine = computePivotLayout.name;
     @observable _collapsed: boolean = false;
     @observable _childClickedScript: Opt<ScriptField>;
     @observable _viewDefDivClick: Opt<ScriptField>;
@@ -35,7 +35,7 @@ export class CollectionTimeView extends CollectionSubView() {
     getAnchor = () => {
         const anchor = Docs.Create.HTMLAnchorDocument([], {
             title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any,
-            annotationOn: this.rootDoc
+            annotationOn: this.rootDoc,
         });
 
         // save view spec information for anchor
@@ -43,81 +43,103 @@ export class CollectionTimeView extends CollectionSubView() {
         proto.pivotField = this.pivotField;
         proto.docFilters = ObjectField.MakeCopy(this.layoutDoc._docFilters as ObjectField) || new List<string>([]);
         proto.docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField) || new List<string>([]);
-        proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType;
+        proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType;
 
         // store anchor in annotations list of document (not technically needed since these anchors are never drawn)
-        if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) {
-            Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor);
+        if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) {
+            Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor);
         } else {
-            this.dataDoc[this.props.fieldKey + "-annotations"] = new List<Doc>([anchor]);
+            this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]);
         }
         return anchor;
-    }
+    };
 
     async componentDidMount() {
         this.props.setContentView?.(this);
         //const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), "");
         ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List<string>(['dropAction']); useRightSplit(alias, shiftKey); ";
         runInAction(() => {
-            this._childClickedScript = ScriptField.MakeScript("openInLightbox(self)", { this: Doc.name });
-            this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" });
+            this._childClickedScript = ScriptField.MakeScript('openInLightbox(self)', { this: Doc.name });
+            this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' });
         });
     }
 
-    get pivotField() { return this._focusPivotField || StrCast(this.layoutDoc._pivotField); }
+    get pivotField() {
+        return this._focusPivotField || StrCast(this.layoutDoc._pivotField);
+    }
     @action
     setViewSpec = (anchor: Doc, preview: boolean) => {
-        if (preview) {   // if in preview, then override document's fields with view spec
+        if (preview) {
+            // if in preview, then override document's fields with view spec
             this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters);
             this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters);
             this._focusPivotField = StrCast(anchor.pivotField);
-        } else if (anchor.pivotField !== undefined) {  // otherwise set document's fields based on anchor view spec
+        } else if (anchor.pivotField !== undefined) {
+            // otherwise set document's fields based on anchor view spec
             this.layoutDoc._prevFilterIndex = 1;
             this.layoutDoc._pivotField = StrCast(anchor.pivotField);
             this.layoutDoc._docFilters = new List<string>(StrListCast(anchor.docFilters));
             this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters));
         }
         return 0;
-    }
+    };
 
     layoutEngine = () => this._layoutEngine;
-    toggleVisibility = action(() => this._collapsed = !this._collapsed);
+    toggleVisibility = action(() => (this._collapsed = !this._collapsed));
 
     onMinDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
-            const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0));
-            const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10));
-            this.props.Document[this.props.fieldKey + "-timelineMinReq"] = minReq + (maxReq - minReq) * delta[0] / this.props.PanelWidth();
-            this.props.Document[this.props.fieldKey + "-timelineSpan"] = undefined;
-            return false;
-        }), returnFalse, emptyFunction);
-    }
+        setupMoveUpEvents(
+            this,
+            e,
+            action((e: PointerEvent, down: number[], delta: number[]) => {
+                const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0));
+                const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10));
+                this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
+                this.props.Document[this.props.fieldKey + '-timelineSpan'] = undefined;
+                return false;
+            }),
+            returnFalse,
+            emptyFunction
+        );
+    };
 
     onMaxDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
-            const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0));
-            const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10));
-            this.props.Document[this.props.fieldKey + "-timelineMaxReq"] = maxReq + (maxReq - minReq) * delta[0] / this.props.PanelWidth();
-            return false;
-        }), returnFalse, emptyFunction);
-    }
+        setupMoveUpEvents(
+            this,
+            e,
+            action((e: PointerEvent, down: number[], delta: number[]) => {
+                const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0));
+                const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10));
+                this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq + ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
+                return false;
+            }),
+            returnFalse,
+            emptyFunction
+        );
+    };
 
     onMidDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
-            const minReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMinReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMin"], 0));
-            const maxReq = NumCast(this.props.Document[this.props.fieldKey + "-timelineMaxReq"], NumCast(this.props.Document[this.props.fieldKey + "-timelineMax"], 10));
-            this.props.Document[this.props.fieldKey + "-timelineMinReq"] = minReq - (maxReq - minReq) * delta[0] / this.props.PanelWidth();
-            this.props.Document[this.props.fieldKey + "-timelineMaxReq"] = maxReq - (maxReq - minReq) * delta[0] / this.props.PanelWidth();
-            return false;
-        }), returnFalse, emptyFunction);
-    }
+        setupMoveUpEvents(
+            this,
+            e,
+            action((e: PointerEvent, down: number[], delta: number[]) => {
+                const minReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMinReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMin'], 0));
+                const maxReq = NumCast(this.props.Document[this.props.fieldKey + '-timelineMaxReq'], NumCast(this.props.Document[this.props.fieldKey + '-timelineMax'], 10));
+                this.props.Document[this.props.fieldKey + '-timelineMinReq'] = minReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
+                this.props.Document[this.props.fieldKey + '-timelineMaxReq'] = maxReq - ((maxReq - minReq) * delta[0]) / this.props.PanelWidth();
+                return false;
+            }),
+            returnFalse,
+            emptyFunction
+        );
+    };
 
     goTo = (prevFilterIndex: number) => {
-        this.layoutDoc._pivotField = this.layoutDoc["_prevPivotFields" + prevFilterIndex];
-        this.layoutDoc._docFilters = ObjectField.MakeCopy(this.layoutDoc["_prevDocFilter" + prevFilterIndex] as ObjectField);
-        this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc["_prevDocRangeFilters" + prevFilterIndex] as ObjectField);
+        this.layoutDoc._pivotField = this.layoutDoc['_prevPivotFields' + prevFilterIndex];
+        this.layoutDoc._docFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocFilter' + prevFilterIndex] as ObjectField);
+        this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocRangeFilters' + prevFilterIndex] as ObjectField);
         this.layoutDoc._prevFilterIndex = prevFilterIndex;
-    }
+    };
 
     @action
     contentsDown = (e: React.MouseEvent) => {
@@ -127,37 +149,58 @@ export class CollectionTimeView extends CollectionSubView() {
         } else {
             this.layoutDoc._docFilters = new List([]);
         }
-    }
+    };
 
     dontScaleFilter = (doc: Doc) => doc.type === DocumentType.RTF;
     @computed get contents() {
-        return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : "none" }}
-            onClick={this.contentsDown}>
-            <CollectionFreeFormView {...this.props}
-                engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }}
-                fitContentsToBox={returnTrue}
-                childClickScript={this._childClickedScript}
-                viewDefDivClick={this._viewDefDivClick}
-                //dontScaleFilter={this.dontScaleFilter}
-                layoutEngine={this.layoutEngine} />
-        </div>;
+        return (
+            <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
+                <CollectionFreeFormView
+                    {...this.props}
+                    engineProps={{ pivotField: this.pivotField, docFilters: this.childDocFilters, docRangeFilters: this.childDocRangeFilters }}
+                    fitContentsToBox={returnTrue}
+                    childClickScript={this._childClickedScript}
+                    viewDefDivClick={this._viewDefDivClick}
+                    //dontScaleFilter={this.dontScaleFilter}
+                    layoutEngine={this.layoutEngine}
+                />
+            </div>
+        );
     }
 
     public static SyncTimelineToPresentation(doc: Doc) {
         const fieldKey = Doc.LayoutFieldKey(doc);
-        doc[fieldKey + "-timelineCur"] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)");
+        doc[fieldKey + '-timelineCur'] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)");
     }
     specificMenu = (e: React.MouseEvent) => {
         const layoutItems: ContextMenuProps[] = [];
         const doc = this.layoutDoc;
 
-        layoutItems.push({ description: "Force Timeline", event: () => { doc._forceRenderEngine = "timeline"; }, icon: "compress-arrows-alt" });
-        layoutItems.push({ description: "Force Pivot", event: () => { doc._forceRenderEngine = "pivot"; }, icon: "compress-arrows-alt" });
-        layoutItems.push({ description: "Auto Time/Pivot layout", event: () => { doc._forceRenderEngine = undefined; }, icon: "compress-arrows-alt" });
-        layoutItems.push({ description: "Sync with presentation", event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: "compress-arrows-alt" });
+        layoutItems.push({
+            description: 'Force Timeline',
+            event: () => {
+                doc._forceRenderEngine = computeTimelineLayout.name;
+            },
+            icon: 'compress-arrows-alt',
+        });
+        layoutItems.push({
+            description: 'Force Pivot',
+            event: () => {
+                doc._forceRenderEngine = computePivotLayout.name;
+            },
+            icon: 'compress-arrows-alt',
+        });
+        layoutItems.push({
+            description: 'Auto Time/Pivot layout',
+            event: () => {
+                doc._forceRenderEngine = undefined;
+            },
+            icon: 'compress-arrows-alt',
+        });
+        layoutItems.push({ description: 'Sync with presentation', event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: 'compress-arrows-alt' });
 
-        ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" });
-    }
+        ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
+    };
     @computed get _allFacets() {
         const facets = new Set<string>();
         this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
@@ -169,37 +212,40 @@ export class CollectionTimeView extends CollectionSubView() {
         const docItems: ContextMenuProps[] = [];
         const keySet: Set<string> = new Set();
 
-        this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey =>
-            pair.layout[fieldKey] instanceof RichTextField ||
-            typeof (pair.layout[fieldKey]) === "number" ||
-            typeof (pair.layout[fieldKey]) === "boolean" ||
-            typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] !== "#" || fieldKey === "#") && (fieldKey === "tags" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey)));
-        Array.from(keySet).map(fieldKey =>
-            docItems.push({ description: ":" + fieldKey, event: () => this.layoutDoc._pivotField = fieldKey, icon: "compress-arrows-alt" }));
-        docItems.push({ description: ":default", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" });
-        ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" });
+        this.childLayoutPairs.map(pair =>
+            this._allFacets
+                .filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof pair.layout[fieldKey] === 'number' || typeof pair.layout[fieldKey] === 'boolean' || typeof pair.layout[fieldKey] === 'string')
+                .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey[0] !== '#' || fieldKey === '#') && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0]))
+                .map(fieldKey => keySet.add(fieldKey))
+        );
+        Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' }));
+        docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' });
+        ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' });
         const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y);
-        ContextMenu.Instance.displayMenu(x, y, ":");
-    }
+        ContextMenu.Instance.displayMenu(x, y, ':');
+    };
 
     @computed get pivotKeyUI() {
-        return <div className={"pivotKeyEntry"}>
-            <EditableView
-                GetValue={returnEmptyString}
-                SetValue={(value: any) => {
-                    if (value?.length) {
-                        this.layoutDoc._pivotField = value;
-                        return true;
-                    }
-                    return false;
-                }}
-                toggle={this.toggleVisibility}
-                background={"#f1efeb"} // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
-                contents={":" + StrCast(this.layoutDoc._pivotField)}
-                showMenuOnLoad={true}
-                display={"inline"}
-                menuCallback={this.menuCallback} />
-        </div>;
+        return (
+            <div className={'pivotKeyEntry'}>
+                <EditableView
+                    GetValue={returnEmptyString}
+                    SetValue={(value: any) => {
+                        if (value?.length) {
+                            this.layoutDoc._pivotField = value;
+                            return true;
+                        }
+                        return false;
+                    }}
+                    toggle={this.toggleVisibility}
+                    background={'#f1efeb'} // this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
+                    contents={':' + StrCast(this.layoutDoc._pivotField)}
+                    showMenuOnLoad={true}
+                    display={'inline'}
+                    menuCallback={this.menuCallback}
+                />
+            </div>
+        );
     }
 
     render() {
@@ -211,55 +257,62 @@ export class CollectionTimeView extends CollectionSubView() {
             }
         });
         const forceLayout = StrCast(this.layoutDoc._forceRenderEngine);
-        const doTimeline = forceLayout ? (forceLayout === "timeline") : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6;
-        if (doTimeline !== (this._layoutEngine === "timeline")) {
+        const doTimeline = forceLayout ? forceLayout === computeTimelineLayout.name : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6;
+        if (doTimeline !== (this._layoutEngine === computeTimelineLayout.name)) {
             if (!this._changing) {
                 this._changing = true;
-                setTimeout(action(() => {
-                    this._layoutEngine = doTimeline ? "timeline" : "pivot";
-                    this._changing = false;
-                }), 0);
+                setTimeout(
+                    action(() => {
+                        this._layoutEngine = doTimeline ? computeTimelineLayout.name : computePivotLayout.name;
+                        this._changing = false;
+                    }),
+                    0
+                );
             }
         }
 
-        return <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu}
-            style={{ width: this.props.PanelWidth(), height: "100%" }}>
-            {this.pivotKeyUI}
-            {this.contents}
-            {!this.props.isSelected() || !doTimeline ? (null) : <>
-                <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
-                <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} />
-                <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
-            </>}
-        </div>;
+        return (
+            <div className={'collectionTimeView' + (doTimeline ? '' : '-pivot')} onContextMenu={this.specificMenu} style={{ width: this.props.PanelWidth(), height: '100%' }}>
+                {this.pivotKeyUI}
+                {this.contents}
+                {!this.props.isSelected() || !doTimeline ? null : (
+                    <>
+                        <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
+                        <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} />
+                        <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
+                    </>
+                )}
+            </div>
+        );
     }
 }
 
 ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
-    const pivotField = StrCast(pivotDoc._pivotField) || "author";
+    const pivotField = StrCast(pivotDoc._pivotField) || 'author';
     let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
     const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField));
-    pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
-    pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
-    pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotField;
+    pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
+    pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField);
+    pivotDoc['_prevPivotFields' + prevFilterIndex] = pivotField;
     pivotDoc._prevFilterIndex = ++prevFilterIndex;
     pivotDoc._docFilters = new List();
-    setTimeout(action(() => {
-        const filterVals = (bounds.payload as string[]);
-        filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, "check"));
-        const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
-        if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
-            if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
-                pivotDoc._pivotField = filterVals[0];
+    setTimeout(
+        action(() => {
+            const filterVals = bounds.payload as string[];
+            filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, 'check'));
+            const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc);
+            if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
+                if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
+                    pivotDoc._pivotField = filterVals[0];
+                }
             }
-        }
-        const newFilters = StrListCast(pivotDoc._docFilters);
-        if (newFilters.length && originalFilter.length &&
-            newFilters.lastElement() === originalFilter.lastElement()) {
-            pivotDoc._prevFilterIndex = --prevFilterIndex;
-            pivotDoc["_prevDocFilter" + prevFilterIndex] = undefined;
-            pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = undefined;
-            pivotDoc["_prevPivotFields" + prevFilterIndex] = undefined;
-        }
-    }));
-});
\ No newline at end of file
+            const newFilters = StrListCast(pivotDoc._docFilters);
+            if (newFilters.length && originalFilter.length && newFilters.lastElement() === originalFilter.lastElement()) {
+                pivotDoc._prevFilterIndex = --prevFilterIndex;
+                pivotDoc['_prevDocFilter' + prevFilterIndex] = undefined;
+                pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = undefined;
+                pivotDoc['_prevPivotFields' + prevFilterIndex] = undefined;
+            }
+        })
+    );
+});
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 625d4e9e5..917d7618c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -16,6 +16,7 @@ import { InteractionUtils } from '../../util/InteractionUtils';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
 import { CollectionCarouselView } from './CollectionCarouselView';
@@ -174,7 +175,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
                 vtype => {
                     const newRendition = Doc.MakeAlias(this.rootDoc);
                     newRendition._viewType = vtype;
-                    this.props.addDocTab(newRendition, 'add:right');
+                    this.props.addDocTab(newRendition, OpenWhere.addRight);
                     return newRendition;
                 },
                 false
@@ -184,17 +185,17 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             const optionItems = options && 'subitems' in options ? options.subitems : [];
             !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.rootDoc.forceActive = !this.rootDoc.forceActive), icon: 'project-diagram' }) : null;
             if (this.rootDoc.childLayout instanceof Doc) {
-                optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, 'add:right'), icon: 'project-diagram' });
+                optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, OpenWhere.addRight), icon: 'project-diagram' });
             }
             if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) {
-                optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, 'add:right'), icon: 'project-diagram' });
+                optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' });
             }
             !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' });
 
             if (!Doc.noviceMode && false) {
                 optionItems.push({
                     description: 'Create Branch',
-                    event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), 'add:right'),
+                    event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), OpenWhere.addRight),
                     icon: 'project-diagram',
                 });
                 optionItems.push({
@@ -225,7 +226,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
                         event: (obj: any) => {
                             const alias = Doc.MakeAlias(this.rootDoc);
                             DocUtils.makeCustomViewClicked(alias, undefined, func.key);
-                            this.props.addDocTab(alias, 'add:right');
+                            this.props.addDocTab(alias, OpenWhere.addRight);
                         },
                     })
                 );
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index e21649648..2cc588b78 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -26,7 +26,7 @@ import { DashboardView } from '../DashboardView';
 import { Colors, Shadows } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
 import { MainView } from '../MainView';
-import { DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
 import { DashFieldView } from '../nodes/formattedText/DashFieldView';
 import { PinProps, PresBox, PresMovement } from '../nodes/trails';
 import { DefaultStyleProvider, StyleProp } from '../StyleProvider';
@@ -296,7 +296,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         ) {
             const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []);
             if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1);
-            CollectionDockingView.AddSplit(curPres, 'right');
+            CollectionDockingView.AddSplit(curPres, OpenWhereMod.right);
             setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things
         }
         setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
@@ -343,34 +343,30 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     //                                  "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name,
     //                                   "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
     //  inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack
-    addDocTab = (doc: Doc, location: string) => {
+    addDocTab = (doc: Doc, location: OpenWhere) => {
         SelectionManager.DeselectAll();
-        const locationFields = doc._viewType === CollectionViewType.Docking ? ['dashboard'] : location.split(':');
-        const locationParams = locationFields.length > 1 ? locationFields[1] : '';
+        const locationFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':');
+        const locationParams: OpenWhereMod = locationFields.length > 1 ? (locationFields[1] as OpenWhereMod) : OpenWhereMod.none;
         switch (locationFields[0]) {
-            case 'dashboard':
+            case OpenWhere.dashboard:
                 return DashboardView.openDashboard(doc);
-            case 'close':
+            case OpenWhere.close:
                 return CollectionDockingView.CloseSplit(doc, locationParams);
-            case 'fullScreen':
+            case OpenWhere.fullScreen:
                 return CollectionDockingView.OpenFullScreen(doc);
-            case 'replace':
+            case OpenWhere.replace:
                 return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack);
-            // case "lightbox": {
-            //     // TabDocView.PinDoc(doc, { hidePresBox: true });
-            //     return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab);
-            // }
-            case 'inPlace':
+            case OpenWhere.inPlace:
                 const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined;
                 if (inPlaceView) {
                     inPlaceView.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List<Doc>([doc]);
                     return true;
-                }
-            case 'lightbox':
+                } // fall through to lightbox
+            case OpenWhere.lightbox:
                 return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab);
-            case 'toggle':
+            case OpenWhere.toggle:
                 return CollectionDockingView.ToggleSplit(doc, locationParams, this.stack);
-            case 'add':
+            case OpenWhere.add:
             default:
                 return CollectionDockingView.AddSplit(doc, locationParams, this.stack);
         }
@@ -509,7 +505,7 @@ interface TabMinimapViewProps {
     document: Doc;
     hideMinimap: () => boolean;
     tabView: () => DocumentView | undefined;
-    addDocTab: (doc: Doc, where: string) => boolean;
+    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
     PanelWidth: () => number;
     PanelHeight: () => number;
     background: () => string;
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 13cf64558..bd326f917 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -22,7 +22,7 @@ import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { EditableView } from '../EditableView';
 import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss';
-import { DocumentView, DocumentViewInternal, DocumentViewProps, StyleProviderFunc } from '../nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, DocumentViewProps, OpenWhere, StyleProviderFunc } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
@@ -44,7 +44,7 @@ export interface TreeViewProps {
     containerCollection: Doc;
     renderDepth: number;
     dropAction: dropActionType;
-    addDocTab: (doc: Doc, where: string) => boolean;
+    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
     panelWidth: () => number;
     panelHeight: () => number;
     addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean;
@@ -236,7 +236,7 @@ export class TreeView extends React.Component<TreeViewProps> {
             const bestAlias =
                 docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
             const nextBestAlias = DocListCast(this.props.document.aliases).find(doc => doc.author === Doc.CurrentUserEmail);
-            this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), 'lightbox');
+            this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), OpenWhere.lightbox);
         }
     };
 
@@ -1109,7 +1109,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         remove: undefined | ((doc: Doc | Doc[]) => boolean),
         move: DragManager.MoveFunction,
         dropAction: dropActionType,
-        addDocTab: (doc: Doc, where: string) => boolean,
+        addDocTab: (doc: Doc, where: OpenWhere) => boolean,
         styleProvider: undefined | StyleProviderFunc,
         screenToLocalXf: () => Transform,
         isContentActive: (outsideReaction?: boolean) => boolean,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 54be6ba0f..7dd9cdb8b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -82,7 +82,7 @@ interface PivotColumn {
     filters: string[];
 }
 
-export function computerPassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
+export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
     const docMap = new Map<string, PoolData>();
     childPairs.forEach(({ layout, data }, i) => {
         docMap.set(layout[Id], {
@@ -97,7 +97,7 @@ export function computerPassLayout(poolData: Map<string, PoolData>, pivotDoc: Do
     return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
 }
 
-export function computerStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
+export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
     const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel
     const docMap = new Map<string, PoolData>();
     const docSize = mustFit ? panelDim[0] * 0.33 : 75; // assume an icon sized at 75
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 57cccec4a..8cabf060d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -37,7 +37,7 @@ import { GestureOverlay } from '../../GestureOverlay';
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
 import { LightboxView } from '../../LightboxView';
 import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
-import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView';
 import { FieldViewProps } from '../../nodes/FieldView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
 import { PresBox } from '../../nodes/trails/PresBox';
@@ -47,7 +47,7 @@ import { StyleProp } from '../../StyleProvider';
 import { CollectionSubView } from '../CollectionSubView';
 import { TreeViewType } from '../CollectionTreeView';
 import { TabDocView } from '../TabDocView';
-import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
+import { computePivotLayout, computePassLayout as computePassLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
 import './CollectionFreeFormView.scss';
 import { MarqueeView } from './MarqueeView';
@@ -1272,7 +1272,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     pointerEvents = () => {
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
-        const pointerEvents = this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === 'pass' && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
+        const pointerEvents =
+            this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
         return pointerEvents;
     };
     getChildDocView(entry: PoolData) {
@@ -1328,8 +1329,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             />
         );
     }
-    addDocTab = action((doc: Doc, where: string) => {
-        if (where === 'inParent') {
+    addDocTab = action((doc: Doc, where: OpenWhere) => {
+        if (where === OpenWhere.inParent) {
             (doc instanceof Doc ? [doc] : doc).forEach(doc => {
                 const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
                 doc.x = pt[0];
@@ -1337,7 +1338,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             });
             return this.props.addDocument?.(doc) || false;
         }
-        if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
+        if (where === OpenWhere.inPlace && this.layoutDoc.isInPlaceContainer) {
             this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List<Doc>(doc as any as Doc[]);
             return true;
         }
@@ -1457,10 +1458,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const newPool = new Map<string, PoolData>();
         // prettier-ignore
         switch (this.layoutEngine) {
-            case 'pass':      return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
-            case 'timeline':  return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
-            case 'pivot':     return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
-            case 'starburst': return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) };
+            case computePassLayout.name :      return { newPool, computedElementData: this.doEngineLayout(newPool, computePassLayout) };
+            case computeTimelineLayout.name:  return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
+            case computePivotLayout.name:     return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
+            case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) };
         }
         return { newPool, computedElementData: this.doFreeformLayout(newPool) };
     }
@@ -1724,7 +1725,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             doc.x = scr?.[0];
             doc.y = scr?.[1];
         });
-        this.props.addDocTab(childDocs as any as Doc, 'inParent');
+        this.props.addDocTab(childDocs as any as Doc, OpenWhere.inParent);
         this.props.ContainingCollectionView?.removeDocument(this.props.Document);
     };
 
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index e5a2d9007..b88da5191 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -234,13 +234,6 @@ export class CollectionMulticolumnView extends CollectionSubView() {
     onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
 
-    addDocTab = (doc: Doc, where: string) => {
-        if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) {
-            this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
-            return true;
-        }
-        return this.props.addDocTab(doc, where);
-    };
     focusDocument = (doc: Doc, options: DocFocusOptions) => this.props.focus(this.rootDoc, options);
     isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
     isChildContentActive = () => (((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.isContentActive() ? true : false);
@@ -278,7 +271,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
                 moveDocument={this.props.moveDocument}
                 removeDocument={this.props.removeDocument}
                 whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
-                addDocTab={this.addDocTab}
+                addDocTab={this.props.addDocTab}
                 pinToPres={this.props.pinToPres}
                 bringToFront={returnFalse}
             />
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index adcd9e1e3..ef75fb159 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -1,35 +1,36 @@
-import React = require("react");
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable } from "mobx";
-import { observer } from "mobx-react";
-import { extname } from "path";
-import DatePicker from "react-datepicker";
-import { CellInfo } from "react-table";
-import { DateField } from "../../../../fields/DateField";
-import { Doc, DocListCast, Field, Opt } from "../../../../fields/Doc";
-import { Id } from "../../../../fields/FieldSymbols";
-import { List } from "../../../../fields/List";
-import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
-import { ComputedField } from "../../../../fields/ScriptField";
-import { BoolCast, Cast, DateCast, FieldValue, StrCast } from "../../../../fields/Types";
-import { ImageField } from "../../../../fields/URLField";
-import { emptyFunction, Utils } from "../../../../Utils";
-import { Docs } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { DocumentManager } from "../../../util/DocumentManager";
-import { DragManager } from "../../../util/DragManager";
-import { KeyCodes } from "../../../util/KeyCodes";
-import { CompileScript } from "../../../util/Scripting";
-import { SearchUtil } from "../../../util/SearchUtil";
-import { SnappingManager } from "../../../util/SnappingManager";
-import { undoBatch } from "../../../util/UndoManager";
+import React = require('react');
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { extname } from 'path';
+import DatePicker from 'react-datepicker';
+import { CellInfo } from 'react-table';
+import { DateField } from '../../../../fields/DateField';
+import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
+import { List } from '../../../../fields/List';
+import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
+import { ComputedField } from '../../../../fields/ScriptField';
+import { BoolCast, Cast, DateCast, FieldValue, StrCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { emptyFunction, Utils } from '../../../../Utils';
+import { Docs } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { DragManager } from '../../../util/DragManager';
+import { KeyCodes } from '../../../util/KeyCodes';
+import { CompileScript } from '../../../util/Scripting';
+import { SearchUtil } from '../../../util/SearchUtil';
+import { SnappingManager } from '../../../util/SnappingManager';
+import { undoBatch } from '../../../util/UndoManager';
 import '../../../views/DocumentDecorations.scss';
-import { EditableView } from "../../EditableView";
+import { EditableView } from '../../EditableView';
 import { MAX_ROW_HEIGHT } from '../../global/globalCssVariables.scss';
-import { DocumentIconContainer } from "../../nodes/DocumentIcon";
-import { OverlayView } from "../../OverlayView";
-import { CollectionView } from "../CollectionView";
-import "./CollectionSchemaView.scss";
+import { DocumentIconContainer } from '../../nodes/DocumentIcon';
+import { OverlayView } from '../../OverlayView';
+import { CollectionView } from '../CollectionView';
+import './CollectionSchemaView.scss';
+import { OpenWhere } from '../../nodes/DocumentView';
 
 // intialize cell properties
 export interface CellProps {
@@ -46,10 +47,9 @@ export interface CellProps {
     // currently unused
     renderDepth: number;
     // called when a button is pressed on the node itself
-    addDocTab: (document: Doc, where: string) => boolean;
+    addDocTab: (document: Doc, where: OpenWhere) => boolean;
     pinToPres: (document: Doc) => void;
-    moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined,
-        addDocument: (document: Doc | Doc[]) => boolean) => boolean;
+    moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
     isFocused: boolean;
     changeFocusedCellByIndex: (row: number, col: number) => void;
     // set whether the cell is in the isEditing mode
@@ -67,7 +67,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
     // return a field key that is corrected for whether it COMMENT
     public static resolvedFieldKey(column: string, rowDoc: Doc) {
         const fieldKey = column;
-        if (fieldKey.startsWith("*")) {
+        if (fieldKey.startsWith('*')) {
             const rootKey = fieldKey.substring(1);
             const allKeys = [...Array.from(Object.keys(rowDoc)), ...Array.from(Object.keys(Doc.GetProto(rowDoc)))];
             const matchedKeys = allKeys.filter(key => key.includes(rootKey));
@@ -82,33 +82,37 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
     protected _rowDataDoc = Doc.GetProto(this.props.rowProps.original);
     // methods for dragging and dropping
     protected _dropDisposer?: DragManager.DragDropDisposer;
-    @observable contents: string = "";
+    @observable contents: string = '';
 
-    componentDidMount() { document.addEventListener("keydown", this.onKeyDown); }
-    componentWillUnmount() { document.removeEventListener("keydown", this.onKeyDown); }
+    componentDidMount() {
+        document.addEventListener('keydown', this.onKeyDown);
+    }
+    componentWillUnmount() {
+        document.removeEventListener('keydown', this.onKeyDown);
+    }
 
     @action
     onKeyDown = (e: KeyboardEvent): void => {
         // If a cell is editable and clicked, hitting enter shoudl allow the user to edit it
         if (this.props.isFocused && this.props.isEditable && e.keyCode === KeyCodes.ENTER) {
-            document.removeEventListener("keydown", this.onKeyDown);
+            document.removeEventListener('keydown', this.onKeyDown);
             this._isEditing = true;
             this.props.setIsEditing(true);
         }
-    }
+    };
 
     @action
     isEditingCallback = (isEditing: boolean): void => {
         // a general method that takes a boolean that determines whether the cell should be in
         // is-editing mode
         // remove the event listener if it's there
-        document.removeEventListener("keydown", this.onKeyDown);
+        document.removeEventListener('keydown', this.onKeyDown);
         // it's not already in is-editing mode, re-add the event listener
-        isEditing && document.addEventListener("keydown", this.onKeyDown);
+        isEditing && document.addEventListener('keydown', this.onKeyDown);
         this._isEditing = isEditing;
         this.props.setIsEditing(isEditing);
         this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
-    }
+    };
 
     @action
     onPointerDown = async (e: React.PointerEvent): Promise<void> => {
@@ -119,19 +123,19 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         this.props.setPreviewDoc(this.props.rowProps.original);
 
         let url: string;
-        if (url = StrCast(this.props.rowProps.row.href)) {
+        if ((url = StrCast(this.props.rowProps.row.href))) {
             // opens up the the doc in a new window, blurring the old one
             try {
                 new URL(url);
                 const temp = window.open(url)!;
                 temp.blur();
                 window.focus();
-            } catch { }
+            } catch {}
         }
 
         const doc = Cast(this._rowDoc[this.renderFieldKey], Doc, null);
         doc && this.props.setPreviewDoc(doc);
-    }
+    };
 
     @undoBatch
     applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => {
@@ -142,7 +146,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         doc[this.renderFieldKey] = res.result;
         return true;
         // return whether the change was successful
-    }
+    };
 
     private drop = (e: Event, de: DragManager.DropEvent) => {
         // if the drag has data at its completion
@@ -151,41 +155,51 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             if (de.complete.docDragData.draggedDocuments.length === 1) {
                 // update the renderFieldKey
                 this._rowDataDoc[this.renderFieldKey] = de.complete.docDragData.draggedDocuments[0];
-            }
-            else {
+            } else {
                 // create schema document reflecting the new column arrangement
-                const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.complete.docDragData.draggedDocuments, {});
+                const coll = Docs.Create.SchemaDocument([new SchemaHeaderField('title', '#f1efeb')], de.complete.docDragData.draggedDocuments, {});
                 this._rowDataDoc[this.renderFieldKey] = coll;
             }
             e.stopPropagation();
         }
-    }
+    };
 
     protected dropRef = (ele: HTMLElement | null) => {
         // if the drop disposer is not undefined, run its function
         this._dropDisposer?.();
         // if ele is not null, give ele a non-undefined drop disposer
         ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
-    }
+    };
 
     returnHighlights(contents: string, positions?: number[]) {
         if (positions) {
             const results = [];
             StrCast(this.props.Document._searchString);
             const length = StrCast(this.props.Document._searchString).length;
-            const color = contents ? "black" : "grey";
+            const color = contents ? 'black' : 'grey';
 
-            results.push(<span key="-1" style={{ color }}>{contents?.slice(0, positions[0])}</span>);
+            results.push(
+                <span key="-1" style={{ color }}>
+                    {contents?.slice(0, positions[0])}
+                </span>
+            );
             positions.forEach((num, cur) => {
-                results.push(<span key={"start" + cur} style={{ backgroundColor: "#FFFF00", color }}>{contents?.slice(num, num + length)}</span>);
+                results.push(
+                    <span key={'start' + cur} style={{ backgroundColor: '#FFFF00', color }}>
+                        {contents?.slice(num, num + length)}
+                    </span>
+                );
                 let end = 0;
-                cur === positions.length - 1 ? end = contents.length : end = positions[cur + 1];
-                results.push(<span key={"end" + cur} style={{ color }}>{contents?.slice(num + length, end)}</span>);
-            }
-            );
+                cur === positions.length - 1 ? (end = contents.length) : (end = positions[cur + 1]);
+                results.push(
+                    <span key={'end' + cur} style={{ color }}>
+                        {contents?.slice(num + length, end)}
+                    </span>
+                );
+            });
             return results;
         }
-        return <span style={{ color: contents ? "black" : "grey" }}>{contents ? contents?.valueOf() : "undefined"}</span>;
+        return <span style={{ color: contents ? 'black' : 'grey' }}>{contents ? contents?.valueOf() : 'undefined'}</span>;
     }
 
     @computed get renderFieldKey() {
@@ -199,10 +213,9 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
             const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc);
             const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null);
             // Jump to the this document
-            DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext ? [targetContext] : [],
-                undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc));
+            DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext ? [targetContext] : [], undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc));
         }
-    }
+    };
 
     renderCellWithType(type: string | undefined) {
         const dragRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -214,29 +227,29 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
 
         const onPointerEnter = (e: React.PointerEvent): void => {
             // e.buttons === 1 means the left moue pointer is down
-            if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === "document" || type === undefined)) {
-                dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over";
+            if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === 'document' || type === undefined)) {
+                dragRef.current!.className = 'collectionSchemaView-cellContainer doc-drag-over';
             }
         };
         const onPointerLeave = (e: React.PointerEvent): void => {
             // change the class name to indicate that the cell is no longer being dragged
-            dragRef.current!.className = "collectionSchemaView-cellContainer";
+            dragRef.current!.className = 'collectionSchemaView-cellContainer';
         };
 
         let contents = Field.toString(field as Field);
         // display 2 hyphens instead of a blank box for empty cells
-        contents = contents === "" ? "--" : contents;
+        contents = contents === '' ? '--' : contents;
 
         // classname reflects the tatus of the cell
-        let className = "collectionSchemaView-cellWrapper";
-        if (this._isEditing) className += " editing";
-        if (this.props.isFocused && this.props.isEditable) className += " focused";
-        if (this.props.isFocused && !this.props.isEditable) className += " inactive";
+        let className = 'collectionSchemaView-cellWrapper';
+        if (this._isEditing) className += ' editing';
+        if (this.props.isFocused && this.props.isEditable) className += ' focused';
+        if (this.props.isFocused && !this.props.isEditable) className += ' inactive';
 
         const positions = [];
-        if (StrCast(this.props.Document._searchString).toLowerCase() !== "") {
+        if (StrCast(this.props.Document._searchString).toLowerCase() !== '') {
             // term is ...promise pending... if the field is a Promise, otherwise it is the cell's contents
-            let term = (field instanceof Promise) ? "...promise pending..." : contents.toLowerCase();
+            let term = field instanceof Promise ? '...promise pending...' : contents.toLowerCase();
             const search = StrCast(this.props.Document._searchString).toLowerCase();
             let start = term.indexOf(search);
             let tally = 0;
@@ -256,56 +269,60 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
                 positions.pop();
             }
         }
-        const placeholder = type === "number" ? "0" : contents === "" ? "--" : "undefined";
+        const placeholder = type === 'number' ? '0' : contents === '' ? '--' : 'undefined';
         return (
-            <div className="collectionSchemaView-cellContainer" style={{ cursor: field instanceof Doc ? "grab" : "auto" }}
-                ref={dragRef} onPointerDown={this.onPointerDown} onClick={action(e => this._isEditing = true)} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}>
+            <div
+                className="collectionSchemaView-cellContainer"
+                style={{ cursor: field instanceof Doc ? 'grab' : 'auto' }}
+                ref={dragRef}
+                onPointerDown={this.onPointerDown}
+                onClick={action(e => (this._isEditing = true))}
+                onPointerEnter={onPointerEnter}
+                onPointerLeave={onPointerLeave}>
                 <div className={className} ref={this._focusRef} tabIndex={-1}>
-                    <div className="collectionSchemaView-cellContents" ref={type === undefined || type === "document" ? this.dropRef : null}>
-                        {!this.props.Document._searchDoc ?
+                    <div className="collectionSchemaView-cellContents" ref={type === undefined || type === 'document' ? this.dropRef : null}>
+                        {!this.props.Document._searchDoc ? (
                             <EditableView
                                 editing={this._isEditing}
                                 isEditingCallback={this.isEditingCallback}
-                                display={"inline"}
+                                display={'inline'}
                                 contents={contents}
-                                height={"auto"}
+                                height={'auto'}
                                 maxHeight={Number(MAX_ROW_HEIGHT)}
                                 placeholder={placeholder}
                                 GetValue={() => {
                                     const cfield = ComputedField.WithoutComputed(() => FieldValue(field));
                                     const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined;
-                                    const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1];
-                                    return cscript ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) :
-                                        Field.IsField(cfield) ? Field.toScriptString(cfield) : "";
+                                    const cfinalScript = cscript?.split('return')[cscript.split('return').length - 1];
+                                    return cscript ? (cfinalScript?.endsWith(';') ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : Field.IsField(cfield) ? Field.toScriptString(cfield) : '';
                                 }}
                                 SetValue={action((value: string) => {
                                     // sets what is displayed after the user makes an input
                                     let retVal = false;
-                                    if (value.startsWith(":=") || value.startsWith("=:=")) {
+                                    if (value.startsWith(':=') || value.startsWith('=:=')) {
                                         // decides how to compute a value when given either of the above strings
-                                        const script = value.substring(value.startsWith("=:=") ? 3 : 2);
-                                        retVal = this.props.setComputed(script, value.startsWith(":=") ? this._rowDataDoc : this._rowDoc, this.renderFieldKey, this.props.row, this.props.col);
+                                        const script = value.substring(value.startsWith('=:=') ? 3 : 2);
+                                        retVal = this.props.setComputed(script, value.startsWith(':=') ? this._rowDataDoc : this._rowDoc, this.renderFieldKey, this.props.row, this.props.col);
                                     } else {
                                         // check if the input is a number
                                         let inputIsNum = true;
                                         for (const s of value) {
-                                            if (isNaN(parseInt(s)) && !(s === ".") && !(s === ",")) {
+                                            if (isNaN(parseInt(s)) && !(s === '.') && !(s === ',')) {
                                                 inputIsNum = false;
                                             }
                                         }
                                         // check if the input is a boolean
-                                        const inputIsBool: boolean = value === "false" || value === "true";
-                                        // what to do in the case 
-                                        if (!inputIsNum && !inputIsBool && !value.startsWith("=")) {
+                                        const inputIsBool: boolean = value === 'false' || value === 'true';
+                                        // what to do in the case
+                                        if (!inputIsNum && !inputIsBool && !value.startsWith('=')) {
                                             // if it's not a number, it's a string, and should be processed as such
-                                            // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically 
+                                            // strips the string of quotes when it is edited to prevent quotes form being added to the text automatically
                                             // after each edit
                                             let valueSansQuotes = value;
                                             if (this._isEditing) {
                                                 const vsqLength = valueSansQuotes.length;
                                                 // get rid of outer quotes
-                                                valueSansQuotes = valueSansQuotes.substring(value.startsWith("\"") ? 1 : 0,
-                                                    valueSansQuotes.charAt(vsqLength - 1) === "\"" ? vsqLength - 1 : vsqLength);
+                                                valueSansQuotes = valueSansQuotes.substring(value.startsWith('"') ? 1 : 0, valueSansQuotes.charAt(vsqLength - 1) === '"' ? vsqLength - 1 : vsqLength);
                                             }
                                             let inputAsString = '"';
                                             // escape any quotes in the string
@@ -319,27 +336,27 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
                                             // add a closing quote
                                             inputAsString += '"';
                                             //two options here: we can strip off outer quotes or we can figure out what's going on with the script
-                                            const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+                                            const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
                                             const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length;
                                             // change it if a change is made, otherwise, just compile using the old cell conetnts
                                             script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
                                             // handle numbers and expressions
-                                        } else if (inputIsNum || value.startsWith("=")) {
+                                        } else if (inputIsNum || value.startsWith('=')) {
                                             //TODO: make accept numbers
-                                            const inputscript = value.substring(value.startsWith("=") ? 1 : 0);
+                                            const inputscript = value.substring(value.startsWith('=') ? 1 : 0);
                                             // if commas are not stripped, the parser only considers the numbers after the last comma
-                                            let inputSansCommas = "";
+                                            let inputSansCommas = '';
                                             for (const s of inputscript) {
-                                                if (!(s === ",")) {
+                                                if (!(s === ',')) {
                                                     inputSansCommas += s;
                                                 }
                                             }
-                                            const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+                                            const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
                                             const changeMade = value.length - 2 !== value.length;
                                             script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
                                             // handle booleans
                                         } else if (inputIsBool) {
-                                            const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
+                                            const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
                                             const changeMade = value.length - 2 !== value.length;
                                             script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
                                         }
@@ -352,33 +369,47 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
                                 })}
                                 OnFillDown={async (value: string) => {
                                     // computes all of the value preceded by :=
-                                    const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
-                                    script.compiled && DocListCast(this.props.Document[this.props.fieldKey]).
-                                        forEach((doc, i) => value.startsWith(":=") ?
-                                            this.props.setComputed(value.substring(2), Doc.GetProto(doc), this.renderFieldKey, i, this.props.col) :
-                                            this.applyToDoc(Doc.GetProto(doc), i, this.props.col, script.run));
+                                    const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: 'number', $c: 'number', $: 'any' } });
+                                    script.compiled &&
+                                        DocListCast(this.props.Document[this.props.fieldKey]).forEach((doc, i) =>
+                                            value.startsWith(':=') ? this.props.setComputed(value.substring(2), Doc.GetProto(doc), this.renderFieldKey, i, this.props.col) : this.applyToDoc(Doc.GetProto(doc), i, this.props.col, script.run)
+                                        );
                                 }}
                             />
-                            :
+                        ) : (
                             this.returnHighlights(contents, positions)
-                        }
-                    </div >
+                        )}
+                    </div>
                 </div>
             </div>
         );
     }
 
-    render() { return this.renderCellWithType(undefined); }
+    render() {
+        return this.renderCellWithType(undefined);
+    }
 }
 
 @observer
-export class CollectionSchemaNumberCell extends CollectionSchemaCell { render() { return this.renderCellWithType("number"); } }
+export class CollectionSchemaNumberCell extends CollectionSchemaCell {
+    render() {
+        return this.renderCellWithType('number');
+    }
+}
 
 @observer
-export class CollectionSchemaBooleanCell extends CollectionSchemaCell { render() { return this.renderCellWithType("boolean"); } }
+export class CollectionSchemaBooleanCell extends CollectionSchemaCell {
+    render() {
+        return this.renderCellWithType('boolean');
+    }
+}
 
 @observer
-export class CollectionSchemaStringCell extends CollectionSchemaCell { render() { return this.renderCellWithType("string"); } }
+export class CollectionSchemaStringCell extends CollectionSchemaCell {
+    render() {
+        return this.renderCellWithType('string');
+    }
+}
 
 @observer
 export class CollectionSchemaDateCell extends CollectionSchemaCell {
@@ -396,24 +427,24 @@ export class CollectionSchemaDateCell extends CollectionSchemaCell {
         // ^ DateCast is always undefined for some reason, but that is what the field should be set to
         this._rowDoc[this.renderFieldKey] = new DateField(date as Date);
         //}
-    }
+    };
 
     render() {
-        return !this.props.isFocused ? <span onPointerDown={this.onPointerDown}>{this._date ? Field.toString(this._date as Field) : "--"}</span> :
-            <DatePicker
-                selected={this._date?.date || new Date}
-                onSelect={date => this.handleChange(date)}
-                onChange={date => this.handleChange(date)}
-            />;
+        return !this.props.isFocused ? (
+            <span onPointerDown={this.onPointerDown}>{this._date ? Field.toString(this._date as Field) : '--'}</span>
+        ) : (
+            <DatePicker selected={this._date?.date || new Date()} onSelect={date => this.handleChange(date)} onChange={date => this.handleChange(date)} />
+        );
     }
 }
 
 @observer
 export class CollectionSchemaDocCell extends CollectionSchemaCell {
-
     _overlayDisposer?: () => void;
 
-    @computed get _doc() { return FieldValue(Cast(this._rowDoc[this.renderFieldKey], Doc)); }
+    @computed get _doc() {
+        return FieldValue(Cast(this._rowDoc[this.renderFieldKey], Doc));
+    }
 
     @action
     onSetValue = (value: string) => {
@@ -422,7 +453,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
         const script = CompileScript(value, {
             addReturn: true,
             typecheck: true,
-            transformer: DocumentIconContainer.getTransformer()
+            transformer: DocumentIconContainer.getTransformer(),
         });
         // compile the script
         const results = script.compiled && script.run();
@@ -432,44 +463,43 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
             return true;
         }
         return false;
-    }
+    };
 
-    componentWillUnmount() { this.onBlur(); }
+    componentWillUnmount() {
+        this.onBlur();
+    }
 
-    onBlur = () => { this._overlayDisposer?.(); };
+    onBlur = () => {
+        this._overlayDisposer?.();
+    };
     onFocus = () => {
         this.onBlur();
         this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
-    }
+    };
 
     @action
     isEditingCallback = (isEditing: boolean): void => {
         // the isEditingCallback from a general CollectionSchemaCell
-        document.removeEventListener("keydown", this.onKeyDown);
-        isEditing && document.addEventListener("keydown", this.onKeyDown);
+        document.removeEventListener('keydown', this.onKeyDown);
+        isEditing && document.addEventListener('keydown', this.onKeyDown);
         this._isEditing = isEditing;
         this.props.setIsEditing(isEditing);
         this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
-    }
+    };
 
     render() {
         // if there's a doc, render it
-        return !this._doc ? this.renderCellWithType("document") :
-            <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1}
-                onPointerDown={this.onPointerDown}
-            >
-                <div className="collectionSchemaView-cellContents-document"
-                    style={{ padding: "5.9px" }}
-                    ref={this.dropRef}
-                    onFocus={this.onFocus}
-                    onBlur={this.onBlur}
-                >
+        return !this._doc ? (
+            this.renderCellWithType('document')
+        ) : (
+            <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
+                <div className="collectionSchemaView-cellContents-document" style={{ padding: '5.9px' }} ref={this.dropRef} onFocus={this.onFocus} onBlur={this.onBlur}>
                     <EditableView
                         editing={this._isEditing}
                         isEditingCallback={this.isEditingCallback}
-                        display={"inline"}
-                        contents={this._doc.title || "--"}
-                        height={"auto"}
+                        display={'inline'}
+                        contents={this._doc.title || '--'}
+                        height={'auto'}
                         maxHeight={Number(MAX_ROW_HEIGHT)}
                         GetValue={() => StrCast(this._doc?.title)}
                         SetValue={action((value: string) => {
@@ -477,33 +507,36 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
                             return true;
                         })}
                     />
-                </div >
-                <div onClick={() => this._doc && this.props.addDocTab(this._doc, "add:right")} className="collectionSchemaView-cellContents-docButton">
+                </div>
+                <div onClick={() => this._doc && this.props.addDocTab(this._doc, OpenWhere.addRight)} className="collectionSchemaView-cellContents-docButton">
                     <FontAwesomeIcon icon="external-link-alt" size="lg" />
                 </div>
-            </div>;
+            </div>
+        );
     }
 }
 
 @observer
 export class CollectionSchemaImageCell extends CollectionSchemaCell {
-
     choosePath(url: URL) {
-        if (url.protocol === "data") return url.href; // if the url ises the data protocol, just return the href
+        if (url.protocol === 'data') return url.href; // if the url ises the data protocol, just return the href
         if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
-        if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href;//Why is this here — good question
+        if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href; //Why is this here — good question
 
         const ext = extname(url.href);
-        return url.href.replace(ext, "_o" + ext);
+        return url.href.replace(ext, '_o' + ext);
     }
 
     render() {
         const field = Cast(this._rowDoc[this.renderFieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
-        const alts = DocListCast(this._rowDoc[this.renderFieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
-        const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
+        const alts = DocListCast(this._rowDoc[this.renderFieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images
+        const altpaths = alts
+            .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url)
+            .filter(url => url)
+            .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
         const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
         // If there is a path, follow it; otherwise, follow a link to a default image icon
-        const url = paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
+        const url = paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
 
         const aspect = Doc.NativeAspect(this._rowDoc); // aspect ratio
         let width = Math.min(75, this.props.rowProps.width); // get a with that is no smaller than 75px
@@ -511,25 +544,28 @@ export class CollectionSchemaImageCell extends CollectionSchemaCell {
         width = height * aspect; // increase the width of the image if necessary to maintain proportionality
 
         const reference = React.createRef<HTMLDivElement>();
-        return <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
-            <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} ref={reference}>
-                <img src={url[0]}
-                    width={paths.length ? width : "20px"}
-                    height={paths.length ? height : "20px"} />
-            </div >
-        </div>;
+        return (
+            <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
+                <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} ref={reference}>
+                    <img src={url[0]} width={paths.length ? width : '20px'} height={paths.length ? height : '20px'} />
+                </div>
+            </div>
+        );
     }
 }
 
-
 @observer
 export class CollectionSchemaListCell extends CollectionSchemaCell {
     _overlayDisposer?: () => void;
 
-    @computed get _field() { return this._rowDoc[this.renderFieldKey]; }
-    @computed get _optionsList() { return this._field as List<any>; }
+    @computed get _field() {
+        return this._rowDoc[this.renderFieldKey];
+    }
+    @computed get _optionsList() {
+        return this._field as List<any>;
+    }
     @observable private _opened = false; // whether the list is opened
-    @observable private _text = "select an item";
+    @observable private _text = 'select an item';
     @observable private _selectedNum = 0; // the index of the list item selected
 
     @action
@@ -538,102 +574,109 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
         this._optionsList[this._selectedNum] = this._text = value;
 
         (this._field as List<any>).splice(this._selectedNum, 1, value);
-    }
+    };
 
     @action
     onSelected = (element: string, index: number) => {
         // if an item is selected, the private variables should update to reflect this
         this._text = element;
         this._selectedNum = index;
-    }
+    };
 
     onFocus = () => {
         this._overlayDisposer?.();
         this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
-    }
+    };
 
     render() {
         const link = false;
         const reference = React.createRef<HTMLDivElement>();
 
-        // if the list is not opened, don't display it; otherwise, do. 
+        // if the list is not opened, don't display it; otherwise, do.
         if (this._optionsList?.length) {
-            const options = !this._opened ? (null) :
+            const options = !this._opened ? null : (
                 <div>
                     {this._optionsList.map((element, index) => {
                         const val = Field.toString(element);
-                        return <div className="collectionSchemaView-dropdownOption" key={index} style={{ padding: "6px" }} onPointerDown={(e) => this.onSelected(StrCast(element), index)} >
-                            {val}
-                        </div>;
+                        return (
+                            <div className="collectionSchemaView-dropdownOption" key={index} style={{ padding: '6px' }} onPointerDown={e => this.onSelected(StrCast(element), index)}>
+                                {val}
+                            </div>
+                        );
                     })}
-                </div>;
-
-            const plainText = <div style={{ padding: "5.9px" }}>{this._text}</div>;
-            const textarea = <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} style={{ padding: "5.9px" }} ref={this.dropRef} >
-                <EditableView
-                    editing={this._isEditing}
-                    isEditingCallback={this.isEditingCallback}
-                    display={"inline"}
-                    contents={this._text}
-                    height={"auto"}
-                    maxHeight={Number(MAX_ROW_HEIGHT)}
-                    GetValue={() => this._text}
-                    SetValue={action((value: string) => {
-                        // add special for params 
-                        this.onSetValue(value);
-                        return true;
-                    })}
-                />
-            </div >;
+                </div>
+            );
+
+            const plainText = <div style={{ padding: '5.9px' }}>{this._text}</div>;
+            const textarea = (
+                <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} style={{ padding: '5.9px' }} ref={this.dropRef}>
+                    <EditableView
+                        editing={this._isEditing}
+                        isEditingCallback={this.isEditingCallback}
+                        display={'inline'}
+                        contents={this._text}
+                        height={'auto'}
+                        maxHeight={Number(MAX_ROW_HEIGHT)}
+                        GetValue={() => this._text}
+                        SetValue={action((value: string) => {
+                            // add special for params
+                            this.onSetValue(value);
+                            return true;
+                        })}
+                    />
+                </div>
+            );
 
             //☰
             return (
                 <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
                     <div className="collectionSchemaView-cellContents" key={this._rowDoc[Id]} ref={reference}>
                         <div className="collectionSchemaView-dropDownWrapper">
-                            <button type="button" className="collectionSchemaView-dropdownButton" style={{ right: "length", position: "relative" }}
-                                onClick={action(e => this._opened = !this._opened)} >
-                                <FontAwesomeIcon icon={this._opened ? "caret-up" : "caret-down"} size="sm" />
+                            <button type="button" className="collectionSchemaView-dropdownButton" style={{ right: 'length', position: 'relative' }} onClick={action(e => (this._opened = !this._opened))}>
+                                <FontAwesomeIcon icon={this._opened ? 'caret-up' : 'caret-down'} size="sm" />
                             </button>
                             <div className="collectionSchemaView-dropdownText"> {link ? plainText : textarea} </div>
                         </div>
                         {options}
-                    </div >
+                    </div>
                 </div>
             );
         }
-        return this.renderCellWithType("list");
+        return this.renderCellWithType('list');
     }
 }
 
-
 @observer
 export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
-    @computed get _isChecked() { return BoolCast(this._rowDoc[this.renderFieldKey]); }
+    @computed get _isChecked() {
+        return BoolCast(this._rowDoc[this.renderFieldKey]);
+    }
 
     render() {
         const reference = React.createRef<HTMLDivElement>();
         return (
             <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
-                <input type="checkbox" checked={this._isChecked} onChange={e => this._rowDoc[this.renderFieldKey] = e.target.checked} />
+                <input type="checkbox" checked={this._isChecked} onChange={e => (this._rowDoc[this.renderFieldKey] = e.target.checked)} />
             </div>
         );
     }
 }
 
-
 @observer
 export class CollectionSchemaButtons extends CollectionSchemaCell {
     // the navigation buttons for schema view when it is used for search.
     render() {
-        return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? <></> :
-            <div style={{ paddingTop: 8, paddingLeft: 3 }} >
+        return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? (
+            <></>
+        ) : (
+            <div style={{ paddingTop: 8, paddingLeft: 3 }}>
                 <button style={{ padding: 2, left: 77 }} onClick={() => Doc.SearchMatchNext(this._rowDoc, true)}>
                     <FontAwesomeIcon icon="arrow-up" size="sm" />
                 </button>
-                <button style={{ padding: 2 }} onClick={() => Doc.SearchMatchNext(this._rowDoc, false)} >
+                <button style={{ padding: 2 }} onClick={() => Doc.SearchMatchNext(this._rowDoc, false)}>
                     <FontAwesomeIcon icon="arrow-down" size="sm" />
                 </button>
-            </div>;
+            </div>
+        );
     }
 }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
index f872637e5..3cb2df7d3 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
@@ -10,6 +10,7 @@ import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
+import { OpenWhere } from '../../nodes/DocumentView';
 import './CollectionSchemaView.scss';
 
 export interface MovableRowProps {
@@ -138,7 +139,7 @@ export class MovableRow extends React.Component<React.PropsWithChildren<MovableR
                             <div className="row-option" style={{ cursor: 'grab' }} ref={reference} onPointerDown={onItemDown}>
                                 <FontAwesomeIcon icon="grip-vertical" size="sm" />
                             </div>
-                            <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, 'add:right')}>
+                            <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, OpenWhere.addRight)}>
                                 <FontAwesomeIcon icon="external-link-alt" size="sm" />
                             </div>
                         </div>
diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx
index fafea5ce3..45ad4f86b 100644
--- a/src/client/views/collections/collectionSchema/SchemaTable.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx
@@ -23,7 +23,7 @@ import { undoBatch } from '../../../util/UndoManager';
 import '../../../views/DocumentDecorations.scss';
 import { ContextMenu } from '../../ContextMenu';
 import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
-import { DocumentView } from '../../nodes/DocumentView';
+import { DocumentView, OpenWhere } from '../../nodes/DocumentView';
 import { DefaultStyleProvider } from '../../StyleProvider';
 import { CollectionView } from '../CollectionView';
 import {
@@ -86,7 +86,7 @@ export interface SchemaTableProps {
     ScreenToLocalTransform: () => Transform;
     active: (outsideReaction: boolean | undefined) => boolean | undefined;
     onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
-    addDocTab: (document: Doc, where: string) => boolean;
+    addDocTab: (document: Doc, where: OpenWhere) => boolean;
     pinToPres: (document: Doc) => void;
     isSelected: (outsideReaction?: boolean) => boolean;
     isFocused: (document: Doc, outsideReaction: boolean) => boolean;
@@ -625,7 +625,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     };
 
     onOpenClick = () => {
-        this._showDoc && this.props.addDocTab(this._showDoc, 'add:right');
+        this._showDoc && this.props.addDocTab(this._showDoc, OpenWhere.addRight);
     };
 
     getPreviewTransform = (): Transform => {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 570039550..868822fbf 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -16,7 +16,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
 import { DocComponent } from '../DocComponent';
 import { StyleProp } from '../StyleProvider';
 import './CollectionFreeFormDocumentView.scss';
-import { DocumentView, DocumentViewProps } from './DocumentView';
+import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
 import React = require('react');
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
@@ -199,7 +199,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 topDoc.x = spt[0];
                 topDoc.y = spt[1];
                 this.props.removeDocument?.(topDoc);
-                this.props.addDocTab(topDoc, 'inParent');
+                this.props.addDocTab(topDoc, OpenWhere.inParent);
             } else {
                 const spt = this.screenToLocalTransform().inverse().transformPoint(0, 0);
                 const fpt = screenXf.transformPoint(spt[0], spt[1]);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fb07f1033..2729a5047 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -69,6 +69,27 @@ export enum ViewAdjustment {
     doNothing = 0,
 }
 
+export enum OpenWhere {
+    inPlace = 'inPlace',
+    lightbox = 'lightbox',
+    add = 'add',
+    addRight = 'add:right',
+    addBottom = 'add:bottom',
+    dashboard = 'dashboard',
+    close = 'close',
+    fullScreen = 'fullScreen',
+    toggle = 'toggle',
+    replace = 'replace',
+    inParent = 'inParent',
+}
+export enum OpenWhereMod {
+    none = '',
+    left = 'left',
+    right = 'right',
+    top = 'top',
+    bottom = 'bottom',
+}
+
 export const ViewSpecPrefix = 'viewSpec'; // field prefix for anchor fields that are immediately copied over to the target document when link is followed.  Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document)
 
 export interface DocFocusOptions {
@@ -139,7 +160,7 @@ export interface DocumentViewSharedProps {
     showTitle?: () => string;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
     rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
-    addDocTab: (doc: Doc, where: string) => boolean;
+    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
     filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example)
     addDocument?: (doc: Doc | Doc[]) => boolean;
     removeDocument?: (doc: Doc | Doc[]) => boolean;
@@ -474,7 +495,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
         RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
 
-        // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 });
+        // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: "map-pin", selected: -1 });
         const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
         (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) &&
             RadialMenu.Instance.addItem({
@@ -485,7 +506,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 icon: 'external-link-square-alt',
                 selected: -1,
             });
-        // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 });
+        // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, OpenWhere.addRight), icon: "trash", selected: -1 });
         RadialMenu.Instance.addItem({ description: 'Pin', event: () => this.props.pinToPres(this.props.Document, {}), icon: 'map-pin', selected: -1 });
         RadialMenu.Instance.addItem({ description: 'Open', event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: 'trash', selected: -1 });
 
@@ -586,7 +607,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         );
                     UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
                 } else if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isLinkButton) {
-                    UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, 'lightbox', this.props.LayoutTemplate?.(), this.props.addDocTab), 'double tap');
+                    UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.(), this.props.addDocTab), 'double tap');
                     SelectionManager.DeselectAll();
                     Doc.UnBrushDoc(this.props.Document);
                 }
@@ -857,7 +878,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
             const appearance = cm.findByDescription('UI Controls...');
             const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
-            !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this.props.addDocTab(templateDoc, 'add:right'), icon: 'eye' });
+            !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
             !Doc.noviceMode &&
                 appearanceItems.push({
                     description: 'Add a Field',
@@ -957,8 +978,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         const help = cm.findByDescription('Help...');
         const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
-        helpItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), 'add:right'), icon: 'layer-group' });
-        !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), 'add:right'), icon: 'keyboard' });
+        helpItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'layer-group' });
+        !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' });
         !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' });
         !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' });
 
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 7d04c4b64..18c5b81ec 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -20,6 +20,7 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import e = require('express');
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import { ImageBox } from './ImageBox';
+import { OpenWhere } from './DocumentView';
 
 export type KVPScript = {
     script: CompiledScript;
@@ -259,8 +260,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         openItems.push({
             description: 'Default Perspective',
             event: () => {
-                this.props.addDocTab(this.props.Document, 'close');
-                this.props.addDocTab(this.fieldDocToLayout, 'add:right');
+                this.props.addDocTab(this.props.Document, OpenWhere.close);
+                this.props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight);
             },
             icon: 'image',
         });
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 80def3025..e74ef4a39 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,18 +1,19 @@
 import { action, observable } from 'mobx';
-import { observer } from "mobx-react";
+import { observer } from 'mobx-react';
 import { Doc, Field, Opt } from '../../../fields/Doc';
 import { emptyFunction, returnFalse, returnOne, returnZero, returnEmptyFilter, returnEmptyDoclist, emptyPath } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
 import { ContextMenu } from '../ContextMenu';
-import { EditableView } from "../EditableView";
+import { EditableView } from '../EditableView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { KeyValueBox } from './KeyValueBox';
-import "./KeyValueBox.scss";
-import "./KeyValuePair.scss";
-import React = require("react");
+import './KeyValueBox.scss';
+import './KeyValuePair.scss';
+import React = require('react');
 import { DefaultStyleProvider } from '../StyleProvider';
+import { OpenWhere } from './DocumentView';
 
 // Represents one row in a key value plane
 
@@ -23,7 +24,7 @@ export interface KeyValuePairProps {
     keyWidth: number;
     PanelHeight: () => number;
     PanelWidth: () => number;
-    addDocTab: (doc: Doc, where: string) => boolean;
+    addDocTab: (doc: Doc, where: OpenWhere) => boolean;
 }
 @observer
 export class KeyValuePair extends React.Component<KeyValuePairProps> {
@@ -34,23 +35,23 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
     @action
     handleCheck = (e: React.ChangeEvent<HTMLInputElement>) => {
         this.isChecked = e.currentTarget.checked;
-    }
+    };
 
     @action
     uncheck = () => {
         this.checkbox.current!.checked = false;
         this.isChecked = false;
-    }
+    };
 
     onContextMenu = (e: React.MouseEvent) => {
         const value = this.props.doc[this.props.keyName];
         if (value instanceof Doc) {
             e.stopPropagation();
             e.preventDefault();
-            ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
+            ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'layer-group' });
             ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
         }
-    }
+    };
 
     render() {
         const props: FieldViewProps = {
@@ -68,7 +69,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             isSelected: returnFalse,
             setHeight: returnFalse,
             select: emptyFunction,
-            dropAction: "alias",
+            dropAction: 'alias',
             bringToFront: emptyFunction,
             renderDepth: 1,
             isContentActive: returnFalse,
@@ -92,30 +93,30 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
             doc = doc.proto;
         }
         const parenCount = Math.max(0, protoCount - 1);
-        const keyStyle = protoCount === 0 ? "black" : "blue";
+        const keyStyle = protoCount === 0 ? 'black' : 'blue';
 
-        const hover = { transition: "0.3s ease opacity", opacity: this.isPointerOver || this.isChecked ? 1 : 0 };
+        const hover = { transition: '0.3s ease opacity', opacity: this.isPointerOver || this.isChecked ? 1 : 0 };
 
         return (
-            <tr className={this.props.rowStyle} onPointerEnter={action(() => this.isPointerOver = true)} onPointerLeave={action(() => this.isPointerOver = false)}>
+            <tr className={this.props.rowStyle} onPointerEnter={action(() => (this.isPointerOver = true))} onPointerLeave={action(() => (this.isPointerOver = false))}>
                 <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
                     <div className="keyValuePair-td-key-container">
-                        <button style={hover} className="keyValuePair-td-key-delete" onClick={undoBatch(() => {
-                            if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
-                                delete props.Document[props.fieldKey];
-                            }
-                            else delete props.Document.proto![props.fieldKey];
-                        })}>
+                        <button
+                            style={hover}
+                            className="keyValuePair-td-key-delete"
+                            onClick={undoBatch(() => {
+                                if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
+                                    delete props.Document[props.fieldKey];
+                                } else delete props.Document.proto![props.fieldKey];
+                            })}>
                             X
                         </button>
-                        <input
-                            className={"keyValuePair-td-key-check"}
-                            type="checkbox"
-                            style={hover}
-                            onChange={this.handleCheck}
-                            ref={this.checkbox}
-                        />
-                        <div className="keyValuePair-keyField" style={{ color: keyStyle }}>{"(".repeat(parenCount)}{props.fieldKey}{")".repeat(parenCount)}</div>
+                        <input className={'keyValuePair-td-key-check'} type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} />
+                        <div className="keyValuePair-keyField" style={{ color: keyStyle }}>
+                            {'('.repeat(parenCount)}
+                            {props.fieldKey}
+                            {')'.repeat(parenCount)}
+                        </div>
                     </div>
                 </td>
                 <td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }} onContextMenu={this.onContextMenu}>
@@ -123,13 +124,13 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                         <EditableView
                             contents={contents}
                             maxHeight={36}
-                            height={"auto"}
+                            height={'auto'}
                             GetValue={() => Field.toKeyValueString(props.Document, props.fieldKey)}
-                            SetValue={(value: string) =>
-                                KeyValueBox.SetField(props.Document, props.fieldKey, value)} />
+                            SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)}
+                        />
                     </div>
                 </td>
             </tr>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index d6cf79f87..be9565452 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -17,6 +17,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import './LinkAnchorBox.scss';
 import { LinkDocPreview } from './LinkDocPreview';
 import React = require('react');
+import { OpenWhere } from './DocumentView';
 const higflyout = require('@hig/flyout');
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -88,13 +89,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     openLinkDocOnRight = (e: React.MouseEvent) => {
-        this.props.addDocTab(this.rootDoc, 'add:right');
+        this.props.addDocTab(this.rootDoc, OpenWhere.addRight);
     };
     openLinkTargetOnRight = (e: React.MouseEvent) => {
         const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null));
         alias._isLinkButton = undefined;
         alias.layoutKey = 'layout';
-        this.props.addDocTab(alias, 'add:right');
+        this.props.addDocTab(alias, OpenWhere.addRight);
     };
     @action
     openLinkEditor = action((e: React.MouseEvent) => {
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index a47577701..135fbca31 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -14,7 +14,7 @@ import { LinkFollower } from '../../util/LinkFollower';
 import { LinkManager } from '../../util/LinkManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch } from '../../util/UndoManager';
-import { DocumentView, DocumentViewSharedProps } from './DocumentView';
+import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
 import './LinkDocPreview.scss';
 import React = require('react');
 import { LinkEditor } from '../linking/LinkEditor';
@@ -156,7 +156,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
             LinkDocPreview.Clear();
             LinkFollower.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false);
         } else if (this.props.hrefs?.length) {
-            this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), 'add:right');
+            this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), OpenWhere.addRight);
         }
     };
 
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 70ac84fa4..82d5b00f9 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -31,6 +31,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { RecordingBox } from './RecordingBox';
 import './VideoBox.scss';
 import { ObjectField } from '../../../fields/ObjectField';
+import { OpenWhere } from './DocumentView';
 const path = require('path');
 
 /**
@@ -273,7 +274,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             this.player && this._contentRef && this._contentRef.requestFullscreen();
         }
         try {
-            this._youtubePlayer && this.props.addDocTab(this.rootDoc, 'add');
+            this._youtubePlayer && this.props.addDocTab(this.rootDoc, OpenWhere.add);
         } catch (e) {
             console.log('Video FullScreen Exception:', e);
         }
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 42d20ba99..c71552a6f 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -26,6 +26,7 @@ import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, SetAc
 import { InkTranscription } from '../../InkTranscription';
 import { StyleProp } from '../../StyleProvider';
 import { FieldView, FieldViewProps } from '.././FieldView';
+import { OpenWhere } from '../DocumentView';
 import { RichTextMenu } from '../formattedText/RichTextMenu';
 import { WebBox } from '../WebBox';
 import { FontIconBadge } from './FontIconBadge';
@@ -61,7 +62,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     }
     showTemplate = (): void => {
         const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
-        dragFactory && this.props.addDocTab(dragFactory, 'add:right');
+        dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight);
     };
     dragAsTemplate = (): void => {
         this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 1e7cb6ea5..63347015b 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -17,6 +17,7 @@ import { Tooltip } from '@material-ui/core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { CollectionViewType } from '../../../documents/DocumentTypes';
 import { NodeSelection } from 'prosemirror-state';
+import { OpenWhere } from '../DocumentView';
 
 export class DashFieldView {
     dom: HTMLDivElement; // container for label and value
@@ -227,7 +228,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
             list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb'));
             list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb'));
             alias._pivotField = this._fieldKey.startsWith('#') ? '#' : this._fieldKey;
-            this.props.tbox.props.addDocTab(alias, 'add:right');
+            this.props.tbox.props.addDocTab(alias, OpenWhere.addRight);
         }
     };
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fdd61463d..ce4639b76 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -45,7 +45,7 @@ import { LightboxView } from '../../LightboxView';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
-import { DocumentViewInternal } from '../DocumentView';
+import { DocumentViewInternal, OpenWhere } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { LinkDocPreview } from '../LinkDocPreview';
 import { DashDocCommentView } from './DashDocCommentView';
@@ -1428,7 +1428,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     const func = () => {
                         const docView = DocumentManager.Instance.getDocumentView(audiodoc);
                         if (!docView) {
-                            this.props.addDocTab(audiodoc, 'add:bottom');
+                            this.props.addDocTab(audiodoc, OpenWhere.addBottom);
                             setTimeout(func);
                         } else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that
                     };
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 3d9bd6add..68b0488a2 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -9,6 +9,7 @@ import { GetEffectiveAcl } from '../../../../fields/util';
 import { Utils } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { SelectionManager } from '../../../util/SelectionManager';
+import { OpenWhere } from '../DocumentView';
 import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
 
 const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false;
@@ -135,7 +136,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
     //Command to create a new Tab with a PDF of all the command shortcuts
     bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => {
         const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 });
-        props.addDocTab(newDoc, 'add:right');
+        props.addDocTab(newDoc, OpenWhere.addRight);
     });
 
     //Commands to modify BlockType
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e19b53f50..adfd2fda1 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -23,7 +23,7 @@ import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm';
+import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm';
 import { CollectionView } from '../../collections/CollectionView';
 import { TabDocView } from '../../collections/TabDocView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
@@ -35,6 +35,7 @@ import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresMovement, PresStatus } from './PresEnums';
 import { map } from 'bluebird';
+import { OpenWhere, OpenWhereMod } from '../DocumentView';
 const { Howl } = require('howler');
 
 export interface PinProps {
@@ -196,7 +197,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         );
         this.props.setContentView?.(this);
         this._unmounting = false;
-        this.rootDoc._forceRenderEngine = 'timeline';
+        this.rootDoc._forceRenderEngine = computeTimelineLayout.name;
         this.layoutDoc.presStatus = PresStatus.Edit;
         this.layoutDoc._gridGap = 0;
         this.layoutDoc._yMargin = 0;
@@ -563,7 +564,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             self._eleArray.splice(0, self._eleArray.length, ...eleViewCache);
         });
         const openInTab = (doc: Doc, finished?: () => void) => {
-            (collectionDocView ?? this).props.addDocTab(doc, '');
+            (collectionDocView ?? this).props.addDocTab(doc, OpenWhere.add);
             this.layoutDoc.presCollection = targetDoc;
             // this still needs some fixing
             setTimeout(resetSelection, 500);
@@ -725,7 +726,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
             this.layoutDoc.presStatus = PresStatus.Edit;
             Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc);
-            CollectionDockingView.AddSplit(this.rootDoc, 'right');
+            CollectionDockingView.AddSplit(this.rootDoc, OpenWhereMod.right);
         } else {
             this.layoutDoc.presStatus = PresStatus.Edit;
             clearTimeout(this._presTimer);
@@ -1760,7 +1761,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 TabDocView.PinDoc(doc, {});
                 this.gotoDocument(this.childDocs.length, this.activeItem);
             } else {
-                this.props.addDocTab(doc, 'add:right');
+                this.props.addDocTab(doc, OpenWhere.addRight);
             }
         }
     };
@@ -2322,7 +2323,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     static NavigateToDoc(bestTarget: Doc, activeItem: Doc) {
         const srcContext = Cast(bestTarget.context, Doc, null) ?? Cast(Cast(bestTarget.annotationOn, Doc, null)?.context, Doc, null);
         const openInTab = (doc: Doc, finished?: () => void) => {
-            CollectionDockingView.AddSplit(doc, 'right');
+            CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
             finished?.();
         };
         PresBox.NavigateToTarget(bestTarget, activeItem, openInTab, srcContext);
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index 8265de445..2ae597b0b 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -700,7 +700,7 @@ export class MobileInterface extends React.Component {
                     className="docButton"
                     title={Doc.isDocPinned(this._activeDoc) ? 'Unpin from presentation' : 'Pin to presentation'}
                     style={{ backgroundColor: isPinned ? 'black' : 'white', color: isPinned ? 'white' : 'black' }}
-                    onClick={e => TabDocView.PinDoc(this._activeDoc)}>
+                    onClick={e => TabDocView.PinDoc(this._activeDoc, {})}>
                     <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" />
                 </div>
             );
-- 
cgit v1.2.3-70-g09d2


From 6d32fe60ce32d650a2ba0d5eb8e36dccb591521f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 7 Jan 2023 00:36:06 -0500
Subject: fixed pinning pdf documents without a selection to trail

---
 src/client/views/collections/CollectionSubView.tsx | 2 +-
 src/client/views/collections/CollectionView.tsx    | 1 +
 src/client/views/nodes/trails/PresBox.tsx          | 1 +
 3 files changed, 3 insertions(+), 1 deletion(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 4f3f21cea..d2074219a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -111,7 +111,7 @@ export function CollectionSubView<X>(moreProps?: X) {
                 rawdocs = rootDoc && !this.props.isAnnotationOverlay ? [Doc.GetProto(rootDoc)] : [];
             }
 
-            const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && !d.unrendered).map(d => d as Doc);
+            const docs = rawdocs.filter(d => !(d instanceof Promise) && GetEffectiveAcl(Doc.GetProto(d)) !== AclPrivate && (this.props.ignoreUnrendered || !d.unrendered)).map(d => d as Doc);
             const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
             const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 917d7618c..a28b1ca19 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -43,6 +43,7 @@ interface CollectionViewProps_ extends FieldViewProps {
     layoutEngine?: () => string;
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void;
     setBrushViewer?: (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => void;
+    ignoreUnrendered?: boolean;
 
     // property overrides for child documents
     childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 2e3135416..6f3dc5e82 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -2195,6 +2195,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 PanelHeight={this.panelHeight}
                                 childIgnoreNativeSize={true}
                                 moveDocument={returnFalse}
+                                ignoreUnrendered={true}
                                 //childFitWidth={returnTrue}
                                 childOpacity={returnOne}
                                 childLayoutTemplate={this.childLayoutTemplate}
-- 
cgit v1.2.3-70-g09d2


From e17b1bdb09bfcadc717e687b09d2c18596341a10 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 9 Feb 2023 21:15:58 -0500
Subject: fixed childLayoutString to work.  made images capable of fitWidth.
 fixed animating data field pres changes.  fixed lightbox to ignore
 annotations on collections.  fixed double-click on icon to open in lightbox.
 added options for turning off ink labels, and opening ink in lightbox. fixed
 closing ink strokes by dragging. fixed drawing ink to use coord sys of
 starting point and to render ink the correct width and to honor
 GestureOverlay mode properly. .

---
 src/client/documents/Documents.ts                  |  4 +-
 src/client/util/CurrentUserUtils.ts                |  6 +-
 src/client/util/SettingsManager.tsx                |  4 ++
 src/client/views/DocumentDecorations.tsx           | 13 ++--
 src/client/views/GestureOverlay.tsx                | 22 ++++---
 src/client/views/GlobalKeyHandler.ts               | 29 ++++++++-
 src/client/views/InkTranscription.tsx              |  2 +-
 src/client/views/InkingStroke.tsx                  | 12 ++--
 src/client/views/LightboxView.tsx                  | 64 +++++++++----------
 src/client/views/PropertiesButtons.tsx             | 10 +++
 src/client/views/PropertiesView.tsx                |  2 +-
 src/client/views/StyleProvider.tsx                 |  4 +-
 src/client/views/collections/CollectionView.tsx    |  2 +-
 src/client/views/collections/TabDocView.tsx        | 14 ++---
 src/client/views/collections/TreeView.tsx          |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 66 +++++++++++--------
 .../collections/collectionFreeForm/MarqueeView.tsx |  4 +-
 src/client/views/nodes/DocumentView.tsx            | 21 ++++---
 src/client/views/nodes/ImageBox.tsx                | 73 +++++++++++++++++-----
 src/client/views/nodes/LinkAnchorBox.tsx           |  1 +
 src/client/views/nodes/MapBox/MapBox.tsx           |  4 +-
 src/client/views/nodes/WebBox.tsx                  | 11 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  1 -
 src/client/views/nodes/trails/PresBox.tsx          |  4 ++
 src/client/views/pdf/AnchorMenu.tsx                |  1 +
 src/client/views/pdf/PDFViewer.tsx                 | 17 ++---
 src/fields/util.ts                                 |  2 +-
 27 files changed, 257 insertions(+), 140 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 692d09629..3faa6e11d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -149,6 +149,8 @@ export class DocumentOptions {
     _height?: NUMt = new NumInfo('displayed height of document');
     _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)');
     _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)');
+    _nativeDimModifiable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers');
+    _nativeHeightUnfrozen?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers');
     _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height");
     _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
     _fitWidth?: BOOLt = new BoolInfo('whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)');
@@ -848,7 +850,7 @@ export namespace Docs {
 
         export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) {
             const imgField = url instanceof ImageField ? url : new ImageField(url);
-            return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc);
+            return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { _nativeDimModifiable: false, _nativeHeightUnfrozen: false, title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc);
         }
 
         export function PresDocument(options: DocumentOptions = {}) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 5f183cf91..f678c8936 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -183,7 +183,7 @@ export class CurrentUserUtils {
             const allopts = {system: true, ...opts};
             return DocUtils.AssignScripts(  (curIcon?.iconTemplate === opts.iconTemplate ? 
                       DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))),
-                {onClick:"deiconifyView(documentView)"});
+                {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView"});
         };
         const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({
             textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px",  _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
@@ -803,7 +803,9 @@ export class CurrentUserUtils {
         doc._showLabel ?? (doc._showLabel = true);
         doc.textAlign ?? (doc.textAlign = "left");
         doc.activeTool = InkTool.None;
-        doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");;
+        doc.openInkInLightbox ?? (doc.openInkInLightbox = false);
+        doc.activeInkHideTextLabels ?? (doc.activeInkHideTextLabels = false);
+        doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");
         doc.activeInkWidth ?? (doc.activeInkWidth = 1);
         doc.activeInkBezier ?? (doc.activeInkBezier = "0");
         doc.activeFillColor ?? (doc.activeFillColor = "");
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 179a1ac39..6c823e80a 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -199,6 +199,10 @@ export class SettingsManager extends React.Component<{}> {
                     <input type="checkbox" onChange={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)} checked={BoolCast(Doc.UserDoc().activeInkHideTextLabels)} />
                     <div className="preferences-check">Hide Labels In Ink Shapes</div>
                 </div>
+                <div>
+                    <input type="checkbox" onChange={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)} checked={BoolCast(Doc.UserDoc().openInkInLightbox)} />
+                    <div className="preferences-check">Open Ink Docs in Lightbox</div>
+                </div>
             </div>
         );
     }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d1f0bf2ac..41f4a17fb 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -581,7 +581,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     }
                     let actualdW = Math.max(width + dW * scale, 20);
                     let actualdH = Math.max(height + dH * scale, 20);
-                    const fixedAspect = nwidth && nheight && (!doc._fitWidth || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable);
+                    const preserveNativeDim = doc._nativeHeightUnfrozen === false && doc._nativeDimModifiable === false;
+                    const fixedAspect = nwidth && nheight && (!doc._fitWidth || preserveNativeDim || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable);
                     if (fixedAspect) {
                         if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) {
                             if (dragRight && modifyNativeDim) {
@@ -589,7 +590,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                                     doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc);
                                 }
                             } else {
-                                if (!doc._fitWidth) {
+                                if (!doc._fitWidth || preserveNativeDim) {
                                     actualdH = (nheight / nwidth) * actualdW;
                                     doc._height = actualdH;
                                 } else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
@@ -597,11 +598,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                             doc._width = actualdW;
                         } else {
                             if ((dragBottom || dragTop) && (modifyNativeDim || (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) {
-                                // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height.  But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
+                                // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height.  But they are marked to allow their nativeHeight
+                                // to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match
+                                // a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
                                 doc._nativeHeight = (actualdH / (doc._height || 1)) * Doc.NativeHeight(doc);
                                 doc._autoHeight = false;
                             } else {
-                                if (!doc._fitWidth) {
+                                if (!doc._fitWidth || preserveNativeDim) {
                                     actualdW = (nwidth / nheight) * actualdH;
                                     doc._width = actualdW;
                                 } else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
@@ -615,7 +618,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         const rotCtr = [NumCast(doc._width) / 2, NumCast(doc._height) / 2];
                         const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._rotation) / 180) * Math.PI);
 
-                        const maxHeight = doc.nativHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
+                        const maxHeight = doc.nativeHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
                         dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH);
                         dW && (doc._width = actualdW);
                         dH && (doc._autoHeight = false);
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index e3328fb4c..6058eaaf9 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -1,9 +1,9 @@
 import React = require('react');
 import * as fitCurve from 'fit-curve';
-import { action, computed, observable, runInAction, trace } from 'mobx';
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../fields/Doc';
 import { InkData, InkTool } from '../../fields/InkField';
-import { List } from '../../fields/List';
 import { ScriptField } from '../../fields/ScriptField';
 import { Cast, FieldValue, NumCast } from '../../fields/Types';
 import MobileInkOverlay from '../../mobile/MobileInkOverlay';
@@ -14,7 +14,6 @@ import { CognitiveServices } from '../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../documents/Documents';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { ScriptingGlobals } from '../util/ScriptingGlobals';
-import { SelectionManager } from '../util/SelectionManager';
 import { Transform } from '../util/Transform';
 import './GestureOverlay.scss';
 import {
@@ -39,7 +38,6 @@ import { RadialMenu } from './nodes/RadialMenu';
 import HorizontalPalette from './Palette';
 import { Touchable } from './Touchable';
 import TouchScrollableMenu, { TouchScrollableMenuItem } from './TouchScrollableMenu';
-import { observer } from 'mobx-react';
 
 interface GestureOverlayProps {
     isActive: boolean;
@@ -47,6 +45,7 @@ interface GestureOverlayProps {
 @observer
 export class GestureOverlay extends Touchable<GestureOverlayProps> {
     static Instance: GestureOverlay;
+    static Instances: GestureOverlay[] = [];
 
     @observable public InkShape: Opt<GestureUtils.Gestures>;
     @observable public SavedColor?: string;
@@ -66,6 +65,8 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
     @observable private _clipboardDoc?: JSX.Element;
     @observable private _possibilities: JSX.Element[] = [];
 
+    public static DownDocView: DocumentView | undefined;
+
     @computed private get height(): number {
         return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 100, 100);
     }
@@ -89,7 +90,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
     constructor(props: any) {
         super(props);
 
-        GestureOverlay.Instance = this;
+        GestureOverlay.Instances.push(this);
     }
 
     static setupThumbButtons(doc: Doc) {
@@ -154,7 +155,13 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
         }
         return Cast(userDoc.thumbDoc, Doc);
     }
+
+    componentWillUnmount() {
+        GestureOverlay.Instances.splice(GestureOverlay.Instances.indexOf(this), 1);
+        GestureOverlay.Instance = GestureOverlay.Instances.lastElement();
+    }
     componentDidMount = () => {
+        GestureOverlay.Instance = this;
         this._thumbDoc = FieldValue(Cast(GestureOverlay.setupThumbDoc(Doc.UserDoc()), Doc));
         this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc));
     };
@@ -627,6 +634,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
     }
     @action
     onPointerUp = (e: PointerEvent) => {
+        GestureOverlay.DownDocView = undefined;
         if (this._points.length > 1) {
             const B = this.svgBounds;
             const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
@@ -906,8 +914,8 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
     }
 
     @computed get elements() {
-        const selView = SelectionManager.Views().lastElement();
-        const width = (Number(ActiveInkWidth()) * NumCast(selView?.rootDoc._viewScale, 1)) / (selView?.props.ScreenToLocalTransform().Scale || 1);
+        const selView = GestureOverlay.DownDocView;
+        const width = Number(ActiveInkWidth()) * NumCast(selView?.rootDoc._viewScale, 1); // * (selView?.props.ScreenToLocalTransform().Scale || 1);
         const rect = this._overlayRef.current?.getBoundingClientRect();
         const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true);
         B.left = B.left - width / 2;
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 5e700e281..6c8a078ec 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -1,12 +1,12 @@
 import { random } from 'lodash';
-import { action, observable, runInAction } from 'mobx';
+import { action, runInAction } from 'mobx';
 import { DateField } from '../../fields/DateField';
 import { Doc, DocListCast } from '../../fields/Doc';
 import { Id } from '../../fields/FieldSymbols';
 import { InkTool } from '../../fields/InkField';
 import { List } from '../../fields/List';
 import { ScriptField } from '../../fields/ScriptField';
-import { Cast, DocCast, PromiseValue } from '../../fields/Types';
+import { Cast, PromiseValue } from '../../fields/Types';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { DocServer } from '../DocServer';
 import { DocumentType } from '../documents/DocumentTypes';
@@ -18,6 +18,7 @@ import { SharingManager } from '../util/SharingManager';
 import { SnappingManager } from '../util/SnappingManager';
 import { undoBatch, UndoManager } from '../util/UndoManager';
 import { CollectionDockingView } from './collections/CollectionDockingView';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
 import { CollectionFreeFormViewChrome } from './collections/CollectionMenu';
 import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline';
 import { ContextMenu } from './ContextMenu';
@@ -103,6 +104,20 @@ export class KeyManager {
 
                 const groupings = SelectionManager.Views().slice();
                 const randomGroup = random(0, 1000);
+                const collectionView = groupings.reduce(
+                    (col, g) => (col === null || g.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView === col ? g.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView : undefined),
+                    null as null | undefined | CollectionFreeFormView
+                );
+                if (collectionView) {
+                    UndoManager.RunInBatch(() => {
+                        collectionView._marqueeViewRef.current?.collection(
+                            e,
+                            true,
+                            groupings.map(g => g.rootDoc)
+                        );
+                    }, 'grouping');
+                    break;
+                }
                 UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group');
                 SelectionManager.DeselectAll();
                 break;
@@ -192,6 +207,16 @@ export class KeyManager {
             case 'arrowdown':
                 UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 10)), 'nudge down');
                 break;
+            case 'g':
+                if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') {
+                    return { stopPropagation: false, preventDefault: false };
+                }
+
+                const groupings = SelectionManager.Views().slice();
+                const randomGroup = random(0, 1000);
+                UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group');
+                SelectionManager.DeselectAll();
+                break;
         }
 
         return {
diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx
index bf0e8081d..246b887a6 100644
--- a/src/client/views/InkTranscription.tsx
+++ b/src/client/views/InkTranscription.tsx
@@ -67,7 +67,7 @@ export class InkTranscription extends React.Component {
                 : null;
         }
 
-        r.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
+        r?.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef));
 
         return (this._mathRef = r);
     };
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 7a5151634..d7e8b1c05 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -54,7 +54,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(InkingStroke, fieldStr);
     }
     public static IsClosed(inkData: InkData) {
-        return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
+        return inkData?.length && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
     }
     private _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
     private _disposers: { [key: string]: IReactionDisposer } = {};
@@ -264,9 +264,13 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
             .map(p => ({ X: p[0], Y: p[1] }));
         const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
 
-        this._nearestT = nearestT;
-        this._nearestSeg = nearestSeg;
-        this._nearestScrPt = nearestPt;
+        if (distance < 40) {
+            this._nearestT = nearestT;
+            this._nearestSeg = nearestSeg;
+            this._nearestScrPt = nearestPt;
+        } else {
+            this._nearestT = this._nearestSeg = this._nearestScrPt = undefined;
+        }
     };
 
     /**
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 3627aa783..e531bf71c 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -27,6 +27,13 @@ interface LightboxViewProps {
     maxBorder: number[];
 }
 
+type LightboxSavedState = {
+    panX: Opt<number>;
+    panY: Opt<number>;
+    scale: Opt<number>;
+    scrollTop: Opt<number>;
+    layoutKey: Opt<string>;
+};
 @observer
 export class LightboxView extends React.Component<LightboxViewProps> {
     @computed public static get LightboxDoc() {
@@ -34,21 +41,22 @@ export class LightboxView extends React.Component<LightboxViewProps> {
     }
     private static LightboxDocTemplate = () => LightboxView._layoutTemplate;
     @observable private static _layoutTemplate: Opt<Doc>;
+    @observable private static _layoutTemplateString: Opt<string>;
     @observable private static _doc: Opt<Doc>;
     @observable private static _docTarget: Opt<Doc>;
     @observable private static _docFilters: string[] = []; // filters
-    private static _savedState: Opt<{ panX: Opt<number>; panY: Opt<number>; scale: Opt<number>; scrollTop: Opt<number> }>;
+    private static _savedState: Opt<LightboxSavedState>;
     private static _history: Opt<{ doc: Doc; target?: Doc }[]> = [];
     @observable private static _future: Opt<Doc[]> = [];
     @observable private static _docView: Opt<DocumentView>;
-    private static openInTabFunc: any;
-    static path: { doc: Opt<Doc>; target: Opt<Doc>; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt<Doc[]>; saved: Opt<{ panX: Opt<number>; panY: Opt<number>; scale: Opt<number>; scrollTop: Opt<number> }> }[] = [];
-    @action public static SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc) {
+    static path: { doc: Opt<Doc>; target: Opt<Doc>; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt<Doc[]>; saved: Opt<LightboxSavedState> }[] = [];
+    @action public static SetLightboxDoc(doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) {
         if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) {
-            this.LightboxDoc._panX = this._savedState.panX;
-            this.LightboxDoc._panY = this._savedState.panY;
-            this.LightboxDoc._scrollTop = this._savedState.scrollTop;
-            this.LightboxDoc._viewScale = this._savedState.scale;
+            if (this._savedState.panX !== undefined) this.LightboxDoc._panX = this._savedState.panX;
+            if (this._savedState.panY !== undefined) this.LightboxDoc._panY = this._savedState.panY;
+            if (this._savedState.scrollTop !== undefined) this.LightboxDoc._scrollTop = this._savedState.scrollTop;
+            if (this._savedState.scale !== undefined) this.LightboxDoc._viewScale = this._savedState.scale;
+            this.LightboxDoc.layoutKey = this._savedState.layoutKey;
         }
         if (!doc) {
             this._docFilters && (this._docFilters.length = 0);
@@ -69,10 +77,11 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]);
             if (doc !== LightboxView.LightboxDoc) {
                 this._savedState = {
-                    panX: Cast(doc._panX, 'number', null),
-                    panY: Cast(doc._panY, 'number', null),
-                    scale: Cast(doc._viewScale, 'number', null),
-                    scrollTop: Cast(doc._scrollTop, 'number', null),
+                    layoutKey: StrCast(doc.layoutKey),
+                    panX: Cast(doc.panX, 'number', null),
+                    panY: Cast(doc.panY, 'number', null),
+                    scale: Cast(doc.viewScale, 'number', null),
+                    scrollTop: Cast(doc.scrollTop, 'number', null),
                 };
             }
         }
@@ -87,7 +96,10 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             ];
         }
         this._doc = doc;
-        this._layoutTemplate = layoutTemplate;
+        this._layoutTemplate = layoutTemplate instanceof Doc ? layoutTemplate : undefined;
+        if (doc && (typeof layoutTemplate === 'string' ? layoutTemplate : undefined)) {
+            doc.layoutKey = layoutTemplate;
+        }
         this._docTarget = target || doc;
 
         return true;
@@ -132,7 +144,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
             this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`);
         }
     }
-    public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc, openInTabFunc?: any) => {
+    public static AddDocTab = (doc: Doc, location: OpenWhere, layoutTemplate?: Doc | string) => {
         if (location !== OpenWhere.lightbox) {
             const inPlaceView = DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined;
             if (inPlaceView) {
@@ -140,12 +152,13 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                 return true;
             }
         }
-        LightboxView.openInTabFunc = openInTabFunc;
         SelectionManager.DeselectAll();
         return LightboxView.SetLightboxDoc(
             doc,
             undefined,
-            [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '-annotations']), ...(LightboxView._future ?? [])].sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)),
+            [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '-annotations']).filter(anno => anno.annotationOn !== doc), ...(LightboxView._future ?? [])].sort(
+                (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)
+            ),
             layoutTemplate
         );
     };
@@ -193,8 +206,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
         const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc);
         if (docView) {
             LightboxView._docTarget = target;
-            if (!target) docView.ComponentView?.shrinkWrap?.();
-            else docView.focus(target, { willPanZoom: true, zoomScale: 0.9 });
+            target && docView.focus(target, { willPanZoom: true, zoomScale: 0.9 });
         } else {
             LightboxView.SetLightboxDoc(doc, target);
         }
@@ -218,7 +230,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                 .filter(doc => doc)
                 .map(doc => doc!);
             LightboxView.SetLightboxDoc(coll, undefined, contents.length ? contents : links);
-            TabDocView.PinDoc(coll, { hidePresBox: true });
         }
     };
 
@@ -251,19 +262,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
 
                     <GestureOverlay isActive={true}>
                         <DocumentView
-                            ref={action((r: DocumentView | null) => {
-                                LightboxView._docView = r !== null ? r : undefined;
-                                r &&
-                                    setTimeout(
-                                        action(() => {
-                                            const target = LightboxView._docTarget;
-                                            const doc = LightboxView._doc;
-                                            //const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target);
-                                            //if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.();
-                                            //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here?  it breaks smooth navigation in lightbox using 'next' button
-                                        })
-                                    );
-                            })}
+                            ref={action((r: DocumentView | null) => (LightboxView._docView = r !== null ? r : undefined))}
                             Document={LightboxView.LightboxDoc}
                             DataDoc={undefined}
                             PanelWidth={this.lightboxWidth}
@@ -332,7 +331,6 @@ export class LightboxView extends React.Component<LightboxViewProps> {
                     onClick={e => {
                         e.stopPropagation();
                         CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, OpenWhereMod.none);
-                        //LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace");
                         SelectionManager.DeselectAll();
                         LightboxView.SetLightboxDoc(undefined);
                     }}>
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 66c3ed439..65a950711 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -77,6 +77,14 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc)
         );
     }
+    @computed get hideImageButton() {
+        return this.propertyToggleBtn(
+            'Background',
+            '_hideImage',
+            on => (on ? 'Show Image' : 'Show Background'),
+            on => 'portrait'
+        );
+    }
     @computed get clustersButton() {
         return this.propertyToggleBtn(
             'Clusters',
@@ -383,6 +391,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)];
         const isText = layoutField instanceof RichTextField;
         const isInk = layoutField instanceof InkField;
+        const isImage = layoutField instanceof ImageField;
         const isMap = this.selectedDoc?.type === DocumentType.MAP;
         const isCollection = this.selectedDoc?.type === DocumentType.COL;
         //TODO: will likely need to create separate note-taking view type here
@@ -410,6 +419,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 {toggle(this.inPlaceContainerButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
                 {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })}
                 {toggle(this.maskButton, { display: !isInk ? 'none' : '' })}
+                {toggle(this.hideImageButton, { display: !isImage ? 'none' : '' })}
                 {toggle(this.chromeButton, { display: !isCollection || isNovice ? 'none' : '' })}
                 {toggle(this.gridButton, { display: !isCollection ? 'none' : '' })}
                 {toggle(this.groupButton, { display: isTabView || !isCollection ? 'none' : '' })}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 411f51d84..a2bc37095 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -143,7 +143,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         : layoutDoc._fitWidth
                         ? !Doc.NativeHeight(this.dataDoc)
                             ? NumCast(this.props.height)
-                            : Math.min((this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / Doc.NativeWidth(layoutDoc) || NumCast(this.props.height))
+                            : Math.min((this.docWidth() * Doc.NativeHeight(layoutDoc)) / Doc.NativeWidth(layoutDoc) || NumCast(this.props.height))
                         : NumCast(layoutDoc._height) || 50
                 )
             );
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index ece224c68..3cb920ba0 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -269,9 +269,9 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
                 default:
                     return doc.z
                         ? `#9c9396  ${StrCast(doc?.boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
-                        : props?.ContainingCollectionDoc?._useClusters && doc.type !== DocumentType.INK
+                        : props?.ContainingCollectionDoc?._useClusters
                         ? `${backgroundCol()} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
-                        : NumCast(doc.group, -1) !== -1 && doc.type !== DocumentType.INK
+                        : NumCast(doc.group, -1) !== -1
                         ? `gray ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                         : isBackground()
                         ? undefined // if it's a background & has a cluster color, make the shadow spread really big
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a28b1ca19..90406ed3c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -256,7 +256,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
     childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle);
     childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null);
     @computed get childLayoutString() {
-        return StrCast(this.rootDoc.childLayoutString);
+        return StrCast(this.rootDoc.childLayoutString, this.props.childLayoutString);
     }
 
     isContentActive = (outsideReaction?: boolean) => this.props.isContentActive();
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 25fccd89c..bf8d449ea 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -274,7 +274,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                 pinDoc.presStartTime = NumCast(doc.clipStart);
                 pinDoc.presEndTime = NumCast(doc.clipEnd, duration);
             }
-            PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, pinDoc);
+            PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, DocCast(pinDoc.proto));
             pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)');
             Doc.AddDocToList(curPres, 'data', pinDoc, presSelected);
             //save position
@@ -359,7 +359,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         // prettier-ignore
         switch (whereFields[0]) {
             case OpenWhere.inPlace:  // fall through to lightbox
-            case OpenWhere.lightbox:   return LightboxView.AddDocTab(doc, location, undefined, this.addDocTab);
+            case OpenWhere.lightbox:   return LightboxView.AddDocTab(doc, location);
             case OpenWhere.dashboard:  return DashboardView.openDashboard(doc);
             case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc);
             case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
@@ -382,14 +382,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     };
     @action
     focusFunc = (doc: Doc, options: DocFocusOptions) => {
-        const shrinkwrap = options?.originalTarget === this._document && this.view?.ComponentView?.shrinkWrap;
-        if (options?.willPanZoom !== false && shrinkwrap && this._document) {
-            const focusSpeed = options.zoomTime ?? 500;
-            shrinkwrap();
-            this._view?.setViewTransition('transform', focusSpeed, () => options?.afterFocus?.(false));
-        } else {
-            options?.afterFocus?.(false);
-        }
+        options?.afterFocus?.(false);
+
         if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
             this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
         }
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index bd326f917..2398d8f58 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -410,7 +410,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                 if (aspect) return this.embeddedPanelWidth() / (aspect || 1);
                 return layoutDoc._fitWidth
                     ? !Doc.NativeHeight(layoutDoc)
-                        ? NumCast(layoutDoc._height) //this.props.containerCollection._height)
+                        ? NumCast(layoutDoc._height)
                         : Math.min((this.embeddedPanelWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)))
                     : (this.embeddedPanelWidth() * layoutDoc[HeightSym]()) / layoutDoc[WidthSym]();
             })()
@@ -957,6 +957,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         );
     };
 
+    fitWidthFilter = (doc: Doc) => (doc.type === DocumentType.IMG ? false : undefined);
     renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => {
         return (
             <div style={{ height: this.embeddedPanelHeight(), width: this.embeddedPanelWidth() }}>
@@ -965,6 +966,7 @@ export class TreeView extends React.Component<TreeViewProps> {
                     ref={action((r: DocumentView | null) => (this._dref = r))}
                     Document={this.doc}
                     DataDoc={undefined}
+                    fitWidth={this.fitWidthFilter}
                     PanelWidth={this.embeddedPanelWidth}
                     PanelHeight={this.embeddedPanelHeight}
                     LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 51672578e..d6e95f97f 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -16,7 +16,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro
 import { ImageField } from '../../../../fields/URLField';
 import { TraceMobx } from '../../../../fields/util';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnTransparent, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -61,6 +61,7 @@ export type collectionFreeformViewProps = {
     scaleField?: string;
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
     engineProps?: any;
+    getScrollHeight?: () => number | undefined;
     dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them
     dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.
     // However, this screws up interactions since only the top layer gets events.  so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents.
@@ -363,10 +364,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1);
                 if (grouping !== -1) {
                     const layoutDoc = Doc.Layout(cd);
-                    const cx = NumCast(cd.x) - this._clusterDistance;
-                    const cy = NumCast(cd.y) - this._clusterDistance;
-                    const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance;
-                    const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance;
+                    const cx = NumCast(cd.x) - this._clusterDistance / 2;
+                    const cy = NumCast(cd.y) - this._clusterDistance / 2;
+                    const cw = NumCast(layoutDoc._width) + this._clusterDistance;
+                    const ch = NumCast(layoutDoc._height) + this._clusterDistance;
                     return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
                 }
                 return cluster;
@@ -545,7 +546,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     case InkTool.None:
                         if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
                             this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
-                            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, false);
+                            setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false);
                         }
                         break;
                 }
@@ -771,7 +772,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         } else if (!e.cancelBubble) {
             if (this.tryDragCluster(e, this._hitCluster)) {
                 return true;
-            } else this.pan(e);
+            }
+            this.pan(e);
         }
         return false;
     };
@@ -1004,6 +1006,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (deltaScale * invTransform.Scale > 20) {
             deltaScale = 20 / invTransform.Scale;
         }
+        if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) {
+            return;
+        }
         if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) {
             deltaScale = NumCast(this.rootDoc._viewScaleMin, 1) / invTransform.Scale;
         }
@@ -1012,29 +1017,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
             const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
             this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
-            this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
+            this.setPan(-localTransform.TranslateX / safeScale, NumCast(this.props.Document.scrollTop) * safeScale || -localTransform.TranslateY / safeScale);
         }
     };
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
+        if (this.Document._isGroup) return; // group style collections neither pan nor zoom
         PresBox.Instance?.pauseAutoPres();
         if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
         e.stopPropagation();
         e.preventDefault();
         switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) {
             case freeformScrollMode.Pan:
-                // if shift is selected then zoom
+                // if ctrl is selected then zoom
                 if (e.ctrlKey) {
-                    if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
-                        // things that can scroll vertically should do that instead of zooming
-                    } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
+                    if (this.props.isContentActive(true)) {
                         !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
                     }
-                    // otherwise pan
-                } else if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
-                    // things that can scroll vertically should do that instead of zooming
-                } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
+                } // otherwise pan
+                else if (this.props.isContentActive(true)) {
                     const dx = -e.deltaX;
                     const dy = -e.deltaY;
                     if (e.shiftKey) {
@@ -1046,9 +1048,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 break;
             default:
             case freeformScrollMode.Zoom:
-                if (!e.ctrlKey && (this.props.Document.scrollHeight ?? this.props.DocumentView?.().ComponentView?.getScrollHeight?.()) !== undefined) {
-                    // things that can scroll vertically should do that instead of zooming
-                } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
+                if (this.props.isContentActive(true)) {
                     !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
                 }
                 break;
@@ -1096,7 +1096,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const minPanX = NumCast(this.rootDoc._panXMin, 0);
             const minPanY = NumCast(this.rootDoc._panYMin, 0);
             const newPanX = Math.min(minPanX + (1 - minScale / scale) * NumCast(this.rootDoc._panXMax, this.nativeWidth), Math.max(minPanX, panX));
-            const newPanY = Math.min(this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : minPanY + (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, this.nativeHeight), Math.max(minPanY, panY));
+            const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this.props.PanelWidth() - this.props.PanelHeight()) * this.props.ScreenToLocalTransform().Scale) / minScale;
+            const nativeHeight = (this.props.PanelHeight() / this.props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
+            const maxScrollTop = this.nativeHeight / this.props.ScreenToLocalTransform().Scale - this.props.PanelHeight();
+            const maxPanY =
+                minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by fitWidth
+                (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, nativeHeight) +
+                (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
+            let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
+            if (NumCast(this.rootDoc.scrollTop) && NumCast(this.rootDoc._viewScale, minScale) !== minScale) {
+                const relTop = NumCast(this.rootDoc.scrollTop) / maxScrollTop;
+                this.rootDoc.scrollTop = undefined;
+                newPanY = minPanY + relTop * (maxPanY - minPanY);
+            } else if (fitYscroll && this.rootDoc.scrollTop === undefined && NumCast(this.rootDoc._viewScale, minScale) === minScale) {
+                const maxPanY = minPanY + fitYscroll;
+                const relTop = (panY - minPanY) / (maxPanY - minPanY);
+                setTimeout(() => {
+                    this.rootDoc.scrollTop = relTop * maxScrollTop;
+                }, 10);
+                newPanY = minPanY;
+            }
             !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX);
             !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY);
         }
@@ -1169,7 +1188,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         // if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
         //     SelectionManager.DeselectAll();
         // }
-        if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined || doc.type === DocumentType.MARKER) {
+        if (this.props.getScrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined || doc.type === DocumentType.MARKER) {
             this.props.focus(doc, options);
         } else {
             const xfToCollection = options?.docTransform ?? Transform.Identity();
@@ -2033,10 +2052,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             : (this.props.pointerEvents?.() as any),
                     transform: `scale(${this.nativeDimScaling || 1})`,
                     width: `${100 / (this.nativeDimScaling || 1)}%`,
-                    height:
-                        this.isAnnotationOverlay && (this.Document.scrollHeight || this.props.DocumentView?.().ComponentView?.getScrollHeight?.())
-                            ? NumCast(this.Document.scrollHeight || this.props.DocumentView?.().ComponentView?.getScrollHeight?.())
-                            : `${100 / (this.nativeDimScaling || 1)}%`, // : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+                    height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
                 }}>
                 {this._firstRender ? this.placeholder : this.marqueeView}
                 {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 0b7854926..bc3b17cd9 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -431,8 +431,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @undoBatch
     @action
-    collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => {
-        const selected = this.marqueeSelect(false);
+    collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => {
+        const selected = selection ?? this.marqueeSelect(false);
         const activeFrame = selected.reduce((v, d) => v ?? Cast(d._activeFrame, 'number', null), undefined as number | undefined);
         if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) {
             this.props.removeDocument?.(selected);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index b94db2c6b..36c0240f1 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -56,6 +56,7 @@ import { ScriptingBox } from './ScriptingBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps } from './trails/PresBox';
 import React = require('react');
+import { GestureOverlay } from '../GestureOverlay';
 const { Howl } = require('howler');
 
 interface Window {
@@ -139,7 +140,6 @@ export interface DocComponentView {
     fieldKey?: string;
     annotationKey?: string;
     getTitle?: () => string;
-    getScrollHeight?: () => number;
     getCenter?: (xf: Transform) => { X: number; Y: number };
     ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
     ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
@@ -170,7 +170,7 @@ export interface DocumentViewSharedProps {
     dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt
     styleProvider: Opt<StyleProviderFunc>;
     focus: DocFocusFunc;
-    fitWidth?: (doc: Doc) => boolean;
+    fitWidth?: (doc: Doc) => boolean | undefined;
     docFilters: () => string[];
     docRangeFilters: () => string[];
     searchFilterDocs: () => Doc[];
@@ -637,8 +637,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             console.log
                         );
                     UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
-                } else if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isLinkButton) {
-                    UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.(), this.props.addDocTab), 'double tap');
+                } else if (!Doc.IsSystem(this.rootDoc) && (![DocumentType.INK].includes(this.rootDoc.type as any) || Doc.UserDoc().openInkInLightbox) && !this.rootDoc.isLinkButton) {
+                    UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.()), 'double tap');
                     SelectionManager.DeselectAll();
                     Doc.UnBrushDoc(this.props.Document);
                 }
@@ -669,7 +669,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     this._timeout = setTimeout(() => {
                         this._timeout = undefined;
                         clickFunc();
-                    }, 350);
+                    }, 150);
                 } else clickFunc();
             } else if (!this._longPress && this.allLinks.length && this.Document.type !== DocumentType.LINK && !isScriptBox() && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
                 SelectionManager.DeselectAll();
@@ -698,6 +698,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        if (!(e.nativeEvent as any).DownDocView) (e.nativeEvent as any).DownDocView = GestureOverlay.DownDocView = this.props.DocumentView();
         if (this.rootDoc.type === DocumentType.INK && Doc.ActiveTool === InkTool.Eraser) return;
         // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document)
         if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool))) {
@@ -1640,7 +1641,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?.LayoutFieldKey || 'layout';
     }
     get fitWidth() {
-        return this.props.fitWidth?.(this.rootDoc) || this.layoutDoc.fitWidth;
+        return this.props.fitWidth?.(this.rootDoc) ?? this.layoutDoc?.fitWidth;
     }
 
     @computed get hideLinkButton() {
@@ -1688,8 +1689,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
     @computed get panelHeight() {
         if (this.effectiveNativeHeight && !this.layoutDoc.nativeHeightUnfrozen) {
-            const scrollHeight = this.fitWidth ? Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight)) : 0;
-            return Math.min(this.props.PanelHeight(), Math.max(scrollHeight, this.effectiveNativeHeight) * this.nativeScaling);
+            return Math.min(this.props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
         }
         return this.props.PanelHeight();
     }
@@ -1902,6 +1902,11 @@ ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
     documentView.select(false);
 });
 
+ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) {
+    //documentView.iconify(() =>
+    LightboxView.AddDocTab(documentView.rootDoc, OpenWhere.lightbox, 'layout'); //, 0);
+});
+
 ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
     if (dv.Document.layoutKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(false, 'layout');
     else dv.switchViews(true, detailLayoutKeySuffix, undefined, true);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index bdd99528b..540958941 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -11,7 +11,7 @@ import { ComputedField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
+import { DashColor, emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -26,13 +26,15 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComp
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
 import { AnchorMenu } from '../pdf/AnchorMenu';
 import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, DocumentViewProps } from './DocumentView';
+import { DocFocusOptions, OpenWhere } from './DocumentView';
 import { FaceRectangles } from './FaceRectangles';
 import { FieldView, FieldViewProps } from './FieldView';
 import './ImageBox.scss';
 import { PresBox } from './trails';
 import React = require('react');
 import Color = require('color');
+import { LinkDocPreview } from './LinkDocPreview';
+import { DocumentManager } from '../../util/DocumentManager';
 
 export const pageSchema = createSchema({
     googlePhotosUrl: 'string',
@@ -51,6 +53,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ImageBox, fieldKey);
     }
+    private _ignoreScroll = false;
+    private _forcedScroll = false;
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
@@ -119,6 +123,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             },
             { fireImmediately: true }
         );
+        this._disposers.scroll = reaction(
+            () => this.layoutDoc._scrollTop,
+            s_top => {
+                this._forcedScroll = true;
+                !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(s_top));
+                this._mainCont.current?.scrollTo({ top: NumCast(s_top) });
+                this._forcedScroll = false;
+            },
+            { fireImmediately: true }
+        );
     }
 
     componentWillUnmount() {
@@ -155,6 +169,21 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @undoBatch
     setUseAlt = () => (this.layoutDoc[this.fieldKey + '-useAlt'] = !this.layoutDoc[this.fieldKey + '-useAlt']);
 
+    @undoBatch
+    setNativeSize = action(() => {
+        const scaling = (this.props.DocumentView?.().props.ScreenToLocalTransform().Scale || 1) / NumCast(this.rootDoc._viewScale, 1);
+        const nscale = NumCast(this.props.PanelWidth()) / scaling;
+        const nh = nscale / NumCast(this.dataDoc[this.fieldKey + '-nativeHeight']);
+        const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '-nativeWidth']);
+        this.dataDoc[this.fieldKey + '-nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '-nativeHeight']) * nh;
+        this.dataDoc[this.fieldKey + '-nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '-nativeWidth']) * nw;
+        this.rootDoc._panX = nh * NumCast(this.rootDoc._panX);
+        this.rootDoc._panY = nw * NumCast(this.rootDoc._panY);
+        this.dataDoc._panXMax = this.dataDoc._panXMax ? nh * NumCast(this.dataDoc._panXMax) : undefined;
+        this.dataDoc._panXMin = this.dataDoc._panXMin ? nh * NumCast(this.dataDoc._panXMin) : undefined;
+        this.dataDoc._panYMax = this.dataDoc._panYMax ? nw * NumCast(this.dataDoc._panYMax) : undefined;
+        this.dataDoc._panYMin = this.dataDoc._panYMin ? nw * NumCast(this.dataDoc._panYMin) : undefined;
+    });
     @undoBatch
     rotate = action(() => {
         const nw = NumCast(this.dataDoc[this.fieldKey + '-nativeWidth']);
@@ -189,6 +218,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         const croppingProto = Doc.GetProto(cropping);
         croppingProto.annotationOn = undefined;
         croppingProto.isPrototype = true;
+        croppingProto.backgroundColor = undefined;
         croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
         croppingProto.type = DocumentType.IMG;
         croppingProto.layout = ImageBox.LayoutString('data');
@@ -205,7 +235,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         croppingProto.panYMax = anchh / viewScale;
         if (addCrop) {
             DocUtils.MakeLink({ doc: region }, { doc: cropping }, 'cropped image', '');
+            cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]();
+            cropping.y = NumCast(this.rootDoc.y);
+            this.props.addDocTab(cropping, OpenWhere.inParent);
         }
+        DocumentManager.Instance.AddViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200));
         this.props.bringToFront(cropping);
         return cropping;
     };
@@ -216,6 +250,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             const funcs: ContextMenuProps[] = [];
             funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'expand-arrows-alt' });
             funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand-arrows-alt' });
+            funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
             funcs.push({ description: `${this.layoutDoc[this.fieldKey + '-useAlt'] ? 'Show Alternate' : 'Show Primary'}`, event: this.setUseAlt, icon: 'expand-arrows-alt' });
             funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'expand-arrows-alt' });
             if (!Doc.noviceMode) {
@@ -282,6 +317,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return !tags ? null : <img id={'google-tags'} src={'/assets/google_tags.png'} />;
     };
 
+    getScrollHeight = () => (this.props.fitWidth?.(this.rootDoc) !== false && NumCast(this.rootDoc._viewScale, 1) === NumCast(this.rootDoc._viewScaleMin, 1) ? this.nativeSize.nativeHeight : undefined);
+
     @computed
     private get considerDownloadIcon() {
         const data = this.dataDoc[this.fieldKey];
@@ -346,9 +383,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @computed get content() {
         TraceMobx();
 
-        const col = Color(this.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor));
-        const srcpath = !col.alpha() ? '' : this.paths[0];
-        const fadepath = !col.alpha() ? '' : this.paths.lastElement();
+        const col = DashColor(this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor));
+        const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
+        const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
         const { nativeWidth, nativeHeight, nativeOrientation } = this.nativeSize;
         const rotation = NumCast(this.dataDoc[this.fieldKey + '-rotation']);
         const aspect = rotation % 180 ? nativeHeight / nativeWidth : 1;
@@ -366,7 +403,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
         return (
             <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
-                <div className="imageBox-fader" style={{ opacity: col.alpha(), overflow: Array.from(this.props.docViewPath?.()).slice(-1)[0].fitWidth ? 'auto' : undefined }}>
+                <div className="imageBox-fader" style={{ opacity: col.alpha() }}>
                     <img key="paths" src={srcpath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} />
                     {fadepath === srcpath ? null : (
                         <div
@@ -383,7 +420,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         );
     }
 
-    screenToLocalTransform = this.props.ScreenToLocalTransform;
     contentFunc = () => [this.content];
 
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
@@ -394,6 +430,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         TraceMobx();
         return <div className="imageBox-annotationLayer" style={{ height: this.props.PanelHeight() }} ref={this._annotationLayer} />;
     }
+    screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop) * this.props.ScreenToLocalTransform().Scale);
     marqueeDown = (e: React.PointerEvent) => {
         if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale, 1) <= NumCast(this.rootDoc.viewScaleMin, 1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
             setupMoveUpEvents(
@@ -410,7 +447,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             );
         }
     };
-    getScrollHeight = () => this.nativeSize.nativeHeight;
     @action
     finishMarquee = () => {
         this._getAnchor = AnchorMenu.Instance?.GetAnchor;
@@ -418,11 +454,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this.props.select(false);
     };
     savedAnnotations = () => this._savedAnnotations;
-    styleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
-        if (property === StyleProp.BoxShadow) return undefined;
-        return this.props.styleProvider?.(doc, props, property);
-    };
-
     render() {
         TraceMobx();
         const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
@@ -432,25 +463,35 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 className="imageBox"
                 onContextMenu={this.specificContextMenu}
                 ref={this._mainCont}
+                onScroll={action(e => {
+                    if (!this._forcedScroll) {
+                        if (this.layoutDoc._scrollTop || this._mainCont.current?.scrollTop) {
+                            this._ignoreScroll = true;
+                            this.layoutDoc._scrollTop = this._mainCont.current?.scrollTop;
+                            this._ignoreScroll = false;
+                        }
+                    }
+                })}
                 style={{
                     width: this.props.PanelWidth() ? undefined : `100%`,
                     height: this.props.PanelWidth() ? undefined : `100%`,
                     pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
                     borderRadius,
-                    overflow: 'auto',
+                    overflow: this.layoutDoc.fitWidth || this.props.fitWidth?.(this.rootDoc) ? 'auto' : undefined,
                 }}>
                 <CollectionFreeFormView
                     {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
                     renderDepth={this.props.renderDepth + 1}
                     fieldKey={this.annotationKey}
-                    styleProvider={this.styleProvider}
+                    styleProvider={this.props.styleProvider}
                     CollectionView={undefined}
                     isAnnotationOverlay={true}
                     annotationLayerHostsContent={true}
                     PanelWidth={this.props.PanelWidth}
-                    PanelHeight={this.props.fitWidth ? '100%' : this.props.PanelHeight}
+                    PanelHeight={this.props.PanelHeight}
                     ScreenToLocalTransform={this.screenToLocalTransform}
                     select={emptyFunction}
+                    getScrollHeight={this.getScrollHeight}
                     NativeDimScaling={returnOne}
                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     removeDocument={this.removeDocument}
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index eba9e281d..1b37dc7ab 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -60,6 +60,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
             } else {
                 this.rootDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100;
                 this.rootDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100;
+                this.rootDoc.linkAutoMove = false;
             }
         }
         return false;
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 95cb49037..5940fc075 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -542,8 +542,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         // }
     };
 
-    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
-    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth();
+    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
     transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
     opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()];
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index d0d638e98..c4cca9679 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -295,12 +295,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1;
         if (doc !== this.rootDoc && this._outerRef.current) {
             const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-            const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(doc.y) + doc[HeightSym](), this.getScrollHeight()));
+            const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(doc.y) + doc[HeightSym](), this._scrollHeight));
             if (scrollTo !== undefined && this._initialScroll === undefined) {
                 const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
                 this.goTo(scrollTo, focusSpeed, options.easeFunc);
                 return focusSpeed;
-            } else if (!this._webPageHasBeenRendered || !this.getScrollHeight() || this._initialScroll !== undefined) {
+            } else if (!this._webPageHasBeenRendered || !this._scrollHeight || this._initialScroll !== undefined) {
                 this._initialScroll = scrollTo;
             }
         }
@@ -376,9 +376,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             ContextMenu.Instance.setIgnoreEvents(true);
         }
     };
-
-    getScrollHeight = () => this._scrollHeight;
-
     isFirefox = () => {
         return 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1;
     };
@@ -969,8 +966,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value);
     showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
     setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func);
-    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
-    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth;
+    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
     scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._scrollTop));
     anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
     transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 619c59f0e..80b18b8b9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -994,7 +994,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return this._didScroll ? this._focusSpeed : didToggle ? 1 : undefined; // if we actually scrolled, then return some focusSpeed
     };
 
-    getScrollHeight = () => this.scrollHeight;
     // if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc.
     // Since we also monitor all component height changes, this will update the document's height.
     resetNativeHeight = (scrollHeight: number) => {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 427911bd3..0a96297b7 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -35,6 +35,7 @@ import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
 import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
+import { PresElementBox } from './PresElementBox';
 const { Howl } = require('howler');
 
 export interface PinProps {
@@ -383,9 +384,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             Doc.GetProto(bestTarget)[fkey + '-annotations'] = new List<Doc>([...DocListCast(bestTarget[fkey + '-annotations']).filter(doc => doc.unrendered), ...DocListCast(activeItem.presAnnotations)]);
         }
         if (pinDataTypes.dataview && activeItem.presData !== undefined) {
+            bestTarget._dataTransition = `all ${transTime}ms`;
             const fkey = Doc.LayoutFieldKey(bestTarget);
             Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
             bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt;
+            setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
         }
         if (pinDataTypes.textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData;
         if (pinDataTypes.poslayoutview) {
@@ -2244,6 +2247,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 ignoreUnrendered={true}
                                 //childFitWidth={returnTrue}
                                 childOpacity={returnOne}
+                                //childLayoutString={PresElementBox.LayoutString('data')}
                                 childLayoutTemplate={this.childLayoutTemplate}
                                 childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined}
                                 filterAddDocument={this.addDocumentFilter}
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index c53cc608c..4a7f5d038 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -11,6 +11,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
 import { LinkPopup } from '../linking/LinkPopup';
 import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu';
 import './AnchorMenu.scss';
+import { LightboxView } from '../LightboxView';
 
 @observer
 export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index b0b7816b8..9610a71ac 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -144,6 +144,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
     };
 
+    @observable _scrollHeight = 0;
+
     @action
     initialLoad = async () => {
         if (this._pageSizes.length === 0) {
@@ -164,8 +166,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
                     )
                 )
             );
-            this.props.Document.scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72;
         }
+        runInAction(() => (this._scrollHeight = (this._pageSizes.reduce((size, page) => size + page.height, 0) * 96) / 72));
     };
 
     _scrollStopper: undefined | (() => void);
@@ -177,7 +179,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         let focusSpeed: Opt<number>;
         if (doc !== this.props.rootDoc && mainCont) {
             const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
-            const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight));
+            const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, this._scrollHeight);
             if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) {
                 if (!this._pdfViewer) this._initialScroll = { loc: scrollTo, easeFunc: options.easeFunc };
                 else if (!options.instant) this._scrollStopper = smoothScroll((focusSpeed = options.zoomTime ?? 500), mainCont, scrollTo, options.easeFunc, this._scrollStopper);
@@ -453,10 +455,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
         }
     };
 
-    scrollXf = () => {
-        return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._scrollTop)) : this.props.ScreenToLocalTransform();
-    };
-
     onClick = (e: React.MouseEvent) => {
         this._scrollStopper?.();
         if (this._setPreviewCursor && e.button === 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
@@ -504,10 +502,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
         );
     }
 
+    getScrollHeight = () => this._scrollHeight;
     showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno));
+    scrollXf = () => (this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._scrollTop)) : this.props.ScreenToLocalTransform());
     overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1));
-    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0);
-    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document);
+    panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1);
+    panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1);
     transparentFilter = () => [...this.props.docFilters(), Utils.IsTransparentFilter()];
     opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])];
     childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
@@ -533,6 +533,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 isAnnotationOverlay={true}
                 fieldKey={this.props.fieldKey + '-annotations'}
                 CollectionView={undefined}
+                getScrollHeight={this.getScrollHeight}
                 setPreviewCursor={this.setPreviewCursor}
                 setBrushViewer={this.setBrushViewer}
                 PanelHeight={this.panelHeight}
diff --git a/src/fields/util.ts b/src/fields/util.ts
index dc0b41276..3a7484cfd 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -350,7 +350,7 @@ export function getter(target: any, prop: string | symbol, proxy: any): any {
                         return target[prop];
         case AclSym :   return target[AclSym];
         case $mobx:     return target.__fields[prop];
-        case LayoutSym: return target.__Layout__;
+        case LayoutSym: return target.__LAYOUT__;
         case HeightSym: case WidthSym: if (GetEffectiveAcl(target) === AclPrivate) return returnZero;
         default :
             if (typeof prop === 'symbol')  return target[prop];
-- 
cgit v1.2.3-70-g09d2


From 9c63f3833f15cc995c2255b83923384686127f3e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 1 Mar 2023 11:57:05 -0500
Subject: changed inPlace link following to be lightbox.  allowed collections
 to be labeled as lightboxes.  lightbox collections display link targets in an
 overlay that hides the rest of their content

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/util/DocumentManager.ts                 | 28 ++++++----
 src/client/util/LinkFollower.ts                    | 10 ++--
 src/client/views/DocumentDecorations.tsx           | 13 ++---
 src/client/views/MainView.tsx                      |  1 -
 src/client/views/PropertiesButtons.tsx             | 62 +++++++++++-----------
 src/client/views/PropertiesView.tsx                |  1 -
 src/client/views/collections/CollectionView.tsx    |  2 +-
 src/client/views/collections/TabDocView.tsx        | 22 ++++----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 src/client/views/nodes/DocumentView.tsx            | 17 +++---
 src/client/views/nodes/MapBox/MapBox.tsx           |  1 -
 src/fields/documentSchemas.ts                      |  4 +-
 13 files changed, 88 insertions(+), 78 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d2ab67849..70c57e8fa 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -160,6 +160,7 @@ export class DocumentOptions {
     _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
     _followLinkToggle?: boolean; // whether document, when clicked, toggles display of its link target
     _showTitle?: string; // field name to display in header (:hover is an optional suffix)
+    _isLightbox?: boolean; // whether a collection acts as a lightbox by opening lightbox links by hiding all other documents in collection besides link target
     _showCaption?: string; // which field to display in the caption area.  leave empty to have no caption
     _scrollTop?: number; // scroll location for pdfs
     _noAutoscroll?: boolean; // whether collections autoscroll when this item is dragged
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index d9273c2c9..0d10bed43 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -10,7 +10,7 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie
 import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
 import { CollectionView } from '../views/collections/CollectionView';
 import { LightboxView } from '../views/LightboxView';
-import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView';
+import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView';
 import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
 import { ScriptingGlobals } from './ScriptingGlobals';
 import { SelectionManager } from './SelectionManager';
@@ -307,16 +307,22 @@ export class DocumentManager {
             }
             //}
             if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc);
-            const doFocus = (forceDidFocus: boolean) =>
-                focusView.focus(originalTarget, {
-                    ...options,
-                    originalTarget,
-                    afterFocus: (didFocus: boolean) =>
-                        new Promise<ViewAdjustment>(res => {
-                            focusAndFinish(forceDidFocus || didFocus);
-                            res(ViewAdjustment.doNothing);
-                        }),
-                });
+            const doFocus = (forceDidFocus: boolean) => {
+                if (options.openInLightbox || (!options.originatingDoc?.followLinkLocation && DocCast(focusView.rootDoc.context)?._isLightbox)) {
+                    focusView.props.addDocTab(targetDoc, OpenWhere.lightbox);
+                    focusAndFinish(true);
+                } else {
+                    focusView.focus(originalTarget, {
+                        ...options,
+                        originalTarget,
+                        afterFocus: (didFocus: boolean) =>
+                            new Promise<ViewAdjustment>(res => {
+                                focusAndFinish(forceDidFocus || didFocus);
+                                res(ViewAdjustment.doNothing);
+                            }),
+                    });
+                }
+            };
             if (focusView.props.Document.layoutKey === 'layout_icon' && focusView.rootDoc.type !== DocumentType.SCRIPTING) {
                 focusView.iconify(() => doFocus(true));
             } else {
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 50bc89ed2..e86e68c21 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -3,6 +3,7 @@ import { Doc, DocListCast, Opt } from '../../fields/Doc';
 import { List } from '../../fields/List';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { DocumentType } from '../documents/DocumentTypes';
+import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
 import { DocumentDecorations } from '../views/DocumentDecorations';
 import { LightboxView } from '../views/LightboxView';
 import { DocFocusOptions, DocumentViewSharedProps, OpenWhere, ViewAdjustment } from '../views/nodes/DocumentView';
@@ -36,7 +37,9 @@ export class LinkFollower {
             const createTabForTarget = (didFocus: boolean) =>
                 new Promise<ViewAdjustment>(res => {
                     const where = LightboxView.LightboxDoc ? OpenWhere.lightbox : (StrCast(sourceDoc.followLinkLocation, followLoc) as OpenWhere);
-                    docViewProps.addDocTab(doc, where);
+                    const lightbox = where === OpenWhere.lightbox && DocumentManager.GetContextPath(doc).find(container => container.isLightbox && DocumentManager.Instance.getDocumentView(container));
+                    const addDocTab = lightbox ? DocumentManager.Instance.getDocumentView(lightbox)?.ComponentView?.addDocTab : undefined;
+                    (addDocTab ?? docViewProps.addDocTab)(doc, where);
                     setTimeout(() => {
                         const targDocView = DocumentManager.Instance.getFirstDocumentView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise.
                         if (targDocView) {
@@ -51,7 +54,7 @@ export class LinkFollower {
                             });
                         } else {
                             finished?.();
-                            res(where !== OpenWhere.inPlace || BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace'  resetting the initial focus&zoom would negate the zoom into the target
+                            res(BoolCast(sourceDoc.followLinkZoom) ? ViewAdjustment.resetView : ViewAdjustment.doNothing);
                         }
                     }, 100);
                 });
@@ -109,6 +112,7 @@ export class LinkFollower {
                         zoomTime: NumCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkTransitionTime, 500),
                         zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null),
                         easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any,
+                        openInLightbox: sourceDoc.followLinkLocation === OpenWhere.lightbox,
                         effect: sourceDoc,
                         originatingDoc: sourceDoc,
                         zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText),
@@ -123,7 +127,7 @@ export class LinkFollower {
                     } else {
                         const containerDocContext = DocumentManager.GetContextPath(target);
                         const targetContexts = !sourceDoc.followLinkToOuterContext && containerDocContext.length ? [containerDocContext.lastElement()] : containerDocContext;
-                        DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, OpenWhere.inPlace), finished), targetContexts, allFinished);
+                        DocumentManager.Instance.jumpToDocument(target, options, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, OpenWhere.lightbox), finished), targetContexts, allFinished);
                     }
                 };
                 let movedTarget = false;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4e51f10a8..eeef01d17 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -27,7 +27,7 @@ import { Colors } from './global/globalEnums';
 import { InkingStroke } from './InkingStroke';
 import { InkStrokeProperties } from './InkStrokeProperties';
 import { LightboxView } from './LightboxView';
-import { DocumentView, OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { ImageBox } from './nodes/ImageBox';
 import React = require('react');
@@ -283,11 +283,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                     openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc);
                     Doc.deiconifyView(openDoc);
                 }
-                LightboxView.SetLightboxDoc(
-                    openDoc,
-                    undefined,
-                    selectedDocs.slice(1).map(view => view.props.Document)
-                );
+                selectedDocs[0].props.addDocTab(openDoc, OpenWhere.lightbox);
+                // LightboxView.SetLightboxDoc(
+                //     openDoc,
+                //     undefined,
+                //     selectedDocs.slice(1).map(view => view.props.Document)
+                // );
             }
         }
         SelectionManager.DeselectAll();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 1e9220e1e..95c0f3755 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -692,7 +692,6 @@ export class MainView extends React.Component {
         if (doc.dockingConfig) return DashboardView.openDashboard(doc);
         // prettier-ignore
         switch (whereFields[0]) {
-            case OpenWhere.inPlace:  // fall through to lightbox
             case OpenWhere.lightbox:   return LightboxView.AddDocTab(doc, location);
             case OpenWhere.dashboard:  return DashboardView.openDashboard(doc);
             case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc);
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 7e33ee495..a5c01490f 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -7,7 +7,7 @@ import { Id } from '../../fields/FieldSymbols';
 import { InkField } from '../../fields/InkField';
 import { RichTextField } from '../../fields/RichTextField';
 import { ScriptField } from '../../fields/ScriptField';
-import { BoolCast, StrCast } from '../../fields/Types';
+import { BoolCast, ScriptCast, StrCast } from '../../fields/Types';
 import { ImageField } from '../../fields/URLField';
 import { DocUtils } from '../documents/Documents';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
@@ -20,6 +20,7 @@ import { VideoBox } from './nodes/VideoBox';
 import { pasteImageBitmap } from './nodes/WebBoxRenderer';
 import './PropertiesButtons.scss';
 import React = require('react');
+import { LinkManager } from '../util/LinkManager';
 const higflyout = require('@hig/flyout');
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -118,14 +119,14 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             on => 'object-group'
         );
     }
-    // this implments a container pattern by marking the targetDoc (collection) as an inPlace container,
-    // and then making the contained collection be a "menu" such that when any of its contents are clicked,
-    // they will open their targets in the outer container.  To get back to the "menu", you click on the main container.
-    @computed get inPlaceContainerButton() {
+    // this implments a container pattern by marking the targetDoc (collection) as a lightbox
+    // that always fits its contents to its container and that hides all other documents when
+    // a link is followed that targets a 'lightbox' destination
+    @computed get isLightboxButton() {
         return this.propertyToggleBtn(
-            'In Place',
-            'isInPlaceContainer',
-            on => `${on ? 'Make' : 'Remove'} in place container flag`,
+            'Lightbox',
+            'isLightbox',
+            on => `${on ? 'Set' : 'Remove'} lightbox flag`,
             on => 'window-restore',
             onClick => {
                 SelectionManager.Views().forEach(dv => {
@@ -134,16 +135,12 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                     // containerDoc.noShadow =
                     // containerDoc.noHighlighting =
                     // containerDoc._forceActive =
-                    containerDoc._fitContentsToBox = containerDoc._isInPlaceContainer = !containerDoc._isInPlaceContainer;
-                    containerDoc._xPadding = containerDoc._yPadding = containerDoc._isInPlaceContainer ? 10 : undefined;
+                    containerDoc._fitContentsToBox = containerDoc._isLightbox = !containerDoc._isLightbox;
+                    containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
                     const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
                     dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
                     containerContents.forEach(doc => {
-                        DocListCast(doc.links).forEach(link => {
-                            doc.isLinkButton = true;
-                            //doc.followLinkLocation = OpenWhere.inPlace;
-                            link.linkDisplay = false;
-                        });
+                        DocListCast(doc.links).forEach(link => (link.linkDisplay = false));
                     });
                 });
             }
@@ -290,7 +287,6 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     @undoBatch
     @action
     handleOptionChange = (onClick: string) => {
-        this.selectedDoc && (this.selectedDoc.onClickBehavior = onClick);
         SelectionManager.Views()
             .filter(dv => dv.docView)
             .map(dv => dv.docView!)
@@ -305,7 +301,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                         break;
                     case 'linkInPlace':
                         docView.toggleFollowLink(false, false);
-                        docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.inPlace : undefined;
+                        docView.props.Document.followLinkLocation = docView.props.Document._isLinkButton ? OpenWhere.lightbox : undefined;
                         break;
                     case 'linkOnRight':
                         docView.toggleFollowLink(false, false);
@@ -327,24 +323,26 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             ['nothing', 'Select Document'],
             ['enterPortal', 'Enter Portal'],
             ['toggleDetail', 'Toggle Detail'],
-            ['linkInPlace', 'Open in Place'],
+            ['linkInPlace', 'Open Link in Lightbox'],
             ['linkOnRight', 'Open Link on Right'],
         ];
-        const currentSelection = this.selectedDoc.onClickBehavior;
-        // Get items to place into the list
-
         const list = buttonList.map(value => {
-            const click = () => {
-                this.handleOptionChange(value[0]);
-            };
+            const click = () => this.handleOptionChange(value[0]);
+            const linkButton = BoolCast(this.selectedDoc._isLinkButton);
+            const followLoc = this.selectedDoc._followLinkLocation;
+            const linkedToLightboxView = () => DocListCast(this.selectedDoc.links).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox);
+
+            let active = false;
+            // prettier-ignore
+            switch (value[0]) {
+                case 'linkInPlace': active = linkButton && followLoc === OpenWhere.lightbox && !linkedToLightboxView();  break;
+                case 'linkOnRight': active = linkButton && followLoc === OpenWhere.addRight; break;
+                case 'enterPortal': active = linkButton && this.selectedDoc._followLinkLocation === OpenWhere.lightbox && linkedToLightboxView(); break;
+                case 'toggleDetail':active = ScriptCast(this.selectedDoc.onClick)?.script.originalScript.includes('toggleDetail'); break;
+                case 'nothing':     active = !linkButton && this.selectedDoc.onClick === undefined;break;
+            }
             return (
-                <div
-                    className="list-item"
-                    key={`${value}`}
-                    style={{
-                        backgroundColor: value[0] === currentSelection ? Colors.LIGHT_BLUE : undefined,
-                    }}
-                    onClick={click}>
+                <div className="list-item" key={`${value}`} style={{ backgroundColor: active ? Colors.LIGHT_BLUE : undefined }} onClick={click}>
                     {value[1]}
                 </div>
             );
@@ -413,7 +411,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 {toggle(this.freezeThumb)}
                 {toggle(this.forceActiveButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
                 {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
-                {toggle(this.inPlaceContainerButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
+                {toggle(this.isLightboxButton, { display: !isFreeForm && !isMap ? 'none' : '' })}
                 {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })}
                 {toggle(this.maskButton, { display: !isInk ? 'none' : '' })}
                 {toggle(this.hideImageButton, { display: !isImage ? 'none' : '' })}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index e9d1433c3..0fe165783 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -1626,7 +1626,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                                                 <option value={OpenWhere.add}>Opening in new tab</option>
                                                 <option value={OpenWhere.replace}>Replacing current tab</option>
                                                 <option value={OpenWhere.inParent}>Opening in same collection</option>
-                                                <option value={OpenWhere.inPlace}>Opening in place</option>
                                                 {LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
                                             </select>
                                         </div>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 90406ed3c..316f1e4e9 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -191,7 +191,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) {
                 optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' });
             }
-            !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' });
+            !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.rootDoc._isLightbox = !this.rootDoc._isLightbox), icon: 'project-diagram' });
 
             if (!Doc.noviceMode && false) {
                 optionItems.push({
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 01872df84..042c2b71b 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -350,7 +350,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     //  replace[:{left,right,top,bottom,<any string>}] - e.g., "replace" will replace the current stack contents,
     //                                  "replace:right" - will replace the stack on the right named "right" if it exists, or create a stack on the right with that name,
     //                                   "replace:monkeys" - will replace any tab that has the label 'monkeys', or a tab with that label will be created by default on the right
-    //  inPlace - will add the document to any collection along the path from the document to the docking view that has a field isInPlaceContainer. if none is found, inPlace adds a tab to current stack
+    //  lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox
     addDocTab = (doc: Doc, location: OpenWhere) => {
         SelectionManager.DeselectAll();
         const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':');
@@ -359,17 +359,15 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         // prettier-ignore
         switch (whereFields[0]) {
             case undefined:
-            case OpenWhere.inPlace:    {
-                if (this.layoutDoc?.isInPlaceContainer) {
-                    const inPlaceView = !doc.annotationOn && DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined;
-                    const data = inPlaceView?.dataDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)];
-                    if (inPlaceView && (!data || data instanceof List)) {
-                        inPlaceView.layoutDoc[Doc.LayoutFieldKey(inPlaceView.rootDoc)] = new List<Doc>([doc]);
-                        return true;
-                    }
-                }
-            }
-            case OpenWhere.lightbox:   return LightboxView.AddDocTab(doc, location);
+            case OpenWhere.lightbox:   if (this.layoutDoc?._isLightbox) {
+                                            const lightboxView = !doc.annotationOn && DocCast(doc.context) ? DocumentManager.Instance.getFirstDocumentView(DocCast(doc.context)) : undefined;
+                                            const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)];
+                                            if (lightboxView && (!data || data instanceof List)) {
+                                                lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.rootDoc)] = new List<Doc>([doc]);
+                                                return true;
+                                            }
+                                       }
+                                       return LightboxView.AddDocTab(doc, location);
             case OpenWhere.dashboard:  return DashboardView.openDashboard(doc);
             case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc);
             case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 78804b070..be8a72369 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1414,8 +1414,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     false
                 );
             case undefined:
-            case OpenWhere.inPlace:
-                if (this.layoutDoc.isInPlaceContainer) {
+            case OpenWhere.lightbox:
+                if (this.layoutDoc._isLightbox) {
                     this.layoutDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc);
                     return true;
                 }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index beb0f8e3a..8257c9c49 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -72,7 +72,6 @@ export enum ViewAdjustment {
 }
 
 export enum OpenWhere {
-    inPlace = 'inPlace',
     lightbox = 'lightbox',
     add = 'add',
     addLeft = 'add:left',
@@ -109,6 +108,7 @@ export interface DocFocusOptions {
     effect?: Doc; // animation effect for focus
     noSelect?: boolean; // whether target should be selected after focusing
     playAudio?: boolean; // whether to play audio annotation on focus
+    openInLightbox?: boolean; // whether to open target in lightbox or just focus on it
     zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
     toggleTarget?: boolean; // whether to toggle target on and off
     originatingDoc?: Doc; // document that triggered the focus
@@ -123,6 +123,7 @@ export interface DocComponentView {
     scrollPreview?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
     scrollFocus?: (docView: DocumentView, doc: Doc, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
     brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void;
+    addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
     reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height.  However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
     shrinkWrap?: () => void; // requests a document to display all of its contents with no white space.  currently only implemented (needed?) for freeform views
     menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
@@ -640,7 +641,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         );
                     UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click');
                 } else if (!Doc.IsSystem(this.rootDoc) && (![DocumentType.INK].includes(this.rootDoc.type as any) || Doc.UserDoc().openInkInLightbox) && !this.rootDoc.isLinkButton) {
-                    UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.()), 'double tap');
+                    //UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox, this.props.LayoutTemplate?.()), 'double tap');
+                    UndoManager.RunInBatch(() => this.props.addDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap');
                     SelectionManager.DeselectAll();
                     Doc.UnBrushDoc(this.props.Document);
                 }
@@ -870,13 +872,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     @undoBatch
     @action
-    makeIntoPortal = async () => {
+    makeIntoPortal = () => {
         const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
         if (!portalLink) {
-            const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' });
-            DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, 'portal to:portal from');
+            DocUtils.MakeLink(
+                { doc: this.props.Document },
+                { doc: Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _isLightbox: true, _fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' }) },
+                'portal to:portal from'
+            );
         }
-        this.Document.followLinkLocation = OpenWhere.inPlace;
+        this.Document.followLinkLocation = OpenWhere.lightbox;
         this.Document.followLinkZoom = true;
         this.Document._isLinkButton = true;
     };
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 039f11cd1..c182dfd19 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -331,7 +331,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         docs.forEach(doc => {
             if (doc.lat !== undefined && doc.lng !== undefined) {
                 const existingMarker = this.allMapMarkers.find(marker => marker.lat === doc.lat && marker.lng === doc.lng);
-                doc.onClickBehavior = 'enterPortal';
                 if (existingMarker) {
                     Doc.AddDocToList(existingMarker, 'data', doc);
                 } else {
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 10324449f..5b489a96c 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -84,11 +84,11 @@ export const documentSchema = createSchema({
     onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
     onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
     onDragStart: ScriptField, // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
-    followLinkLocation: 'string', // flag for where to place content when following a click interaction (e.g., add:right, inPlace, default, )
+    followLinkLocation: 'string', // flag for where to place content when following a click interaction (e.g., add:right, lightbox, default, )
     hideLinkButton: 'boolean', // whether the blue link counter button should be hidden
     hideAllLinks: 'boolean', // whether all individual blue anchor dots should be hidden
     linkDisplay: 'boolean', // whether a link connection should be shown between link anchor endpoints.
-    isInPlaceContainer: 'boolean', // whether the marked object will display addDocTab() calls that target "inPlace" destinations
+    isLightbox: 'boolean', // whether the marked object will display addDocTab() calls that target "lightbox" destinations
     isLinkButton: 'boolean', // whether document functions as a link follow button to follow the first link on the document when clicked
     layers: listSpec('string'), // which layers the document is part of
     _lockedPosition: 'boolean', // whether the document can be moved (dragged)
-- 
cgit v1.2.3-70-g09d2


From 096371e683b3f91edfadb2aaf9f8da3309b86014 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 9 Mar 2023 21:39:21 -0500
Subject: added reordering of progressivized docs

---
 src/client/documents/Documents.ts                  |  1 +
 src/client/util/CurrentUserUtils.ts                |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  4 +--
 src/client/views/collections/CollectionView.tsx    |  4 +--
 src/client/views/collections/TreeView.tsx          | 12 ++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/trails/PresBox.tsx          | 41 ++++++++++++----------
 7 files changed, 37 insertions(+), 29 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a7378c431..6296b0df4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -335,6 +335,7 @@ export class DocumentOptions {
     strokeWidth?: number;
     freezeChildren?: string; // whether children are now allowed to be added and or removed from a collection
     treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view
+    treeViewHideUnrendered?: boolean; // tells tree view not to display documents that have an 'unrendered' tag unless they also have a treeViewFieldKey tag (presBox)
     treeViewHideHeaderIfTemplate?: boolean; // whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)
     treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view
     treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items.
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index a7b2c3d04..2b0a2cdac 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -269,7 +269,7 @@ export class CurrentUserUtils {
             {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }},
             {key: "Script",     creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
             // {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument(opts),         opts: { _width: 300, _height: 300 }},
-            {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}},
+            {key: "Header",     creator: headerTemplate,                                    opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, treeViewHideUnrendered: true}},
             {key: "Trail",      creator: Docs.Create.PresDocument,                          opts: { _width: 400, _height: 30, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, treeViewHideTitle: true, _fitWidth:true, _chromeHidden: true, boxShadow: "0 0" }},
             {key: "Tab",        creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 500, _height: 800, _fitWidth: true, _backgroundGridShow: true, }},
             {key: "Slide",      creator: opts => Docs.Create.TreeDocument([], opts),        opts: { _width: 300, _height: 200, _viewType: CollectionViewType.Tree, 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 330e116d1..553967b95 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -38,8 +38,8 @@ export type collectionTreeViewProps = {
     onCheckedClick?: () => ScriptField;
     onChildClick?: () => ScriptField;
     // TODO: [AL] add these fields
-    AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
-    RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+    AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+    RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
     hierarchyIndex?: number[];
 };
 
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 316f1e4e9..48e5748a0 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -64,8 +64,8 @@ interface CollectionViewProps_ extends FieldViewProps {
     childClickScript?: ScriptField;
     childDoubleClickScript?: ScriptField;
     //TODO: [AL] add these fields
-    AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
-    RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+    AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+    RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
     hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views)
 }
 export interface CollectionViewProps extends React.PropsWithChildren<CollectionViewProps_> {}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 0d2968ba1..c8dc68eae 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -66,8 +66,8 @@ export interface TreeViewProps {
     skipFields?: string[];
     firstLevel: boolean;
     // TODO: [AL] add these
-    AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
-    RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[];
+    AddToMap?: (treeViewDoc: Doc, index: number[]) => void;
+    RemFromMap?: (treeViewDoc: Doc, index: number[]) => void;
     hierarchyIndex?: number[];
 }
 
@@ -1028,7 +1028,9 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     // renders the text version of a document as the header.  This is used in the file system mode and in other vanilla tree views.
     @computed get renderTitleAsHeader() {
-        return (
+        return this.props.treeView.Document.treeViewHideUnrendered && this.doc.unrendered && !this.doc.treeViewFieldKey ? (
+            <div></div>
+        ) : (
             <>
                 {this.renderBullet}
                 {this.renderTitle}
@@ -1146,8 +1148,8 @@ export class TreeView extends React.Component<TreeViewProps> {
         unobserveHeight: (ref: any) => void,
         contextMenuItems: { script: ScriptField; filter: ScriptField; label: string; icon: string }[],
         // TODO: [AL] add these
-        AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
-        RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[],
+        AddToMap?: (treeViewDoc: Doc, index: number[]) => void,
+        RemFromMap?: (treeViewDoc: Doc, index: number[]) => void,
         hierarchyIndex?: number[],
         renderCount?: number
     ) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b92a8ec4d..87177cd74 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -519,7 +519,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 this._clusterSets.push([doc]);
             } else if (this._clusterSets.length) {
                 for (let i = this._clusterSets.length; i <= doc.cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
-                this._clusterSets[doc.cluster].push(doc);
+                this._clusterSets[doc.cluster ?? 0].push(doc);
             }
         }
     }
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 2975e49ab..2d9aaeca4 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -9,7 +9,7 @@ import { InkField, InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { listSpec } from '../../../../fields/Schema';
-import { ScriptField } from '../../../../fields/ScriptField';
+import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { AudioField } from '../../../../fields/URLField';
 import { emptyFunction, emptyPath, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
@@ -35,6 +35,7 @@ import { FieldView, FieldViewProps } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
+import { tag } from 'xregexp/types';
 const { Howl } = require('howler');
 
 export interface pinDataTypes {
@@ -306,8 +307,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         this.activeItem.presIndexed = presIndexed + 1;
                     }
                     return true;
-                } else {
-                    console.log(this.activeItem.presIndexed);
                 }
             }
         };
@@ -359,8 +358,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         Doc.UnBrushAllDocs();
         if (index >= 0 && index < this.childDocs.length) {
             this.rootDoc._itemIndex = index;
-            const activeItem: Doc = this.activeItem;
-            const targetDoc: Doc = this.targetDoc;
             if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) {
                 DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
             }
@@ -368,8 +365,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 this.stopTempMedia(from.presentationTargetDoc);
             }
             // If next slide is audio / video 'Play automatically' then the next slide should be played
-            if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && activeItem.mediaStart === 'auto') {
-                this.startTempMedia(targetDoc, activeItem);
+            if (this.layoutDoc.presStatus !== PresStatus.Edit && (this.targetDoc.type === DocumentType.AUDIO || this.targetDoc.type === DocumentType.VID) && this.activeItem.mediaStart === 'auto') {
+                this.startTempMedia(this.targetDoc, this.activeItem);
             }
             if (!group) this.clearSelectedArray();
             this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array
@@ -755,17 +752,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const curDoc = Cast(doc, Doc, null);
             const tagDoc = PresBox.targetRenderedDoc(curDoc);
             const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc);
+            let opacity: Opt<number> = index === this.itemIndex ? 1 : undefined;
             if (curDoc.presHide) {
                 if (index !== this.itemIndex) {
-                    tagDoc.opacity = 1;
+                    opacity = 1;
                 }
             }
             const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement();
             if (curDoc.presHideBefore && index === hidingIndBef) {
                 if (index > this.itemIndex) {
-                    tagDoc.opacity = 0;
+                    opacity = 0;
                 } else if (index === this.itemIndex || !curDoc.presHideAfter) {
-                    tagDoc.opacity = 1;
+                    opacity = 1;
                     setTimeout(() => (tagDoc._dataTransition = undefined), 1000);
                 }
             }
@@ -776,17 +774,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     .find(item => item <= this.itemIndex) ?? itemIndexes.lastElement();
             if (curDoc.presHideAfter && index === hidingIndAft) {
                 if (index < this.itemIndex) {
-                    tagDoc.opacity = 0;
+                    opacity = 0;
                 } else if (index === this.itemIndex || !curDoc.presHideBefore) {
-                    tagDoc.opacity = 1;
+                    opacity = 1;
                 }
             }
             const hidingInd = itemIndexes.find(item => item === this.itemIndex);
             if (curDoc.presHide && index === hidingInd) {
                 if (index === this.itemIndex) {
-                    tagDoc.opacity = 0;
+                    opacity = 0;
                 }
             }
+            opacity !== undefined && (tagDoc.opacity = opacity);
         });
     };
 
@@ -882,7 +881,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (doc.presHideBefore && index > startIndex) tagDoc.opacity = 0;
             if (doc.presHideAfter && index < startIndex) tagDoc.opacity = 0;
             if (doc.presIndexed !== undefined && index >= startIndex) {
-                const type = DocCast(tagDoc.annotationOn)?.type ?? tagDoc.type;
                 const startInd = NumCast(doc.presIndexedStart);
                 this.progressivizedItems(doc)
                     ?.slice(startInd)
@@ -1552,6 +1550,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 const tagDoc = PresBox.targetRenderedDoc(this.activeItem);
                                 const type = DocCast(tagDoc?.annotationOn)?.type ?? tagDoc.type;
                                 activeItem.presIndexedStart = type === DocumentType.COL ? 1 : 0;
+                                // a progressivized slide doesn't have sub-slides, but rather iterates over the data list of the target being progressivized.
+                                // to avoid creating a new slide to correspond to each of the target's data list, we simply reference the target's data list.
+                                let dataField = Doc.LayoutFieldKey(tagDoc);
+                                if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '-annotations';
+
+                                if (DocCast(activeItem.presentationTargetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentationTargetDoc.annotationOn["${dataField}"]`);
+                                else activeItem.data = ComputedField.MakeFunction(`self.presentationTargetDoc["${dataField}"]`);
                             }}
                             checked={Cast(activeItem.presIndexed, 'number', null) !== undefined ? true : false}
                         />
@@ -2364,7 +2369,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         clearTimeout(this._presTimer);
     };
 
-    AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+    AddToMap = (treeViewDoc: Doc, index: number[]) => {
+        if (!treeViewDoc.presentationTargetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
         var indexNum = 0;
         for (let i = 0; i < index.length; i++) {
             indexNum += index[i] * 10 ** -i;
@@ -2377,15 +2383,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs
             }
         }
-        return this.childDocs;
     };
 
-    RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+    RemFromMap = (treeViewDoc: Doc, index: number[]) => {
+        if (!treeViewDoc.presentationTargetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
         if (!this._unmounting && this.isTree) {
             this._treeViewMap.delete(treeViewDoc);
             this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap));
         }
-        return this.childDocs;
     };
 
     sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);
-- 
cgit v1.2.3-70-g09d2


From 20c0190e820f2bd343693368b7ef55a91f19c880 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 14 Mar 2023 20:31:06 -0400
Subject: simplified Deserialize code.  streamlined currentUserUtils to need
 fewer compiled functions by parameterizing more functions.  .

---
 src/client/documents/Documents.ts                  |  13 +-
 src/client/util/CurrentUserUtils.ts                | 145 +++++------
 src/client/util/Scripting.ts                       |   4 +-
 src/client/util/SelectionManager.ts                |   7 +-
 src/client/util/SerializationHelper.ts             |  86 +------
 src/client/views/DashboardView.tsx                 |   1 -
 src/client/views/GestureOverlay.tsx                |   7 +-
 src/client/views/MainView.tsx                      |  20 --
 src/client/views/OverlayView.tsx                   |   1 +
 .../views/collections/CollectionDockingView.tsx    |  19 +-
 src/client/views/collections/CollectionView.tsx    |   2 +-
 src/client/views/collections/TreeView.tsx          |  10 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +
 src/client/views/nodes/WebBox.tsx                  |   8 +-
 src/client/views/nodes/button/FontIconBox.tsx      | 272 +++++++--------------
 src/fields/Doc.ts                                  |  57 ++---
 src/fields/ScriptField.ts                          |  31 +--
 src/fields/util.ts                                 |  22 +-
 18 files changed, 253 insertions(+), 454 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 251e432ef..457811e26 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -200,6 +200,8 @@ export class DocumentOptions {
     'icon-nativeWidth'?: NUMt = new NumInfo('native width of icon view');
     'icon-nativeHeight'?: NUMt = new NumInfo('native height of icon view');
     'dragFactory-count'?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)');
+    openFactoryLocation?: string; // an OpenWhere value to place the factory created document
+    openFactoryAsDelegate?: boolean; //
     lat?: number;
     lng?: number;
     infoWindowOpen?: boolean;
@@ -220,6 +222,8 @@ export class DocumentOptions {
     autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline.
     dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it.
     toolTip?: string; // tooltip to display on hover
+    toolType?: string; // type of pen tool
+    expertMode?: boolean; // something available only in expert (not novice) mode
     contextMenuFilters?: List<ScriptField>;
     contextMenuScripts?: List<ScriptField>;
     contextMenuLabels?: List<string>;
@@ -1353,6 +1357,8 @@ export namespace DocUtils {
                 const script = scripts[key];
                 if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) {
                     doc[key] = ScriptField.MakeScript(script, {
+                        self: Doc.name,
+                        this: Doc.name,
                         dragData: DragManager.DocumentDragData.name,
                         value: 'any',
                         _readOnly_: 'boolean',
@@ -1862,11 +1868,8 @@ export namespace DocUtils {
 }
 
 ScriptingGlobals.add('Docs', Docs);
-ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) {
-    return DocUtils.copyDragFactory(dragFactory);
-});
-ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) {
-    return DocUtils.delegateDragFactory(dragFactory);
+ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) {
+    return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory;
 });
 ScriptingGlobals.add(function makeDelegate(proto: any) {
     const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title });
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index c4fb4788c..2820c66ee 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -21,6 +21,7 @@ import { DashboardView } from "../views/DashboardView";
 import { Colors } from "../views/global/globalEnums";
 import { MainView } from "../views/MainView";
 import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
+import { OpenWhere } from "../views/nodes/DocumentView";
 import { OverlayView } from "../views/OverlayView";
 import { DragManager } from "./DragManager";
 import { MakeTemplate } from "./DropConverter";
@@ -40,6 +41,8 @@ interface Button {
     numBtnMax?: number;
     switchToggle?: boolean;
     width?: number;
+    toolType?: string; // type of pen tool
+    expertMode?: boolean;// available only in expert mode
     btnList?: List<string>;
     ignoreClick?: boolean;
     buttonText?: string;
@@ -78,7 +81,7 @@ export class CurrentUserUtils {
         ];
         const requiredTypes = requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => {
             const tempBtn =  DocListCast(tempDocs?.data)?.find(doc => doc.title === btnOpts.title);
-            const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory); }' };
+            const reqdScripts = { onDragStart:  '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }' };
             const assignBtnAndTempOpts = (templateBtn:Opt<Doc>, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => {
                 if (templateBtn) {
                     DocUtils.AssignOpts(templateBtn,btnOpts);
@@ -105,7 +108,7 @@ export class CurrentUserUtils {
         const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, system: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
             { opts: { title: "Open In Target",        targetScriptKey: "onChildClick"},       script: "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self])))"},
-            { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: "openOnRight(self.doubleClickView)"}];
+            { opts: { title: "Open Detail On Right",  targetScriptKey: "onChildDoubleClick"}, script: `openDoc(self.doubleClickView.${OpenWhere.addRight})`}];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
             const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
@@ -121,11 +124,11 @@ export class CurrentUserUtils {
         const tempClicks = DocCast(doc[field]);
         const reqdClickOpts:DocumentOptions = { _width: 300, _height:200, system: true};
         const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
-            { opts: { title: "onClick"},             script: "console.log( 'click')"},
-            { opts: { title: "onDoubleClick"},       script: "console.log( 'double click')"},
-            { opts: { title: "onChildClick"},        script: "console.log( 'child click')"},
-            { opts: { title: "onChildDoubleClick"},  script: "console.log( 'child double click')"},
-            { opts: { title: "onCheckedClick"},      script: "console.log( heading, checked, containingTreeView)"},
+            { opts: { title: "onClick"},             script: "console.log('click')"},
+            { opts: { title: "onDoubleClick"},       script: "console.log('click')"},
+            { opts: { title: "onChildClick"},        script: "console.log('click')"},
+            { opts: { title: "onChildDoubleClick"},  script: "console.log('click')"},
+            { opts: { title: "onCheckedClick"},      script: "console.log(heading, checked, containingTreeView)"},
         ];
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
@@ -183,7 +186,7 @@ export class CurrentUserUtils {
             const allopts = {system: true, ...opts};
             return DocUtils.AssignScripts(  (curIcon?.iconTemplate === opts.iconTemplate ? 
                       DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))),
-                {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView"});
+                {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)"});
         };
         const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({
             textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px",  _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
@@ -211,7 +214,7 @@ export class CurrentUserUtils {
     /// initalizes the set of "empty<DocType>" versions of each document type with default fields.  e.g.,. emptyNote, emptyTrail
     static creatorBtnDescriptors(doc: Doc): {
         title: string, toolTip: string, icon: string,  ignoreClick?: boolean, dragFactory?: Doc, 
-        backgroundColor?: string,  clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string},
+        backgroundColor?: string, openFactoryAsDelegate?:boolean, openFactoryLocation?:string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string},
     }[] {
         const standardOps = (key:string) => ({ title : "Untitled "+ key, _fitWidth: false, system: true, "dragFactory-count": 0, cloneFieldFilter: new List<string>(["system"]) });
         const json = {
@@ -257,13 +260,13 @@ export class CurrentUserUtils {
                           creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist
               }[] = [
             {key: "Note",       creator: opts => Docs.Create.TextDocument("", opts),        opts: { _width: 200, _autoHeight: true }},
-            {key: "Noteboard",  creator: opts => Docs.Create.NoteTakingDocument([], opts),  opts: { _width: 250, _height: 200 }},
+            {key: "Noteboard",  creator: opts => Docs.Create.NoteTakingDocument([], opts),  opts: { _width: 250, _height: 200, _fitWidth: true}},
             {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts),    opts: { _width: 150, _height: 100, _fitWidth: true }},
             {key: "Equation",   creator: opts => Docs.Create.EquationDocument(opts),        opts: { _width: 300, _height: 35, _backgroundGridShow: true, }},
             {key: "Webpage",    creator: opts => Docs.Create.WebDocument("",opts),          opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }},
             {key: "Comparison", creator: Docs.Create.ComparisonDocument,                    opts: { _width: 300, _height: 300 }},
             {key: "Audio",      creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }},
-            {key: "Map",        creator: opts => Docs.Create.MapDocument([], opts),         opts: { _width: 800, _height: 600, _showSidebar: true,  }},
+            {key: "Map",        creator: opts => Docs.Create.MapDocument([], opts),         opts: { _width: 800, _height: 600, _fitWidth: true, _showSidebar: true,  }},
             {key: "Screengrab", creator: Docs.Create.ScreenshotDocument,                    opts: { _width: 400, _height: 200 }},
             {key: "WebCam",     creator: opts => Docs.Create.WebCamDocument("", opts),      opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List<string>(["system"]) }},
             {key: "Button",     creator: Docs.Create.ButtonDocument,                        opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }},
@@ -282,30 +285,34 @@ export class CurrentUserUtils {
         emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, undefined, thing.funcs));
         
         return [
-            { toolTip: "Tap or drag to create a note",            title: "Note", icon: "sticky-note",   dragFactory: doc.emptyNote as Doc, },
-            { toolTip: "Tap or drag to create a note board",      title: "Notes", icon: "folder",       dragFactory: doc.emptyNoteboard as Doc, },
-            { toolTip: "Tap or drag to create a collection",      title: "Col", icon: "folder",         dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab),  scripts: { onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, },
-            { toolTip: "Tap or drag to create an equation",       title: "Math", icon: "calculator",    dragFactory: doc.emptyEquation as Doc, },
-            { toolTip: "Tap or drag to create a webpage",         title: "Web", icon: "globe-asia",     dragFactory: doc.emptyWebpage as Doc, },
-            { toolTip: "Tap or drag to create a comparison box",  title: "Compare", icon: "columns",    dragFactory: doc.emptyComparison as Doc, },
-            { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone",   dragFactory: doc.emptyAudio as Doc,       scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, },
-            { toolTip: "Tap or drag to create a map",             title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, },
-            { toolTip: "Tap or drag to create a screen grabber",  title: "Grab", icon: "photo-video",   dragFactory: doc.emptyScreengrab as Doc,  scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'} },
-            { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc,      scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'}},
-            { toolTip: "Tap or drag to create a button",          title: "Button", icon: "bolt",        dragFactory: doc.emptyButton as Doc,      funcs:   { hidden: 'IsNoviceMode()'} },
-            { toolTip: "Tap or drag to create a scripting box",   title: "Script", icon: "terminal",    dragFactory: doc.emptyScript as Doc,      funcs:   { hidden: 'IsNoviceMode()'}},
-            { toolTip: "Tap or drag to create a data viz node",   title: "DataViz", icon: "file",       dragFactory: doc.emptyDataViz as Doc, },
-            { toolTip: "Tap or drag to create a bullet slide",    title: "PPT Slide", icon: "file",     dragFactory: doc.emptySlide as Doc,       funcs:   { hidden: 'IsNoviceMode()'}},
-            { toolTip: "Tap or drag to create a data note",       title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc,scripts: { onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'},  },
-            { toolTip: "Toggle a Calculator REPL",                title: "repl", icon: "calculator",                                              scripts: { onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } },
-          ].map(tuple => ({scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, ...tuple, }))
+            { toolTip: "Tap or drag to create a note",            title: "Note", icon: "sticky-note",   dragFactory: doc.emptyNote as Doc,      clickFactory: DocCast(doc.emptyNote)},
+            { toolTip: "Tap or drag to create a note board",      title: "Notes", icon: "folder",       dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)},
+            { toolTip: "Tap or drag to create a collection",      title: "Col", icon: "folder",         dragFactory: doc.emptyCollection as Doc,clickFactory: DocCast(doc.emptyTab)},
+            { toolTip: "Tap or drag to create an equation",       title: "Math", icon: "calculator",    dragFactory: doc.emptyEquation as Doc,  clickFactory: DocCast(doc.emptyEquation)},
+            { toolTip: "Tap or drag to create a webpage",         title: "Web", icon: "globe-asia",     dragFactory: doc.emptyWebpage as Doc,   clickFactory: DocCast(doc.emptyWebpage)},
+            { toolTip: "Tap or drag to create a comparison box",  title: "Compare", icon: "columns",    dragFactory: doc.emptyComparison as Doc,clickFactory: DocCast(doc.emptyComparison)},
+            { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone",   dragFactory: doc.emptyAudio as Doc,     clickFactory: DocCast(doc.emptyAudio),      openFactoryLocation: OpenWhere.overlay},
+            { toolTip: "Tap or drag to create a map",             title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc,       clickFactory: DocCast(doc.emptyMap)},
+            { toolTip: "Tap or drag to create a screen grabber",  title: "Grab", icon: "photo-video",   dragFactory: doc.emptyScreengrab as Doc,clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay},
+            { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc,    clickFactory: DocCast(doc.emptyWebCam),     openFactoryLocation: OpenWhere.overlay},
+            { toolTip: "Tap or drag to create a button",          title: "Button", icon: "bolt",        dragFactory: doc.emptyButton as Doc,    clickFactory: DocCast(doc.emptyButton)},
+            { toolTip: "Tap or drag to create a scripting box",   title: "Script", icon: "terminal",    dragFactory: doc.emptyScript as Doc,    clickFactory: DocCast(doc.emptyScript)},
+            { toolTip: "Tap or drag to create a data viz node",   title: "DataViz", icon: "file",       dragFactory: doc.emptyDataViz as Doc,   clickFactory: DocCast(doc.emptyDataViz)},
+            { toolTip: "Tap or drag to create a bullet slide",    title: "PPT Slide", icon: "file",     dragFactory: doc.emptySlide as Doc,     clickFactory: DocCast(doc.emptySlide),      openFactoryLocation: OpenWhere.overlay},
+            { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc,    clickFactory: DocCast(doc.emptyHeader),     openFactoryAsDelegate: true },
+            { toolTip: "Toggle a Calculator REPL",                title: "repl", icon: "calculator",                                            clickFactory: "repl" as any,                openFactoryLocation: OpenWhere.overlay},
+          ].map(tuple => (
+              { openFactoryLocation: OpenWhere.addRight, 
+                scripts: { onClick:     'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)', 
+                           onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }'}, 
+                ...tuple, }))
     }
 
     /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
     static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc {
         const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
             const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
-            const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit,
+            const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, 
                 _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, 
                 btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true,
                 _removeDropProperties: new List<string>(["_stayInCollection"]),
@@ -603,53 +610,50 @@ export class CurrentUserUtils {
 
     static textTools():Button[] {
         return [
-            { title: "Font",    toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,               ignoreClick: true, scripts: {script: 'setFont(value, _readOnly_)'},
+            { title: "Font",    toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},
               btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
-            { title: "Size",    toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton,               ignoreClick: true, scripts: {script:  '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions },
-            { title: "Color",   toolTip: "Font color",           btnType: ButtonType.ColorButton,  icon: "font", ignoreClick: true, scripts: {script:  '{ return setFontColor(value, _readOnly_);}'}},
-            { title: "Highlight",toolTip:"Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", ignoreClick: true, scripts: {script:  '{ return setFontHighlight(value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
-            { title: "Bold",    toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",                    scripts: {onClick: '{ return toggleBold(_readOnly_); }'} },
-            { title: "Italic",  toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",                  scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} },
-            { title: "Under",   toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",               scripts: {onClick: '{ return toggleUnderline(_readOnly_);}'} },
-            { title: "Bullets", toolTip: "Bullet List",          btnType: ButtonType.ToggleButton, icon: "list",                    scripts: {onClick: '{ return setBulletList("bullet", _readOnly_);}'} },
-            { title: "#",       toolTip: "Number List",          btnType: ButtonType.ToggleButton, icon: "list-ol",                 scripts: {onClick: '{ return setBulletList("decimal", _readOnly_);}'} },
-
+            { title: "Size",    toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton,                      toolType:"fontSize", ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions },
+            { title: "Color",   toolTip: "Font color",           btnType: ButtonType.ColorButton,  icon: "font",        toolType:"fontColor",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'}},
+            { title: "Highlight",toolTip:"Font highlight",       btnType: ButtonType.ColorButton,  icon: "highlighter", toolType:"highlight",ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
+            { title: "Bold",    toolTip: "Bold (Ctrl+B)",        btnType: ButtonType.ToggleButton, icon: "bold",        toolType:"bold",                          scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Italic",  toolTip: "Italic (Ctrl+I)",      btnType: ButtonType.ToggleButton, icon: "italic",      toolType:"italics",                       scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Under",   toolTip: "Underline (Ctrl+U)",   btnType: ButtonType.ToggleButton, icon: "underline",   toolType:"underline",                     scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Bullets", toolTip: "Bullet List",          btnType: ButtonType.ToggleButton, icon: "list",        toolType:"bullet",                        scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "#",       toolTip: "Number List",          btnType: ButtonType.ToggleButton, icon: "list-ol",     toolType:"decimal",                       scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Left",    toolTip: "Left align",           btnType: ButtonType.ToggleButton, icon: "align-left",  toolType:"left",                          scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
+            { title: "Center",  toolTip: "Center align",         btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",                        scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Right",   toolTip: "Right align",          btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right",                         scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+            { title: "Dictate", toolTip: "Dictate",              btnType: ButtonType.ToggleButton, icon: "microphone",  toolType:"dictation",                     scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
+            { title: "NoLink",  toolTip: "Auto Link",            btnType: ButtonType.ToggleButton, icon: "link",        toolType:"noAutoLink", expertMode:true,   scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
             // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
             // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}},
             // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}},
-            { title: "Left",   toolTip: "Left align",   btnType: ButtonType.ToggleButton, icon: "align-left",   scripts: {onClick:'{ return setAlignment("left", _readOnly_);}' }},
-            { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", scripts: {onClick:'{ return setAlignment("center", _readOnly_);}'} },
-            { title: "Right",  toolTip: "Right align",  btnType: ButtonType.ToggleButton, icon: "align-right",  scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} },
-            { title: "NoLink", toolTip: "Auto Link",    btnType: ButtonType.ToggleButton, icon: "link",         scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}},
-            { title: "Dictate",toolTip: "Dictate",      btnType: ButtonType.ToggleButton, icon: "microphone",   scripts: {onClick:'{ return toggleDictation(_readOnly_);}'}},
-        ];
+             ];
     }
 
     static inkTools():Button[] {
         return [
-            { title: "Pen",    toolTip: "Pen (Ctrl+P)",          btnType: ButtonType.ToggleButton, icon: "pen-nib",                     scripts: {onClick:'{ return setActiveTool("pen", false, _readOnly_);}' }},
-            { title: "Write",  toolTip: "Write (Ctrl+Shift+P)",  btnType: ButtonType.ToggleButton, icon: "pen",                         scripts: {onClick:'{ return setActiveTool("write", false, _readOnly_);}'} },
-            { title: "Eraser", toolTip: "Eraser (Ctrl+E)",       btnType: ButtonType.ToggleButton, icon: "eraser",                      scripts: {onClick:'{ return setActiveTool("eraser", false, _readOnly_);}' }},
-            // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} },
-            { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",           scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} },
-            { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",           scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} },
-            { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",            scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _readOnly_);}`} },
-            { title: "Mask",   toolTip: "Mask",                  btnType: ButtonType.ToggleButton, icon: "user-circle",                 scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} },
-            { title: "Fill",   toolTip: "Fill color",            btnType: ButtonType.ColorButton,  icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} },
-            { title: "Width",  toolTip: "Stroke width",          btnType: ButtonType.NumberButton,                   ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1},
-            { title: "Color",  toolTip: "Stroke color",          btnType: ButtonType.ColorButton,  icon: "pen",      ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} },
+            { title: "Pen",    toolTip: "Pen (Ctrl+P)",                     btnType: ButtonType.ToggleButton, icon: "pen-nib",    toolType: "pen",                            scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }},
+            { title: "Write",  toolTip: "Write (Ctrl+Shift+P)",             btnType: ButtonType.ToggleButton, icon: "pen",        toolType: "write",                          scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}'} },
+            { title: "Eraser", toolTip: "Eraser (Ctrl+E)",                  btnType: ButtonType.ToggleButton, icon: "eraser",     toolType: "eraser",                         scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }},
+            { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle",     toolType:GestureUtils.Gestures.Circle,      scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
+            { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square",     toolType:GestureUtils.Gestures.Rectangle,   scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
+            { title: "Line",   toolTip: "Line (double tap to lock mode)",   btnType: ButtonType.ToggleButton, icon: "minus",      toolType:GestureUtils.Gestures.Line,        scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} },
+            { title: "Mask",   toolTip: "Mask",                             btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask",                        scripts: {onClick:'{ return setInkProperty(self.toolType, value, _readOnly_);}'} },
+            { title: "Fill",   toolTip: "Fill color",                       btnType: ButtonType.ColorButton,  icon: "fill-drip",  toolType: "fillColor",   ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'} },
+            { title: "Width",  toolTip: "Stroke width",                     btnType: ButtonType.NumberButton,                     toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1},
+            { title: "Color",  toolTip: "Stroke color",                     btnType: ButtonType.ColorButton,  icon: "pen",        toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'} },
         ];
     }
 
     static schemaTools():Button[] {
-        return [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{return toggleSchemaPreview(_readOnly_);}'}, }];
+        return [{ title: "Show preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'}, }];
     }
 
     static webTools() {
         return [
             { title: "Back",    toolTip: "Go back",         btnType: ButtonType.ClickButton,  icon: "arrow-left",              scripts: { onClick: '{ return webBack(_readOnly_); }' }},
             { title: "Forward", toolTip: "Go forward",      btnType: ButtonType.ClickButton,  icon: "arrow-right",             scripts: { onClick: '{ return webForward(_readOnly_); }'}},
-            //{ title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' },
             { title: "URL",     toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
         ];
     }
@@ -661,18 +665,18 @@ export class CurrentUserUtils {
                     CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
                     CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
                     CollectionViewType.Grid, CollectionViewType.NoteTaking]),
-              title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                    ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
-            { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'},                        width: 20,  scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}},
-            { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
-            { title: "Num",icon: "",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton,          funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
-            { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
-            { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true, width: 20,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
-            { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         funcs: {hidden: '!SelectionManager_selectedDocType()'},                     ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}},
-            { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform", true)'},                         scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform
-            { title: "Text",    icon: "Text",          toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   funcs: {hidden: 'false',                                                                        linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.RTF}")`} }, // Always available
-            { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    funcs: {hidden: 'false',                                                                        linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.INK}")`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
-            { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    funcs: {hidden: `!SelectionManager_selectedDocType("${DocumentType.WEB}")`,                     linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.WEB}")`,                     } }, // Only when Web is selected
-            { title: "Schema",  icon: "Schema",        toolTip: "Schema functions",     subMenu: CurrentUserUtils.schemaTools(), funcs: {hidden: `!SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`, linearViewIsExpanded: `SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected
+              title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                                                                    ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
+            { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,         expertMode: false, toolType:"tab",                       funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, width: 20,  scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}},
+            { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         expertMode: false, toolType:"tab",                                                                                          ignoreClick: true, width: 20,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
+            { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         expertMode: false, toolType:"tab",                                                                                          ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}},
+            { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'},       scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform
+            { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+            { title: "Num", icon:"",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton,          expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
+            { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
+            { title: "Text",    icon: "Text",          toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                                             linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
+            { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    expertMode: false, toolType:DocumentType.INK,            funcs: {                                                                             linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
+            { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
+            { title: "Schema",  icon: "Schema",        toolTip: "Schema functions",     subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema,   funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected
         ];
     }
 
@@ -684,6 +688,7 @@ export class CurrentUserUtils {
             color: Colors.WHITE, system: true, dontUndo: true, 
             _nativeWidth: params.width ?? 30, _width: params.width ?? 30,
             _height: 30, _nativeHeight: 30, 
+            toolType: params.toolType, expertMode: params.expertMode,
             _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true,
             _removeDropProperties: new List<string>([ "_stayInCollection"]),
         };
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 6dcdcb71b..d32298c83 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -248,8 +248,10 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
         ScriptingGlobals.resetScriptingGlobals();
     }
     !signature.includes('XXX') && ScriptField._scriptFieldCache.set(script + ':' + signature, result as CompiledScript);
-    //console.log('COMPILED: ' + script + ':' + signature);
     return result;
 }
 
 ScriptingGlobals.add(CompileScript);
+ScriptingGlobals.add(function runScript(self: Doc, script: ScriptField) {
+    return script?.script.run({ this: self, self: self }).result;
+});
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index c0fc25376..0f4f77588 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -104,10 +104,11 @@ export namespace SelectionManager {
         return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
     }
 }
-ScriptingGlobals.add(function SelectionManager_selectedDocType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) {
-    if (colType === ('tab' as any)) {
+ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
+    if (Doc.noviceMode && expertMode) return false;
+    if (type === 'tab') {
         return SelectionManager.Views().lastElement()?.props.renderDepth === 0;
     }
     let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
-    return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true;
+    return selected?.type === type || selected?.viewType === type || !type;
 });
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 2d1f61cfb..76037a7e9 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -44,12 +44,8 @@ export namespace SerializationHelper {
         }
 
         if (!obj.__type) {
-            if (true || ClientUtils.RELEASE) {
-                console.warn("No property 'type' found in JSON.");
-                return undefined;
-            } else {
-                throw Error("No property 'type' found in JSON.");
-            }
+            console.warn("No property 'type' found in JSON.");
+            return undefined;
         }
 
         if (!(obj.__type in serializationTypes)) {
@@ -58,9 +54,8 @@ export namespace SerializationHelper {
 
         const type = serializationTypes[obj.__type];
         const value = await new Promise(res => deserialize(type.ctor, obj, (err, result) => res(result)));
-        if (type.afterDeserialize) {
-            type.afterDeserialize(value);
-        }
+        type.afterDeserialize?.(value);
+
         return value;
     }
 }
@@ -68,75 +63,20 @@ export namespace SerializationHelper {
 const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
 const reverseMap: { [ctor: string]: string } = {};
 
-export interface DeserializableOpts {
-    (constructor: { new (...args: any[]): any }): void;
-    withFields(fields: string[]): Function;
-}
-
-export function Deserializable(name: string, afterDeserialize?: (obj: any) => void | Promise<any>): DeserializableOpts;
-export function Deserializable(constructor: { new (...args: any[]): any }): void;
-export function Deserializable(constructor: { new (...args: any[]): any } | string, afterDeserialize?: (obj: any) => void): DeserializableOpts | void {
-    function addToMap(name: string, ctor: { new (...args: any[]): any }) {
+export function Deserializable(className: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
+    function addToMap(className: string, ctor: { new (...args: any[]): any }) {
         const schema = getDefaultModelSchema(ctor) as any;
-        if (schema.targetClass !== ctor) {
-            const newSchema = { ...schema, factory: () => new ctor() };
-            setDefaultModelSchema(ctor, newSchema);
+        if (schema.targetClass !== ctor || constructorArgs) {
+            setDefaultModelSchema(ctor, { ...schema, factory: (context: any) => new ctor(...(constructorArgs ?? [])?.map(arg => context.json[arg])) });
         }
-        if (!(name in serializationTypes)) {
-            serializationTypes[name] = { ctor, afterDeserialize };
-            reverseMap[ctor.name] = name;
+        if (!(className in serializationTypes)) {
+            serializationTypes[className] = { ctor, afterDeserialize };
+            reverseMap[ctor.name] = className;
         } else {
-            throw new Error(`Name ${name} has already been registered as deserializable`);
+            throw new Error(`Name ${className} has already been registered as deserializable`);
         }
     }
-    if (typeof constructor === 'string') {
-        return Object.assign(
-            (ctor: { new (...args: any[]): any }) => {
-                addToMap(constructor, ctor);
-            },
-            { withFields: (fields: string[]) => Deserializable.withFields(fields, constructor, afterDeserialize) }
-        );
-    }
-    addToMap(constructor.name, constructor);
-}
-
-export namespace Deserializable {
-    export function withFields(fields: string[], name?: string, afterDeserialize?: (obj: any) => void | Promise<any>) {
-        return function (constructor: { new (...fields: any[]): any }) {
-            Deserializable(name || constructor.name, afterDeserialize)(constructor);
-            let schema = getDefaultModelSchema(constructor);
-            if (schema) {
-                schema.factory = context => {
-                    const args = fields.map(key => context.json[key]);
-                    return new constructor(...args);
-                };
-                // TODO A modified version of this would let us not reassign fields that we're passing into the constructor later on in deserializing
-                // fields.forEach(field => {
-                //     if (field in schema.props) {
-                //         let propSchema = schema.props[field];
-                //         if (propSchema === false) {
-                //             return;
-                //         } else if (propSchema === true) {
-                //             propSchema = primitive();
-                //         }
-                //         schema.props[field] = custom(propSchema.serializer,
-                //             () => {
-                //                 return SKIP;
-                //             });
-                //     }
-                // });
-            } else {
-                schema = {
-                    props: {},
-                    factory: context => {
-                        const args = fields.map(key => context.json[key]);
-                        return new constructor(...args);
-                    },
-                };
-                setDefaultModelSchema(constructor, schema);
-            }
-        };
-    }
+    return (ctor: { new (...args: any[]): any }) => addToMap(className, ctor);
 }
 
 export function autoObject(): PropSchema {
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index 7ebe8d0e3..2b586b0e2 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -233,7 +233,6 @@ export class DashboardView extends React.Component {
     /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url)
     public static openDashboard = (doc: Doc | undefined, fromHistory = false) => {
         if (!doc) return false;
-        Doc.MainDocId = doc[Id];
         Doc.AddDocToList(Doc.MyDashboards, 'data', doc);
 
         // this has the side-effect of setting the main container since we're assigning the active/guest dashboard
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 6058eaaf9..0feccb742 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -94,7 +94,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
     }
 
     static setupThumbButtons(doc: Doc) {
-        const docProtoData: { title: string; icon: string; drag?: string; ignoreClick?: boolean; pointerDown?: string; pointerUp?: string; clipboard?: Doc; backgroundColor?: string; dragFactory?: Doc }[] = [
+        const docProtoData: { title: string; icon: string; drag?: string; toolType?: string; ignoreClick?: boolean; pointerDown?: string; pointerUp?: string; clipboard?: Doc; backgroundColor?: string; dragFactory?: Doc }[] = [
             { title: 'use pen', icon: 'pen-nib', pointerUp: 'resetPen()', pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: 'blue' },
             { title: 'use highlighter', icon: 'highlighter', pointerUp: 'resetPen()', pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: 'yellow' },
             {
@@ -105,8 +105,8 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }),
                 backgroundColor: 'orange',
             },
-            { title: 'interpret text', icon: 'font', pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: 'orange' },
-            { title: 'ignore gestures', icon: 'signature', pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: 'green' },
+            { title: 'interpret text', icon: 'font', toolType: 'inktotext', pointerUp: "setToolglass('none')", pointerDown: 'setToolglass(self.toolType)', backgroundColor: 'orange' },
+            { title: 'ignore gestures', icon: 'signature', toolType: 'ignoregesture', pointerUp: "setToolglass('none')", pointerDown: 'setToolglass(self.toolType)', backgroundColor: 'green' },
         ];
         return docProtoData.map(data =>
             Docs.Create.FontIconDocument({
@@ -116,6 +116,7 @@ export class GestureOverlay extends Touchable<GestureOverlayProps> {
                 _height: 10,
                 title: data.title,
                 icon: data.icon,
+                toolType: data.toolType,
                 _dropAction: data.pointerDown ? 'copy' : undefined,
                 ignoreClick: data.ignoreClick,
                 onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 945cd61db..2e04ca3dd 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -230,8 +230,6 @@ export class MainView extends React.Component {
         if (window.location.pathname !== '/home') {
             const pathname = window.location.pathname.substr(1).split('/');
             if (pathname.length > 1 && pathname[0] === 'doc') {
-                Doc.MainDocId = pathname[1];
-                //!this.userDoc &&
                 DocServer.GetRefField(pathname[1]).then(
                     action(field => {
                         if (field instanceof Doc && field._viewType !== CollectionViewType.Docking) {
@@ -482,7 +480,6 @@ export class MainView extends React.Component {
                 fa.faHandPointUp,
             ]
         );
-        this.initAuthenticationRouters();
     }
 
     globalPointerDown = action((e: PointerEvent) => {
@@ -521,23 +518,6 @@ export class MainView extends React.Component {
         document.oncontextmenu = () => false;
     };
 
-    initAuthenticationRouters = async () => {
-        const received = Doc.MainDocId;
-        if (received && !this.userDoc) {
-            reaction(
-                () => Doc.GuestTarget,
-                target => target && DashboardView.createNewDashboard(),
-                { fireImmediately: true }
-            );
-        }
-        // else {
-        //     PromiseValue(this.userDoc.activeDashboard).then(dash => {
-        //         if (dash instanceof Doc) DashboardView.openDashboard(dash);
-        //         else Doc.createNewDashboard();
-        //     });
-        // }
-    };
-
     @action
     createNewPresentation = () => {
         const pres = Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true);
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 08285ff0c..34e8cd6dd 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -255,4 +255,5 @@ export class OverlayView extends React.Component {
 // bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error
 ScriptingGlobals.add(function addOverlayWindow(type: string, options: OverlayElementOptions) {
     OverlayView.Instance.addWindow(<ScriptingRepl />, options);
+    addOverlayWindow('ScriptingRepl', { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
 });
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1ead80bd0..9b6554d67 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -26,6 +26,9 @@ import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import { TabDocView } from './TabDocView';
 import React = require('react');
 import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { OverlayView } from '../OverlayView';
+import { ScriptingRepl } from '../ScriptingRepl';
+import { ScriptField } from '../../../fields/ScriptField';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -580,15 +583,21 @@ ScriptingGlobals.add(
     '(doc: any)'
 );
 ScriptingGlobals.add(
-    function openOnRight(doc: any) {
-        return CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
+    function openDoc(doc: any, where: OpenWhere) {
+        switch (where) {
+            case OpenWhere.addRight:
+                return CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
+            case OpenWhere.overlay:
+                if (doc === 'repl') OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' });
+                else Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+        }
     },
-    'opens up document in tab on right side of the screen',
+    'opens up document in location specified',
     '(doc: any)'
 );
 ScriptingGlobals.add(
-    function openInOverlay(doc: any) {
-        return Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+    function openRepl() {
+        return 'openRepl';
     },
     'opens up document in screen overlay layer',
     '(doc: any)'
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 48e5748a0..aed88aa1a 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -169,7 +169,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
 
     onContextMenu = (e: React.MouseEvent): void => {
         const cm = ContextMenu.Instance;
-        if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== Doc.MainDocId) {
+        if (cm && !e.isPropagationStopped()) {
             // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             this.setupViewTypes(
                 'UI Controls...',
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 2bdcf472f..af2d148e0 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
+import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
 import { RichTextField } from '../../../fields/RichTextField';
@@ -172,11 +172,7 @@ export class TreeView extends React.Component<TreeViewProps> {
 
     childDocList(field: string) {
         const layout = Cast(Doc.LayoutField(this.doc), Doc, null);
-        return (
-            (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field
-            (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields
-            DocListCastOrNull(this.doc[field])
-        ); // otherwise use the document's data field
+        return DocListCast(this.props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.doc[field])));
     }
     moving: boolean = false;
     @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
@@ -759,7 +755,7 @@ export class TreeView extends React.Component<TreeViewProps> {
         const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' };
         const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'Delete' };
         const folderOp = this.childDocs?.length ? [makeFolder] : [];
-        const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: 'copy', label: 'Open Alias' };
+        const openAlias = { script: ScriptField.MakeFunction(`openDoc(getAlias(self), ${OpenWhere.addRight})`)!, icon: 'copy', label: 'Open Alias' };
         const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' };
         return [
             ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 964231e9d..02af30d0c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -80,6 +80,7 @@ export enum OpenWhere {
     replaceLeft = 'replace:left',
     inParent = 'inParent',
     inParentFromScreen = 'inParentFromScreen',
+    overlay = 'overlay',
 }
 export enum OpenWhereMod {
     none = '',
@@ -612,6 +613,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                                 altKey,
                                 shiftKey,
                                 ctrlKey,
+                                value: undefined,
                             },
                             console.log
                         );
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index b1acef44a..f7425b26a 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -145,6 +145,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
         const nativeHeight = (nativeWidth * this.props.PanelHeight()) / this.props.PanelWidth();
         if (
+            !this.props.isSelected(true) &&
+            !Doc.IsBrushedDegree(this.rootDoc) &&
+            !this.isAnyChildContentActive() &&
             !this.rootDoc.thumbLockout &&
             !this.props.dontRegisterView &&
             this._iframe &&
@@ -181,6 +184,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 });
         }
     };
+    _thumbTimer: any;
     async componentDidMount() {
         this.props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document.  this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
 
@@ -197,6 +201,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             () => this.props.isSelected(true) || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document),
             async selected => {
                 if (selected) {
+                    this._thumbTimer && clearTimeout(this._thumbTimer);
                     this._webPageHasBeenRendered = true;
                 } else if (
                     (!this.props.isContentActive(true) || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail)
@@ -204,7 +209,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                     LightboxView.LightboxDoc !== this.rootDoc
                 ) {
                     // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty.
-                    this.updateThumb();
+                    this._thumbTimer && clearTimeout(this._thumbTimer);
+                    this._thumbTimer = setTimeout(this.updateThumb, 2000);
                 }
             },
             { fireImmediately: this.props.isSelected(true) || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegreeUnmemoized(this.props.Document) ? true : false) }
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 0468d1c47..b3a3c3ae4 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,4 +1,5 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { faAlignRight } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { action, computed, observable, runInAction } from 'mobx';
@@ -11,7 +12,7 @@ import { ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { WebField } from '../../../../fields/URLField';
 import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, StopEvent, Utils } from '../../../../Utils';
+import { aggregateBounds, Utils } from '../../../../Utils';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { LinkManager } from '../../../util/LinkManager';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -134,10 +135,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     @computed get numberButton() {
         const numBtnType: string = StrCast(this.rootDoc.numBtnType);
         const numScript = ScriptCast(this.rootDoc.script);
-        const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ value, _readOnly_: false }), 'set num value');
+        const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ self: this.rootDoc, value, _readOnly_: false }), 'set num value');
 
         // Script for checking the outcome of the toggle
-        const checkResult = Number(numScript?.script.run({ value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3));
+        const checkResult = Number(numScript?.script.run({ self: this.rootDoc, value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3));
 
         const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>;
 
@@ -150,7 +151,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                         min={NumCast(this.rootDoc.numBtnMin, 0)}
                         max={NumCast(this.rootDoc.numBtnMax, 100)}
                         value={checkResult}
-                        className={'menu-slider'}
+                        className="menu-slider"
                         onPointerDown={() => (this._batch = UndoManager.StartBatch('presDuration'))}
                         onPointerUp={() => this._batch?.end()}
                         onChange={e => {
@@ -284,27 +285,23 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                     text = 'User Default';
                 }
                 noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
-            } else if (script?.script.originalScript.startsWith('setFont')) {
-                const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-                text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
-                noviceList = ['Roboto', 'Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text'];
-            }
+            } else text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
         } catch (e) {
             console.log(e);
         }
 
         // Get items to place into the list
         const list = this.buttonList
-            .filter(value => !Doc.noviceMode || noviceList.includes(value))
+            .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value))
             .map(value => (
                 <div
                     className="list-item"
                     key={`${value}`}
                     style={{
-                        fontFamily: script.script.originalScript.startsWith('setFont') ? value : undefined,
+                        fontFamily: script.script.originalScript.startsWith('{ return setFont') ? value : undefined,
                         backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined,
                     }}
-                    onClick={undoBatch(() => script.script.run({ value }))}>
+                    onClick={undoBatch(() => script.script.run({ self: this.rootDoc, value }))}>
                     {value[0].toUpperCase() + value.slice(1)}
                 </div>
             ));
@@ -357,7 +354,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             ev.preventDefault();
             ev.stopPropagation();
             const s = this.colorScript;
-            s && undoBatch(() => s.script.run({ value: Utils.colorString(value), _readOnly_: false }).result)();
+            s && undoBatch(() => s.script.run({ self: this.rootDoc, value: Utils.colorString(value), _readOnly_: false }).result)();
         };
         const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent'];
         return <SketchPicker onChange={change as any /* SketchPicker passes the mouse event to the callback, but the type system doesn't know that */} color={curColor} presetColors={presets} />;
@@ -368,7 +365,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
     @computed get colorButton() {
         const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
         const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
-        const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? 'transparent';
+        const curColor = this.colorScript?.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent';
 
         const label =
             !this.label || !FontIconBox.GetShowLabels() ? null : (
@@ -377,13 +374,6 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 </div>
             );
 
-        // dropdown caret seems superfluous since clicking the color button does the same thing
-        // const dropdownCaret = <div
-        //     className="menuButton-dropDown"
-        //     style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
-        //     <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
-        // </div>;
-        //setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView
         return (
             <div
                 className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')} ${this.colorPickerClosed}`}
@@ -598,121 +588,70 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
 });
 
-/** TEXT
- *      setFont
- *      setFontSize
- *      toggleBold
- *      toggleUnderline
- *      toggleItalic
- *      setAlignment
- *      toggleBold
- *      toggleItalic
- *      toggleUnderline
- **/
-
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setFont(font: string, checkResult?: boolean) {
-    if (checkResult) return RichTextMenu.Instance?.fontFamily;
-    font && RichTextMenu.Instance.setFontFamily(font);
-});
-
-ScriptingGlobals.add(function getActiveTextInfo(info: 'family' | 'size' | 'color' | 'highlight') {
-    const editorView = RichTextMenu.Instance.TextView?.EditorView;
-    const style = editorView?.state && RichTextMenu.Instance.getActiveFontStylesOnSelection();
-    // prettier-ignore
-    switch (info) {
-        case 'family':    return style?.activeFamilies[0];
-        case 'size':      return style?.activeSizes[0];
-        case 'color':     return style?.activeColors[0];
-        case 'highlight': return style?.activeHighlights[0];
-    }
-});
-
-ScriptingGlobals.add(function setAlignment(align: 'left' | 'right' | 'center', checkResult?: boolean) {
-    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-    if (checkResult) {
-        return (editorView ? RichTextMenu.Instance.textAlign : Doc.UserDoc().textAlign) === align ? Colors.MEDIUM_BLUE : 'transparent';
-    }
-    if (editorView?.state) RichTextMenu.Instance.align(editorView, editorView.dispatch, align);
-    else Doc.UserDoc().textAlign = align;
-});
-
-ScriptingGlobals.add(function setBulletList(mapStyle: 'bullet' | 'decimal', checkResult?: boolean) {
+ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize', value: any, checkResult?: boolean) {
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-    if (checkResult) {
-        const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle();
-        if (active === mapStyle) return Colors.MEDIUM_BLUE;
-        return 'transparent';
-    }
-    editorView?.state && RichTextMenu.Instance.changeListType(mapStyle);
-});
-
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setFontColor(color?: string, checkResult?: boolean) {
-    if (checkResult) return RichTextMenu.Instance.fontColor;
-    color && RichTextMenu.Instance.setColor(color);
-});
-
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setFontHighlight(color?: string, checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
-    const editorView = RichTextMenu.Instance.TextView?.EditorView;
+    // prettier-ignore
+    const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void; setMode?: () => void }> = new Map([
+        ['font', {
+            checkResult: () => RichTextMenu.Instance?.fontFamily,
+            setDoc: () => value && RichTextMenu.Instance.setFontFamily(value),
+            setMode: () => Doc.UserDoc().textAlign = value,
+        }],
+        ['highlight', {
+            checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight,
+            setDoc: () => value  && RichTextMenu.Instance.setHighlight(value),
+        }],
+        ['fontColor', {
+            checkResult: () => RichTextMenu.Instance?.fontColor,
+            setDoc: () => value && RichTextMenu.Instance.setColor(value),
+        }],
+        ['fontSize', {
+            checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''),
+            setDoc: () => {
+                if (typeof value === 'number') value = value.toString();
+                if (value && Number(value).toString() === value) value += 'px';
+                RichTextMenu.Instance.setFontSize(value);
+            },
+        }],
+    ]);
 
     if (checkResult) {
-        return (selected ?? Doc.UserDoc())._fontHighlight;
+        return map.get(attr)?.checkResult();
     }
-    color && RichTextMenu.Instance.setHighlight(color);
+    if (editorView?.state) map.get(attr)?.setDoc();
+    else map.get(attr)?.setMode?.();
 });
 
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) {
-    if (checkResult) {
-        return RichTextMenu.Instance?.fontSize.replace('px', '');
-    }
-    if (typeof size === 'number') size = size.toString();
-    if (size && Number(size).toString() === size) size += 'px';
-    RichTextMenu.Instance.setFontSize(size);
-});
-ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) {
-    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-    if (checkResult) {
-        return (editorView ? RichTextMenu.Instance.noAutoLink : false) ? Colors.MEDIUM_BLUE : 'transparent';
-    }
-    if (editorView) RichTextMenu.Instance?.toggleNoAutoLinkAnchor();
-});
-ScriptingGlobals.add(function toggleDictation(checkResult?: boolean) {
+type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal';
+type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }];
+ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
     const textView = RichTextMenu.Instance?.TextView;
-    if (checkResult) {
-        return textView?._recording ? Colors.MEDIUM_BLUE : 'transparent';
-    }
-    if (textView) runInAction(() => (textView._recording = !textView._recording));
-});
-
-ScriptingGlobals.add(function toggleBold(checkResult?: boolean) {
-    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-    if (checkResult) {
-        return (editorView ? RichTextMenu.Instance.bold : Doc.UserDoc().fontWeight === 'bold') ? Colors.MEDIUM_BLUE : 'transparent';
-    }
-    if (editorView) RichTextMenu.Instance?.toggleBold();
-    else Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold';
-});
-
-ScriptingGlobals.add(function toggleUnderline(checkResult?: boolean) {
-    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-    if (checkResult) {
-        return (editorView ? RichTextMenu.Instance.underline : Doc.UserDoc().textDecoration === 'underline') ? Colors.MEDIUM_BLUE : 'transparent';
-    }
-    if (editorView) RichTextMenu.Instance?.toggleUnderline();
-    else Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline';
-});
-
-ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) {
-    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-    if (checkResult) {
-        return (editorView ? RichTextMenu.Instance.italics : Doc.UserDoc().fontStyle === 'italics') ? Colors.MEDIUM_BLUE : 'transparent';
-    }
-    if (editorView) RichTextMenu.Instance?.toggleItalics();
-    else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics';
+    const editorView = textView?.EditorView;
+    // prettier-ignore
+    const alignments:attrfuncs[] = (['left','right','center'] as ("left"|"center"|"right")[]).map((where) =>
+        [ where, {      checkResult: () =>(editorView ? (RichTextMenu.Instance.textAlign ===where): (Doc.UserDoc().textAlign ===where) ? true:false),
+                        toggle: () => (editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, where):(Doc.UserDoc().textAlign = where))}]);
+    // prettier-ignore
+    const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
+        [ list, {       checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false),
+                        toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]);
+    // prettier-ignore
+    const attrs:attrfuncs[] = [
+        ['dictation', { checkResult: () => textView?._recording ? true:false,
+                        toggle: () => textView && runInAction(() => (textView._recording = !textView._recording)) }],
+        ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false),
+                        toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
+        ['bold',      { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
+                        toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
+        ['italics',   { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
+                        toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
+        ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false),
+                        toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
+
+    const map = new Map(attrs.concat(alignments).concat(listings));
+    if (checkResult) return map.get(charStyle)?.checkResult() ? Colors.MEDIUM_BLUE : 'transparent';
+    map.get(charStyle)?.toggle();
 });
 
 export function checkInksToGroup() {
@@ -832,62 +771,39 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean,
 ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');
 
 // toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setIsInkMask(checkResult?: boolean) {
-    const selected = SelectionManager.Docs().lastElement();
-    if (checkResult) {
-        if (selected?.type === DocumentType.INK) {
-            return BoolCast(selected.isInkMask) ? Colors.MEDIUM_BLUE : 'transparent';
-        }
-        return ActiveIsInkMask() ? Colors.MEDIUM_BLUE : 'transparent';
-    }
-    SetActiveIsInkMask(!ActiveIsInkMask());
-    SelectionManager.Docs()
-        .filter(doc => doc.type === DocumentType.INK)
-        .map(doc => (doc.isInkMask = !doc.isInkMask));
-});
-
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setFillColor(color?: string, checkResult?: boolean) {
+ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
-    if (checkResult) {
-        if (selected?.type === DocumentType.INK) {
-            return StrCast(selected.fillColor);
-        }
-        return ActiveFillColor();
-    }
-    SetActiveFillColor(StrCast(color));
-    SelectionManager.Docs()
-        .filter(doc => doc.type === DocumentType.INK)
-        .map(doc => (doc.fillColor = color));
-});
-
-ScriptingGlobals.add(function setStrokeWidth(width: number, checkResult?: boolean) {
-    if (checkResult) {
-        const selected = SelectionManager.Docs().lastElement();
-        if (selected?.type === DocumentType.INK) {
-            return NumCast(selected.strokeWidth);
-        }
-        return ActiveInkWidth();
-    }
-    SetActiveInkWidth(width.toString());
-    SelectionManager.Docs()
-        .filter(doc => doc.type === DocumentType.INK)
-        .map(doc => (doc.strokeWidth = Number(width)));
-});
+    // prettier-ignore
+    const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
+        ['inkMask', {
+            checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.isInkMask) : ActiveIsInkMask()) ? Colors.MEDIUM_BLUE : 'transparent'),
+            setInk: (doc: Doc) => (doc.isInkMask = !doc.isInkMask),
+            setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
+        }],
+        ['fillColor', {
+            checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ? Colors.MEDIUM_BLUE : 'transparent'),
+            setInk: (doc: Doc) => (doc.fillColor = StrCast(value)),
+            setMode: () => SetActiveFillColor(StrCast(value)),
+        }],
+        [ 'strokeWidth', {
+            checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.strokeWidth) : ActiveInkWidth()),
+            setInk: (doc: Doc) => (doc.strokeWidth = NumCast(value)),
+            setMode: () => SetActiveInkWidth(value.toString()),
+        }],
+        ['strokeColor', {
+            checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.color) : ActiveInkColor()),
+            setInk: (doc: Doc) => (doc.color = String(value)),
+            setMode: () => SetActiveInkColor(StrCast(value)),
+        }],
+    ]);
 
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function setStrokeColor(color?: string, checkResult?: boolean) {
     if (checkResult) {
-        const selected = SelectionManager.Docs().lastElement();
-        if (selected?.type === DocumentType.INK) {
-            return StrCast(selected.color);
-        }
-        return ActiveInkColor();
+        return map.get(option)?.checkResult();
     }
-    SetActiveInkColor(StrCast(color));
+    map.get(option)?.setMode();
     SelectionManager.Docs()
         .filter(doc => doc.type === DocumentType.INK)
-        .map(doc => (doc.color = String(color)));
+        .map(doc => map.get(option)?.setInk(doc));
 });
 
 /** WEB
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ee164ab31..de94ed5db 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -33,20 +33,19 @@ export namespace Field {
         return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field));
     }
     export function toScriptString(field: Field): string {
-        if (typeof field === 'string') {
-            if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ".  In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
-            return `"${field}"`;
+        switch (typeof field) {
+            case 'string':
+                if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ".  In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s
+                return `"${field}"`;
+            case 'number':
+            case 'boolean':
+                return String(field);
         }
-        if (typeof field === 'number' || typeof field === 'boolean') return String(field);
-        if (field === undefined || field === null) return 'null';
-        return field[ToScriptString]();
+        return field?.[ToScriptString]?.() ?? 'null';
     }
     export function toString(field: Field): string {
-        if (typeof field === 'string') return field;
-        if (typeof field === 'number' || typeof field === 'boolean') return String(field);
-        if (field instanceof ObjectField) return field[ToString]();
-        if (field instanceof RefField) return field[ToString]();
-        return '';
+        if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field);
+        return field?.[ToString]?.() || '';
     }
     export function IsField(field: any): field is Field;
     export function IsField(field: any, includeUndefined: true): field is Field | undefined;
@@ -79,17 +78,14 @@ export async function DocCastAsync(field: FieldResult): Promise<Opt<Doc>> {
     return Cast(field, Doc);
 }
 
-export function NumListCast(field: FieldResult) {
-    return Cast(field, listSpec('number'), []);
+export function NumListCast(field: FieldResult, defaultVal: number[] = []) {
+    return Cast(field, listSpec('number'), defaultVal);
 }
-export function StrListCast(field: FieldResult) {
-    return Cast(field, listSpec('string'), []);
+export function StrListCast(field: FieldResult, defaultVal: string[] = []) {
+    return Cast(field, listSpec('string'), defaultVal);
 }
-export function DocListCast(field: FieldResult) {
-    return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[];
-}
-export function DocListCastOrNull(field: FieldResult) {
-    return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined;
+export function DocListCast(field: FieldResult, defaultVal: Doc[] = []) {
+    return Cast(field, listSpec(Doc), defaultVal).filter(d => d instanceof Doc) as Doc[];
 }
 
 export const WidthSym = Symbol('Width');
@@ -153,17 +149,8 @@ export function updateCachedAcls(doc: Doc) {
 }
 
 @scriptingGlobal
-@Deserializable('Doc', updateCachedAcls).withFields(['id'])
+@Deserializable('Doc', updateCachedAcls, ['id'])
 export class Doc extends RefField {
-    //TODO tfs: these should be temporary...
-    private static mainDocId: string | undefined;
-    public static get MainDocId() {
-        return this.mainDocId;
-    }
-    public static set MainDocId(id: string | undefined) {
-        this.mainDocId = id;
-    }
-
     @observable public static CurrentlyLoading: Doc[];
     // removes from currently loading display
     @action
@@ -582,21 +569,13 @@ export namespace Doc {
 
     // compare whether documents or their protos match
     export function AreProtosEqual(doc?: Doc, other?: Doc) {
-        if (!doc || !other) return false;
-        const r = doc === other;
-        const r2 = Doc.GetProto(doc) === other;
-        const r3 = Doc.GetProto(other) === doc;
-        const r4 = Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined;
-        return r || r2 || r3 || r4;
+        return doc && other && Doc.GetProto(doc) === Doc.GetProto(other);
     }
 
     // Gets the data document for the document.  Note: this is mis-named -- it does not specifically
     // return the doc's proto, but rather recursively searches through the proto inheritance chain
     // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype').
     export function GetProto(doc: Doc): Doc {
-        if (doc instanceof Promise) {
-            // console.log("GetProto: warning: got Promise insead of Doc");
-        }
         const proto = doc && (Doc.GetT(doc, 'isPrototype', 'boolean', true) ? doc : doc.proto || doc);
         return proto === doc ? proto : Doc.GetProto(proto);
     }
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index b5eca78dd..feb419597 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -103,30 +103,9 @@ export class ScriptField extends ObjectField {
         }
         this.rawscript = rawscript;
         this.setterscript = setterscript;
-        this.script = script ?? (CompileScript('false', { addReturn: true }) as CompiledScript);
+        this.script = script ?? ScriptField.GetScriptFieldCache('false:') ?? (CompileScript('false', { addReturn: true }) as CompiledScript);
     }
 
-    //     init(callback: (res: Field) => any) {
-    //         const options = this.options!;
-    //         const keys = Object.keys(options.options.capturedIds);
-    //         Server.GetFields(keys).then(fields => {
-    //             let captured: { [name: string]: Field } = {};
-    //             keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]);
-    //             const opts: ScriptOptions = {
-    //                 addReturn: options.options.addReturn,
-    //                 params: options.options.params,
-    //                 requiredType: options.options.requiredType,
-    //                 capturedVariables: captured
-    //             };
-    //             const script = CompileScript(options.script, opts);
-    //             if (!script.compiled) {
-    //                 throw new Error("Can't compile script");
-    //             }
-    //             this._script = script;
-    //             callback(this);
-    //         });
-    //     }
-
     [Copy](): ObjectField {
         return new ScriptField(this.script, this.setterscript, this.rawscript);
     }
@@ -172,7 +151,7 @@ export class ComputedField extends ScriptField {
     _lastComputedResult: any;
     //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
     value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
-    _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
+    _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) ?? doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
 
     [ToValue](doc: Doc) {
         return ComputedField.toValue(doc, this);
@@ -181,12 +160,8 @@ export class ComputedField extends ScriptField {
         return new ComputedField(this.script, this.setterscript, this.rawscript);
     }
 
-    public static MakeScript(script: string, params: object = {}) {
-        const compiled = ScriptField.CompileScript(script, params, false);
-        return compiled.compiled ? new ComputedField(compiled) : undefined;
-    }
     public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) {
-        const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
+        const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables });
         const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined;
         const compiledsetscript = compiledsetter?.compiled ? compiledsetter : undefined;
         return compiled.compiled ? new ComputedField(compiled, compiledsetscript) : undefined;
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 6ff13d5d3..70d9ed61f 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -48,15 +48,11 @@ export function TraceMobx() {
 }
 
 const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
-    if (SerializationHelper.IsSerializing()) {
+    if (SerializationHelper.IsSerializing() || typeof prop === 'symbol') {
         target[prop] = value;
         return true;
     }
 
-    if (typeof prop === 'symbol') {
-        target[prop] = value;
-        return true;
-    }
     if (value !== undefined) {
         value = value[SelfProxy] || value;
     }
@@ -95,12 +91,8 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
             delete target.__fields[prop];
         } else {
             target.__fieldKeys && (target.__fieldKeys[prop] = true);
-            // if (target.__fields[prop] !== value) {
-            //     console.log("ASSIGN " + prop + " " + value);
-            // }
             target.__fields[prop] = value;
         }
-        //if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
 
         if (writeToServer) {
             if (value === undefined) target[Update]({ $unset: { ['fields.' + prop]: '' } });
@@ -143,13 +135,6 @@ export function denormalizeEmail(email: string) {
     return email.replace(/__/g, '.');
 }
 
-// playground mode allows the user to add/delete documents or make layout changes without them saving to the server
-// let playgroundMode = false;
-
-// export function togglePlaygroundMode() {
-//     playgroundMode = !playgroundMode;
-// }
-
 /**
  * Copies parent's acl fields to the child
  */
@@ -317,7 +302,6 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
     if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true;
     // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
     if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true;
-    // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true;
 
     if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) {
         if (!prop.startsWith('__')) prop = prop.substring(1);
@@ -377,9 +361,9 @@ export function getField(target: any, prop: string | number, ignoreProto: boolea
 export function deleteProperty(target: any, prop: string | number | symbol) {
     if (typeof prop === 'symbol') {
         delete target[prop];
-        return true;
+    } else {
+        target[SelfProxy][prop] = undefined;
     }
-    target[SelfProxy][prop] = undefined;
     return true;
 }
 
-- 
cgit v1.2.3-70-g09d2


From 2a4e86be98ad0f8d4aa4cb09b982d448b542d916 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 22 Mar 2023 14:20:09 -0400
Subject: removed gitlike and branch stuff.  updated fortawesome.  added Z
 ordering buttons.  moved ctrl-t/alt-t to edit text title into
 formattedTextBox

---
 package-lock.json                                  |  66 +++---
 package.json                                       |  10 +-
 src/client/documents/Gitlike.ts                    | 226 ++++++++++-----------
 src/client/util/CurrentUserUtils.ts                |   8 +
 src/client/util/DragManager.ts                     |   7 +
 src/client/views/MainView.tsx                      |   4 +
 .../views/collections/CollectionDockingView.tsx    |   3 +-
 src/client/views/collections/CollectionView.tsx    |  38 ++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   6 +
 src/client/views/nodes/DocumentView.tsx            |  32 +--
 src/client/views/nodes/button/ButtonScripts.ts     |  16 --
 src/client/views/nodes/button/FontIconBox.tsx      |   1 -
 .../views/nodes/formattedText/FormattedTextBox.tsx |   4 +-
 src/fields/Doc.ts                                  |  26 ++-
 14 files changed, 211 insertions(+), 236 deletions(-)
 delete mode 100644 src/client/views/nodes/button/ButtonScripts.ts

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/package-lock.json b/package-lock.json
index cc51ad9e0..18a435a41 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -698,67 +698,53 @@
             }
         },
         "@fortawesome/fontawesome-common-types": {
-            "version": "0.3.0",
-            "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.3.0.tgz",
-            "integrity": "sha512-CA3MAZBTxVsF6SkfkHXDerkhcQs0QPofy43eFdbWJJkZiq3SfiaH1msOkac59rQaqto5EqWnASboY1dBuKen5w=="
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz",
+            "integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg=="
         },
         "@fortawesome/fontawesome-svg-core": {
-            "version": "1.3.0",
-            "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.3.0.tgz",
-            "integrity": "sha512-UIL6crBWhjTNQcONt96ExjUnKt1D68foe3xjEensLDclqQ6YagwCRYVQdrp/hW0ALRp/5Fv/VKw+MqTUWYYvPg==",
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.3.0.tgz",
+            "integrity": "sha512-uz9YifyKlixV6AcKlOX8WNdtF7l6nakGyLYxYaCa823bEBqyj/U2ssqtctO38itNEwXb8/lMzjdoJ+aaJuOdrw==",
             "requires": {
-                "@fortawesome/fontawesome-common-types": "^0.3.0"
+                "@fortawesome/fontawesome-common-types": "6.3.0"
             }
         },
         "@fortawesome/free-brands-svg-icons": {
-            "version": "5.15.4",
-            "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz",
-            "integrity": "sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g==",
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.3.0.tgz",
+            "integrity": "sha512-xI0c+a8xnKItAXCN8rZgCNCJQiVAd2Y7p9e2ND6zN3J3ekneu96qrePieJ7yA7073C1JxxoM3vH1RU7rYsaj8w==",
             "requires": {
-                "@fortawesome/fontawesome-common-types": "^0.2.36"
-            },
-            "dependencies": {
-                "@fortawesome/fontawesome-common-types": {
-                    "version": "0.2.36",
-                    "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
-                    "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
-                }
+                "@fortawesome/fontawesome-common-types": "6.3.0"
             }
         },
         "@fortawesome/free-regular-svg-icons": {
-            "version": "5.15.4",
-            "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz",
-            "integrity": "sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==",
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.3.0.tgz",
+            "integrity": "sha512-cZnwiVHZ51SVzWHOaNCIA+u9wevZjCuAGSvSYpNlm6A4H4Vhwh8481Bf/5rwheIC3fFKlgXxLKaw8Xeroz8Ntg==",
             "requires": {
-                "@fortawesome/fontawesome-common-types": "^0.2.36"
+                "@fortawesome/fontawesome-common-types": "6.3.0"
             },
             "dependencies": {
                 "@fortawesome/fontawesome-common-types": {
-                    "version": "0.2.36",
-                    "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
-                    "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
+                    "version": "6.3.0",
+                    "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.3.0.tgz",
+                    "integrity": "sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg=="
                 }
             }
         },
         "@fortawesome/free-solid-svg-icons": {
-            "version": "5.15.4",
-            "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz",
-            "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==",
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.3.0.tgz",
+            "integrity": "sha512-x5tMwzF2lTH8pyv8yeZRodItP2IVlzzmBuD1M7BjawWgg9XAvktqJJ91Qjgoaf8qJpHQ8FEU9VxRfOkLhh86QA==",
             "requires": {
-                "@fortawesome/fontawesome-common-types": "^0.2.36"
-            },
-            "dependencies": {
-                "@fortawesome/fontawesome-common-types": {
-                    "version": "0.2.36",
-                    "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
-                    "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg=="
-                }
+                "@fortawesome/fontawesome-common-types": "6.3.0"
             }
         },
         "@fortawesome/react-fontawesome": {
-            "version": "0.1.19",
-            "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz",
-            "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==",
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
+            "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==",
             "requires": {
                 "prop-types": "^15.8.1"
             }
@@ -20903,7 +20889,7 @@
         "sparse-bitfield": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
-            "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
+            "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
             "optional": true,
             "requires": {
                 "memory-pager": "^1.0.2"
diff --git a/package.json b/package.json
index 2c4c41917..41bdd5f8f 100644
--- a/package.json
+++ b/package.json
@@ -129,11 +129,11 @@
     "dependencies": {
         "@ffmpeg/core": "0.10.0",
         "@ffmpeg/ffmpeg": "0.10.0",
-        "@fortawesome/fontawesome-svg-core": "^1.3.0",
-        "@fortawesome/free-brands-svg-icons": "^5.15.4",
-        "@fortawesome/free-regular-svg-icons": "^5.15.4",
-        "@fortawesome/free-solid-svg-icons": "^5.15.4",
-        "@fortawesome/react-fontawesome": "^0.1.19",
+        "@fortawesome/fontawesome-svg-core": "^6.3.0",
+        "@fortawesome/free-brands-svg-icons": "^6.3.0",
+        "@fortawesome/free-regular-svg-icons": "^6.3.0",
+        "@fortawesome/free-solid-svg-icons": "^6.3.0",
+        "@fortawesome/react-fontawesome": "^0.2.0",
         "@hig/flyout": "^1.3.1",
         "@hig/theme-context": "^2.1.3",
         "@hig/theme-data": "^2.23.1",
diff --git a/src/client/documents/Gitlike.ts b/src/client/documents/Gitlike.ts
index 575c984f5..5e2baf924 100644
--- a/src/client/documents/Gitlike.ts
+++ b/src/client/documents/Gitlike.ts
@@ -1,118 +1,118 @@
-import { Doc, DocListCast, DocListCastAsync, Field } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { Cast, DateCast } from "../../fields/Types";
-import { DateField } from "../../fields/DateField";
-import { Id } from "../../fields/FieldSymbols";
+// import { Doc, DocListCast, DocListCastAsync, Field } from "../../fields/Doc";
+// import { List } from "../../fields/List";
+// import { Cast, DateCast } from "../../fields/Types";
+// import { DateField } from "../../fields/DateField";
+// import { Id } from "../../fields/FieldSymbols";
 
-// synchs matching documents on the two branches that are being merged/pulled
-// currently this just synchs the main 'fieldKey' component of the data since 
-// we don't have individual timestamps for all fields -- this is a problematic design issue.
-function GitlikeSynchDocs(bd: Doc, md: Doc) {
-    const fieldKey = Doc.LayoutFieldKey(md);
-    const bdate = DateCast(bd[`${fieldKey}-lastModified`])?.date;
-    const mdate = DateCast(md[`${fieldKey}-lastModified`])?.date;
-    const bdproto = bd && Doc.GetProto(bd);
-    if (bdate !== mdate && bdate <= mdate) {
-        if (bdproto && md) {
-            bdproto[fieldKey] = Field.Copy(md[fieldKey]);
-            bdproto[`${fieldKey}-lastModified`] = new DateField();
-        }
-    }
-    const bldate = DateCast(bd._lastModified)?.date;
-    const mldate = DateCast(md._lastModified)?.date;
-    if (bldate === mldate || bldate > mldate) return;
-    if (bdproto && md) {
-        bd.x = Field.Copy(md.x);
-        bd.y = Field.Copy(md.y);
-        bd.width = Field.Copy(md.width);
-        bd.height = Field.Copy(md.height);
-        bdproto._lastModified = new DateField();
-    }
-}
+// // synchs matching documents on the two branches that are being merged/pulled
+// // currently this just synchs the main 'fieldKey' component of the data since
+// // we don't have individual timestamps for all fields -- this is a problematic design issue.
+// function GitlikeSynchDocs(bd: Doc, md: Doc) {
+//     const fieldKey = Doc.LayoutFieldKey(md);
+//     const bdate = DateCast(bd[`${fieldKey}-lastModified`])?.date;
+//     const mdate = DateCast(md[`${fieldKey}-lastModified`])?.date;
+//     const bdproto = bd && Doc.GetProto(bd);
+//     if (bdate !== mdate && bdate <= mdate) {
+//         if (bdproto && md) {
+//             bdproto[fieldKey] = Field.Copy(md[fieldKey]);
+//             bdproto[`${fieldKey}-lastModified`] = new DateField();
+//         }
+//     }
+//     const bldate = DateCast(bd._lastModified)?.date;
+//     const mldate = DateCast(md._lastModified)?.date;
+//     if (bldate === mldate || bldate > mldate) return;
+//     if (bdproto && md) {
+//         bd.x = Field.Copy(md.x);
+//         bd.y = Field.Copy(md.y);
+//         bd.width = Field.Copy(md.width);
+//         bd.height = Field.Copy(md.height);
+//         bdproto._lastModified = new DateField();
+//     }
+// }
 
-// pulls documents onto a branch from the branch's master
-// if a document exists on master but not on the branch, it is branched and added
-// NOTE: need to set a timestamp on the branch that is equal to the master's last merge timestamp.
-async function GitlikePullFromMaster(branch: Doc, suffix = "") {
-    const masterMain = Cast(branch.branchOf, Doc, null);
-    // get the set of documents on both the branch and master
-    const masterMainDocs = masterMain && await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]);
-    const branchMainDocs = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
-    // get the master documents that correspond to the branch documents
-    const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd);
-    const branchMasterMainDocProtos = branchMasterMainDocs?.map(doc => Doc.GetProto(doc));
-    // get documents on master that don't have a corresponding master doc (form a branch doc), and ...
-    const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
-    const oldDocsFromMaster = masterMainDocs?.filter(md => branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
-    oldDocsFromMaster?.forEach(md => {
-        const bd = branchMainDocs?.find(bd => (Cast(bd.branchOf, Doc, null) || bd) === md);
-        bd && GitlikeSynchDocs(bd, md);
-    });
-    const cloneMap = new Map<string, Doc>(); cloneMap.set(masterMain[Id], branch);
-    // make branch clones of them, then add them to the branch
-    const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true, cloneMap)).clone) || []);
-    newlyBranchedDocs.forEach(nd => {
-        Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd);
-        nd.context = branch;
-    });
-    // if a branch doc's corresponding main branch doc doesn't have a context, then it was deleted.  
-    const remDocsFromMaster = branchMainDocs?.filter(bd => Cast(bd.branchOf, Doc, null) && !Cast(bd.branchOf, Doc, null)?.context);
-    // so then remove all the deleted main docs from this branch.
-    remDocsFromMaster?.forEach(rd => Doc.RemoveDocFromList(branch, Doc.LayoutFieldKey(branch) + suffix, rd));
-}
+// // pulls documents onto a branch from the branch's master
+// // if a document exists on master but not on the branch, it is branched and added
+// // NOTE: need to set a timestamp on the branch that is equal to the master's last merge timestamp.
+// async function GitlikePullFromMaster(branch: Doc, suffix = "") {
+//     const masterMain = Cast(branch.branchOf, Doc, null);
+//     // get the set of documents on both the branch and master
+//     const masterMainDocs = masterMain && await DocListCastAsync(masterMain[Doc.LayoutFieldKey(masterMain) + suffix]);
+//     const branchMainDocs = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
+//     // get the master documents that correspond to the branch documents
+//     const branchMasterMainDocs = branchMainDocs?.map(bd => Cast(bd.branchOf, Doc, null) || bd);
+//     const branchMasterMainDocProtos = branchMasterMainDocs?.map(doc => Doc.GetProto(doc));
+//     // get documents on master that don't have a corresponding master doc (form a branch doc), and ...
+//     const newDocsFromMaster = masterMainDocs?.filter(md => !branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
+//     const oldDocsFromMaster = masterMainDocs?.filter(md => branchMasterMainDocProtos?.includes(Doc.GetProto(md)));
+//     oldDocsFromMaster?.forEach(md => {
+//         const bd = branchMainDocs?.find(bd => (Cast(bd.branchOf, Doc, null) || bd) === md);
+//         bd && GitlikeSynchDocs(bd, md);
+//     });
+//     const cloneMap = new Map<string, Doc>(); cloneMap.set(masterMain[Id], branch);
+//     // make branch clones of them, then add them to the branch
+//     const newlyBranchedDocs = await Promise.all(newDocsFromMaster?.map(async md => (await Doc.MakeClone(md, false, true, cloneMap)).clone) || []);
+//     newlyBranchedDocs.forEach(nd => {
+//         Doc.AddDocToList(branch, Doc.LayoutFieldKey(branch) + suffix, nd);
+//         nd.context = branch;
+//     });
+//     // if a branch doc's corresponding main branch doc doesn't have a context, then it was deleted.
+//     const remDocsFromMaster = branchMainDocs?.filter(bd => Cast(bd.branchOf, Doc, null) && !Cast(bd.branchOf, Doc, null)?.context);
+//     // so then remove all the deleted main docs from this branch.
+//     remDocsFromMaster?.forEach(rd => Doc.RemoveDocFromList(branch, Doc.LayoutFieldKey(branch) + suffix, rd));
+// }
 
-// merges all branches from the master branch by first merging the top-level collection of documents, 
-// and then merging all the annotations on those documents.
-// TODO: need to add an incrementing timestamp whenever anything merges.  don't allow a branch to merge if it's last pull timestamp isn't equal to the last merge timestamp.
-async function GitlikeMergeWithMaster(master: Doc, suffix = "") {
-    const branches = await DocListCastAsync(master.branches);
-    branches?.map(async branch => {
-        const branchChildren = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
-        branchChildren && await Promise.all(branchChildren.map(async bd => {
-            const cloneMap = new Map<string, Doc>(); cloneMap.set(master[Id], branch);
-            // see if the branch's child exists on master.  
-            const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true, cloneMap)).clone;
-            // if the branch's child didn't exist on master, we make a branch clone of the child to add to master.  
-            // however, since master is supposed to have the "main" clone, and branches, the "branch" clones, we have to reverse the fields
-            // on the branch child and master clone.
-            if (masterChild.branchOf) {
-                const branchDocProto = Doc.GetProto(bd);
-                const masterChildProto = Doc.GetProto(masterChild);
-                const branchTitle = bd.title;
-                branchDocProto.title = masterChildProto.title;
-                masterChildProto.title = branchTitle;
-                masterChildProto.branchOf = masterChild.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf'
-                masterChildProto.branches = new List<Doc>([bd]); // the master child's branches needs to include the branch child
-                Doc.RemoveDocFromList(branchDocProto, "branches", masterChildProto);   // the branch child should not have the master child in its branch list.
-                branchDocProto.branchOf = masterChild;                                 // the branch child is now a branch of the master child
-            }
-            Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op)
-            masterChild.context = master;
-            GitlikeSynchDocs(masterChild, bd);//Doc.GetProto(masterChild), bd);
-        }));
-        const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]);
-        masterChildren?.forEach(mc => {                      // see if any master children
-            if (!branchChildren?.find(bc => bc.branchOf === mc)) { //    are not in the list of children for this branch. 
-                Doc.RemoveDocFromList(master, Doc.LayoutFieldKey(master) + suffix, mc); // if so, delete the master child since the branch has deleted it.
-                mc.context = undefined;      // NOTE if we merge a branch that didn't do a pull, it will look like the branch deleted documents -- need edit timestamps that prevent merging if branch isn't up-to-date with last edit timestamp
-            }
-        });
-    });
-}
+// // merges all branches from the master branch by first merging the top-level collection of documents,
+// // and then merging all the annotations on those documents.
+// // TODO: need to add an incrementing timestamp whenever anything merges.  don't allow a branch to merge if it's last pull timestamp isn't equal to the last merge timestamp.
+// async function GitlikeMergeWithMaster(master: Doc, suffix = "") {
+//     const branches = await DocListCastAsync(master.branches);
+//     branches?.map(async branch => {
+//         const branchChildren = await DocListCastAsync(branch[Doc.LayoutFieldKey(branch) + suffix]);
+//         branchChildren && await Promise.all(branchChildren.map(async bd => {
+//             const cloneMap = new Map<string, Doc>(); cloneMap.set(master[Id], branch);
+//             // see if the branch's child exists on master.
+//             const masterChild = Cast(bd.branchOf, Doc, null) || (await Doc.MakeClone(bd, false, true, cloneMap)).clone;
+//             // if the branch's child didn't exist on master, we make a branch clone of the child to add to master.
+//             // however, since master is supposed to have the "main" clone, and branches, the "branch" clones, we have to reverse the fields
+//             // on the branch child and master clone.
+//             if (masterChild.branchOf) {
+//                 const branchDocProto = Doc.GetProto(bd);
+//                 const masterChildProto = Doc.GetProto(masterChild);
+//                 const branchTitle = bd.title;
+//                 branchDocProto.title = masterChildProto.title;
+//                 masterChildProto.title = branchTitle;
+//                 masterChildProto.branchOf = masterChild.branchOf = undefined; // the master child should not be a branch of the branch child, so unset 'branchOf'
+//                 masterChildProto.branches = new List<Doc>([bd]); // the master child's branches needs to include the branch child
+//                 Doc.RemoveDocFromList(branchDocProto, "branches", masterChildProto);   // the branch child should not have the master child in its branch list.
+//                 branchDocProto.branchOf = masterChild;                                 // the branch child is now a branch of the master child
+//             }
+//             Doc.AddDocToList(master, Doc.LayoutFieldKey(master) + suffix, masterChild); // add the masterChild to master (if it's already there, this is a no-op)
+//             masterChild.context = master;
+//             GitlikeSynchDocs(masterChild, bd);//Doc.GetProto(masterChild), bd);
+//         }));
+//         const masterChildren = await DocListCastAsync(master[Doc.LayoutFieldKey(master) + suffix]);
+//         masterChildren?.forEach(mc => {                      // see if any master children
+//             if (!branchChildren?.find(bc => bc.branchOf === mc)) { //    are not in the list of children for this branch.
+//                 Doc.RemoveDocFromList(master, Doc.LayoutFieldKey(master) + suffix, mc); // if so, delete the master child since the branch has deleted it.
+//                 mc.context = undefined;      // NOTE if we merge a branch that didn't do a pull, it will look like the branch deleted documents -- need edit timestamps that prevent merging if branch isn't up-to-date with last edit timestamp
+//             }
+//         });
+//     });
+// }
 
-// performs a "git"-like task: pull or merge
-//    if pull, then target is a specific branch document that will be updated from its associated master
-//    if merge, then target is the master doc that will merge in all branches associated with it.
-// TODO: parameterize 'merge' to specify which branch(es) should be merged.   
-//       extend 'merge' to allow a specific branch to be merge target (not just master);
-//       make pull/merge be recursive (ie, this func currently just operates on the main doc and its children)
-export async function BranchTask(target: Doc, action: "pull" | "merge") {
-    const func = action === "pull" ? GitlikePullFromMaster : GitlikeMergeWithMaster;
-    await func(target, "");
-    await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-annotations"));
-    await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-sidebar"));
-}
+// // performs a "git"-like task: pull or merge
+// //    if pull, then target is a specific branch document that will be updated from its associated master
+// //    if merge, then target is the master doc that will merge in all branches associated with it.
+// // TODO: parameterize 'merge' to specify which branch(es) should be merged.
+// //       extend 'merge' to allow a specific branch to be merge target (not just master);
+// //       make pull/merge be recursive (ie, this func currently just operates on the main doc and its children)
+// export async function BranchTask(target: Doc, action: "pull" | "merge") {
+//     const func = action === "pull" ? GitlikePullFromMaster : GitlikeMergeWithMaster;
+//     await func(target, "");
+//     await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-annotations"));
+//     await DocListCast(target[Doc.LayoutFieldKey(target)]).forEach(async targetChild => func(targetChild, "-sidebar"));
+// }
 
-export async function BranchCreate(target: Doc) {
-    return (await Doc.MakeClone(target, false, true)).clone;
-}
\ No newline at end of file
+// export async function BranchCreate(target: Doc) {
+//     return (await Doc.MakeClone(target, false, true)).clone;
+// }
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index c038fd6ab..f9766c3fb 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -608,6 +608,13 @@ export class CurrentUserUtils {
         return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
     }
 
+    static zTools(): Button[] {
+        return [
+            { title: "Bottom",  icon: "arrows-down-to-line",  toolTip: "Make doc topmost",btnType: ButtonType.ClickButton,        expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform
+            { title: "Top",     icon: "arrows-up-to-line",    toolTip: "Make doc bottommost",btnType: ButtonType.ClickButton,     expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform
+            { title: "Z order", icon: "z",                    toolTip: "Bring Forward on Drag",btnType: ButtonType.ToggleButton,  expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: 'tab'},                                                                     scripts: { onClick: 'toggleRaiseOnDrag(_readOnly_)'}}, // Only when floating document is selected in freeform
+        ]
+    }
     static textTools():Button[] {
         return [
             { title: "Font",    toolTip: "Font", width: 100,     btnType: ButtonType.DropdownList,                      toolType:"font",     ignoreClick: true,   scripts: {script:  '{ return setFontAttr(self.toolType, value, _readOnly_);}'},
@@ -675,6 +682,7 @@ export class CurrentUserUtils {
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
             { title: "Text",    icon: "Text",          toolTip: "Text functions",       subMenu: CurrentUserUtils.textTools(),   expertMode: false, toolType:DocumentType.RTF,            funcs: {                                                                             linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
             { title: "Ink",     icon: "Ink",           toolTip: "Ink functions",        subMenu: CurrentUserUtils.inkTools(),    expertMode: false, toolType:DocumentType.INK,            funcs: {                                                                             linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
+            { title: "Z",       icon: "z",             toolTip: "Z order functions",    subMenu: CurrentUserUtils.zTools(),      expertMode: false, toolType:"tab",                       funcs: {                                                                             linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
             { title: "Schema",  icon: "Schema",        toolTip: "Schema functions",     subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema,   funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected
         ];
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index a56f87075..5a35fcd53 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -10,7 +10,9 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../fields/Types
 import { emptyFunction, Utils } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import * as globalCssVariables from '../views/global/globalCssVariables.scss';
+import { Colors } from '../views/global/globalEnums';
 import { DocumentView } from '../views/nodes/DocumentView';
+import { ScriptingGlobals } from './ScriptingGlobals';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
 
@@ -590,3 +592,8 @@ export namespace DragManager {
         endDrag?.();
     }
 }
+
+ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) {
+    if (readOnly) return DragManager.GetRaiseWhenDragged() ? Colors.MEDIUM_BLUE : 'transparent';
+    DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged());
+});
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 118635a38..e755f204b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -477,6 +477,10 @@ export class MainView extends React.Component {
                 fa.faHighlighter,
                 fa.faRemoveFormat,
                 fa.faHandPointUp,
+                fa.faXRay,
+                fa.faZ,
+                fa.faArrowsUpToLine,
+                fa.faArrowsDownToLine,
             ]
         );
     }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 9b6554d67..057c1e30f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -410,11 +410,10 @@ export class CollectionDockingView extends CollectionSubView() {
             const _width = Number(getComputedStyle(content).width.replace('px', ''));
             const _height = Number(getComputedStyle(content).height.replace('px', ''));
             return CollectionFreeFormView.UpdateIcon(this.layoutDoc[Id] + '-icon' + new Date().getTime(), content, _width, _height, _width, _height, 0, 1, true, this.layoutDoc[Id] + '-icon', (iconFile, _nativeWidth, _nativeHeight) => {
-                const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: this.rootDoc.title + '-icon', _width, _height, _nativeWidth, _nativeHeight });
                 const proto = this.dataDoc; // Cast(img.proto, Doc, null)!;
                 proto['thumb-nativeWidth'] = _width;
                 proto['thumb-nativeHeight'] = _height;
-                this.dataDoc.thumb = new ImageField(iconFile);
+                proto.thumb = new ImageField(iconFile);
             });
         }
     }
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index aed88aa1a..eafa50d27 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -2,7 +2,6 @@ import { computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../fields/Doc';
-import { Id } from '../../../fields/FieldSymbols';
 import { ObjectField } from '../../../fields/ObjectField';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
@@ -10,13 +9,12 @@ import { TraceMobx } from '../../../fields/util';
 import { returnEmptyString } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { CollectionViewType } from '../../documents/DocumentTypes';
-import { BranchCreate, BranchTask } from '../../documents/Gitlike';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { InteractionUtils } from '../../util/InteractionUtils';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
-import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
+import { OpenWhere } from '../nodes/DocumentView';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
 import { CollectionCarouselView } from './CollectionCarouselView';
@@ -193,23 +191,23 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
             }
             !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => (this.rootDoc._isLightbox = !this.rootDoc._isLightbox), icon: 'project-diagram' });
 
-            if (!Doc.noviceMode && false) {
-                optionItems.push({
-                    description: 'Create Branch',
-                    event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), OpenWhere.addRight),
-                    icon: 'project-diagram',
-                });
-                optionItems.push({
-                    description: 'Pull Master',
-                    event: () => BranchTask(this.rootDoc, 'pull'),
-                    icon: 'project-diagram',
-                });
-                optionItems.push({
-                    description: 'Merge Branches',
-                    event: () => BranchTask(this.rootDoc, 'merge'),
-                    icon: 'project-diagram',
-                });
-            }
+            // if (!Doc.noviceMode && false) {
+            //     optionItems.push({
+            //         description: 'Create Branch',
+            //         event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), OpenWhere.addRight),
+            //         icon: 'project-diagram',
+            //     });
+            //     optionItems.push({
+            //         description: 'Pull Master',
+            //         event: () => BranchTask(this.rootDoc, 'pull'),
+            //         icon: 'project-diagram',
+            //     });
+            //     optionItems.push({
+            //         description: 'Merge Branches',
+            //         event: () => BranchTask(this.rootDoc, 'merge'),
+            //         icon: 'project-diagram',
+            //     });
+            // }
 
             !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' });
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ac90c67a5..0ea614472 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -2280,3 +2280,9 @@ ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: bool
             TabDocView.PinDoc(view.rootDoc, { currentFrame: Cast(view.rootDoc.currentFrame, 'number', null), pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) })
         );
 });
+ScriptingGlobals.add(function bringToFront() {
+    SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc));
+});
+ScriptingGlobals.add(function sendToBack(doc: Doc) {
+    SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc, true));
+});
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index edf508c95..c1665ea3d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -162,6 +162,7 @@ export interface DocumentViewSharedProps {
     childHideResizeHandles?: () => boolean;
     dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt
     styleProvider: Opt<StyleProviderFunc>;
+    setTitleFocus?: () => void;
     focus: DocFocusFunc;
     fitWidth?: (doc: Doc) => boolean | undefined;
     docFilters: () => string[];
@@ -539,18 +540,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 .ScreenToLocalTransform()
                 .scale(this.NativeDimScaling)
                 .transformDirection(x - left, y - top);
-            // dragData.offset[0] = Math.min(this.rootDoc[WidthSym](), dragData.offset[0]);  // bcz: this was breaking dragging rotated objects since the offset may be out of bounds with regard to the unrotated document
-            // dragData.offset[1] = Math.min(this.rootDoc[HeightSym](), dragData.offset[1]);
             dragData.dropAction = dropAction;
             dragData.treeViewDoc = this.props.treeViewDoc;
             dragData.removeDocument = this.props.removeDocument;
             dragData.moveDocument = this.props.moveDocument;
             dragData.canEmbed = this.props.canEmbedOnDrag;
-            //dragData.dimSource :
-            // dragEffects field, set dim
-            // add kv pairs to a doc, swap properties with the node while dragging, and then swap when dropping
-            // add a dragEffects prop to DocumentView as a function that sets up. Each view has its own prop, when you start dragging:
-            // in Draganager, figure out which doc(s) you're dragging and change what opacity function returns
             const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
             ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
             DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) }, () =>
@@ -560,22 +554,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     }
 
-    onKeyDown = (e: React.KeyboardEvent) => {
-        if (e.altKey) {
-            e.stopPropagation();
-            e.preventDefault();
-            if (e.key === '†' || e.key === 't') {
-                if (!StrCast(this.layoutDoc._showTitle)) this.layoutDoc._showTitle = 'title';
-                if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true));
-                else if (!this._titleRef.current.setIsFocused(true)) {
-                    // if focus didn't change, focus on interior text...
-                    this._titleRef.current?.setIsFocused(false);
-                    this._componentView?.setFocus?.();
-                }
-            }
-        }
-    };
-
     defaultRestoreTargetView = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => {
         const targetMatch =
             Doc.AreProtosEqual(anchor, this.rootDoc) || // anchor is this document, so anchor's properties apply to this document
@@ -585,6 +563,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         return targetMatch && PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined;
     };
 
+    // switches text input focus to the title bar of the document (and displays the title bar if it hadn't been)
+    setTitleFocus = () => {
+        if (!StrCast(this.layoutDoc._showTitle)) this.layoutDoc._showTitle = 'title';
+        setTimeout(() => this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined
+    };
+
     onClick = action((e: React.MouseEvent | React.PointerEvent) => {
         if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
             let stopPropagate = true;
@@ -1176,6 +1160,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     rootSelected={this.rootSelected}
                     onClick={this.onClickFunc}
                     focus={this.props.focus}
+                    setTitleFocus={this.setTitleFocus}
                     layoutKey={this.finalLayoutKey}
                 />
                 {this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints}
@@ -1492,7 +1477,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 className={`${DocumentView.ROOT_DIV} docView-hack`}
                 ref={this._mainCont}
                 onContextMenu={this.onContextMenu}
-                onKeyDown={this.onKeyDown}
                 onPointerDown={this.onPointerDown}
                 onClick={this.onClick}
                 onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)}
diff --git a/src/client/views/nodes/button/ButtonScripts.ts b/src/client/views/nodes/button/ButtonScripts.ts
deleted file mode 100644
index b4a382faf..000000000
--- a/src/client/views/nodes/button/ButtonScripts.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { Colors } from "../../global/globalEnums";
-
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function changeView(view: string) {
-    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
-    selected ? selected.Document._viewType = view : console.log("[FontIconBox.tsx] changeView failed");
-});
-
-// toggle: Set overlay status of selected document
-ScriptingGlobals.add(function toggleOverlay(readOnly?: boolean) {
-    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
-    if (readOnly) return selected?.Document.z ? Colors.MEDIUM_BLUE : "transparent";
-    selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("failed");
-});
\ No newline at end of file
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 339887757..cb962cad3 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,5 +1,4 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { faAlignRight } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { action, computed, observable, runInAction } from 'mobx';
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index da10bb0dc..a2c1195cb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1669,8 +1669,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     onKeyDown = (e: React.KeyboardEvent) => {
-        if (e.altKey) {
+        if ((e.altKey || e.ctrlKey) && e.key === 't') {
             e.preventDefault();
+            e.stopPropagation();
+            this.props.setTitleFocus?.();
             return;
         }
         const state = this._editorView!.state;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 40da482d2..4d82551db 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -698,7 +698,7 @@ export namespace Doc {
     // this lists out all the tag ids that can be  in a RichTextField that might contain document ids.
     // if a document is cloned, we need to make sure to clone all of these referenced documents as well;
     export const DocsInTextFieldIds = ['audioId', 'textId', 'anchorId', 'docId'];
-    export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<string, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], cloneLinks: boolean, asBranch: boolean): Promise<Doc> {
+    export async function makeClone(doc: Doc, cloneMap: Map<string, Doc>, linkMap: Map<string, Doc>, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], cloneLinks: boolean): Promise<Doc> {
         if (Doc.IsBaseProto(doc)) return doc;
         if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!;
         const copy = new Doc(undefined, true);
@@ -714,7 +714,7 @@ export namespace Doc {
                     const list = await Cast(doc[key], listSpec(Doc));
                     const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc);
                     if (docs !== undefined && docs.length) {
-                        const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch)));
+                        const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, cloneLinks)));
                         assignKey(new List<Doc>(clones));
                     } else {
                         assignKey(ObjectField.MakeCopy(field));
@@ -731,7 +731,7 @@ export namespace Doc {
                                 );
                                 const results = docids && (await DocServer.GetRefFields(docids));
                                 const docs = results && Array.from(Object.keys(results)).map(key => DocCast(results[key]));
-                                docs && docs.map(doc => Doc.makeClone(doc, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch));
+                                docs && docs.map(doc => Doc.makeClone(doc, cloneMap, linkMap, rtfs, exclusions, cloneLinks));
                                 rtfs.push({ copy, key, field });
                             }
                         }
@@ -740,7 +740,7 @@ export namespace Doc {
                 const docAtKey = doc[key];
                 if (docAtKey instanceof Doc) {
                     if (!Doc.IsSystem(docAtKey) && (key.startsWith('layout') || key === 'annotationOn' || key === 'proto' || ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) {
-                        assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch));
+                        assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, cloneLinks));
                     } else {
                         assignKey(docAtKey);
                     }
@@ -763,14 +763,11 @@ export namespace Doc {
                 cloneLinks ||
                 ((cloneMap.has(DocCast(link.anchor1)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor1)?.annotationOn)?.[Id])) && (cloneMap.has(DocCast(link.anchor2)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor2)?.annotationOn)?.[Id])))
             ) {
-                linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch));
+                linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, cloneLinks));
             }
         });
-        Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true);
-        asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc);
-        if (!Doc.IsPrototype(copy)) {
-            Doc.AddDocToList(doc, 'branches', Doc.GetProto(copy));
-        }
+        Doc.SetInPlace(copy, 'title', 'CLONE: ' + doc.title, true);
+        copy.cloneOf = doc;
         cloneMap.set(doc[Id], copy);
 
         Doc.AddFileOrphan(copy);
@@ -794,14 +791,15 @@ export namespace Doc {
                 }
             });
     }
-    export function MakeClones(docs: Doc[], cloneLinks: boolean, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
-        return docs.map(doc => Doc.MakeClone(doc, cloneLinks, asBranch, cloneMap));
+    export function MakeClones(docs: Doc[], cloneLinks: boolean) {
+        const cloneMap = new Map<string, Doc>();
+        return docs.map(doc => Doc.MakeClone(doc, cloneLinks, cloneMap));
     }
 
-    export async function MakeClone(doc: Doc, cloneLinks = true, asBranch = false, cloneMap: Map<string, Doc> = new Map()) {
+    export async function MakeClone(doc: Doc, cloneLinks = true, cloneMap: Map<string, Doc> = new Map()) {
         const linkMap = new Map<string, Doc>();
         const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = [];
-        const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], cloneLinks, asBranch);
+        const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf'], cloneLinks);
         const repaired = new Set<Doc>();
         const linkedDocs = Array.from(linkMap.values());
         const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs];
-- 
cgit v1.2.3-70-g09d2


From 3a3e496dda5f9a1f5286a2c9f62524de59794ade Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 24 Mar 2023 03:37:41 -0400
Subject: overhaul of selection api so that schema and other views behave like
 freeform and use document views onClick for selection

---
 src/client/util/SelectionManager.ts                |  45 +--
 .../views/collections/CollectionCarousel3DView.tsx | 149 +++++-----
 src/client/views/collections/CollectionView.tsx    |   2 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   7 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   9 +-
 .../collectionFreeForm/MarqueeOptionsMenu.tsx      |   2 -
 .../CollectionMulticolumnView.tsx                  |   3 +-
 .../CollectionMultirowView.tsx                     |   3 +-
 .../collectionSchema/CollectionSchemaView.tsx      | 318 +++++++++------------
 .../collections/collectionSchema/SchemaRowBox.tsx  |  31 +-
 .../nodes/CollectionFreeFormDocumentView.scss      |   4 +-
 src/client/views/nodes/DocumentView.tsx            |  18 +-
 12 files changed, 282 insertions(+), 309 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 0f4f77588..313c255a0 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,3 +1,4 @@
+import { ModalManager } from '@material-ui/core';
 import { action, observable, ObservableMap } from 'mobx';
 import { computedFn } from 'mobx-utils';
 import { Doc, Opt } from '../../fields/Doc';
@@ -10,7 +11,8 @@ import { ScriptingGlobals } from './ScriptingGlobals';
 export namespace SelectionManager {
     class Manager {
         @observable IsDragging: boolean = false;
-        SelectedViews: ObservableMap<DocumentView, Doc> = new ObservableMap();
+        SelectedViewsMap: ObservableMap<DocumentView, Doc> = new ObservableMap();
+        @observable SelectedViews: DocumentView[] = [];
         @observable SelectedSchemaDocument: Doc | undefined;
 
         @action
@@ -20,7 +22,7 @@ export namespace SelectionManager {
         @action
         SelectView(docView: DocumentView, ctrlPressed: boolean): void {
             // if doc is not in SelectedDocuments, add it
-            if (!manager.SelectedViews.get(docView) && docView.props.Document.type !== DocumentType.MARKER) {
+            if (!manager.SelectedViewsMap.get(docView) && docView.props.Document.type !== DocumentType.MARKER) {
                 if (!ctrlPressed) {
                     if (LinkManager.currentLink && !LinkManager.Links(docView.rootDoc).includes(LinkManager.currentLink) && docView.rootDoc !== LinkManager.currentLink) {
                         LinkManager.currentLink = undefined;
@@ -28,33 +30,38 @@ export namespace SelectionManager {
                     this.DeselectAll();
                 }
 
-                manager.SelectedViews.set(docView, docView.rootDoc);
+                manager.SelectedViews.push(docView);
+                manager.SelectedViewsMap.set(docView, docView.rootDoc);
                 docView.props.whenChildContentsActiveChanged(true);
-            } else if (!ctrlPressed && (Array.from(manager.SelectedViews.entries()).length > 1 || manager.SelectedSchemaDocument)) {
-                Array.from(manager.SelectedViews.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false));
+            } else if (!ctrlPressed && (Array.from(manager.SelectedViewsMap.entries()).length > 1 || manager.SelectedSchemaDocument)) {
+                Array.from(manager.SelectedViewsMap.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false));
                 manager.SelectedSchemaDocument = undefined;
-                manager.SelectedViews.clear();
-                manager.SelectedViews.set(docView, docView.rootDoc);
+                manager.SelectedViews.length = 0;
+                manager.SelectedViewsMap.clear();
+                manager.SelectedViews.push(docView);
+                manager.SelectedViewsMap.set(docView, docView.rootDoc);
             }
         }
         @action
-        DeselectView(docView: DocumentView): void {
-            if (manager.SelectedViews.get(docView)) {
-                manager.SelectedViews.delete(docView);
+        DeselectView(docView?: DocumentView): void {
+            if (docView && manager.SelectedViewsMap.get(docView)) {
+                manager.SelectedViewsMap.delete(docView);
+                manager.SelectedViews.splice(manager.SelectedViews.indexOf(docView), 1);
                 docView.props.whenChildContentsActiveChanged(false);
             }
         }
         @action
         DeselectAll(): void {
             manager.SelectedSchemaDocument = undefined;
-            Array.from(manager.SelectedViews.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false));
-            manager.SelectedViews.clear();
+            Array.from(manager.SelectedViewsMap.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false));
+            manager.SelectedViewsMap.clear();
+            manager.SelectedViews.length = 0;
         }
     }
 
     const manager = new Manager();
 
-    export function DeselectView(docView: DocumentView): void {
+    export function DeselectView(docView?: DocumentView): void {
         manager.DeselectView(docView);
     }
     export function SelectView(docView: DocumentView, ctrlPressed: boolean): void {
@@ -67,7 +74,7 @@ export namespace SelectionManager {
 
     const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) {
         // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
-        return manager.SelectedViews.get(doc) ? true : false;
+        return manager.SelectedViewsMap.get(doc) ? true : false;
     });
     // computed functions, such as used in IsSelected generate errors if they're called outside of a
     // reaction context.  Specifying the context with 'outsideReaction' allows an efficiency feature
@@ -76,7 +83,7 @@ export namespace SelectionManager {
         return !doc
             ? false
             : outsideReaction
-            ? manager.SelectedViews.get(doc)
+            ? manager.SelectedViewsMap.get(doc)
                 ? true
                 : false // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get()
             : IsSelectedCache(doc);
@@ -85,7 +92,7 @@ export namespace SelectionManager {
     export function DeselectAll(except?: Doc): void {
         let found: DocumentView | undefined = undefined;
         if (except) {
-            for (const view of Array.from(manager.SelectedViews.keys())) {
+            for (const view of Array.from(manager.SelectedViewsMap.keys())) {
                 if (view.props.Document === except) found = view;
             }
         }
@@ -95,13 +102,15 @@ export namespace SelectionManager {
     }
 
     export function Views(): Array<DocumentView> {
-        return Array.from(manager.SelectedViews.keys()); //.filter(dv => manager.SelectedViews.get(dv)?._viewType !== CollectionViewType.Docking);
+        return manager.SelectedViews;
+        // Array.from(manager.SelectedViewsMap.keys()); //.filter(dv => manager.SelectedViews.get(dv)?._viewType !== CollectionViewType.Docking);
     }
     export function SelectedSchemaDoc(): Doc | undefined {
         return manager.SelectedSchemaDocument;
     }
     export function Docs(): Doc[] {
-        return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
+        return manager.SelectedViews.map(dv => dv.rootDoc).filter(doc => doc?._viewType !== CollectionViewType.Docking);
+        // Array.from(manager.SelectedViewsMap.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
     }
 }
 ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 01f41869e..57ff1b292 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -9,7 +9,7 @@ import { OmitKeys, returnFalse, Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { DocumentView } from '../nodes/DocumentView';
 import { StyleProp } from '../StyleProvider';
-import "./CollectionCarousel3DView.scss";
+import './CollectionCarousel3DView.scss';
 import { CollectionSubView } from './CollectionSubView';
 
 @observer
@@ -20,134 +20,143 @@ export class CollectionCarousel3DView extends CollectionSubView() {
 
     private _dropDisposer?: DragManager.DragDropDisposer;
 
-    componentWillUnmount() { this._dropDisposer?.(); }
+    componentWillUnmount() {
+        this._dropDisposer?.();
+    }
 
-    protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view
+    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+        //used for stacking and masonry view
         this._dropDisposer?.();
         if (ele) {
             this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
         }
-    }
+    };
 
     panelWidth = () => this.props.PanelWidth() / 3;
     panelHeight = () => this.props.PanelHeight() * 0.6;
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
+    isContentActive = () => this.props.isSelected() || this.props.isContentActive() || this.props.isAnyChildContentActive();
+    isChildContentActive = () => (this.isContentActive() ? true : false);
+
     @computed get content() {
         const currentIndex = NumCast(this.layoutDoc._itemIndex);
-        const displayDoc = (childPair: { layout: Doc, data: Doc }) => {
-            return <DocumentView  {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "childLayoutTemplate", "childLayoutString"]).omit}
-                onDoubleClick={this.onChildDoubleClick}
-                renderDepth={this.props.renderDepth + 1}
-                LayoutTemplate={this.props.childLayoutTemplate}
-                LayoutTemplateString={this.props.childLayoutString}
-                Document={childPair.layout}
-                DataDoc={childPair.data}
-                PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight}
-                bringToFront={returnFalse}
-            />;
+        const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
+            return (
+                <DocumentView
+                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'childLayoutTemplate', 'childLayoutString']).omit}
+                    onDoubleClick={this.onChildDoubleClick}
+                    renderDepth={this.props.renderDepth + 1}
+                    LayoutTemplate={this.props.childLayoutTemplate}
+                    LayoutTemplateString={this.props.childLayoutString}
+                    Document={childPair.layout}
+                    DataDoc={childPair.data}
+                    isContentActive={this.isChildContentActive}
+                    isDocumentActive={this.props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
+                    PanelWidth={this.panelWidth}
+                    PanelHeight={this.panelHeight}
+                    bringToFront={returnFalse}
+                />
+            );
         };
 
-        return (this.childLayoutPairs.map((childPair, index) => {
+        return this.childLayoutPairs.map((childPair, index) => {
             return (
-                <div key={childPair.layout[Id]}
-                    className={`collectionCarousel3DView-item${index === currentIndex ? "-active" : ""} ${index}`}
-                    style={index === currentIndex ?
-                        { opacity: '1', transform: 'scale(1.3)', width: this.panelWidth() } :
-                        { opacity: '0.5', transform: 'scale(0.6)', userSelect: 'none', width: this.panelWidth() }}>
+                <div
+                    key={childPair.layout[Id]}
+                    className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`}
+                    style={index === currentIndex ? { opacity: '1', transform: 'scale(1.3)', width: this.panelWidth() } : { opacity: '0.5', transform: 'scale(0.6)', userSelect: 'none', width: this.panelWidth() }}>
                     {displayDoc(childPair)}
-                </div>);
-        }));
+                </div>
+            );
+        });
     }
 
     changeSlide = (direction: number) => {
         this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length;
-    }
+    };
 
     onArrowClick = (e: React.MouseEvent, direction: number) => {
         e.stopPropagation();
         this.changeSlide(direction);
-        !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = (direction === 1) ? "fwd" : "back"); // while autoscroll is on, keep the other autoscroll button hidden
+        !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = direction === 1 ? 'fwd' : 'back'); // while autoscroll is on, keep the other autoscroll button hidden
         !this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on
-    }
+    };
 
     interval?: number;
     startAutoScroll = (direction: number) => {
         this.interval = window.setInterval(() => {
             this.changeSlide(direction);
         }, this.scrollSpeed);
-    }
+    };
 
     stopAutoScroll = () => {
         window.clearInterval(this.interval);
         this.interval = undefined;
         this.fadeScrollButton();
-    }
+    };
 
     toggleAutoScroll = (direction: number) => {
         this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true;
         this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll();
-    }
+    };
 
     fadeScrollButton = () => {
         window.setTimeout(() => {
-            !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = "none"); //fade away after 1.5s if it's not clicked.
+            !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); //fade away after 1.5s if it's not clicked.
         }, 1500);
-    }
+    };
 
     @computed get buttons() {
         if (!this.props.isContentActive()) return null;
-        return <div className="arrow-buttons" >
-            <div key="back" className="carousel3DView-back" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
-                onClick={(e) => this.onArrowClick(e, -1)}
-            >
-                <FontAwesomeIcon icon={"angle-left"} size={"2x"} />
-            </div>
-            <div key="fwd" className="carousel3DView-fwd" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
-                onClick={(e) => this.onArrowClick(e, 1)}
-            >
-                <FontAwesomeIcon icon={"angle-right"} size={"2x"} />
+        return (
+            <div className="arrow-buttons">
+                <div key="back" className="carousel3DView-back" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={e => this.onArrowClick(e, -1)}>
+                    <FontAwesomeIcon icon={'angle-left'} size={'2x'} />
+                </div>
+                <div key="fwd" className="carousel3DView-fwd" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={e => this.onArrowClick(e, 1)}>
+                    <FontAwesomeIcon icon={'angle-right'} size={'2x'} />
+                </div>
+                {this.autoScrollButton}
             </div>
-            {this.autoScrollButton}
-        </div>;
+        );
     }
 
     @computed get autoScrollButton() {
         const whichButton = this.layoutDoc.showScrollButton;
-        return <>
-            <div className={`carousel3DView-back-scroll${whichButton === "back" ? "" : "-hidden"}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
-                onClick={() => this.toggleAutoScroll(-1)}>
-                {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={"pause"} size={"1x"} /> : <FontAwesomeIcon icon={"angle-double-left"} size={"1x"} />}
-            </div>
-            <div className={`carousel3DView-fwd-scroll${whichButton === "fwd" ? "" : "-hidden"}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }}
-                onClick={() => this.toggleAutoScroll(1)}>
-                {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={"pause"} size={"1x"} /> : <FontAwesomeIcon icon={"angle-double-right"} size={"1x"} />}
-            </div>
-        </>;
+        return (
+            <>
+                <div className={`carousel3DView-back-scroll${whichButton === 'back' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(-1)}>
+                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-left'} size={'1x'} />}
+                </div>
+                <div className={`carousel3DView-fwd-scroll${whichButton === 'fwd' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(1)}>
+                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-right'} size={'1x'} />}
+                </div>
+            </>
+        );
     }
 
     @computed get dots() {
-        return (this.childLayoutPairs.map((_child, index) =>
-            <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._itemIndex) ? "-active" : ""}`}
-                onClick={() => this.layoutDoc._itemIndex = index} />));
+        return this.childLayoutPairs.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._itemIndex) ? '-active' : ''}`} onClick={() => (this.layoutDoc._itemIndex = index)} />);
     }
 
     render() {
         const index = NumCast(this.layoutDoc._itemIndex);
         const translateX = this.panelWidth() * (1 - index);
 
-        return <div className="collectionCarousel3DView-outer" ref={this.createDashEventsTarget}
-            style={{
-                background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
-                color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
-            }}  >
-            <div className="carousel-wrapper" style={{ transform: `translateX(${translateX}px)` }}>
-                {this.content}
-            </div>
-            {this.props.Document._chromeHidden ? (null) : this.buttons}
-            <div className="dot-bar">
-                {this.dots}
+        return (
+            <div
+                className="collectionCarousel3DView-outer"
+                ref={this.createDashEventsTarget}
+                style={{
+                    background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
+                    color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
+                }}>
+                <div className="carousel-wrapper" style={{ transform: `translateX(${translateX}px)` }}>
+                    {this.content}
+                </div>
+                {this.props.Document._chromeHidden ? null : this.buttons}
+                <div className="dot-bar">{this.dots}</div>
             </div>
-        </div>;
+        );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index eafa50d27..51624689e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -257,7 +257,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
         return StrCast(this.rootDoc.childLayoutString, this.props.childLayoutString);
     }
 
-    isContentActive = (outsideReaction?: boolean) => this.props.isContentActive();
+    isContentActive = (outsideReaction?: boolean) => this.props.isContentActive() || this.isAnyChildContentActive();
 
     render() {
         TraceMobx();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index fa0695fb2..81b0c4d8a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -116,6 +116,8 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
             zIndex: NumCast(layout.zIndex),
             pair: { layout, data },
             replica: '',
+            color: 'white',
+            backgroundColor: 'white',
         });
     });
     const divider = { type: 'div', color: 'transparent', x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined };
@@ -408,7 +410,7 @@ function normalizeResults(
         .map(ele => {
             const newPosRaw = ele[1];
             if (newPosRaw) {
-                const newPos = {
+                const newPos: PoolData = {
                     x: newPosRaw.x * scale,
                     y: newPosRaw.y * scale,
                     z: newPosRaw.z,
@@ -417,6 +419,9 @@ function normalizeResults(
                     zIndex: newPosRaw.zIndex,
                     width: (newPosRaw.width || 0) * scale,
                     height: newPosRaw.height! * scale,
+                    backgroundColor: newPosRaw.backgroundColor,
+                    opacity: newPosRaw.opacity,
+                    color: newPosRaw.color,
                     pair: ele[1].pair,
                 };
                 poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3a8edb1a5..4f81af95d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1291,10 +1291,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     };
     pointerEvents = () => {
         const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
-        const pointerEvents =
-            this.props.isContentActive() === false || DocumentDecorations.Instance.Interacting
-                ? 'none'
-                : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
+        const pointerEvents = DocumentDecorations.Instance.Interacting
+            ? 'none'
+            : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
         return pointerEvents;
     };
     getChildDocView(entry: PoolData) {
@@ -1995,7 +1994,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         TraceMobx();
         return (
             <div
-                className={'collectionfreeformview-container'}
+                className="collectionfreeformview-container"
                 ref={this.createDashEventsTarget}
                 onWheel={this.onPointerWheel}
                 onClick={this.onClick}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 9581563ce..c9168d40a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -3,8 +3,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
 import { observer } from 'mobx-react';
 import { unimplementedFunction } from '../../../../Utils';
-import { DocumentType } from '../../../documents/DocumentTypes';
-import { SelectionManager } from '../../../util/SelectionManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
 
 @observer
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index 88d045fa7..b73b1d779 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -2,14 +2,13 @@ import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../../fields/Doc';
-import { List } from '../../../../fields/List';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { returnFalse } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
-import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMulticolumnView.scss';
 import ResizeBar from './MulticolumnResizer';
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index f18917bef..0cca83803 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -2,14 +2,13 @@ import { action, computed } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, DocListCast } from '../../../../fields/Doc';
-import { List } from '../../../../fields/List';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { returnFalse } from '../../../../Utils';
 import { DragManager, dropActionType } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
-import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionMultirowView.scss';
 import HeightLabel from './MultirowHeightLabel';
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 151e6cbef..ad31113a2 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,15 +1,17 @@
 import React = require('react');
-import { action, computed, observable, ObservableMap, ObservableSet, untracked } from 'mobx';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, ObservableMap, trace, untracked } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, StrListCast } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
 import { RichTextField } from '../../../../fields/RichTextField';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTransparent, returnTrue, setupMoveUpEvents, smoothScroll, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
 import { Docs, DocUtils } from '../../../documents/Documents';
+import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { Transform } from '../../../util/Transform';
@@ -17,18 +19,14 @@ import { undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { ContextMenuProps } from '../../ContextMenuItem';
 import { EditableView } from '../../EditableView';
-import { DocComponentView, DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
+import { KeyValueBox } from '../../nodes/KeyValueBox';
+import { DefaultStyleProvider } from '../../StyleProvider';
 import { CollectionSubView } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
-import { DefaultStyleProvider } from '../../StyleProvider';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { ScriptField } from '../../../../fields/ScriptField';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { IconButton } from 'browndash-components';
-import { KeyValueBox } from '../../nodes/KeyValueBox';
 
 export enum ColumnType {
     Number,
@@ -43,7 +41,6 @@ const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate',
 @observer
 export class CollectionSchemaView extends CollectionSubView() {
     private _ref: HTMLDivElement | null = null;
-    private _selectedDocSortedArray: Doc[] = [];
     private _closestDropIndex: number = 0;
     private _previewRef: HTMLDivElement | null = null;
     private _makeNewColumn: boolean = false;
@@ -53,13 +50,11 @@ export class CollectionSchemaView extends CollectionSubView() {
     public static _rowMenuWidth: number = 100;
     public static _previewDividerWidth: number = 4;
 
-    @observable _lastSelectedRow: number | undefined;
     @computed get _selectedDocs() {
-        return new Set(SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.props.Document)));
+        return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.props.Document));
     }
     @observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
     @observable _colEles: HTMLDivElement[] = [];
-    @observable _isDragging: boolean = false;
     @observable _displayColumnWidths: number[] | undefined;
     @observable _columnMenuIndex: number | undefined;
     @observable _menuOptions: string[] = [];
@@ -133,7 +128,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     }
 
     componentDidMount() {
-        this.props.setContentView?.(this as DocComponentView);
+        this.props.setContentView?.(this);
         document.addEventListener('keydown', this.onKeyDown);
     }
 
@@ -143,24 +138,36 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     @action
     onKeyDown = (e: KeyboardEvent) => {
-        if (this._selectedDocs.size > 0) {
-            if (e.key == 'ArrowDown') {
-                const lastDoc = Array.from(this._selectedDocs.values()).lastElement();
-                const lastIndex = this.rowIndex(lastDoc);
-                if (lastIndex >= 0 && lastIndex < this.childDocs.length - 1) {
-                    !e.shiftKey && this.clearSelection();
-                    const newDoc = this.childDocs[lastIndex + 1];
-                    this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
-                }
-            }
-            if (e.key == 'ArrowUp') {
-                const firstDoc = Array.from(this._selectedDocs.values())[0];
-                const firstIndex = this.rowIndex(firstDoc);
-                if (firstIndex > 0 && firstIndex < this.childDocs.length) {
-                    !e.shiftKey && this.clearSelection();
-                    const newDoc = this.childDocs[firstIndex - 1];
-                    this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
-                }
+        if (this._selectedDocs.length > 0) {
+            switch (e.key) {
+                case 'ArrowDown':
+                    {
+                        const lastDoc = this._selectedDocs.lastElement();
+                        const lastIndex = this.rowIndex(lastDoc);
+                        const curDoc = this.childDocs[lastIndex];
+                        if (lastIndex >= 0 && lastIndex < this.childDocs.length - 1) {
+                            !e.shiftKey && this.clearSelection();
+                            const newDoc = this.childDocs[lastIndex + 1];
+                            if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+                            else this.addDocToSelection(newDoc, e.shiftKey, lastIndex + 1);
+                        }
+                        e.stopPropagation();
+                    }
+                    break;
+                case 'ArrowUp':
+                    {
+                        const firstDoc = this._selectedDocs.lastElement();
+                        const firstIndex = this.rowIndex(firstDoc);
+                        const curDoc = this.childDocs[firstIndex];
+                        if (firstIndex > 0 && firstIndex < this.childDocs.length) {
+                            !e.shiftKey && this.clearSelection();
+                            const newDoc = this.childDocs[firstIndex - 1];
+                            if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc));
+                            else this.addDocToSelection(newDoc, e.shiftKey, firstIndex - 1);
+                        }
+                        e.stopPropagation();
+                    }
+                    break;
             }
         }
     };
@@ -326,60 +333,25 @@ export class CollectionSchemaView extends CollectionSubView() {
     addDocToSelection = (doc: Doc, extendSelection: boolean, index: number) => {
         const rowDocView = DocumentManager.Instance.getDocumentView(doc);
         if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection);
-        this._lastSelectedRow = index;
-    };
-
-    @action
-    removeDocFromSelection = (doc: Doc) => {
-        const rowDocView = DocumentManager.Instance.getDocumentView(doc);
-        if (rowDocView) SelectionManager.DeselectView(rowDocView);
-        if (this._selectedDocs.size === 0) {
-            this._lastSelectedRow = undefined;
-        }
     };
 
     @action
-    clearSelection = () => {
-        SelectionManager.DeselectAll();
-        this._lastSelectedRow = undefined;
-    };
-
-    rowOnClickScript = ScriptField.MakeFunction('scriptContext.selectRow(self, shiftKey, ctrlKey || metaKey)', { scriptContext: 'any', shiftKey: 'boolean', ctrlKey: 'boolean', metaKey: 'boolean' })!;
-
-    @action
-    selectRow = (doc: Doc, shift: boolean, ctrl: boolean) => {
-        const index = this.childDocs.indexOf(doc);
-        if (index < 0) return;
-        if (shift && this._lastSelectedRow !== undefined) {
-            const startRow = Math.min(this._lastSelectedRow, index);
-            const endRow = Math.max(this._lastSelectedRow, index);
-            for (let i = startRow; i <= endRow; i++) {
-                const currDoc = this.childDocs[i];
-                if (!this._selectedDocs.has(currDoc)) this.addDocToSelection(currDoc, true, i);
-            }
-            this._lastSelectedRow = index;
-        } else if (ctrl) {
-            if (!this._selectedDocs.has(doc)) {
-                this.addDocToSelection(doc, true, index);
-            } else {
-                this.removeDocFromSelection(doc);
-            }
-        } else {
-            if (!this._selectedDocs.has(doc)) {
-                this.clearSelection();
-                this.addDocToSelection(doc, false, index);
-            }
+    clearSelection = () => SelectionManager.DeselectAll();
+
+    selectRows = (rootDoc: Doc, lastSelected: Doc) => {
+        const index = this.childDocs.indexOf(rootDoc);
+        const lastSelectedRow = this.childDocs.indexOf(lastSelected);
+        const startRow = Math.min(lastSelectedRow, index);
+        const endRow = Math.max(lastSelectedRow, index);
+        for (let i = startRow; i <= endRow; i++) {
+            const currDoc = this.childDocs[i];
+            if (!this._selectedDocs.includes(currDoc)) this.addDocToSelection(currDoc, true, i);
         }
     };
 
-    @action
-    sortedSelectedDocs = (): Doc[] => {
-        return this.childDocs.filter(doc => this._selectedDocs.has(doc));
-    };
+    sortedSelectedDocs = () => this.childDocs.filter(doc => this._selectedDocs.includes(doc));
 
-    setDropIndex = (index: number) => {
-        this._closestDropIndex = index;
-    };
+    setDropIndex = (index: number) => (this._closestDropIndex = index);
 
     @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
@@ -394,17 +366,20 @@ export class CollectionSchemaView extends CollectionSubView() {
                 return total + curr;
             }, CollectionSchemaView._rowMenuWidth);
             this.swapColumns(de.complete.columnDragData.colIndex, i);
+            e.stopPropagation();
             return true;
         }
-        if (super.onInternalDrop(e, de)) {
-            this._isDragging = false;
-            const pushedDocs: Doc[] = this.childDocs.filter((doc: Doc, index: number) => index >= this._closestDropIndex && !this._selectedDocs.has(doc));
+        const draggedDocs = de.complete.docDragData?.draggedDocuments;
+        if (draggedDocs && super.onInternalDrop(e, de)) {
+            const pushedDocs = this.childDocs.filter((doc, index) => index >= this._closestDropIndex && !draggedDocs.includes(doc));
             this.props.removeDocument?.(pushedDocs);
-            this.props.removeDocument?.(this._selectedDocSortedArray);
-            this.addDocument(this._selectedDocSortedArray);
+            this.props.removeDocument?.(draggedDocs);
+            this.addDocument(draggedDocs);
             this.addDocument(pushedDocs);
             this.setSort(undefined);
-            this.clearSelection();
+            SelectionManager.DeselectAll();
+            setTimeout(() => draggedDocs.forEach(doc => DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true))), 100);
+            e.stopPropagation();
             return true;
         }
         return false;
@@ -412,46 +387,11 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     @action
     onExternalDrop = async (e: React.DragEvent): Promise<void> => {
-        super.onExternalDrop(
-            e,
-            {},
-            undoBatch(
-                action(docus => {
-                    this._isDragging = false;
-                    docus.map((doc: Doc) => {
-                        this.addDocument(doc);
-                    });
-                })
-            )
-        );
+        super.onExternalDrop(e, {}, undoBatch(action(docus => docus.map((doc: Doc) => this.addDocument(doc)))));
         this.setSort(undefined);
     };
 
-    @action
-    startDrag = (e: PointerEvent, doc: Doc, index: number) => {
-        if (!this._selectedDocs.has(doc)) {
-            this.clearSelection();
-            this.addDocToSelection(doc, false, index);
-        }
-        this._isDragging = true;
-        this._selectedDocSortedArray = this.sortedSelectedDocs();
-        const dragData = new DragManager.DocumentDragData(this._selectedDocSortedArray, 'move');
-        dragData.moveDocument = this.props.moveDocument;
-        const dragItem: HTMLElement[] = Array.from(this._selectedDocs.values()).map((doc: Doc) => this._rowEles.get(doc));
-
-        DragManager.StartDocumentDrag(
-            dragItem.map(ele => ele),
-            dragData,
-            e.clientX,
-            e.clientY,
-            undefined
-        );
-        return true;
-    };
-
-    onDividerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction);
-    };
+    onDividerDown = (e: React.PointerEvent) => setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, emptyFunction);
 
     @action
     onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
@@ -557,9 +497,6 @@ export class CollectionSchemaView extends CollectionSubView() {
         return undefined;
     };
 
-    isChildContentActive = () =>
-        this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined;
-
     @computed get fieldDefaultInput() {
         switch (this._newFieldType) {
             case ColumnType.Number:
@@ -644,23 +581,17 @@ export class CollectionSchemaView extends CollectionSubView() {
         ContextMenu.Instance.clearItems();
         ContextMenu.Instance.addItem({
             description: 'Change field',
-            event: () => {
-                this.openColumnMenu(index, false);
-            },
+            event: () => this.openColumnMenu(index, false),
             icon: 'pencil-alt',
         });
         ContextMenu.Instance.addItem({
             description: 'Filter field',
-            event: () => {
-                this.openFilterMenu(index);
-            },
+            event: () => this.openFilterMenu(index),
             icon: 'filter',
         });
         ContextMenu.Instance.addItem({
             description: 'Delete column',
-            event: () => {
-                this.removeColumn(index);
-            },
+            event: () => this.removeColumn(index),
             icon: 'trash',
         });
         ContextMenu.Instance.displayMenu(x, y, undefined, false);
@@ -672,9 +603,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         this._menuOptions = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
     };
 
-    getFieldFilters = (field: string) => {
-        return StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
-    };
+    getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
 
     removeFieldFilters = (field: string) => {
         this.getFieldFilters(field).forEach(filter => {
@@ -694,9 +623,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
-        this._filterValue = e.target.value;
-    };
+    updateFilterSearch = (e: React.ChangeEvent<HTMLInputElement>) => (this._filterValue = e.target.value);
 
     @computed get newFieldMenu() {
         return (
@@ -705,7 +632,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                     <input
                         type="radio"
                         name="newFieldType"
-                        id=""
                         checked={this._newFieldType == ColumnType.Number}
                         onChange={action(() => {
                             this._newFieldType = ColumnType.Number;
@@ -718,7 +644,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                     <input
                         type="radio"
                         name="newFieldType"
-                        id=""
                         checked={this._newFieldType == ColumnType.Boolean}
                         onChange={action(() => {
                             this._newFieldType = ColumnType.Boolean;
@@ -731,7 +656,6 @@ export class CollectionSchemaView extends CollectionSubView() {
                     <input
                         type="radio"
                         name="newFieldType"
-                        id=""
                         checked={this._newFieldType == ColumnType.String}
                         onChange={action(() => {
                             this._newFieldType = ColumnType.String;
@@ -880,13 +804,11 @@ export class CollectionSchemaView extends CollectionSubView() {
         );
     }
 
-    tableWidthFunc = () => this.tableWidth;
-    rowHeightFunc = () => CollectionSchemaView._rowHeight;
-    rowClickScriptFunc = () => this.rowOnClickScript;
     isContentActive = () => this.props.isSelected() || this.props.isContentActive();
     screenToLocal = () => this.props.ScreenToLocalTransform().translate(-this.tableWidth, 0);
     previewWidthFunc = () => this.previewWidth;
     render() {
+        trace();
         return (
             <div
                 className="collectionSchemaView"
@@ -894,6 +816,12 @@ export class CollectionSchemaView extends CollectionSubView() {
                     this._ref = ele;
                     this.createDashEventsTarget(ele);
                 }}
+                onPointerDown={e => {
+                    // this is analogous to the panning code for a freeform view.
+                    // however, schema views don't pan so it does nothing.  but it does eat the pointerDown event
+                    // if the content is active to prevent the schema from being dragged
+                    this.isContentActive() && setupMoveUpEvents(this, e, returnFalse, emptyFunction, emptyFunction, false);
+                }}
                 onDrop={this.onExternalDrop.bind(this)}>
                 <div className="schema-table">
                     <div className="schema-header-row" style={{ height: CollectionSchemaView._rowHeight }}>
@@ -927,53 +855,16 @@ export class CollectionSchemaView extends CollectionSubView() {
                     </div>
                     {this._columnMenuIndex !== undefined && this.renderColumnMenu}
                     {this._filterColumnIndex !== undefined && this.renderFilterMenu}
-                    <div className="schema-table-content">
-                        {this.childDocs.map((doc: Doc, index: number) => {
-                            const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc;
-                            let dref: Opt<DocumentView>;
-                            return (
-                                <div className="schema-row-wrapper" style={{ maxHeight: CollectionSchemaView._rowHeight }}>
-                                    <DocumentView
-                                        {...this.props}
-                                        ref={r => (dref = r || undefined)}
-                                        LayoutTemplate={this.props.childLayoutTemplate}
-                                        LayoutTemplateString={SchemaRowBox.LayoutString(this.props.fieldKey)}
-                                        Document={doc}
-                                        DataDoc={dataDoc}
-                                        ContainingCollectionView={this.props.CollectionView}
-                                        ContainingCollectionDoc={this.Document}
-                                        PanelWidth={this.tableWidthFunc}
-                                        PanelHeight={this.rowHeightFunc}
-                                        styleProvider={DefaultStyleProvider}
-                                        focus={this.focusDocument}
-                                        docFilters={this.childDocFilters}
-                                        docRangeFilters={this.childDocRangeFilters}
-                                        searchFilterDocs={this.searchFilterDocs}
-                                        rootSelected={this.rootSelected}
-                                        ScreenToLocalTransform={Transform.Identity}
-                                        bringToFront={emptyFunction}
-                                        isDocumentActive={this.isContentActive}
-                                        isContentActive={emptyFunction}
-                                        hideDecorations={true}
-                                        hideTitle={true}
-                                        hideDocumentButtonBar={true}
-                                        hideLinkAnchors={true}
-                                        fitWidth={returnTrue}
-                                        onClick={this.rowClickScriptFunc}
-                                        scriptContext={this}
-                                    />
-                                </div>
-                            );
-                        })}
-                    </div>
+                    <CollectionSchemaViewDocs schema={this} />
+
                     <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
                 </div>
                 {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
                 {this.previewWidth > 0 && (
                     <div style={{ width: `${this.previewWidth}px` }} ref={ref => (this._previewRef = ref)}>
-                        {this._lastSelectedRow !== undefined && (
+                        {Array.from(this._selectedDocs).lastElement() && (
                             <DocumentView
-                                Document={this.childDocs[this._lastSelectedRow]}
+                                Document={Array.from(this._selectedDocs).lastElement()}
                                 DataDoc={undefined}
                                 fitContentsToBox={returnTrue}
                                 dontCenter={'y'}
@@ -1007,3 +898,54 @@ export class CollectionSchemaView extends CollectionSubView() {
         );
     }
 }
+
+interface CollectionSchemaViewDocsProps {
+    schema: CollectionSchemaView;
+}
+
+@observer
+class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsProps> {
+    tableWidthFunc = () => this.props.schema.tableWidth;
+    rowHeightFunc = () => CollectionSchemaView._rowHeight;
+    render() {
+        return (
+            <div className="schema-table-content">
+                {this.props.schema.childDocs.map((doc: Doc, index: number) => {
+                    const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.schema.props.DataDoc;
+                    return (
+                        <div className="schema-row-wrapper" style={{ maxHeight: CollectionSchemaView._rowHeight }}>
+                            <DocumentView
+                                {...this.props.schema.props}
+                                LayoutTemplate={this.props.schema.props.childLayoutTemplate}
+                                LayoutTemplateString={SchemaRowBox.LayoutString(this.props.schema.props.fieldKey)}
+                                Document={doc}
+                                DataDoc={dataDoc}
+                                ContainingCollectionView={this.props.schema.props.CollectionView}
+                                ContainingCollectionDoc={this.props.schema.Document}
+                                PanelWidth={this.tableWidthFunc}
+                                PanelHeight={this.rowHeightFunc}
+                                styleProvider={DefaultStyleProvider}
+                                focus={this.props.schema.focusDocument}
+                                docFilters={this.props.schema.childDocFilters}
+                                docRangeFilters={this.props.schema.childDocRangeFilters}
+                                searchFilterDocs={this.props.schema.searchFilterDocs}
+                                rootSelected={this.props.schema.rootSelected}
+                                ScreenToLocalTransform={Transform.Identity}
+                                bringToFront={emptyFunction}
+                                isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive}
+                                isContentActive={emptyFunction}
+                                whenChildContentsActiveChanged={active => this.props.schema.props.whenChildContentsActiveChanged(active)}
+                                hideDecorations={true}
+                                hideTitle={true}
+                                hideDocumentButtonBar={true}
+                                hideLinkAnchors={true}
+                                fitWidth={returnTrue}
+                                scriptContext={this}
+                            />
+                        </div>
+                    );
+                })}
+            </div>
+        );
+    }
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 87284be70..0c8c0ee59 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -1,9 +1,9 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed } from 'mobx';
+import { computed } from 'mobx';
 import { observer } from 'mobx-react';
-import { emptyFunction, setupMoveUpEvents } from '../../../../Utils';
 import { DragManager } from '../../../util/DragManager';
+import { SnappingManager } from '../../../util/SnappingManager';
 import { undoBatch } from '../../../util/UndoManager';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { Colors } from '../../global/globalEnums';
@@ -36,24 +36,30 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return this.schemaView?.rowIndex(this.rootDoc) ?? -1;
     }
 
-    @action
-    onRowPointerDown = (e: React.PointerEvent) => {
-        if (!this.isContentActive()) return;
-        setupMoveUpEvents(this, e, e => this.schemaView?.startDrag(e, this.rootDoc, this.rowIndex) ?? true, emptyFunction, emptyFunction);
+    componentDidMount(): void {
+        this.props.setContentView?.(this);
+    }
+
+    select = (ctrlKey: boolean, shiftKey: boolean) => {
+        if (!this.schemaView) return;
+        const lastSelected = Array.from(this.schemaView._selectedDocs).lastElement();
+        if (shiftKey && lastSelected) this.schemaView.selectRows(this.rootDoc, lastSelected);
+        else {
+            this.props.select?.(ctrlKey);
+        }
     };
 
     onPointerEnter = (e: any) => {
-        if (!this.schemaView?._isDragging) return;
+        //if (!this.schemaView?._isDragging) return;
+        if (!SnappingManager.GetIsDragging()) return;
         document.removeEventListener('pointermove', this.onPointerMove);
         document.addEventListener('pointermove', this.onPointerMove);
     };
 
     onPointerMove = (e: any) => {
-        if (!this.schemaView?._isDragging) return;
-        let dragIsRow: boolean = true;
-        DragManager.docsBeingDragged.forEach(doc => {
-            dragIsRow = this.schemaView?._selectedDocs.has(doc) ?? false;
-        });
+        if (!SnappingManager.GetIsDragging()) return;
+        const dragIsRow = DragManager.docsBeingDragged.some(doc => doc.context === this.schemaDoc); //  this.schemaView?._selectedDocs.has(doc) ?? false;
+
         if (this._ref && dragIsRow) {
             const rect = this._ref.getBoundingClientRect();
             const y = e.clientY - rect.top; //y position within the element.
@@ -88,7 +94,6 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         ? { height: CollectionSchemaView._rowHeight, backgroundColor: Colors.LIGHT_BLUE, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined /*, opacity: this.props.dragging ? 0.5 : 1 */ }
                         : { height: CollectionSchemaView._rowHeight, pointerEvents: this.schemaView?.props.isContentActive() ? 'all' : undefined }
                 }
-                onPointerDown={this.onRowPointerDown}
                 onPointerEnter={this.onPointerEnter}
                 onPointerLeave={this.onPointerLeave}
                 ref={(row: HTMLDivElement | null) => {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
index 724394025..f99011b8f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
@@ -5,5 +5,5 @@
     touch-action: manipulation;
     top: 0;
     left: 0;
-    pointer-events: none;
-}
\ No newline at end of file
+    //pointer-events: none;
+}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c13934945..1f717932e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -121,6 +121,7 @@ export interface DocComponentView {
     addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
     reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height.  However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
     shrinkWrap?: () => void; // requests a document to display all of its contents with no white space.  currently only implemented (needed?) for freeform views
+    select?: (ctrlKey: boolean, shiftKey: boolean) => void;
     menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
     isAnyChildContentActive?: () => boolean; // is any child content of the document active
     getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
@@ -236,7 +237,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
     NativeHeight: () => number;
     isSelected: (outsideReaction?: boolean) => boolean;
     isHovering: () => boolean;
-    select: (ctrlPressed: boolean) => void;
+    select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
     DocumentView: () => DocumentView;
     viewPath: () => DocumentView[];
 }
@@ -538,7 +539,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
 
     startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
         if (this._mainCont.current) {
-            const dragData = new DragManager.DocumentDragData([this.props.Document]);
+            const views = SelectionManager.Views().filter(dv => dv.docView?._mainCont.current);
+            const selected = views.some(dv => dv.rootDoc === this.Document) ? views : [this.props.DocumentView()];
+            const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.rootDoc));
             const [left, top] = this.props.ScreenToLocalTransform().scale(this.NativeDimScaling).inverse().transformPoint(0, 0);
             dragData.offset = this.props
                 .ScreenToLocalTransform()
@@ -551,8 +554,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             dragData.canEmbed = this.props.canEmbedOnDrag;
             const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
             ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
-            DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) }, () =>
-                setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))
+            DragManager.StartDocumentDrag(
+                selected.map(dv => dv.docView!._mainCont.current!),
+                dragData,
+                x,
+                y,
+                { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart && !this.props.dontHideOnDrag) },
+                () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))
             ); // this needs to happen after the drop event is processed.
             ffview?.setupDragLines(false);
         }
@@ -659,7 +667,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         }),
                         350
                     );
-                    this.props.select(e.ctrlKey || e.shiftKey);
+                    (this._componentView?.select ?? this.props.select)(e.ctrlKey || e.metaKey, e.shiftKey);
                 }
                 preventDefault = false;
             }
-- 
cgit v1.2.3-70-g09d2


From c5c2c309cd88bbeb2f1b668cb040cffaac9c8470 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 4 Apr 2023 20:12:16 -0400
Subject: fixed using freeformview in sidebar of pdfs. fixed issues with
 dragging items out of schema and with selecting the schema view by clicking
 on headers.  Fixed a lot of errors caused by using OmitKeys which masks type
 checking.  fixed some pointerevent problems with treeview and freeformview

---
 src/client/documents/Documents.ts                  |  22 ++--
 src/client/views/DocumentDecorations.tsx           |  40 +++---
 src/client/views/EditableView.tsx                  |   4 +-
 src/client/views/InkStrokeProperties.ts            |   1 -
 src/client/views/InkingStroke.tsx                  |  13 +-
 src/client/views/MainView.tsx                      |   1 +
 src/client/views/PropertiesView.tsx                |  73 ++---------
 src/client/views/SidebarAnnos.tsx                  |  15 +--
 src/client/views/StyleProvider.tsx                 |   2 +-
 src/client/views/TemplateMenu.tsx                  |   2 +-
 .../views/collections/CollectionCarousel3DView.tsx |   7 +-
 .../views/collections/CollectionCarouselView.tsx   |   8 +-
 .../collections/CollectionStackedTimeline.tsx      |  54 +++-----
 .../views/collections/CollectionTreeView.tsx       |  24 ++--
 src/client/views/collections/CollectionView.tsx    |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 139 +++++++++++---------
 .../collections/collectionFreeForm/MarqueeView.tsx |   2 +
 .../collectionGrid/CollectionGridView.tsx          |   9 +-
 .../collectionSchema/CollectionSchemaView.tsx      |  99 +++------------
 .../collectionSchema/SchemaColumnHeader.tsx        |  10 +-
 src/client/views/nodes/AudioBox.tsx                |   6 +-
 src/client/views/nodes/ComparisonBox.tsx           |   6 +-
 src/client/views/nodes/DocumentView.tsx            |  26 +---
 src/client/views/nodes/FieldView.tsx               |   2 +-
 src/client/views/nodes/ImageBox.tsx                |  12 +-
 src/client/views/nodes/MapBox/MapBox.tsx           |   6 +-
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx |   5 +-
 src/client/views/nodes/PDFBox.tsx                  | 141 ++++++++++++++++-----
 src/client/views/nodes/ScreenshotBox.tsx           |  18 ++-
 src/client/views/nodes/VideoBox.tsx                |  12 +-
 src/client/views/nodes/WebBox.tsx                  |  14 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  27 ++--
 src/client/views/pdf/PDFViewer.tsx                 |  15 ++-
 src/fields/Doc.ts                                  |   9 +-
 34 files changed, 389 insertions(+), 437 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9b8b9c877..9cb480c4a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1420,41 +1420,39 @@ export namespace DocUtils {
 
     export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined {
         let created: Doc | undefined;
-        let layout: ((fieldKey: string) => string) | undefined;
         const field = target[fieldKey];
-        const resolved = options || {};
+        const resolved = options ?? {};
         if (field instanceof ImageField) {
             created = Docs.Create.ImageDocument(field.url.href, resolved);
-            layout = ImageBox.LayoutString;
+            created.layout = ImageBox.LayoutString(fieldKey);
         } else if (field instanceof Doc) {
             created = field;
         } else if (field instanceof VideoField) {
             created = Docs.Create.VideoDocument(field.url.href, resolved);
-            layout = VideoBox.LayoutString;
+            created.layout = VideoBox.LayoutString(fieldKey);
         } else if (field instanceof PdfField) {
             created = Docs.Create.PdfDocument(field.url.href, resolved);
-            layout = PDFBox.LayoutString;
+            created.layout = PDFBox.LayoutString(fieldKey);
         } else if (field instanceof AudioField) {
             created = Docs.Create.AudioDocument(field.url.href, resolved);
-            layout = AudioBox.LayoutString;
+            created.layout = AudioBox.LayoutString(fieldKey);
         } else if (field instanceof RecordingField) {
             created = Docs.Create.RecordingDocument(field.url.href, resolved);
-            layout = RecordingBox.LayoutString;
+            created.layout = RecordingBox.LayoutString(fieldKey);
         } else if (field instanceof InkField) {
             created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, ActiveIsInkMask(), resolved);
-            layout = InkingStroke.LayoutString;
+            created.layout = InkingStroke.LayoutString(fieldKey);
         } else if (field instanceof List && field[0] instanceof Doc) {
             created = Docs.Create.StackingDocument(DocListCast(field), resolved);
-            layout = CollectionView.LayoutString;
+            created.layout = CollectionView.LayoutString(fieldKey);
         } else if (field instanceof MapField) {
             created = Docs.Create.MapDocument(DocListCast(field), resolved);
-            layout = MapBox.LayoutString;
+            created.layout = MapBox.LayoutString(fieldKey);
         } else {
             created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved });
-            layout = FormattedTextBox.LayoutString;
+            created.layout = FormattedTextBox.LayoutString(fieldKey);
         }
         if (created) {
-            created.layout = layout?.(fieldKey);
             created.title = fieldKey;
             proto && created.proto && (created.proto = Doc.GetProto(proto));
         }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 985e6f88f..9bc583ce5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -31,6 +31,7 @@ import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { ImageBox } from './nodes/ImageBox';
 import React = require('react');
+import { RichTextField } from '../../fields/RichTextField';
 
 @observer
 export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -127,19 +128,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
                         }
                         //@ts-ignore
                         const titleField = +this._accumulatedTitle == this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle;
-                        Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
 
-                        if (d.rootDoc.syncLayoutFieldWithTitle) {
-                            const title = titleField.toString();
+                        if (titleField.toString().startsWith('<this>')) {
+                            const title = titleField.toString().replace(/<this>\.?/, '');
                             const curKey = Doc.LayoutFieldKey(d.rootDoc);
-                            if (curKey !== title && d.dataDoc[title] === undefined) {
-                                d.rootDoc.layout = FormattedTextBox.LayoutString(title);
-                                setTimeout(() => {
-                                    const val = d.dataDoc[curKey];
-                                    d.dataDoc[curKey] = undefined;
-                                    d.dataDoc[title] = val;
-                                });
+                            if (curKey !== title) {
+                                if (title) {
+                                    if (d.dataDoc[title] === undefined || d.dataDoc[title] instanceof RichTextField || typeof d.dataDoc[title] === 'string') {
+                                        d.rootDoc.layoutKey = `layout_${title}`;
+                                        d.rootDoc[`layout_${title}`] = FormattedTextBox.LayoutString(title);
+                                        d.rootDoc[`${title}-nativeWidth`] = d.rootDoc[`${title}-nativeHeight`] = 0;
+                                    }
+                                } else {
+                                    d.rootDoc.layoutKey = undefined;
+                                }
                             }
+                        } else {
+                            Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true);
                         }
                     }),
                 'title blur'
@@ -453,17 +458,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
-        const views = SelectionManager.Views().map(dv => dv.rootDoc);
-        this._inkDragDocs = views
-            .filter(doc => doc.type === DocumentType.INK)
-            .map(doc => {
-                if (InkStrokeProperties.Instance._lock) {
-                    Doc.SetNativeHeight(doc, NumCast(doc._height));
-                    Doc.SetNativeWidth(doc, NumCast(doc._width));
-                }
-                return { doc, x: NumCast(doc.x), y: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) };
-            });
-
         setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
         this.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
         this._resizeHdlId = e.currentTarget.className;
@@ -487,10 +481,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
         if (!first) return false;
         let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY };
         var fixedAspect = Doc.NativeAspect(first.layoutDoc);
-        InkStrokeProperties.Instance._lock &&
-            SelectionManager.Views()
-                .filter(dv => dv.rootDoc.type === DocumentType.INK)
-                .forEach(dv => (fixedAspect = Doc.NativeAspect(dv.rootDoc)));
 
         const resizeHdl = this._resizeHdlId.split(' ')[0];
         if (fixedAspect && (resizeHdl === 'documentDecorations-bottomRightResizer' || resizeHdl === 'documentDecorations-topLeftResizer')) {
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index bb190e93b..164b6c57a 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -129,7 +129,7 @@ export class EditableView extends React.Component<EditableProps> {
                 this._editing = true;
                 this.props.isEditingCallback?.(true);
             }
-            e.stopPropagation();
+            // e.stopPropagation();
         }
     };
 
@@ -215,7 +215,7 @@ export class EditableView extends React.Component<EditableProps> {
                 className={`editableView-container-editing${this.props.oneLine ? '-oneLine' : ''}`}
                 ref={this._ref}
                 style={{ display: this.props.display, textOverflow: this.props.overflow, minHeight: '10px', whiteSpace: 'nowrap', height: this.props.height || 'auto', maxHeight: this.props.maxHeight }}
-                onPointerDown={e => e.stopPropagation()}
+                //onPointerDown={this.stopPropagation}
                 onClick={this.onClick}
                 placeholder={this.props.placeholder}>
                 <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 1d8d52425..e6df0801c 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -19,7 +19,6 @@ export class InkStrokeProperties {
         return this._Instance || new InkStrokeProperties();
     }
 
-    @observable _lock = false;
     @observable _controlButton = false;
     @observable _currentPoint = -1;
 
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index e3642fdaf..2fd6cc4d6 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -25,10 +25,11 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, HeightSym, WidthSym } from '../../fields/Doc';
 import { InkData, InkField } from '../../fields/InkField';
-import { BoolCast, Cast, DocCast, NumCast, RTFCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, NumCast, RTFCast, StrCast } from '../../fields/Types';
 import { TraceMobx } from '../../fields/util';
-import { DashColor, OmitKeys, returnFalse, setupMoveUpEvents } from '../../Utils';
+import { DashColor, returnFalse, setupMoveUpEvents } from '../../Utils';
 import { CognitiveServices } from '../cognitive_services/CognitiveServices';
+import { Docs } from '../documents/Documents';
 import { InteractionUtils } from '../util/InteractionUtils';
 import { SnappingManager } from '../util/SnappingManager';
 import { Transform } from '../util/Transform';
@@ -41,13 +42,12 @@ import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles';
 import './InkStroke.scss';
 import { InkStrokeProperties } from './InkStrokeProperties';
 import { InkTangentHandles } from './InkTangentHandles';
-import { DocComponentView, DocFocusOptions, DocumentView } from './nodes/DocumentView';
+import { DocComponentView } from './nodes/DocumentView';
 import { FieldView, FieldViewProps } from './nodes/FieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { PinProps, PresBox } from './nodes/trails';
 import { StyleProp } from './StyleProvider';
 import Color = require('color');
-import { Docs } from '../documents/Documents';
-import { PinProps, PresBox } from './nodes/trails';
 
 @observer
 export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -464,7 +464,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() {
                             //top: (this.props.PanelHeight() - (lineHeightGuess * fsize + 20) * (this.props.NativeDimScaling?.() || 1)) / 2,
                         }}>
                         <FormattedTextBox
-                            {...OmitKeys(this.props, ['children']).omit}
+                            {...this.props}
+                            setHeight={undefined}
                             setContentView={this.setSubContentView} // this makes the inkingStroke the "dominant" component - ie, it will show the inking UI when selected (not text)
                             yPadding={10}
                             xPadding={10}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f5adc17d0..befb0200d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -259,6 +259,7 @@ export class MainView extends React.Component {
                 fa.faConciergeBell,
                 fa.faWindowRestore,
                 fa.faFolder,
+                fa.faFolderOpen,
                 fa.faMapPin,
                 fa.faMapMarker,
                 fa.faFingerprint,
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index f3a5a5393..fbc7d7696 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -102,7 +102,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
 
     @observable inOptions: boolean = false;
     @observable _controlButton: boolean = false;
-    @observable _lock: boolean = false;
 
     componentDidMount() {
         this.selectedDocListenerDisposer?.();
@@ -586,16 +585,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                         <FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
                     </div>
                 </Tooltip>
-                <Tooltip title={<div className="dash-tooltip">{InkStrokeProperties.Instance._lock ? 'Unlock ratio' : 'Lock ratio'}</div>}>
-                    <div className="inking-button-lock" onPointerDown={action(() => (InkStrokeProperties.Instance._lock = !InkStrokeProperties.Instance._lock))}>
-                        <FontAwesomeIcon icon={InkStrokeProperties.Instance._lock ? 'lock' : 'unlock'} color="white" size="lg" />
-                    </div>
-                </Tooltip>
-                <Tooltip title={<div className="dash-tooltip">{'Rotate 90˚'}</div>}>
-                    <div className="inking-button-rotate" onPointerDown={action(() => this.rotate(Math.PI / 2))}>
-                        <FontAwesomeIcon icon="undo" color="white" size="lg" />
-                    </div>
-                </Tooltip>
             </div>
         );
     }
@@ -644,9 +633,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @action
     upDownButtons = (dirs: string, field: string) => {
         switch (field) {
-            case 'rot':
-                this.rotate(dirs === 'up' ? 0.1 : -0.1);
-                break;
             case 'Xps':
                 this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10));
                 break;
@@ -662,7 +648,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 const oldX = NumCast(this.selectedDoc?.x);
                 const oldY = NumCast(this.selectedDoc?.y);
                 this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === 'up' ? 10 : -10));
-                InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth) * NumCast(this.selectedDoc?._height));
                 const doc = this.selectedDoc;
                 if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
                     const ink = Cast(doc.data, InkField)?.inkData;
@@ -684,7 +669,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                 const oX = NumCast(this.selectedDoc?.x);
                 const oY = NumCast(this.selectedDoc?.y);
                 this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === 'up' ? 10 : -10));
-                InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight) * NumCast(this.selectedDoc?._width));
                 const docu = this.selectedDoc;
                 if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) {
                     const ink = Cast(docu.data, InkField)?.inkData;
@@ -704,11 +688,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     };
 
     getField(key: string) {
-        //if (this.selectedDoc) {
         return Field.toString(this.selectedDoc?.[key] as Field);
-        // } else {
-        //     return undefined as Opt<string>;
-        // }
     }
 
     @computed get shapeXps() {
@@ -717,9 +697,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @computed get shapeYps() {
         return this.getField('y');
     }
-    @computed get shapeRot() {
-        return this.getField('rotation');
-    }
     @computed get shapeHgt() {
         return this.getField('_height');
     }
@@ -732,18 +709,11 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     set shapeYps(value) {
         this.selectedDoc && (this.selectedDoc.y = Number(value));
     }
-    set shapeRot(value) {
-        this.selectedDoc && (this.selectedDoc.rotation = Number(value));
-    }
     set shapeWid(value) {
-        const oldWidth = NumCast(this.selectedDoc?._width);
         this.selectedDoc && (this.selectedDoc._width = Number(value));
-        InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth);
     }
     set shapeHgt(value) {
-        const oldHeight = NumCast(this.selectedDoc?._height);
         this.selectedDoc && (this.selectedDoc._height = Number(value));
-        InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
     }
 
     @computed get hgtInput() {
@@ -790,30 +760,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
             'Y:'
         );
     }
-    @computed get rotInput() {
-        return this.inputBoxDuo(
-            'rot',
-            this.shapeRot,
-            (val: string) => {
-                if (!isNaN(Number(val))) {
-                    this.rotate(Number(val) - Number(this.shapeRot));
-                    this.shapeRot = val;
-                }
-                return true;
-            },
-            '∠:',
-            'rot',
-            this.shapeRot,
-            (val: string) => {
-                if (!isNaN(Number(val))) {
-                    this.rotate(Number(val) - Number(this.shapeRot));
-                    this.shapeRot = val;
-                }
-                return true;
-            },
-            ''
-        );
-    }
 
     @observable private _fillBtn = false;
     @observable private _lineBtn = false;
@@ -1080,10 +1026,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
     @computed get transformEditor() {
         return (
             <div className="transform-editor">
-                {this.controlPointsButton}
+                {this.isInk ? this.controlPointsButton : null}
                 {this.hgtInput}
                 {this.XpsInput}
-                {this.rotInput}
             </div>
         );
     }
@@ -1194,17 +1139,15 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     </div>
                 )}
 
-                {this.isInk ? (
-                    <div className="propertiesView-transform">
-                        <div className="propertiesView-transform-title" onPointerDown={action(() => (this.openTransform = !this.openTransform))} style={{ backgroundColor: this.openTransform ? 'black' : '' }}>
-                            Transform
-                            <div className="propertiesView-transform-title-icon">
-                                <FontAwesomeIcon icon={this.openTransform ? 'caret-down' : 'caret-right'} size="lg" color="white" />
-                            </div>
+                <div className="propertiesView-transform">
+                    <div className="propertiesView-transform-title" onPointerDown={action(() => (this.openTransform = !this.openTransform))} style={{ backgroundColor: this.openTransform ? 'black' : '' }}>
+                        Transform
+                        <div className="propertiesView-transform-title-icon">
+                            <FontAwesomeIcon icon={this.openTransform ? 'caret-down' : 'caret-right'} size="lg" color="white" />
                         </div>
-                        {this.openTransform ? <div className="propertiesView-transform-content">{this.transformEditor}</div> : null}
                     </div>
-                ) : null}
+                    {this.openTransform ? <div className="propertiesView-transform-content">{this.transformEditor}</div> : null}
+                </div>
             </>
         );
     }
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 02dd15960..48c0150e6 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -5,7 +5,7 @@ import { Id } from '../../fields/FieldSymbols';
 import { List } from '../../fields/List';
 import { RichTextField } from '../../fields/RichTextField';
 import { DocCast, NumCast, StrCast } from '../../fields/Types';
-import { emptyFunction, OmitKeys, returnAll, returnOne, returnTrue, returnZero } from '../../Utils';
+import { emptyFunction, returnAll, returnFalse, returnOne, returnTrue, returnZero } from '../../Utils';
 import { Docs, DocUtils } from '../documents/Documents';
 import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
 import { LinkManager } from '../util/LinkManager';
@@ -26,6 +26,7 @@ interface ExtraProps {
     // usePanelWidth: boolean;
     showSidebar: boolean;
     nativeWidth: number;
+    usePanelWidth?: boolean;
     whenChildContentsActiveChanged: (isActive: boolean) => void;
     ScreenToLocalTransform: () => Transform;
     sidebarAddDocument: (doc: Doc | Doc[], suffix: string) => boolean;
@@ -158,13 +159,10 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
             .ScreenToLocalTransform()
             .translate(Doc.NativeWidth(this.props.dataDoc), 0)
             .scale(this.props.NativeDimScaling?.() || 1);
-    // panelWidth = () => !this.props.layoutDoc._showSidebar ? 0 :
-    //     this.props.usePanelWidth ? this.props.PanelWidth() :
-    //         (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth);
     panelWidth = () =>
         !this.props.showSidebar
             ? 0
-            : this.props.layoutDoc.type === DocumentType.RTF || this.props.layoutDoc.type === DocumentType.MAP
+            : this.props.usePanelWidth // [DocumentType.RTF, DocumentType.MAP].includes(this.props.layoutDoc.type as any)
             ? this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1)
             : ((NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth()) / NumCast(this.props.nativeWidth);
     panelHeight = () => this.props.PanelHeight() - this.filtersHeight();
@@ -224,20 +222,21 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                 </div>
                 <div style={{ width: '100%', height: `calc(100% - 38px)`, position: 'relative' }}>
                     <CollectionStackingView
-                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
-                        ref={this._stackRef}
+                        {...this.props}
+                        setContentView={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
+                        ref={this._stackRef}
                         PanelHeight={this.panelHeight}
                         PanelWidth={this.panelWidth}
                         docFilters={this.docFilters}
-                        scaleField={this.sidebarKey + '-scale'}
                         sortFunc={this.sortByLinkAnchorY}
                         setHeight={this.setHeightCallback}
                         isAnnotationOverlay={false}
                         select={emptyFunction}
                         NativeDimScaling={returnOne}
                         //childShowTitle={this.showTitle}
+                        isAnyChildContentActive={returnFalse}
                         childDocumentsActive={this.props.isContentActive}
                         whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                         childHideDecorationTitle={returnTrue}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 50cf8eba7..f7b9420a7 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -170,7 +170,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
             if (docColor) return docColor;
             const docView = props?.DocumentView?.();
-            const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, 'backgroundColor');
+            const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, StyleProp.BackgroundColor);
             if (!backColor) return undefined;
             return lightOrDark(backColor);
         case StyleProp.Hidden:
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 681ff66e0..45db240a9 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -79,7 +79,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     };
     componentDidMount() {
         !this._addedKeys && (this._addedKeys = new ObservableSet());
-        Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document)))
+        [...Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document))), ...Array.from(Object.keys(this.props.docViews[0].props.Document))]
             .filter(key => key.startsWith('layout_'))
             .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', ''))));
     }
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 57ff1b292..a266c9207 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
 import { Doc } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { OmitKeys, returnFalse, Utils } from '../../../Utils';
+import { returnFalse, returnZero, Utils } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { DocumentView } from '../nodes/DocumentView';
 import { StyleProp } from '../StyleProvider';
@@ -43,7 +43,10 @@ export class CollectionCarousel3DView extends CollectionSubView() {
         const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
             return (
                 <DocumentView
-                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'childLayoutTemplate', 'childLayoutString']).omit}
+                    {...this.props}
+                    NativeWidth={returnZero}
+                    NativeHeight={returnZero}
+                    suppressSetHeight={true}
                     onDoubleClick={this.onChildDoubleClick}
                     renderDepth={this.props.renderDepth + 1}
                     LayoutTemplate={this.props.childLayoutTemplate}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 32f6207ed..c0220f804 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { OmitKeys, returnFalse } from '../../../Utils';
+import { emptyFunction, returnFalse, returnZero } from '../../../Utils';
 import { DragManager } from '../../util/DragManager';
 import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
@@ -47,7 +47,7 @@ export class CollectionCarouselView extends CollectionSubView() {
     @computed get content() {
         const index = NumCast(this.layoutDoc._itemIndex);
         const curDoc = this.childLayoutPairs?.[index];
-        const captionProps = { ...OmitKeys(this.props, ['setHeight']).omit, fieldKey: 'caption' };
+        const captionProps = { ...this.props, fieldKey: 'caption', setHeight: undefined };
         const marginX = NumCast(this.layoutDoc['caption-xMargin']);
         const marginY = NumCast(this.layoutDoc['caption-yMargin']);
         const showCaptions = StrCast(this.layoutDoc._showCaption);
@@ -55,7 +55,9 @@ export class CollectionCarouselView extends CollectionSubView() {
             <>
                 <div className="collectionCarouselView-image" key="image">
                     <DocumentView
-                        {...OmitKeys(this.props, ['setHeight', 'NativeWidth', 'NativeHeight', 'childLayoutTemplate', 'childLayoutString']).omit}
+                        {...this.props}
+                        NativeWidth={returnZero}
+                        NativeHeight={returnZero}
                         onDoubleClick={this.onContentDoubleClick}
                         onClick={this.onContentClick}
                         hideCaptions={showCaptions ? true : false}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 4941bc722..642d29d2a 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -9,7 +9,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { Cast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, OmitKeys, returnFalse, returnNone, returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
+import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -23,7 +23,6 @@ import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { AudioWaveform } from '../AudioWaveform';
 import { CollectionSubView } from '../collections/CollectionSubView';
-import { Colors } from '../global/globalEnums';
 import { LightboxView } from '../LightboxView';
 import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
 import { LabelBox } from '../nodes/LabelBox';
@@ -512,37 +511,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     currentTimecode = () => this.currentTime;
 
-    @computed get renderDictation() {
-        const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null);
-        return !dictation ? null : (
-            <div
-                style={{
-                    position: 'absolute',
-                    height: '100%',
-                    top: this.timelineContentHeight,
-                    background: Colors.LIGHT_BLUE,
-                }}>
-                <DocumentView
-                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
-                    Document={dictation}
-                    PanelHeight={this.dictationHeight}
-                    isAnnotationOverlay={true}
-                    isDocumentActive={returnFalse}
-                    select={emptyFunction}
-                    NativeDimScaling={returnOne}
-                    xMargin={25}
-                    yMargin={10}
-                    ScreenToLocalTransform={this.dictationScreenToLocalTransform}
-                    whenChildContentsActiveChanged={emptyFunction}
-                    removeDocument={returnFalse}
-                    moveDocument={returnFalse}
-                    addDocument={returnFalse}
-                    CollectionView={undefined}
-                    renderDepth={this.props.renderDepth + 1}></DocumentView>
-            </div>
-        );
-    }
-
     // renders selection region on timeline
     @computed get selectionContainer() {
         const markerEnd = CollectionStackedTimeline.SelectingRegion === this ? this.currentTime : this._markerEnd;
@@ -638,7 +606,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                                 PanelWidth={this.timelineContentWidth}
                             />
                         )}
-                        {/* {this.renderDictation} */}
 
                         <div
                             className="collectionStackedTimeline-hover"
@@ -817,16 +784,22 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
     // renders anchor LabelBox
     renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
         const anchor = observable({ view: undefined as any });
-        const focusFunc = (doc: Doc, options: DocFocusOptions) => this.props.playLink(mark);
+        const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => {
+            this.props.playLink(mark);
+            return undefined;
+        };
         return {
             anchor,
             view: (
                 <DocumentView
                     key="view"
-                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+                    {...this.props}
+                    NativeWidth={returnZero}
+                    NativeHeight={returnZero}
                     ref={action((r: DocumentView | null) => (anchor.view = r))}
                     Document={mark}
                     DataDoc={undefined}
+                    docViewPath={returnEmptyDoclist}
                     pointerEvents={this.noEvents ? returnNone : undefined}
                     styleProvider={this.props.styleProvider}
                     renderDepth={this.props.renderDepth + 1}
@@ -837,7 +810,16 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     PanelHeight={height}
                     fitWidth={returnTrue}
                     ScreenToLocalTransform={screenXf}
+                    addDocTab={returnFalse}
+                    pinToPres={emptyFunction}
+                    whenChildContentsActiveChanged={emptyFunction}
                     focus={focusFunc}
+                    isContentActive={returnFalse}
+                    ContainingCollectionView={undefined}
+                    ContainingCollectionDoc={undefined}
+                    searchFilterDocs={returnEmptyDoclist}
+                    docFilters={returnEmptyFilter}
+                    docRangeFilters={returnEmptyFilter}
                     rootSelected={returnFalse}
                     onClick={script}
                     onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 553967b95..4a11e8f0b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -7,7 +7,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../Utils';
+import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager, dropActionType } from '../../util/DragManager';
@@ -384,12 +384,12 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
         this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false;
 
     @observable _headerHeight = 0;
-    contentFunc = () => {
+    @computed get content() {
         const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
         const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined);
         const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? null : this.titleBar;
-        return [
-            <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
+        return (
+            <div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
                 {!this.buttonMenu && !this.noviceExplainer ? null : (
                     <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = Number(getComputedStyle(r).height.replace(/px/, ''))))}>
                         {this.buttonMenu}
@@ -428,9 +428,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                         </div>
                     </div>
                 </div>
-            </div>,
-        ];
-    };
+            </div>
+        );
+    }
     render() {
         TraceMobx();
 
@@ -439,7 +439,11 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
             <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}>
                 {!(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? (
                     <CollectionFreeFormView
-                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                        {...this.props}
+                        setContentView={emptyFunction}
+                        NativeWidth={returnZero}
+                        NativeHeight={returnZero}
+                        pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone}
                         isAnnotationOverlay={true}
                         isAnnotationOverlayScrollable={true}
                         childDocumentsActive={this.props.isDocumentActive}
@@ -451,10 +455,10 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
                         moveDocument={this.moveAnnotationDocument}
                         bringToFront={emptyFunction}
                         renderDepth={this.props.renderDepth + 1}>
-                        {this.contentFunc}
+                        {this.content}
                     </CollectionFreeFormView>
                 ) : (
-                    this.contentFunc()
+                    this.content
                 )}
             </div>
         );
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 51624689e..bc25ad43a 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -45,7 +45,7 @@ interface CollectionViewProps_ extends FieldViewProps {
 
     // property overrides for child documents
     childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
-    childDocumentsActive?: () => boolean; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
+    childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
     childFitWidth?: (child: Doc) => boolean;
     childShowTitle?: () => string;
     childOpacity?: () => number;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index aed3683d4..d39668a5d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
 import { Bezier } from 'bezier-js';
 import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { DateField } from '../../../../fields/DateField';
@@ -53,10 +53,14 @@ import { MarqueeView } from './MarqueeView';
 import React = require('react');
 
 export type collectionFreeformViewProps = {
+    noPointerWheel?: () => boolean; // turn off pointerwheel interactions (see PDFViewer)
+    NativeWidth?: () => number;
+    NativeHeight?: () => number;
+    originTopLeft?: boolean;
     annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
     viewDefDivClick?: ScriptField;
     childPointerEvents?: string;
-    scaleField?: string;
+    viewField?: string;
     noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
     engineProps?: any;
     getScrollHeight?: () => number | undefined;
@@ -97,8 +101,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     private get isAnnotationOverlay() {
         return this.props.isAnnotationOverlay;
     }
-    private get scaleFieldKey() {
-        return this.props.scaleField || '_viewScale';
+    public get scaleFieldKey() {
+        return this.props.viewField ? this.props.viewField + '-viewScale' : '_viewScale';
+    }
+    private get panXFieldKey() {
+        return this.props.viewField ? this.props.viewField + '-panX' : '_panX';
+    }
+    private get panYFieldKey() {
+        return this.props.viewField ? this.props.viewField + '-panY' : '_panY';
     }
     private get borderWidth() {
         return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
@@ -149,21 +159,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                   );
     }
     @computed get nativeWidth() {
-        return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+        return this.props.NativeWidth?.() || (this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
     }
     @computed get nativeHeight() {
-        return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+        return this.props.NativeHeight?.() || (this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
     }
     @computed get cachedCenteringShiftX(): number {
         const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
-        return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+        return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedCenteringShiftY(): number {
         const dv = this.props.DocumentView?.();
         const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
         // if freeform has a native aspect, then the panel height needs to be adjusted to match it
         const aspect = dv?.nativeWidth && dv?.nativeHeight && !dv.layoutDoc.fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
-        return this.props.isAnnotationOverlay ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+        return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
     }
     @computed get cachedGetLocalTransform(): Transform {
         return Transform.Identity()
@@ -236,13 +246,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     reverseNativeScaling = () => (this.fitContentsToBox ? true : false);
     // panx, pany, zoomscale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
     // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
-    panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1));
-    panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1));
+    panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1));
+    panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1));
     zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
     contentTransform = () =>
-        !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1
-            ? ''
-            : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+        this.props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
     getTransform = () => this.cachedGetTransform.copy();
     getLocalTransform = () => this.cachedGetLocalTransform.copy();
     getContainerTransform = () => this.cachedGetContainerTransform.copy();
@@ -293,7 +301,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     groupFocus = (anchor: Doc, options: DocFocusOptions) => {
-        options.docTransform = new Transform(-NumCast(this.rootDoc.panX) + NumCast(anchor.x), -NumCast(this.rootDoc.panY) + NumCast(anchor.y), 1);
+        options.docTransform = new Transform(-NumCast(this.rootDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.rootDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
         const res = this.props.focus(this.rootDoc, options);
         options.docTransform = undefined;
         return res;
@@ -301,7 +309,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     focus = (anchor: Doc, options: DocFocusOptions) => {
         const xfToCollection = options?.docTransform ?? Transform.Identity();
-        const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
+        const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
         const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
         const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale || 0.75 : undefined);
 
@@ -772,7 +780,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         PresBox.Instance?.pauseAutoPres();
         const dx = e.deltaX;
         const dy = e.deltaY;
-        this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
+        this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
     };
 
     @action
@@ -780,7 +788,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         PresBox.Instance?.pauseAutoPres();
         this.props.DocumentView?.().clearViewTransition();
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
-        this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
+        this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
         this._lastX = e.clientX;
         this._lastY = e.clientY;
     };
@@ -979,13 +987,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
             const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
             this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
-            this.setPan(-localTransform.TranslateX / safeScale, NumCast(this.props.Document.scrollTop) * safeScale || -localTransform.TranslateY / safeScale);
+            this.setPan(-localTransform.TranslateX / safeScale, (this.props.originTopLeft ? undefined : NumCast(this.props.Document.scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
         }
     };
 
     @action
     onPointerWheel = (e: React.WheelEvent): void => {
-        if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
+        if (this.props.noPointerWheel?.() || this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
         PresBox.Instance?.pauseAutoPres();
         if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
         e.stopPropagation();
@@ -995,7 +1003,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 // if ctrl is selected then zoom
                 if (e.ctrlKey) {
                     if (this.props.isContentActive(true)) {
-                        !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+                        this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
                     }
                 } // otherwise pan
                 else if (this.props.isContentActive(true)) {
@@ -1039,16 +1047,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                         yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) },
                     }),
                     {
-                        xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
-                        yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
+                        xrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
+                        yrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
                     }
                 );
 
-                const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
-                if (ranges.xrange.min >= panX + panelDim[0] / 2) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
-                else if (ranges.xrange.max <= panX - panelDim[0] / 2) panX = ranges.xrange.min - panelDim[0] / 2;
-                if (ranges.yrange.min >= panY + panelDim[1] / 2) panY = ranges.yrange.max + panelDim[1] / 2;
-                else if (ranges.yrange.max <= panY - panelDim[1] / 2) panY = ranges.yrange.min - panelDim[1] / 2;
+                const panelWidMax = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+                const panelWidMin = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
+                const panelHgtMax = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+                const panelHgtMin = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
+                if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this.props.originTopLeft ? 0 : panelWidMax / 2);
+                else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this.props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
+                if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this.props.originTopLeft ? 0 : panelHgtMax / 2);
+                else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this.props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
             }
         }
         if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(Doc.MyOverlayDocs?.data).includes(this.Document)) {
@@ -1078,8 +1089,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 }, 10);
                 newPanY = minPanY;
             }
-            !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX);
-            !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY);
+            !this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX);
+            !this.Document._horizontalScroll && (this.Document[this.panYFieldKey] = this.isAnnotationOverlay ? newPanY : panY);
         }
     }
 
@@ -1087,8 +1098,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     nudge = (x: number, y: number, nudgeTime: number = 500) => {
         if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) {
             this.setPan(
-                NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
-                NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
+                NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
+                NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
                 nudgeTime,
                 true
             );
@@ -1106,12 +1117,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         } else {
             const docs = this.childLayoutPairs.map(pair => pair.layout).slice();
             docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
-            let zlast = docs.length ? Math.max(docs.length, NumCast(docs[docs.length - 1].zIndex)) : 1;
-            if (zlast - docs.length > 100) {
-                for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
-                zlast = docs.length + 1;
+            let zlast = docs.length ? Math.max(docs.length, NumCast(docs.lastElement().zIndex)) : 1;
+            if (docs.lastElement() !== doc) {
+                if (zlast - docs.length > 100) {
+                    for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
+                    zlast = docs.length + 1;
+                }
+                doc.zIndex = zlast + 1;
             }
-            doc.zIndex = zlast + 1;
         }
     };
 
@@ -1134,8 +1147,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
         const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
         const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
-        this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0];
-        this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
+        this.layoutDoc[this.panXFieldKey] = NumCast(this.layoutDoc[this.panXFieldKey]) - newpan[0];
+        this.layoutDoc[this.panYFieldKey] = NumCast(this.layoutDoc[this.panYFieldKey]) - newpan[1];
     }
 
     calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
@@ -1158,8 +1171,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const panelHeight = this.props.isAnnotationOverlay ? this.nativeHeight : this.props.PanelHeight();
         const pw = panelWidth / NumCast(this.layoutDoc._viewScale, 1);
         const ph = panelHeight / NumCast(this.layoutDoc._viewScale, 1);
-        const cx = NumCast(this.layoutDoc._panX) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
-        const cy = NumCast(this.layoutDoc._panY) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
+        const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
+        const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
         const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
         const maxYShift = Math.max(0, screen.bot - screen.top - (bounds.bot - bounds.top));
         const phborder = bounds.top < screen.top || bounds.bot > screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0;
@@ -1171,8 +1184,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             };
         }
         return {
-            panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panX) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
-            panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panY) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
+            panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
+            panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
         };
     };
 
@@ -1459,7 +1472,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
         if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) {
             // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
-            if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
+            if (this.props.viewField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
             else this.props.Document[this.scaleFieldKey] = Math.max(1, this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey]));
         }
 
@@ -1501,7 +1514,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     cbounds => {
                         if (cbounds) {
                             const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
-                            const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)];
+                            const p = [NumCast(this.layoutDoc[this.panXFieldKey]), NumCast(this.layoutDoc[this.panYFieldKey])];
                             const pbounds = {
                                 x: cbounds.x - p[0] + c[0],
                                 y: cbounds.y - p[1] + c[1],
@@ -1511,8 +1524,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                             if (Number.isFinite(pbounds.r - pbounds.x) && Number.isFinite(pbounds.b - pbounds.y)) {
                                 this.layoutDoc._width = pbounds.r - pbounds.x;
                                 this.layoutDoc._height = pbounds.b - pbounds.y;
-                                this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2;
-                                this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2;
+                                this.layoutDoc[this.panXFieldKey] = (cbounds.r + cbounds.x) / 2;
+                                this.layoutDoc[this.panYFieldKey] = (cbounds.b + cbounds.y) / 2;
                                 this.layoutDoc.x = pbounds.x;
                                 this.layoutDoc.y = pbounds.y;
                             }
@@ -1634,8 +1647,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
             const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
             if (deltaX !== 0 || deltaY !== 0) {
-                this.Document._panY = NumCast(this.Document._panY) + deltaY / 2;
-                this.Document._panX = NumCast(this.Document._panX) + deltaX / 2;
+                this.Document[this.panYFieldKey] = NumCast(this.Document[this.panYFieldKey]) + deltaY / 2;
+                this.Document[this.panXFieldKey] = NumCast(this.Document[this.panXFieldKey]) + deltaX / 2;
             }
         }
         e.stopPropagation();
@@ -1659,8 +1672,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20;
         const dim = Math.ceil(Math.sqrt(docs.length));
         docs.forEach((doc, i) => {
-            doc.x = NumCast(this.Document._panX) + (i % dim) * width - (width * dim) / 2;
-            doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - (height * dim) / 2;
+            doc.x = NumCast(this.Document[this.panXFieldKey]) + (i % dim) * width - (width * dim) / 2;
+            doc.y = NumCast(this.Document[this.panYFieldKey]) + Math.floor(i / dim) * height - (height * dim) / 2;
         });
     };
 
@@ -1675,7 +1688,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         appearanceItems.push({
             description: 'Reset View',
             event: () => {
-                this.props.Document._panX = this.props.Document._panY = 0;
+                this.props.Document[this.panXFieldKey] = this.props.Document[this.panYFieldKey] = 0;
                 this.props.Document[this.scaleFieldKey] = 1;
             },
             icon: 'compress-arrows-alt',
@@ -1795,11 +1808,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
     });
 
-    children = () => {
+    get children() {
         this.incrementalRender();
-        const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : [];
+        const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : this.props.children ? [this.props.children] : [];
         return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />];
-    };
+    }
 
     @computed get placeholder() {
         return (
@@ -1843,6 +1856,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                                 PanelHeight={this.props.PanelHeight}
                                 panX={this.panX}
                                 panY={this.panY}
+                                nativeDimScaling={this.nativeDim}
                                 zoomScaling={this.zoomScaling}
                                 layoutDoc={this.layoutDoc}
                                 isAnnotationOverlay={this.isAnnotationOverlay}
@@ -1877,6 +1891,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const wscale = nw ? this.props.PanelWidth() / nw : 1;
         return wscale < hscale || this.layoutDoc.fitWidth ? wscale : hscale;
     }
+    nativeDim = () => this.nativeDimScaling;
 
     private groupDropDisposer?: DragManager.DragDropDisposer;
     protected createGroupEventsTarget = (ele: HTMLDivElement) => {
@@ -1912,7 +1927,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 ref={r => {
                     this.createDashEventsTarget(r);
                     // prevent wheel events from passivly propagating up through containers
-                    r?.addEventListener('wheel', (e: WheelEvent) => this.props.isSelected() && e.preventDefault(), { passive: false });
+                    !this.props.isAnnotationOverlay && r?.addEventListener('wheel', (e: WheelEvent) => this.props.isSelected() && e.preventDefault(), { passive: false });
                 }}
                 onWheel={this.onPointerWheel}
                 onClick={this.onClick}
@@ -1985,7 +2000,8 @@ interface CollectionFreeFormViewPannableContentsProps {
     transform: () => string;
     zoomScaling: () => number;
     viewDefDivClick?: ScriptField;
-    children: () => JSX.Element[];
+    children?: React.ReactNode | undefined;
+    //children: () => JSX.Element[];
     transition?: string;
     presPaths: () => JSX.Element | null;
     presPinView?: boolean;
@@ -2079,7 +2095,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
                     width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable.  This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
                     //willChange: "transform"
                 }}>
-                {this.props.children()}
+                {this.props.children}
                 {!this.props.brushView.width ? null : (
                     <div
                         className="collectionFreeFormView-brushView"
@@ -2107,6 +2123,7 @@ interface CollectionFreeFormViewBackgroundGridProps {
     PanelWidth: () => number;
     PanelHeight: () => number;
     isAnnotationOverlay?: boolean;
+    nativeDimScaling: () => number;
     zoomScaling: () => number;
     layoutDoc: Doc;
     cachedCenteringShiftX: number;
@@ -2124,10 +2141,10 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
         const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling();
         const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling();
         const renderGridSpace = gridSpace * this.props.zoomScaling();
-        const w = this.props.PanelWidth() + 2 * renderGridSpace;
-        const h = this.props.PanelHeight() + 2 * renderGridSpace;
+        const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
+        const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
         const strokeStyle = Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'rgba(255,255,255,0.5)' : 'rgba(0, 0,0,0.5)';
-        return (
+        return !this.props.nativeDimScaling() ? null : (
             <canvas
                 className="collectionFreeFormView-grid"
                 width={w}
@@ -2174,7 +2191,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
         const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
         let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
         while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
-        const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
+        const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it.  Otherwise, zoom out on the parent ffview
         ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5, browseTransitionTime);
         Doc.linkFollowHighlight(dv?.props.Document, false);
     } else {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index f16371592..043fe0bcf 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -237,6 +237,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     onPointerDown = (e: React.PointerEvent): void => {
+        // if (this.props.pointerEvents?.() === 'none') return;
         this._downX = this._lastX = e.clientX;
         this._downY = this._lastY = e.clientY;
         if (!(e.nativeEvent as any).marqueeHit) {
@@ -345,6 +346,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
 
     @action
     onClick = (e: React.MouseEvent): void => {
+        if (this.props.pointerEvents?.() === 'none') return;
         if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
             if (Doc.ActiveTool === InkTool.None) {
                 if (!(e.nativeEvent as any).marqueeHit) {
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 9468c5f06..e8ae88ae5 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Doc, Opt } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
@@ -186,7 +186,10 @@ export class CollectionGridView extends CollectionSubView() {
     getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) {
         return (
             <DocumentView
-                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+                {...this.props}
+                NativeWidth={returnZero}
+                NativeHeight={returnZero}
+                setContentView={emptyFunction}
                 Document={layout}
                 DataDoc={layout.resolvedDataDoc as Doc}
                 isContentActive={this.isChildContentActive}
@@ -196,7 +199,7 @@ export class CollectionGridView extends CollectionSubView() {
                 whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
                 onClick={this.onChildClickHandler}
                 renderDepth={this.props.renderDepth + 1}
-                dontCenter={this.props.Document.centerY ? '' : 'y'}
+                dontCenter={this.props.Document.centerY ? undefined : 'y'}
             />
         );
     }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index d47c9762c..fd9bcf681 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,23 +1,20 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, ObservableMap, trace, untracked } from 'mobx';
+import { action, computed, observable, ObservableMap, untracked } from 'mobx';
 import { observer } from 'mobx-react';
-import { Doc, DocListCast, Field, StrListCast } from '../../../../fields/Doc';
+import { computedFn } from 'mobx-utils';
+import { Doc, Field, StrListCast } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
-import { RichTextField } from '../../../../fields/RichTextField';
 import { listSpec } from '../../../../fields/Schema';
 import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
-import { ImageField } from '../../../../fields/URLField';
 import { emptyFunction, returnDefault, returnEmptyDoclist, returnEmptyString, returnFalse, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
 import { Docs, DocUtils } from '../../../documents/Documents';
 import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { SelectionManager } from '../../../util/SelectionManager';
-import { Transform } from '../../../util/Transform';
 import { undoBatch } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
-import { ContextMenuProps } from '../../ContextMenuItem';
 import { EditableView } from '../../EditableView';
 import { DocFocusOptions, DocumentView } from '../../nodes/DocumentView';
 import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
@@ -215,9 +212,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    addNewKey = (key: string, defaultVal: any) => {
-        this.childDocs.forEach(doc => (doc[key] = defaultVal));
-    };
+    addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal));
 
     @undoBatch
     @action
@@ -303,9 +298,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    addRowRef = (doc: Doc, ref: HTMLDivElement) => {
-        this._rowEles.set(doc, ref);
-    };
+    addRowRef = (doc: Doc, ref: HTMLDivElement) => this._rowEles.set(doc, ref);
 
     @action
     setColRef = (index: number, ref: HTMLDivElement) => {
@@ -405,68 +398,12 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     menuCallback = (x: number, y: number) => {
         ContextMenu.Instance.clearItems();
-        const layoutItems: ContextMenuProps[] = [];
-        const docItems: ContextMenuProps[] = [];
-        const dataDoc = this.props.DataDoc || this.props.Document;
-
-        DocUtils.addDocumentCreatorMenuItems(
-            doc => {
-                FormattedTextBox.SelectOnLoad = StrCast(doc[Id]);
-                return this.addRow(doc);
-            },
-            this.addRow,
-            x,
-            y,
-            true
-        );
 
-        Array.from(Object.keys(Doc.GetProto(dataDoc)))
-            .filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof dataDoc[fieldKey] === 'string')
-            .map(fieldKey =>
-                docItems.push({
-                    description: ':' + fieldKey,
-                    event: () => {
-                        const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
-                        if (created) {
-                            if (this.props.Document.isTemplateDoc) {
-                                Doc.MakeMetadataFieldTemplate(created, this.props.Document);
-                            }
-                            return this.addRow(created);
-                        }
-                    },
-                    icon: 'compress-arrows-alt',
-                })
-            );
-        Array.from(Object.keys(Doc.GetProto(dataDoc)))
-            .filter(fieldKey => DocListCast(dataDoc[fieldKey]).length)
-            .map(fieldKey =>
-                docItems.push({
-                    description: ':' + fieldKey,
-                    event: () => {
-                        const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
-                        if (created) {
-                            const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
-                            if (container.isTemplateDoc) {
-                                Doc.MakeMetadataFieldTemplate(created, container);
-                                return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
-                            }
-                            return this.addRow(created) || false;
-                        }
-                    },
-                    icon: 'compress-arrows-alt',
-                })
-            );
-        !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
-        !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
+        DocUtils.addDocumentCreatorMenuItems(doc => this.addRow(doc), this.addRow, x, y, true);
+
         ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
             Doc.GetProto(this.props.Document)[name] = '';
-            const created = Docs.Create.TextDocument('', { title: name, _autoHeight: true });
-            if (created) {
-                if (this.props.Document.isTemplateDoc) {
-                    Doc.MakeMetadataFieldTemplate(created, this.props.Document);
-                }
-                this.addRow(created);
-            }
+            this.addRow(Docs.Create.TextDocument('', { title: name, _autoHeight: true }));
         });
         ContextMenu.Instance.displayMenu(x, y, undefined, true);
     };
@@ -543,9 +480,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    closeColumnMenu = () => {
-        this._columnMenuIndex = undefined;
-    };
+    closeColumnMenu = () => (this._columnMenuIndex = undefined);
 
     @action
     openFilterMenu = (index: number) => {
@@ -596,19 +531,14 @@ export class CollectionSchemaView extends CollectionSubView() {
     getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
 
     removeFieldFilters = (field: string) => {
-        this.getFieldFilters(field).forEach(filter => {
-            Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove');
-        });
+        this.getFieldFilters(field).forEach(filter => Doc.setDocFilter(this.Document, field, filter.split(':')[1], 'remove'));
     };
 
     onFilterKeyDown = (e: React.KeyboardEvent) => {
+        //prettier-ignore
         switch (e.key) {
-            case 'Enter':
-                this.closeFilterMenu(true);
-                break;
-            case 'Escape':
-                this.closeFilterMenu(false);
-                break;
+            case 'Enter' : this.closeFilterMenu(true); break;
+            case 'Escape': this.closeFilterMenu(false);break;
         }
     };
 
@@ -903,6 +833,7 @@ interface CollectionSchemaViewDocsProps {
 class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsProps> {
     tableWidthFunc = () => this.props.schema.tableWidth;
     rowHeightFunc = () => CollectionSchemaView._rowHeight;
+    childScreenToLocal = computedFn((index: number) => () => this.props.schema.props.ScreenToLocalTransform().translate(0, -CollectionSchemaView._rowHeight - index * this.rowHeightFunc()));
     render() {
         return (
             <div className="schema-table-content">
@@ -932,7 +863,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
                                 docRangeFilters={this.props.schema.childDocRangeFilters}
                                 searchFilterDocs={this.props.schema.searchFilterDocs}
                                 rootSelected={this.props.schema.rootSelected}
-                                ScreenToLocalTransform={Transform.Identity}
+                                ScreenToLocalTransform={this.childScreenToLocal(index)}
                                 bringToFront={emptyFunction}
                                 isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive}
                                 isContentActive={emptyFunction}
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index cbdaeb933..d88d67c94 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -39,18 +39,12 @@ export class SchemaColumnHeader extends React.Component<SchemaColumnHeaderProps>
 
     @action
     onPointerDown = (e: React.PointerEvent) => {
-        setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction);
+        setupMoveUpEvents(this, e, e => this.props.dragColumn(e, this.props.columnIndex), emptyFunction, emptyFunction, false);
     };
 
     render() {
         return (
-            <div
-                className="schema-column-header"
-                style={{ width: this.props.columnWidths[this.props.columnIndex] }}
-                onPointerDown={this.onPointerDown}
-                ref={(col: HTMLDivElement | null) => {
-                    col && this.props.setColRef(this.props.columnIndex, col);
-                }}>
+            <div className="schema-column-header" style={{ width: this.props.columnWidths[this.props.columnIndex] }} onPointerDown={this.onPointerDown} ref={col => col && this.props.setColRef(this.props.columnIndex, col)}>
                 <div className="schema-column-resizer left" onPointerDown={e => this.props.resizeColumn(e, this.props.columnIndex)}></div>
                 <div className="schema-column-title">{this.fieldKey}</div>
 
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 384975b45..a6acf882c 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -7,7 +7,7 @@ import { Doc, DocListCast } from '../../../fields/Doc';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DateCast, NumCast } from '../../../fields/Types';
 import { AudioField, nullAudio } from '../../../fields/URLField';
-import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, formatTime, returnFalse, setupMoveUpEvents } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { Networking } from '../../Network';
 import { DragManager } from '../../util/DragManager';
@@ -19,7 +19,6 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import './AudioBox.scss';
 import { FieldView, FieldViewProps } from './FieldView';
-import { SelectionManager } from '../../util/SelectionManager';
 import { PinProps, PresBox } from './trails';
 
 /**
@@ -655,7 +654,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         return (
             <CollectionStackedTimeline
                 ref={action((r: any) => (this._stackedTimeline = r))}
-                {...OmitKeys(this.props, ['CollectionFreeFormDocumentView']).omit}
+                {...this.props}
+                CollectionFreeFormDocumentView={undefined}
                 fieldKey={this.annotationKey}
                 dictationKey={this.fieldKey + '-dictation'}
                 mediaPath={this.path}
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index ecffe6c4f..ace388c57 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -3,7 +3,7 @@ import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, OmitKeys, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { SnappingManager } from '../../util/SnappingManager';
@@ -122,7 +122,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                         ref={r => {
                             //whichDoc !== targetDoc && r?.focus(whichDoc, { instant: true });
                         }}
-                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+                        {...this.props}
+                        NativeWidth={returnZero}
+                        NativeHeight={returnZero}
                         isContentActive={returnFalse}
                         isDocumentActive={returnFalse}
                         styleProvider={this.docStyleProvider}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 091a6b050..c7bf37a45 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,7 +12,7 @@ import { ScriptField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
-import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, OmitKeys, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
+import { emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { DocServer } from '../../DocServer';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -512,7 +512,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 this.props.isDocumentActive?.() &&
                 !this.props.onBrowseClick?.() &&
                 !this.Document.ignoreClick &&
-                !e.ctrlKey &&
                 e.button === 0 &&
                 this.pointerEvents !== 'none' &&
                 !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)
@@ -705,22 +704,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             const appearance = cm.findByDescription('UI Controls...');
             const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
             !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template   ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
-            !Doc.noviceMode &&
-                appearanceItems.push({
-                    description: 'Add a Field',
-                    event: () => {
-                        const alias = Doc.MakeAlias(this.rootDoc);
-                        alias.layout = FormattedTextBox.LayoutString('newfield');
-                        alias.title = 'newfield';
-                        alias._height = 35;
-                        alias._width = 100;
-                        alias.syncLayoutFieldWithTitle = true;
-                        alias.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc.width);
-                        alias.y = NumCast(this.rootDoc.y);
-                        this.props.addDocument?.(alias);
-                    },
-                    icon: 'eye',
-                });
             LinkManager.Links(this.Document).length &&
                 appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? 'Show' : 'Hide'} Link Button`, event: action(() => (this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton)), icon: 'eye' });
             !appearance && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' });
@@ -863,7 +846,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
     setHeight = (height: number) => (this.layoutDoc._height = height);
     setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
-    isContentActive = (outsideReaction?: boolean) => {
+    isContentActive = (outsideReaction?: boolean): boolean | undefined => {
         return this.props.isContentActive() === false
             ? false
             : Doc.ActiveTool !== InkTool.None ||
@@ -1064,7 +1047,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
     };
 
-    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
+    captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ':caption');
     @computed get innards() {
         TraceMobx();
         const ffscale = () => this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1;
@@ -1079,7 +1062,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     maxHeight: `max(100%, ${20 * ffscale()}px)`,
                 }}>
                 <FormattedTextBox
-                    {...OmitKeys(this.props, ['children']).omit}
+                    {...this.props}
                     yPadding={10}
                     xPadding={10}
                     fieldKey={this.showCaption}
@@ -1090,7 +1073,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     dontScale={true}
                     renderDepth={this.props.renderDepth}
                     isContentActive={this.isContentActive}
-                    onClick={this.onClickFunc}
                 />
             </div>
         );
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index e53422ab7..8d3534a5c 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -20,7 +20,7 @@ export interface FieldViewProps extends DocumentViewSharedProps {
 
     select: (isCtrlPressed: boolean) => void;
     isContentActive: (outsideReaction?: boolean) => boolean | undefined;
-    isDocumentActive?: () => boolean;
+    isDocumentActive?: () => boolean | undefined;
     isSelected: (outsideReaction?: boolean) => boolean;
     setHeight?: (height: number) => void;
     NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 48e54b722..b5193cd20 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -11,7 +11,7 @@ import { ComputedField } from '../../../fields/ScriptField';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { DashColor, emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
+import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
 import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
 import { Docs, DocUtils } from '../../documents/Documents';
@@ -409,8 +409,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         );
     }
 
-    contentFunc = () => [this.content];
-
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
     @observable _marqueeing: number[] | undefined;
@@ -473,7 +471,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 }}>
                 <CollectionFreeFormView
                     ref={this._ffref}
-                    {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                    {...this.props}
+                    setContentView={emptyFunction}
+                    NativeWidth={returnZero}
+                    NativeHeight={returnZero}
                     renderDepth={this.props.renderDepth + 1}
                     fieldKey={this.annotationKey}
                     styleProvider={this.props.styleProvider}
@@ -487,11 +488,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     focus={this.focus}
                     getScrollHeight={this.getScrollHeight}
                     NativeDimScaling={returnOne}
+                    isAnyChildContentActive={returnFalse}
                     whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     removeDocument={this.removeDocument}
                     moveDocument={this.moveDocument}
                     addDocument={this.addDocument}>
-                    {this.contentFunc}
+                    {this.content}
                 </CollectionFreeFormView>
                 {this.annotationLayer}
                 {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : (
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index ea8f47b39..36be7d257 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -8,7 +8,7 @@ import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DragManager } from '../../../util/DragManager';
 import { SnappingManager } from '../../../util/SnappingManager';
@@ -635,7 +635,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                                 .map(marker => (
                                     <MapBoxInfoWindow
                                         key={marker[Id]}
-                                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                                        {...this.props}
+                                        setContentView={emptyFunction}
                                         place={marker}
                                         markerMap={this.markerMap}
                                         PanelWidth={this.infoWidth}
@@ -674,6 +675,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         rootDoc={this.rootDoc}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
+                        usePanelWidth={true}
                         showSidebar={this.SidebarShown}
                         nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index 00bedafbe..d65ef9c4c 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Doc } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
-import { emptyFunction, OmitKeys, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
+import { emptyFunction, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView';
@@ -52,7 +52,8 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi
                     <div style={{ width: this.props.PanelWidth(), height: this.props.PanelHeight() }}>
                         <CollectionStackingView
                             ref={r => (this._stack = r)}
-                            {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                            {...this.props}
+                            setContentView={emptyFunction}
                             Document={this.props.place}
                             DataDoc={undefined}
                             fieldKey="data"
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 5f207cc0d..a254edd87 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -5,16 +5,20 @@ import * as Pdfjs from 'pdfjs-dist';
 import 'pdfjs-dist/web/pdf_viewer.css';
 import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
-import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
+import { InkTool } from '../../../fields/InkField';
+import { ComputedField } from '../../../fields/ScriptField';
+import { Cast, FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField, PdfField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
+import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
 import { KeyCodes } from '../../util/KeyCodes';
+import { SelectionManager } from '../../util/SelectionManager';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm';
+import { CollectionStackingView } from '../collections/CollectionStackingView';
 import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
@@ -41,7 +45,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     private _initialScrollTarget: Opt<Doc>;
     private _pdfViewer: PDFViewer | undefined;
     private _searchRef = React.createRef<HTMLInputElement>();
-    private _selectReactionDisposer: IReactionDisposer | undefined;
+    private _disposers: { [name: string]: IReactionDisposer } = {};
     private _sidebarRef = React.createRef<SidebarAnnos>();
 
     @observable private _searching: boolean = false;
@@ -184,11 +188,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
 
     componentWillUnmount() {
-        this._selectReactionDisposer?.();
+        Object.values(this._disposers).forEach(disposer => disposer?.());
     }
     componentDidMount() {
         this.props.setContentView?.(this);
-        this._selectReactionDisposer = reaction(
+        this._disposers.select = reaction(
             () => this.props.isSelected(),
             () => {
                 document.removeEventListener('keydown', this.onKeyDown);
@@ -196,6 +200,16 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             },
             { fireImmediately: true }
         );
+        this._disposers.scroll = reaction(
+            () => this.rootDoc.scrollTop,
+            () => {
+                if (!(ComputedField.WithoutComputed(() => FieldValue(this.props.Document[this.SidebarKey + '-panY'])) instanceof ComputedField)) {
+                    this.props.Document[this.SidebarKey + '-panY'] = ComputedField.MakeFunction('this.scrollTop');
+                }
+                this.props.Document[this.SidebarKey + '-viewScale'] = 1;
+                this.props.Document[this.SidebarKey + '-panX'] = 0;
+            }
+        );
     }
 
     brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._pdfViewer?.brushView(view);
@@ -303,7 +317,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
     // adding external documents; to sidebar key
     // if (doc.Geolocation) this.addDocument(doc, this.fieldkey+"-annotation")
-    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => {
+    sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
         if (!this.layoutDoc._showSidebar) this.toggleSidebar();
         return this.addDocument(doc, sidebarKey);
     };
@@ -428,6 +442,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     };
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
+        funcs.push({
+            description: 'Toggle Sidebar Type',
+            event: () => (this.rootDoc.sidebarViewType = this.rootDoc.sidebarViewType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform),
+            icon: 'expand-arrows-alt',
+        });
         funcs.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' });
         funcs.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' });
         //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" });
@@ -467,12 +486,87 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
     }
 
+    public get SidebarKey() {
+        return this.fieldKey + '-sidebar';
+    }
+    @computed get pdfScale() {
+        const pdfNativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']);
+        const nativeWidth = NumCast(this.layoutDoc.nativeWidth, pdfNativeWidth);
+        const pdfRatio = pdfNativeWidth / nativeWidth;
+        return (pdfRatio * this.props.PanelWidth()) / pdfNativeWidth;
+    }
+    @computed get sidebarNativeWidth() {
+        return this.sidebarWidth() / this.pdfScale;
+    }
+    @computed get sidebarNativeHeight() {
+        return this.props.PanelHeight() / this.pdfScale;
+    }
+    sidebarNativeWidthFunc = () => this.sidebarNativeWidth;
+    sidebarNativeHeightFunc = () => this.sidebarNativeHeight;
+    sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
+    sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey);
+    sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate((this.sidebarWidth() - this.props.PanelWidth()) / this.pdfScale, 0);
+    @computed get sidebarCollection() {
+        const renderComponent = (tag: string) => {
+            const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : CollectionStackingView;
+            return ComponentTag === CollectionStackingView ? (
+                <SidebarAnnos
+                    ref={this._sidebarRef}
+                    {...this.props}
+                    rootDoc={this.rootDoc}
+                    layoutDoc={this.layoutDoc}
+                    dataDoc={this.dataDoc}
+                    setHeight={emptyFunction}
+                    nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
+                    showSidebar={this.SidebarShown}
+                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                    sidebarAddDocument={this.sidebarAddDocument}
+                    moveDocument={this.moveDocument}
+                    removeDocument={this.removeDocument}
+                    //PanelWidth={this.sidebarWidth}
+                    //ScreenToLocalTransform={this.sidebarScreenToLocal}
+                />
+            ) : (
+                <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
+                    <ComponentTag
+                        {...this.props}
+                        setContentView={emptyFunction} // override setContentView to do nothing
+                        NativeWidth={this.sidebarNativeWidthFunc}
+                        NativeHeight={this.sidebarNativeHeightFunc}
+                        PanelHeight={this.props.PanelHeight}
+                        PanelWidth={this.sidebarWidth}
+                        xPadding={0}
+                        yPadding={0}
+                        viewField={this.SidebarKey}
+                        isAnnotationOverlay={false}
+                        originTopLeft={true}
+                        isAnyChildContentActive={this.isAnyChildContentActive}
+                        select={emptyFunction}
+                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
+                        removeDocument={this.sidebarRemDocument}
+                        moveDocument={this.sidebarMoveDocument}
+                        addDocument={this.sidebarAddDocument}
+                        CollectionView={undefined}
+                        ScreenToLocalTransform={this.sidebarScreenToLocal}
+                        renderDepth={this.props.renderDepth + 1}
+                        noSidebar={true}
+                        fieldKey={this.SidebarKey}
+                    />
+                </div>
+            );
+        };
+        return (
+            <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: '100%', right: 0, backgroundColor: `white` }}>
+                {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
+            </div>
+        );
+    }
     isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected() || (this.props.renderDepth === 0 && LightboxView.IsLightboxDocView(this.props.docViewPath()));
     @computed get renderPdfView() {
         TraceMobx();
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
-        return (
+        return !this._pdf ? null : (
             <div
                 className={'pdfBox'}
                 onContextMenu={this.specificContextMenu}
@@ -496,7 +590,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         addDocTab={this.sidebarAddDocTab}
                         layoutDoc={this.layoutDoc}
                         dataDoc={this.dataDoc}
-                        pdf={this._pdf!}
+                        pdf={this._pdf}
                         focus={this.focus}
                         url={this.pdfUrl!.url.pathname}
                         isContentActive={this.isPdfContentActive}
@@ -510,22 +604,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                         crop={this.crop}
                     />
                 </div>
-                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}>
-                    <SidebarAnnos
-                        ref={this._sidebarRef}
-                        {...this.props}
-                        rootDoc={this.rootDoc}
-                        layoutDoc={this.layoutDoc}
-                        dataDoc={this.dataDoc}
-                        setHeight={emptyFunction}
-                        nativeWidth={this._previewNativeWidth ?? NumCast(this.layoutDoc._nativeWidth)}
-                        showSidebar={this.SidebarShown}
-                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
-                        sidebarAddDocument={this.sidebarAddDocument}
-                        moveDocument={this.moveDocument}
-                        removeDocument={this.removeDocument}
-                    />
-                </div>
+                <div style={{ position: 'absolute', height: '100%', right: 0, top: 0, width: `calc(100 * ${this.sidebarWidth() / this.props.PanelWidth()}%` }}>{this.sidebarCollection}</div>
                 {this.settingsPanel()}
             </div>
         );
@@ -535,21 +614,17 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     static pdfpromise = new Map<string, Promise<Pdfjs.PDFDocumentProxy>>();
     render() {
         TraceMobx();
-        if (this._pdf) {
-            if (!this.props.thumbShown?.()) {
-                return this.renderPdfView;
-            }
-            return null;
-        }
+        if (this.props.thumbShown?.()) return null;
+        const pdfView = this.renderPdfView;
 
         const href = this.pdfUrl?.url.href;
-        if (href) {
+        if (!pdfView && href) {
             if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href))));
             else {
                 if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise);
                 PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, (this._pdf = pdf))));
             }
         }
-        return this.renderTitleBox;
+        return pdfView ?? this.renderTitleBox;
     }
 }
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 61e4894f0..db11a7776 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -11,7 +11,7 @@ import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, NumCast } from '../../../fields/Types';
 import { AudioField, VideoField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, OmitKeys, returnFalse, returnOne } from '../../../Utils';
+import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
@@ -277,7 +277,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         dictationTextProto.mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState');
         this.dataDoc[this.fieldKey + '-dictation'] = dictationText;
     };
-    contentFunc = () => [this.threed, this.content];
     videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '-nativeHeight'], this.layoutDoc[HeightSym]()) / NumCast(this.dataDoc[this.fieldKey + '-nativeWidth'], this.layoutDoc[WidthSym]())) * this.props.PanelWidth();
     formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
     render() {
@@ -287,7 +286,10 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                 <div className="videoBox-viewer">
                     <div style={{ position: 'relative', height: this.videoPanelHeight() }}>
                         <CollectionFreeFormView
-                            {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+                            {...this.props}
+                            setContentView={emptyFunction}
+                            NativeWidth={returnZero}
+                            NativeHeight={returnZero}
                             PanelHeight={this.videoPanelHeight}
                             PanelWidth={this.props.PanelWidth}
                             focus={this.props.focus}
@@ -296,6 +298,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             select={emptyFunction}
                             isContentActive={returnFalse}
                             NativeDimScaling={returnOne}
+                            isAnyChildContentActive={returnFalse}
                             whenChildContentsActiveChanged={emptyFunction}
                             removeDocument={returnFalse}
                             moveDocument={returnFalse}
@@ -304,17 +307,19 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                             ScreenToLocalTransform={this.props.ScreenToLocalTransform}
                             renderDepth={this.props.renderDepth + 1}
                             ContainingCollectionDoc={this.props.ContainingCollectionDoc}>
-                            {this.contentFunc}
+                            <>
+                                {this.threed}
+                                {this.content}
+                            </>
                         </CollectionFreeFormView>
                     </div>
                     <div style={{ position: 'relative', height: this.formattedPanelHeight() }}>
                         {!(this.dataDoc[this.fieldKey + '-dictation'] instanceof Doc) ? null : (
                             <FormattedTextBox
-                                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
+                                {...this.props}
                                 Document={this.dataDoc[this.fieldKey + '-dictation']}
                                 fieldKey={'text'}
                                 PanelHeight={this.formattedPanelHeight}
-                                isAnnotationOverlay={true}
                                 select={emptyFunction}
                                 isContentActive={emptyFunction}
                                 NativeDimScaling={returnOne}
@@ -324,7 +329,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                                 removeDocument={returnFalse}
                                 moveDocument={returnFalse}
                                 addDocument={returnFalse}
-                                CollectionView={undefined}
                                 renderDepth={this.props.renderDepth + 1}
                                 ContainingCollectionDoc={this.props.ContainingCollectionDoc}></FormattedTextBox>
                         )}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 0b88f5fe3..0570c7fcb 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -9,7 +9,7 @@ import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils';
+import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
@@ -894,8 +894,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     playing = () => this._playing;
 
-    contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
-
     scaling = () => this.props.NativeDimScaling?.() || 1;
 
     panelWidth = () => (this.props.PanelWidth() * this.heightPercent) / 100;
@@ -1068,7 +1066,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             left: (this.props.PanelWidth() - this.panelWidth()) / 2,
                         }}>
                         <CollectionFreeFormView
-                            {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                            {...this.props}
+                            setContentView={emptyFunction}
+                            NativeWidth={returnZero}
+                            NativeHeight={returnZero}
                             renderDepth={this.props.renderDepth + 1}
                             fieldKey={this.annotationKey}
                             CollectionView={undefined}
@@ -1076,6 +1077,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             annotationLayerHostsContent={true}
                             PanelWidth={this.panelWidth}
                             PanelHeight={this.panelHeight}
+                            isAnyChildContentActive={returnFalse}
                             ScreenToLocalTransform={this.screenToLocalTransform}
                             docFilters={this.timelineDocFilter}
                             select={emptyFunction}
@@ -1084,7 +1086,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                             removeDocument={this.removeDocument}
                             moveDocument={this.moveDocument}
                             addDocument={this.addDocWithTimecode}>
-                            {this.contentFunc}
+                            {this.youtubeVideoId ? this.youtubeContent : this.content}
                         </CollectionFreeFormView>
                     </div>
                     {this.annotationLayer}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index d57518a8d..66d0fd385 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema';
 import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types';
 import { ImageField, WebField } from '../../../fields/URLField';
 import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, getWordAtPoint, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils';
+import { emptyFunction, getWordAtPoint, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils';
 import { Docs, DocUtils } from '../../documents/Documents';
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragManager } from '../../util/DragManager';
@@ -305,7 +305,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), anchor[HeightSym](), NumCast(this.layoutDoc._scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[HeightSym](), this._scrollHeight));
             if (scrollTo !== undefined) {
                 if (this._initialScroll === undefined) {
-                    this.goTo(scrollTo, options.zoomTime ?? 500, options.easeFunc);
+                    const focusTime = options.zoomTime ?? 500;
+                    this.goTo(scrollTo, focusTime, options.easeFunc);
+                    return focusTime;
                 } else {
                     this._initialScroll = scrollTo;
                 }
@@ -904,9 +906,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1;
         const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any);
         const scale = previewScale * (this.props.NativeDimScaling?.() || 1);
-        const renderAnnotations = (docFilters?: () => string[]) => (
+        const renderAnnotations = (docFilters: () => string[]) => (
             <CollectionFreeFormView
-                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                {...this.props}
+                setContentView={emptyFunction}
+                NativeWidth={returnZero}
+                NativeHeight={returnZero}
                 renderDepth={this.props.renderDepth + 1}
                 isAnnotationOverlay={true}
                 fieldKey={this.annotationKey}
@@ -921,6 +926,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 dropAction={'alias'}
                 docFilters={docFilters}
                 select={emptyFunction}
+                isAnyChildContentActive={returnFalse}
                 bringToFront={emptyFunction}
                 styleProvider={this.childStyleProvider}
                 whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ee4249b02..d8afc3227 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -20,7 +20,7 @@ import { RichTextUtils } from '../../../../fields/RichTextUtils';
 import { ComputedField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
 import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils';
 import { DocServer } from '../../../DocServer';
 import { Docs, DocUtils } from '../../../documents/Documents';
@@ -1739,12 +1739,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             }
         }
     };
-    fitContentsToBox = () => this.props.Document._fitContentsToBox;
+    fitContentsToBox = () => BoolCast(this.props.Document._fitContentsToBox);
     sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
     sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
         if (!this.layoutDoc._showSidebar) this.toggleSidebar();
-        // console.log("printting allSideBarDocs");
-        // console.log(this.allSidebarDocs);
         return this.addDocument(doc, sidebarKey);
     };
     sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey);
@@ -1785,8 +1783,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 className="formattedTextBox-sidebar-handle"
                 onPointerDown={this.sidebarDown}
                 style={{
-                    backgroundColor: backgroundColor,
-                    color: color,
+                    backgroundColor,
+                    color,
                     opacity: annotated ? 1 : undefined,
                 }}>
                 <FontAwesomeIcon icon={'comment-alt'} />
@@ -1800,33 +1798,36 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 <SidebarAnnos
                     ref={this._sidebarRef}
                     {...this.props}
-                    fieldKey={this.fieldKey}
                     rootDoc={this.rootDoc}
                     layoutDoc={this.layoutDoc}
                     dataDoc={this.dataDoc}
-                    ScreenToLocalTransform={this.sidebarScreenToLocal}
+                    usePanelWidth={true}
                     nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
-                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     showSidebar={this.SidebarShown}
-                    PanelWidth={this.sidebarWidth}
-                    setHeight={this.setSidebarHeight}
+                    whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                     sidebarAddDocument={this.sidebarAddDocument}
                     moveDocument={this.moveDocument}
                     removeDocument={this.removeDocument}
+                    ScreenToLocalTransform={this.sidebarScreenToLocal}
+                    fieldKey={this.fieldKey}
+                    PanelWidth={this.sidebarWidth}
+                    setHeight={this.setSidebarHeight}
                 />
             ) : (
                 <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}>
                     <ComponentTag
-                        {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                        {...this.props}
+                        setContentView={emptyFunction}
                         NativeWidth={returnZero}
                         NativeHeight={returnZero}
                         PanelHeight={this.props.PanelHeight}
                         PanelWidth={this.sidebarWidth}
                         xPadding={0}
                         yPadding={0}
-                        scaleField={this.SidebarKey + '-scale'}
+                        viewField={this.SidebarKey}
                         isAnnotationOverlay={false}
                         select={emptyFunction}
+                        isAnyChildContentActive={returnFalse}
                         NativeDimScaling={this.sidebarContentScaling}
                         whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                         removeDocument={this.sidebarRemDocument}
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index c5060a2c2..6ab541207 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -7,7 +7,7 @@ import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { Cast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, returnNone, smoothScroll, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnAll, returnFalse, returnNone, returnTrue, returnZero, smoothScroll, Utils } from '../../../Utils';
 import { DocUtils } from '../../documents/Documents';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
@@ -16,7 +16,7 @@ import { SnappingManager } from '../../util/SnappingManager';
 import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { MarqueeAnnotator } from '../MarqueeAnnotator';
-import { DocFocusOptions, DocumentViewProps, OpenWhere } from '../nodes/DocumentView';
+import { DocFocusOptions, DocumentViewProps } from '../nodes/DocumentView';
 import { FieldViewProps } from '../nodes/FieldView';
 import { LinkDocPreview } from '../nodes/LinkDocPreview';
 import { StyleProp } from '../StyleProvider';
@@ -512,7 +512,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         return this.props.styleProvider?.(doc, props, property);
     };
 
-    renderAnnotations = (docFilters?: () => string[], mixBlendMode?: any, display?: string) => (
+    renderAnnotations = (docFilters: () => string[], mixBlendMode?: any, display?: string) => (
         <div
             className="pdfViewerDash-overlay"
             style={{
@@ -522,9 +522,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 pointerEvents: Doc.ActiveTool !== InkTool.None ? 'all' : undefined,
             }}>
             <CollectionFreeFormView
-                {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
+                {...this.props}
+                NativeWidth={returnZero}
+                NativeHeight={returnZero}
+                setContentView={emptyFunction} // override setContentView to do nothing
+                pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone}
                 renderDepth={this.props.renderDepth + 1}
-                pointerEvents={returnNone}
                 isAnnotationOverlay={true}
                 fieldKey={this.props.fieldKey + '-annotations'}
                 CollectionView={undefined}
@@ -534,6 +537,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
                 PanelHeight={this.panelHeight}
                 PanelWidth={this.panelWidth}
                 ScreenToLocalTransform={this.overlayTransform}
+                isAnyChildContentActive={returnFalse}
+                isAnnotationOverlayScrollable={true}
                 dropAction={'alias'}
                 docFilters={docFilters}
                 select={emptyFunction}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 553c4525c..cc024d83a 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1213,7 +1213,7 @@ export namespace Doc {
         return doc[StrCast(doc.layoutKey, 'layout')];
     }
     export function LayoutFieldKey(doc: Doc): string {
-        return StrCast(Doc.Layout(doc).layout).split("'")[1];
+        return StrCast(Doc.Layout(doc)[StrCast(doc.layoutKey, 'layout')]).split("'")[1]; // bcz: TODO check on this .  used to always reference 'layout', now it uses the layout speicfied by the current layoutKey
     }
     export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) {
         return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1);
@@ -1222,9 +1222,10 @@ export namespace Doc {
         return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeWidth'], useWidth ? doc[WidthSym]() : 0));
     }
     export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) {
-        const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeHeight'], useHeight ? doc[HeightSym]() : 0) : 0;
-        const nheight = doc ? (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym]() : 0;
-        return !doc ? 0 : NumCast(doc._nativeHeight, nheight || dheight);
+        if (!doc) return 0;
+        const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym]();
+        const dheight = NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeHeight'], useHeight ? doc[HeightSym]() : 0);
+        return NumCast(doc._nativeHeight, nheight || dheight);
     }
     export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) {
         doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeWidth'] = width;
-- 
cgit v1.2.3-70-g09d2

