Browse Source

Merge branch 'master' into saguaro-molstar

# Conflicts:
#	package-lock.json
#	package.json
#	src/viewer/helpers/preset.ts
#	src/viewer/index.ts
bioinsilico 3 years ago
parent
commit
e8ea919e43

+ 57 - 0
CHANGELOG.md

@@ -2,6 +2,63 @@
 
 [Semantic Versioning](https://semver.org/)
 
+## [1.5.1] - 2021-04-30
+### Bug fixes
+- 'Membrane Orientation' preset now honors assembly-ids
+
+## [1.5.0] - 2021-04-22
+### Added
+- feature density preset for ligand validation
+
+### Bug fixes
+- hide 'Membrane Orientation' representation from UI
+
+## [1.4.2] - 2021-04-20
+### Bug fixes
+- fix measurement labels
+- hide 'Membrane Orientation' preset from UI
+
+## [1.4.1] - 2021-04-06
+### Bug fixes
+- structure export: getting transformed data
+
+## [1.4.0] - 2021-04-02
+### Added
+- membrane orientation preset by ANVIL
+
+## [1.3.5] - 2021-03-25
+### Bug fixes
+- make sure only structure objects are filtered in for export
+- do not create unit cell object when alignments are loaded
+
+## [1.3.4] - 2021-03-25
+### Bug fixes
+- structure export: getting children for a referenced node correctly
+
+## [1.3.3] - 2021-03-24
+### Bug fixes
+- build
+
+## [1.3.2] - 2021-03-24
+### General
+- major version update of Mol*
+
+## [1.3.1] - 2021-03-11
+### Bug fixes
+- this is a dummy release
+
+## [1.3.0] - 2021-03-10
+### Added
+- expose video export option
+
+## [1.2.3] - 2021-03-03
+### Bug fixes
+- warn when residues are more than 15 A apart (RO-2597)
+
+## [1.2.2] - 2021-02-26
+### Bug fixes
+- switch to 'model' when ligand preset is requested but ligand is not present in assembly '1' - HELP-16678
+
 ## [1.2.1] - 2021-02-18
 ### Bug fixes
 - limit number of exchanges per position

+ 134 - 74
package-lock.json

@@ -1,6 +1,6 @@
 {
     "name": "@rcsb/rcsb-molstar",
-    "version": "1.3.0-beta.saguaro.1",
+    "version": "1.5.1-beta.saguaro.1",
     "lockfileVersion": 1,
     "requires": true,
     "dependencies": {
@@ -97,9 +97,9 @@
                     }
                 },
                 "chalk": {
-                    "version": "4.1.0",
-                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
-                    "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+                    "version": "4.1.1",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
+                    "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
                     "dev": true,
                     "requires": {
                         "ansi-styles": "^4.1.0",
@@ -217,9 +217,9 @@
             }
         },
         "@types/express-serve-static-core": {
-            "version": "4.17.18",
-            "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz",
-            "integrity": "sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA==",
+            "version": "4.17.19",
+            "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz",
+            "integrity": "sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA==",
             "dev": true,
             "requires": {
                 "@types/node": "*",
@@ -252,9 +252,9 @@
             }
         },
         "@types/jest": {
-            "version": "26.0.20",
-            "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.20.tgz",
-            "integrity": "sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==",
+            "version": "26.0.23",
+            "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz",
+            "integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==",
             "dev": true,
             "requires": {
                 "jest-diff": "^26.0.0",
@@ -274,15 +274,15 @@
             "dev": true
         },
         "@types/node": {
-            "version": "14.14.28",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.28.tgz",
-            "integrity": "sha512-lg55ArB+ZiHHbBBttLpzD07akz0QPrZgUODNakeC09i62dnrywr9mFErHuaPlB6I7z+sEbK+IYmplahvplCj2g==",
+            "version": "14.14.43",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.43.tgz",
+            "integrity": "sha512-3pwDJjp1PWacPTpH0LcfhgjvurQvrZFBrC6xxjaUEZ7ifUtT32jtjPxEMMblpqd2Mvx+k8haqQJLQxolyGN/cQ==",
             "dev": true
         },
         "@types/node-fetch": {
-            "version": "2.5.8",
-            "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz",
-            "integrity": "sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==",
+            "version": "2.5.10",
+            "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.10.tgz",
+            "integrity": "sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==",
             "dev": true,
             "requires": {
                 "@types/node": "*",
@@ -296,9 +296,9 @@
             "dev": true
         },
         "@types/qs": {
-            "version": "6.9.5",
-            "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz",
-            "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==",
+            "version": "6.9.6",
+            "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz",
+            "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==",
             "dev": true
         },
         "@types/range-parser": {
@@ -2226,25 +2226,27 @@
             }
         },
         "es-abstract": {
-            "version": "1.18.0-next.2",
-            "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz",
-            "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==",
+            "version": "1.18.0",
+            "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
+            "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
             "dev": true,
             "requires": {
                 "call-bind": "^1.0.2",
                 "es-to-primitive": "^1.2.1",
                 "function-bind": "^1.1.1",
-                "get-intrinsic": "^1.0.2",
+                "get-intrinsic": "^1.1.1",
                 "has": "^1.0.3",
-                "has-symbols": "^1.0.1",
-                "is-callable": "^1.2.2",
+                "has-symbols": "^1.0.2",
+                "is-callable": "^1.2.3",
                 "is-negative-zero": "^2.0.1",
-                "is-regex": "^1.1.1",
+                "is-regex": "^1.1.2",
+                "is-string": "^1.0.5",
                 "object-inspect": "^1.9.0",
                 "object-keys": "^1.1.1",
                 "object.assign": "^4.1.2",
-                "string.prototype.trimend": "^1.0.3",
-                "string.prototype.trimstart": "^1.0.3"
+                "string.prototype.trimend": "^1.0.4",
+                "string.prototype.trimstart": "^1.0.4",
+                "unbox-primitive": "^1.0.0"
             }
         },
         "es-to-primitive": {
@@ -3421,6 +3423,12 @@
                 }
             }
         },
+        "has-bigints": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
+            "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==",
+            "dev": true
+        },
         "has-flag": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -3428,9 +3436,9 @@
             "dev": true
         },
         "has-symbols": {
-            "version": "1.0.1",
-            "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
-            "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
+            "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
             "dev": true
         },
         "has-unicode": {
@@ -3603,9 +3611,9 @@
             "dev": true
         },
         "immer": {
-            "version": "8.0.1",
-            "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz",
-            "integrity": "sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA==",
+            "version": "8.0.4",
+            "resolved": "https://registry.npmjs.org/immer/-/immer-8.0.4.tgz",
+            "integrity": "sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ==",
             "dev": true
         },
         "immutable": {
@@ -3721,6 +3729,12 @@
             "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
             "dev": true
         },
+        "is-bigint": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz",
+            "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==",
+            "dev": true
+        },
         "is-binary-path": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -3731,6 +3745,15 @@
                 "binary-extensions": "^2.0.0"
             }
         },
+        "is-boolean-object": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz",
+            "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==",
+            "dev": true,
+            "requires": {
+                "call-bind": "^1.0.0"
+            }
+        },
         "is-buffer": {
             "version": "1.1.6",
             "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
@@ -3847,6 +3870,12 @@
                 }
             }
         },
+        "is-number-object": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz",
+            "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==",
+            "dev": true
+        },
         "is-plain-object": {
             "version": "2.0.4",
             "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@@ -3866,6 +3895,12 @@
                 "has-symbols": "^1.0.1"
             }
         },
+        "is-string": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
+            "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
+            "dev": true
+        },
         "is-symbol": {
             "version": "1.0.3",
             "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
@@ -3945,9 +3980,9 @@
                     }
                 },
                 "chalk": {
-                    "version": "4.1.0",
-                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
-                    "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+                    "version": "4.1.1",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
+                    "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
                     "dev": true,
                     "requires": {
                         "ansi-styles": "^4.1.0",
@@ -4496,9 +4531,9 @@
             }
         },
         "molstar": {
-            "version": "1.3.1",
-            "resolved": "https://registry.npmjs.org/molstar/-/molstar-1.3.1.tgz",
-            "integrity": "sha512-AoScAiqOTcqFJxbNFxGyCY22hHIMigmoqNOjzYcHPa7y5lhR+g0drRWKOgCYtwCNPy5H6Gh5quUpUYA/l3PonA==",
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/molstar/-/molstar-2.0.5.tgz",
+            "integrity": "sha512-dmkOpXbtv9MBleYWUa2gQ2DMcq6olm4z/4DY8ISkq3eRNJp04LcOGjNPRnErgzFlvHj3RNEstQwIpH5pNSm8Bg==",
             "dev": true,
             "requires": {
                 "@types/argparse": "^1.0.38",
@@ -4517,14 +4552,14 @@
                 "cors": "^2.8.5",
                 "express": "^4.17.1",
                 "h264-mp4-encoder": "^1.0.12",
-                "immer": "^8.0.0",
+                "immer": "^8.0.1",
                 "immutable": "^3.8.2",
                 "node-fetch": "^2.6.1",
                 "react": "^17.0.1",
                 "react-dom": "^17.0.1",
-                "rxjs": "^6.6.3",
+                "rxjs": "^6.6.6",
                 "swagger-ui-dist": "^3.37.2",
-                "tslib": "^2.0.3",
+                "tslib": "^2.1.0",
                 "util.promisify": "^1.0.1",
                 "xhr2": "^0.2.0"
             }
@@ -4851,9 +4886,9 @@
             }
         },
         "object-inspect": {
-            "version": "1.9.0",
-            "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
-            "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==",
+            "version": "1.10.2",
+            "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.2.tgz",
+            "integrity": "sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA==",
             "dev": true
         },
         "object-keys": {
@@ -4884,14 +4919,14 @@
             }
         },
         "object.getownpropertydescriptors": {
-            "version": "2.1.1",
-            "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz",
-            "integrity": "sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng==",
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz",
+            "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==",
             "dev": true,
             "requires": {
-                "call-bind": "^1.0.0",
+                "call-bind": "^1.0.2",
                 "define-properties": "^1.1.3",
-                "es-abstract": "^1.18.0-next.1"
+                "es-abstract": "^1.18.0-next.2"
             }
         },
         "object.pick": {
@@ -5478,9 +5513,9 @@
             }
         },
         "react-is": {
-            "version": "17.0.1",
-            "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
-            "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==",
+            "version": "17.0.2",
+            "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+            "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
             "dev": true
         },
         "read-pkg": {
@@ -5813,18 +5848,18 @@
             }
         },
         "rxjs": {
-            "version": "6.6.3",
-            "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz",
-            "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==",
+            "version": "6.6.7",
+            "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+            "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
             "dev": true,
             "requires": {
                 "tslib": "^1.9.0"
             },
             "dependencies": {
                 "tslib": {
-                    "version": "1.13.0",
-                    "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
-                    "integrity": "sha1-yIHhPMcBWJTtkUhi0nZDb6mkcEM=",
+                    "version": "1.14.1",
+                    "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+                    "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
                     "dev": true
                 }
             }
@@ -6503,22 +6538,22 @@
             }
         },
         "string.prototype.trimend": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz",
-            "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==",
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
+            "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
             "dev": true,
             "requires": {
-                "call-bind": "^1.0.0",
+                "call-bind": "^1.0.2",
                 "define-properties": "^1.1.3"
             }
         },
         "string.prototype.trimstart": {
-            "version": "1.0.3",
-            "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz",
-            "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==",
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
+            "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
             "dev": true,
             "requires": {
-                "call-bind": "^1.0.0",
+                "call-bind": "^1.0.2",
                 "define-properties": "^1.1.3"
             }
         },
@@ -6623,9 +6658,9 @@
             }
         },
         "swagger-ui-dist": {
-            "version": "3.43.0",
-            "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.43.0.tgz",
-            "integrity": "sha512-PtE+g23bNbYv8qqAVoPBqNQth8hU5Sl5ZsQ7gHXlO5jlCt31dVTiKI9ArHIT1b23ZzUYTnKsFgPYYFoiWyNCAw==",
+            "version": "3.48.0",
+            "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.48.0.tgz",
+            "integrity": "sha512-UgpKIQW5RAb4nYRG8B615blmQzct0DNuvtX4904Fe2aMWAVfWeKHKl4kwzFXuBJgr2WYWTwM1PnhZ+qqkLrpPg==",
             "dev": true
         },
         "table": {
@@ -6965,11 +7000,23 @@
             "dev": true
         },
         "typescript": {
-            "version": "4.1.2",
-            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz",
-            "integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==",
+            "version": "4.2.4",
+            "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
+            "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
             "dev": true
         },
+        "unbox-primitive": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
+            "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==",
+            "dev": true,
+            "requires": {
+                "function-bind": "^1.1.1",
+                "has-bigints": "^1.0.1",
+                "has-symbols": "^1.0.2",
+                "which-boxed-primitive": "^1.0.2"
+            }
+        },
         "union-value": {
             "version": "1.0.1",
             "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@@ -7485,6 +7532,19 @@
                 "isexe": "^2.0.0"
             }
         },
+        "which-boxed-primitive": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+            "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+            "dev": true,
+            "requires": {
+                "is-bigint": "^1.0.1",
+                "is-boolean-object": "^1.1.0",
+                "is-number-object": "^1.0.4",
+                "is-string": "^1.0.5",
+                "is-symbol": "^1.0.3"
+            }
+        },
         "which-module": {
             "version": "2.0.0",
             "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",

+ 7 - 7
package.json

@@ -1,6 +1,6 @@
 {
     "name": "@rcsb/rcsb-molstar",
-    "version": "1.3.0-beta.saguaro.1",
+    "version": "1.5.1-beta.saguaro.1",
     "description": "RCSB PDB apps and props based on Mol*.",
     "homepage": "https://github.com/rcsb/rcsb-molstar#readme",
     "repository": {
@@ -47,19 +47,19 @@
         "eslint": "^7.15.0",
         "extra-watch-webpack-plugin": "^1.0.3",
         "file-loader": "^6.2.0",
+        "fs-extra": "^9.0.1",
         "mini-css-extract-plugin": "^1.3.2",
-        "molstar": "^1.3.1",
+        "molstar": "^2.0.4",
         "node-sass": "^5.0.0",
         "raw-loader": "^4.0.2",
         "react": "^17.0.1",
         "react-dom": "^17.0.1",
-        "rxjs": "^6.6.3",
+        "rxjs": "^6.6.6",
         "sass-loader": "^10.1.0",
         "style-loader": "^2.0.0",
-        "tslib": "^2.0.3",
-        "typescript": "4.1.2",
+        "tslib": "^2.1.0",
+        "typescript": "^4.2.3",
         "webpack": "^4.44.1",
         "webpack-cli": "^3.3.12"
-    },
-    "dependencies": {}
+    }
 }

+ 15 - 9
src/viewer/helpers/export.ts

@@ -1,10 +1,10 @@
 import { PluginContext } from 'molstar/lib/mol-plugin/context';
-import { StateSelection } from 'molstar/lib/mol-state';
+import { StateObjectRef, StateSelection } from 'molstar/lib/mol-state';
 import { PluginStateObject } from 'molstar/lib/mol-plugin-state/objects';
 import { StructureSelection, Structure } from 'molstar/lib/mol-model/structure';
 import { CifExportContext, encode_mmCIF_categories } from 'molstar/lib/mol-model/structure/export/mmcif';
 import { utf8ByteCount, utf8Write } from 'molstar/lib/mol-io/common/utf8';
-import { zip } from 'molstar/lib/mol-util/zip/zip';
+import { Zip } from 'molstar/lib/mol-util/zip/zip';
 import { getFormattedTime } from 'molstar/lib/mol-util/date';
 import { download } from 'molstar/lib/mol-util/download';
 import { CustomPropertyDescriptor } from 'molstar/lib/mol-model/custom-property';
@@ -61,10 +61,16 @@ function extractStructureDataFromState(plugin: PluginContext): { [k: string]: St
     const cells = plugin.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure));
     for (let i = 0; i < cells.length; i++) {
         const c = cells[i];
-        const nodeRef = getDecorator(plugin, c.transform.ref);
-        const children = plugin.state.data.select(StateSelection.Generators.byRef(nodeRef))
-            .map(child => child.obj!.data);
-        const sele = StructureSelection.Sequence(c.obj!.data, children);
+        // get decorated root structure
+        const rootRef = getDecorator(plugin, c.transform.ref);
+        const rootCell = StateObjectRef.resolveAndCheck(plugin.state.data, rootRef);
+        // get all leaf children of root
+        const children = plugin.state.data.tree.children.get(rootRef).toArray()
+            .map(x => plugin.state.data.select(StateSelection.Generators.byRef(x!))[0])
+            .filter(c => c.obj?.type === PluginStateObject.Molecule.Structure.type)
+            .map(x => x.obj!.data as Structure);
+        // merge children
+        const sele = StructureSelection.Sequence(rootCell!.obj!.data, children);
         const structure = StructureSelection.unionStructure(sele);
         const name = `${i + 1}-${structure.model.entryId}`;
         content[name] = structure;
@@ -85,8 +91,8 @@ export function encodeStructureData(plugin: PluginContext): { [k: string]: Uint8
     return content;
 }
 
-export function downloadAsZipFile(content: { [k: string]: Uint8Array }) {
+export async function downloadAsZipFile(plugin: PluginContext, content: { [k: string]: Uint8Array }) {
     const filename = `mol-star_download_${getFormattedTime()}.zip`;
-    const buf = zip(content);
-    download(new Blob([buf]), filename);
+    const buf = await plugin.runTask(Zip(content));
+    download(new Blob([buf], { type : 'application/zip' }), filename);
 }

+ 56 - 19
src/viewer/helpers/preset.ts

@@ -6,7 +6,7 @@
 
 import { PluginContext } from 'molstar/lib/mol-plugin/context';
 import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder';
-import Expression from 'molstar/lib/mol-script/language/expression';
+import { Expression } from 'molstar/lib/mol-script/language/expression';
 import { ParamDefinition as PD } from 'molstar/lib/mol-util/param-definition';
 import { TrajectoryHierarchyPresetProvider } from 'molstar/lib/mol-plugin-state/builder/structure/hierarchy-preset';
 import { ValidationReportGeometryQualityPreset } from 'molstar/lib/extensions/rcsb/validation-report/behavior';
@@ -23,7 +23,13 @@ import {
 import { compile } from 'molstar/lib/mol-script/runtime/query/compiler';
 import { InitVolumeStreaming } from 'molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/transformers';
 import { ViewerState } from '../types';
-import { StateSelection, StateObjectSelector, StateObject, StateTransformer, StateObjectRef } from 'molstar/lib/mol-state';
+import {
+    StateSelection,
+    StateObjectSelector,
+    StateObject,
+    StateTransformer,
+    StateObjectRef
+} from 'molstar/lib/mol-state';
 import { VolumeStreaming } from 'molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/behavior';
 import { Mat4 } from 'molstar/lib/mol-math/linear-algebra';
 import { CustomStructureProperties } from 'molstar/lib/mol-plugin-state/transforms/model';
@@ -32,6 +38,7 @@ import { StructureRepresentationRegistry } from 'molstar/lib/mol-repr/structure/
 import { StructureSelectionQueries as Q } from 'molstar/lib/mol-plugin-state/helpers/structure-selection-query';
 import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
 import { InteractivityManager } from 'molstar/lib/mol-plugin-state/manager/interactivity';
+import { MembraneOrientationPreset } from 'molstar/lib/extensions/anvil/behavior';
 
 type Target = {
     readonly auth_seq_id?: number
@@ -130,7 +137,16 @@ type DensityProps = {
     kind: 'density'
 } & BaseProps
 
-export type PresetProps = ValidationProps | StandardProps | SymmetryProps | FeatureProps | DensityProps | PropsetProps | EmptyProps;
+type MembraneProps = {
+    kind: 'membrane',
+} & BaseProps
+
+type FeatureDensityProps = {
+    kind: 'feature-density',
+    target: Target
+} & BaseProps
+
+export type PresetProps = ValidationProps | StandardProps | SymmetryProps | FeatureProps | DensityProps | PropsetProps | MembraneProps | FeatureDensityProps | EmptyProps;
 
 const RcsbParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
     preset: PD.Value<PresetProps>({ kind: 'standard', assemblyId: '' }, { isHidden: true })
@@ -224,17 +240,16 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
 
         let structure: StructureObject | undefined = undefined;
         let structureProperties: StructureObject | undefined = undefined;
-
+        let unitcell: StateObjectSelector | undefined = undefined;
         // If flexible transformation is allowed, we may need to create a single structure component
         // from transformed substructures
         const allowsFlexTransform = p.kind === 'prop-set';
         if (!allowsFlexTransform) {
             structure = await builder.createStructure(modelProperties || model, structureParams);
             structureProperties = await builder.insertStructureProperties(structure);
+            unitcell = await builder.tryCreateUnitcell(modelProperties, undefined, { isHidden: true });
         }
 
-        const unitcell = await builder.tryCreateUnitcell(modelProperties, undefined, { isHidden: true });
-
         let representation: StructureRepresentationPresetProvider.Result | undefined = undefined;
 
         if (p.kind === 'prop-set') {
@@ -297,25 +312,37 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
             });
         } else if (p.kind === 'empty') {
             console.warn('Using empty representation');
+        } else if (p.kind === 'membrane') {
+            representation = await plugin.builders.structure.representation.applyPreset(structureProperties!, MembraneOrientationPreset);
         } else {
             representation = await plugin.builders.structure.representation.applyPreset(structureProperties!, 'auto');
         }
 
-        if (p.kind === 'feature' && structure?.obj) {
-            const loci = targetToLoci(p.target, structure.obj.data);
+        if ((p.kind === 'feature' || p.kind === 'feature-density') && structure?.obj) {
+            let loci = targetToLoci(p.target, structure.obj.data);
             // if target is only defined by chain: then don't force first residue
             const chainMode = p.target.label_asym_id && !p.target.auth_seq_id && !p.target.label_seq_id && !p.target.label_comp_id;
+            // HELP-16678: check for rare case where ligand is not present in requested assembly
+            if (loci.elements.length === 0 && !!p.assemblyId) {
+                // switch to Model (a.k.a. show coordinate independent of assembly )
+                const { selection } = plugin.managers.structure.hierarchy;
+                const s = selection.structures[0];
+                await plugin.managers.structure.hierarchy.updateStructure(s, { ...params, preset: { ...params.preset, assemblyId: void 0 } });
+                // update loci
+                loci = targetToLoci(p.target, structure.obj.data);
+            }
             const target = chainMode ? loci : StructureElement.Loci.firstResidue(loci);
+
+            if (p.kind === 'feature-density') {
+                await initVolumeStreaming(plugin, structure);
+            }
+
             plugin.managers.structure.focus.setFromLoci(target);
             plugin.managers.camera.focusLoci(target);
         }
 
-        if (p.kind === 'density' && structure?.cell?.parent) {
-            const volumeRoot = StateSelection.findTagInSubtree(structure.cell.parent.tree, structure.cell.transform.ref, VolumeStreaming.RootTag);
-            if (!volumeRoot) {
-                const params = PD.getDefaultValues(InitVolumeStreaming.definition.params!(structure.obj!, plugin));
-                await plugin.runTask(plugin.state.data.applyAction(InitVolumeStreaming, params, structure.ref));
-            }
+        if (p.kind === 'density' && structure) {
+            await initVolumeStreaming(plugin, structure);
 
             await PluginCommands.Toast.Show(plugin, {
                 title: 'Electron Density',
@@ -329,11 +356,6 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
                     await PluginCommands.Toast.Hide(plugin, { key: 'toast-density' });
                 }
             });
-
-            ViewerState(plugin).collapsed.next({
-                ...ViewerState(plugin).collapsed.value,
-                volume: false
-            });
         }
 
         return {
@@ -347,6 +369,21 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
     }
 });
 
+async function initVolumeStreaming(plugin: PluginContext, structure: StructureObject) {
+    if (!structure?.cell?.parent) return;
+
+    const volumeRoot = StateSelection.findTagInSubtree(structure.cell.parent.tree, structure.cell.transform.ref, VolumeStreaming.RootTag);
+    if (!volumeRoot) {
+        const params = PD.getDefaultValues(InitVolumeStreaming.definition.params!(structure.obj!, plugin));
+        await plugin.runTask(plugin.state.data.applyAction(InitVolumeStreaming, params, structure.ref));
+    }
+
+    ViewerState(plugin).collapsed.next({
+        ...ViewerState(plugin).collapsed.value,
+        volume: false
+    });
+}
+
 export function createSelectionExpression(entryId: string, range?: Range): SelectionExpression[] {
     if (range) {
         const residues: number[] = (range.label_seq_id) ? toRange(range.label_seq_id.beg, range.label_seq_id.end) : [];

+ 36 - 0
src/viewer/helpers/viewer.ts

@@ -17,6 +17,17 @@ import {StructureSelectionQuery} from 'molstar/lib/mol-plugin-state/helpers/stru
 
 export namespace ViewerMethods {
 
+    export function selectMultipleSegments(plugin: PluginContext, selection: Array<{modelId: string; asymId: string; begin: number; end: number;}>, mode: 'select'|'hover', modifier: 'add'|'set' ): void {
+        if(mode === 'select'){
+            selection.forEach(sel=>{
+                clearSelection(plugin, mode, {modelId: sel.modelId, labelAsymId: sel.asymId});
+            });
+        }
+        selection.forEach(sel=>{
+            selectSegment(plugin, sel.modelId, sel.asymId, sel.begin, sel.end, mode, modifier);
+        });
+    }
+
     export function selectSegment(plugin: PluginContext, modelId: string, asymId: string, begin: number, end: number, mode: 'select'|'hover', modifier: 'add'|'set'): void {
         const loci: Loci | undefined = getLociFromRange(plugin, modelId, asymId, begin, end);
         if(loci == null)
@@ -94,6 +105,31 @@ export namespace ViewerMethods {
         }, [structureRef]);
     }
 
+    export async function createComponentFromMultipleRange(plugin: PluginContext, componentLabel: string, structureRef: StructureRef, residues: Array<{asymId: string; begin: number; end: number;}>, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>{
+        const seqIdMap: Map<string, Array<number>> = new Map<string, Array<number>>();
+        residues.forEach(res=>{
+            if(!seqIdMap.has(res.asymId)){
+                seqIdMap.set(res.asymId, new Array<number>());
+            }
+            for(let n = res.begin; n <= res.end; n++){
+                seqIdMap.get(res.asymId)!.push(n);
+            }
+        });
+        await plugin.managers.structure.component.add({
+            selection: StructureSelectionQuery(
+                'innerQuery_' + Math.random().toString(36).substr(2),
+                MolScriptBuilder.struct.combinator.merge(
+                    Array.from(seqIdMap).map(([asymId, seqIds])=>MolScriptBuilder.struct.generator.atomGroups({
+                        'chain-test': MolScriptBuilder.core.rel.eq([asymId, MolScriptBuilder.ammp('label_asym_id')]),
+                        'residue-test': MolScriptBuilder.core.set.has([MolScriptBuilder.set(...SetUtils.toArray(new Set(seqIds))), MolScriptBuilder.ammp('label_seq_id')])
+                    }))
+                )
+            ),
+            options: { checkExisting: false, label: componentLabel },
+            representation: representationType,
+        }, [structureRef]);
+    }
+
     export function setFocusFromRange(plugin: PluginContext, modelId: string, asymId: string, begin: number, end: number): void{
         const loci: Loci | undefined = getLociFromRange(plugin, modelId, asymId, begin, end);
         if(loci == null)

+ 46 - 1
src/viewer/index.html

@@ -53,7 +53,7 @@
             var props = _props && JSON.parse(_props)
             var _loadPdbIds = getQueryParam('loadPdbIds')
             var loadPdbIds = _loadPdbIds && JSON.parse(_loadPdbIds)
-            
+
             // create an instance of the plugin
             var viewer = new rcsbMolstar.Viewer('viewer', {
                 showImportControls: !pdbId,
@@ -85,6 +85,10 @@
             Superposed
             <button style="padding: 3px;" onclick="superposed()">3PQR | 1U19</button>
 
+            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+
+            Ligand Focus on wrong assembly
+            <button style="padding: 3px" onclick="ligandAssembly()">5RLA</button>
         </div>
         <script>
 
@@ -281,6 +285,39 @@
                             label_asym_id: 'E'
                         }
                     }
+                },
+                {
+                    id: '1BRR',
+                    info: 'membrane: X-RAY STRUCTURE OF THE BACTERIORHODOPSIN TRIMER/LIPID COMPLEX with ANVIL membrane prediction',
+                    props: {
+                        kind: 'membrane'
+                    }
+                },
+                {
+                    id: '3SN6',
+                    info: 'membrane: Crystal structure of the beta2 adrenergic receptor-Gs protein complex with ANVIL membrane prediction',
+                    props: {
+                        kind: 'membrane'
+                    }
+                },
+                {
+                    id: '1SQX',
+                    info: 'membrane + assembly: Crystal Structure Analysis of Bovine Bc1 with Stigmatellin A',
+                    props: {
+                        kind: 'membrane',
+                        assemblyId: '1'
+                    }
+                },
+                {
+                    id: '6WJC',
+                    info: 'ligand validation: Muscarinic acetylcholine receptor 1 - muscarinic toxin 7 complex: Focus + Density',
+                    props: {
+                        kind: 'feature-density',
+                        target: {
+                            label_asym_id: 'D',
+                            auth_seq_id: 502
+                        }
+                    }
                 }
             ];
 
@@ -311,6 +348,14 @@
                         viewer.resetCamera(0)
                     });
             }
+
+            function ligandAssembly() {
+                viewer.clear()
+                    .then(function() {
+                        // UR7 is not present in assembly 1
+                        return viewer.loadPdbId('5RL9', { kind: 'feature', assemblyId: '1', target: { label_comp_id: 'UR7' } });
+                    });
+            }
         </script>
     </body>
 </html>

+ 44 - 26
src/viewer/index.ts

@@ -6,7 +6,6 @@
  */
 
 import { BehaviorSubject } from 'rxjs';
-import { DefaultPluginSpec } from 'molstar/lib/mol-plugin';
 import { Plugin } from 'molstar/lib/mol-plugin-ui/plugin';
 import { PluginContext } from 'molstar/lib/mol-plugin/context';
 import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
@@ -30,9 +29,14 @@ import { ObjectKeys } from 'molstar/lib/mol-util/type-helpers';
 import { PluginLayoutControlsDisplay } from 'molstar/lib/mol-plugin/layout';
 import { SuperposeColorThemeProvider } from './helpers/superpose/color';
 import { encodeStructureData, downloadAsZipFile } from './helpers/export';
-import {StructureRef} from 'molstar/lib/mol-plugin-state/manager/structure/hierarchy-state';
-import {StructureRepresentationRegistry} from 'molstar/lib/mol-repr/structure/registry';
 import {ViewerMethods} from './helpers/viewer';
+import { StructureRef } from 'molstar/lib/mol-plugin-state/manager/structure/hierarchy-state';
+import { StructureRepresentationRegistry } from 'molstar/lib/mol-repr/structure/registry';
+import { Mp4Export } from 'molstar/lib/extensions/mp4-export';
+import { DefaultPluginUISpec, PluginUISpec } from 'molstar/lib/mol-plugin-ui/spec';
+import { PluginUIContext } from 'molstar/lib/mol-plugin-ui/context';
+import { ANVILMembraneOrientation, MembraneOrientationPreset } from 'molstar/lib/extensions/anvil/behavior';
+import { MembraneOrientationRepresentationProvider } from 'molstar/lib/extensions/anvil/representation';
 
 /** package version, filled in at bundle build time */
 declare const __RCSB_MOLSTAR_VERSION__: string;
@@ -45,7 +49,9 @@ export const BUILD_DATE = new Date(BUILD_TIMESTAMP);
 
 const Extensions = {
     'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry),
-    'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport)
+    'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport),
+    'mp4-export': PluginSpec.Behavior(Mp4Export),
+    'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation)
 };
 
 const DefaultViewerProps = {
@@ -85,42 +91,44 @@ export type ViewerProps = typeof DefaultViewerProps
 
 
 export class Viewer {
-    private readonly plugin: PluginContext;
+    private readonly plugin: PluginUIContext;
     private readonly modelUrlProviders: ModelUrlProvider[];
 
     private get customState() {
         return this.plugin.customState as ViewerState;
     }
 
-    constructor(target: string | HTMLElement, props: Partial<ViewerProps> = {}) {
-        target = typeof target === 'string' ? document.getElementById(target)! : target;
+    constructor(elementOrId: string | HTMLElement, props: Partial<ViewerProps> = {}) {
+        const element = typeof elementOrId === 'string' ? document.getElementById(elementOrId)! : elementOrId;
+        if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
 
         const o = { ...DefaultViewerProps, ...props };
 
-        const spec: PluginSpec = {
-            actions: [...DefaultPluginSpec.actions],
+        const defaultSpec = DefaultPluginUISpec();
+        const spec: PluginUISpec = {
+            ...defaultSpec,
+            actions: defaultSpec.actions,
             behaviors: [
-                ...DefaultPluginSpec.behaviors,
+                ...defaultSpec.behaviors,
                 ...o.extensions.map(e => Extensions[e]),
             ],
-            animations: [...DefaultPluginSpec.animations || []],
-            customParamEditors: DefaultPluginSpec.customParamEditors,
+            animations: [...defaultSpec.animations || []],
             layout: {
                 initial: {
                     isExpanded: o.layoutIsExpanded,
                     showControls: o.layoutShowControls,
                     controlsDisplay: o.layoutControlsDisplay,
                 },
+            },
+            components: {
+                ...defaultSpec.components,
                 controls: {
-                    ...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
+                    ...defaultSpec.components?.controls,
                     top: o.layoutShowSequence ? undefined : 'none',
                     bottom: o.layoutShowLog ? undefined : 'none',
                     left: 'none',
                     right: ControlsWrapper,
-                }
-            },
-            components: {
-                ...DefaultPluginSpec.components,
+                },
                 remoteState: 'none',
             },
             config: [
@@ -133,7 +141,7 @@ export class Viewer {
             ]
         };
 
-        this.plugin = new PluginContext(spec);
+        this.plugin = new PluginUIContext(spec);
         this.modelUrlProviders = o.modelUrlProviders;
 
         (this.plugin.customState as ViewerState) = {
@@ -150,12 +158,16 @@ export class Viewer {
                 superposition: true,
                 component: false,
                 volume: true,
-                custom: true,
+                custom: true
             })
         };
 
-        this.plugin.init();
-        ReactDOM.render(React.createElement(Plugin, { plugin: this.plugin }), target);
+        this.plugin.init().then(() => {
+            // hide 'Membrane Orientation' preset from UI
+            this.plugin.builders.structure.representation.unregisterPreset(MembraneOrientationPreset);
+            this.plugin.representation.structure.registry.remove(MembraneOrientationRepresentationProvider);
+        });
+        ReactDOM.render(React.createElement(Plugin, { plugin: this.plugin }), element);
 
         // TODO Check why this.plugin.canvas3d can be null
         // this.plugin.canvas3d can be null. The value is not assigned until React Plugin component is mounted
@@ -254,7 +266,7 @@ export class Viewer {
 
     exportLoadedStructures() {
         const content = encodeStructureData(this.plugin);
-        downloadAsZipFile(content);
+        return downloadAsZipFile(this.plugin, content);
     }
 
     pluginCall(f: (plugin: PluginContext) => void){
@@ -278,15 +290,18 @@ export class Viewer {
     }
 
     public select(selection: Array<{modelId: string; asymId: string; position: number;}>, mode: 'select'|'hover', modifier: 'add'|'set'): void;
+    public select(selection: Array<{modelId: string; asymId: string; begin: number; end: number;}>, mode: 'select'|'hover', modifier: 'add'|'set'): void;
     public select(modelId: string, asymId: string, position: number, mode: 'select'|'hover', modifier: 'add'|'set'): void;
     public select(modelId: string, asymId: string, begin: number, end: number, mode: 'select'|'hover', modifier: 'add'|'set'): void;
     public select(...args: any[]){
-        if(args.length === 3){
+        if(args.length === 3 && (args[0] as Array<{modelId: string; asymId: string; position: number;}>).length > 0 && typeof (args[0] as Array<{modelId: string; asymId: string; position: number;}>)[0].position === 'number'){
             if(args[2] === 'set')
                 this.clearSelection('select');
             (args[0] as Array<{modelId: string; asymId: string; position: number;}>).forEach(r=>{
                 ViewerMethods.selectSegment(this.plugin, r.modelId, r.asymId, r.position, r.position, args[1], 'add');
             });
+        }else if(args.length === 3 && (args[0] as Array<{modelId: string; asymId: string; begin: number; end: number;}>).length > 0 && typeof (args[0] as Array<{modelId: string; asymId: string; begin: number; end: number;}>)[0].begin === 'number'){
+            ViewerMethods.selectMultipleSegments(this.plugin, args[0], args[1], args[2]);
         }else if(args.length === 5){
             ViewerMethods.selectSegment(this.plugin, args[0], args[1], args[2], args[2], args[3], args[4]);
         }else if(args.length === 6){
@@ -299,7 +314,8 @@ export class Viewer {
     }
 
     public async createComponent(componentLabel: string, modelId: string, asymId: string, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
-    public async createComponent(componentLabel: string, modelId: string, residues: Array<{asymId: string, position: number}>, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
+    public async createComponent(componentLabel: string, modelId: string, residues: Array<{asymId: string; position: number;}>, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
+    public async createComponent(componentLabel: string, modelId: string, residues: Array<{asymId: string; begin: number; end: number;}>, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
     public async createComponent(componentLabel: string, modelId: string, asymId: string, begin: number, end: number, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
     public async createComponent(...args: any[]): Promise<void>{
         const structureRef: StructureRef | undefined = ViewerMethods.getStructureRefWithModelId(this.plugin.managers.structure.hierarchy.current.structures, args[1]);
@@ -307,9 +323,11 @@ export class Viewer {
             throw 'createComponent error: model not found';
         if (args.length === 4 && typeof args[2] === 'string') {
             await ViewerMethods.createComponentFromChain(this.plugin, args[0], structureRef, args[2], args[3]);
-        } else if (args.length === 4 && args[2] instanceof Array) {
+        } else if (args.length === 4 && args[2] instanceof Array && args[2].length > 0 && typeof args[2][0].position === 'number') {
             await ViewerMethods.createComponentFromSet(this.plugin, args[0], structureRef, args[2], args[3]);
-        } else if (args.length === 6) {
+        } else if (args.length === 4 && args[2] instanceof Array && args[2].length > 0 && typeof args[2][0].begin === 'number') {
+            await ViewerMethods.createComponentFromMultipleRange(this.plugin, args[0], structureRef, args[2], args[3]);
+        }else if (args.length === 6) {
             await ViewerMethods.createComponentFromRange(this.plugin, args[0], structureRef, args[2], args[3], args[4], args[5]);
         }
     }

+ 1 - 2
src/viewer/ui/controls.tsx

@@ -4,7 +4,6 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import * as React from 'react';
 import { PluginUIComponent } from 'molstar/lib/mol-plugin-ui/base';
 import { ViewerState } from '../types';
 import { CustomStructureControls } from 'molstar/lib/mol-plugin-ui/controls';
@@ -16,7 +15,7 @@ import { StructureSuperpositionControls } from 'molstar/lib/mol-plugin-ui/struct
 import { StructureComponentControls } from 'molstar/lib/mol-plugin-ui/structure/components';
 import { VolumeStreamingControls } from 'molstar/lib/mol-plugin-ui/structure/volume';
 import { SessionControls } from './session';
-import {StrucmotifSubmitControls} from './strucmotif';
+import { StrucmotifSubmitControls } from './strucmotif';
 
 export class StructureTools extends PluginUIComponent {
     get customState() {

+ 2 - 2
src/viewer/ui/exchanges.tsx

@@ -4,8 +4,8 @@
  * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  */
 import * as React from 'react';
-import {Button} from 'molstar/lib/mol-plugin-ui/controls/common';
-import {MAX_EXCHANGES, Residue} from './strucmotif';
+import { Button } from 'molstar/lib/mol-plugin-ui/controls/common';
+import { MAX_EXCHANGES, Residue } from './strucmotif';
 
 export const DefaultExchanges = [
     ['ALA', 'Alanine'],

+ 2 - 3
src/viewer/ui/export.tsx

@@ -1,4 +1,3 @@
-import * as React from 'react';
 import { CollapsableControls, CollapsableState, PluginUIComponent } from 'molstar/lib/mol-plugin-ui/base';
 import { Button } from 'molstar/lib/mol-plugin-ui/controls/common';
 import { GetAppSvg } from 'molstar/lib/mol-plugin-ui/controls/icons';
@@ -28,9 +27,9 @@ export class ExportControls extends CollapsableControls {
 }
 
 class CoordinatesExportControls extends PluginUIComponent {
-    download = () => {
+    download = async () => {
         const content = encodeStructureData(this.plugin);
-        downloadAsZipFile(content);
+        await downloadAsZipFile(this.plugin, content);
     }
 
     render() {

+ 0 - 1
src/viewer/ui/import.tsx

@@ -4,7 +4,6 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import * as React from 'react';
 import { StateTransform } from 'molstar/lib/mol-state';
 import { OpenFiles } from 'molstar/lib/mol-plugin-state/actions/file';
 import { DownloadStructure } from 'molstar/lib/mol-plugin-state/actions/structure';

+ 0 - 1
src/viewer/ui/session.tsx

@@ -4,7 +4,6 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import * as React from 'react';
 import { CollapsableControls } from 'molstar/lib/mol-plugin-ui/base';
 import { LocalStateSnapshots, LocalStateSnapshotList, StateExportImportControls } from 'molstar/lib/mol-plugin-ui/state/snapshots';
 import { SaveOutlinedSvg } from 'molstar/lib/mol-plugin-ui/controls/icons';

+ 68 - 15
src/viewer/ui/strucmotif.tsx

@@ -4,9 +4,8 @@
  * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  */
 
-import * as React from 'react';
-import {CollapsableControls, PurePluginUIComponent} from 'molstar/lib/mol-plugin-ui/base';
-import {Button, IconButton, ToggleButton} from 'molstar/lib/mol-plugin-ui/controls/common';
+import { CollapsableControls, PurePluginUIComponent } from 'molstar/lib/mol-plugin-ui/base';
+import { Button, IconButton, ToggleButton } from 'molstar/lib/mol-plugin-ui/controls/common';
 import {
     ArrowDownwardSvg,
     ArrowUpwardSvg,
@@ -15,18 +14,24 @@ import {
     Icon,
     TuneSvg
 } from 'molstar/lib/mol-plugin-ui/controls/icons';
-import {ActionMenu} from 'molstar/lib/mol-plugin-ui/controls/action-menu';
-import {StructureSelectionHistoryEntry} from 'molstar/lib/mol-plugin-state/manager/structure/selection';
-import {StructureElement, StructureProperties} from 'molstar/lib/mol-model/structure/structure';
-import {ToggleSelectionModeButton} from 'molstar/lib/mol-plugin-ui/structure/selection';
-import {OrderedSet} from 'molstar/lib/mol-data/int';
-import {ExchangesControl} from './exchanges';
+import { ActionMenu } from 'molstar/lib/mol-plugin-ui/controls/action-menu';
+import { StructureSelectionHistoryEntry } from 'molstar/lib/mol-plugin-state/manager/structure/selection';
+import { StructureElement, StructureProperties } from 'molstar/lib/mol-model/structure/structure';
+import { ToggleSelectionModeButton } from 'molstar/lib/mol-plugin-ui/structure/selection';
+import { OrderedSet } from 'molstar/lib/mol-data/int';
+import { ExchangesControl } from './exchanges';
+import { Vec3 } from 'molstar/lib/mol-math/linear-algebra/3d/vec3';
+import { Structure } from 'molstar/lib/mol-model/structure/structure/structure';
+import { Unit } from 'molstar/lib/mol-model/structure/structure/unit';
+import { UnitIndex } from 'molstar/lib/mol-model/structure/structure/element/element';
 
 const ADVANCED_SEARCH_URL = 'https://rcsb.org/search?query=';
 const RETURN_TYPE = '&return_type=assembly';
 const MIN_MOTIF_SIZE = 3;
 const MAX_MOTIF_SIZE = 10;
 export const MAX_EXCHANGES = 4;
+const MAX_MOTIF_EXTENT = 15;
+const MAX_MOTIF_EXTENT_SQUARED = MAX_MOTIF_EXTENT * MAX_MOTIF_EXTENT;
 
 /**
  * The top-level component that exposes the strucmotif search.
@@ -89,22 +94,45 @@ class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, residu
     }
 
     submitSearch = () => {
+        const { label_atom_id, x, y, z } = StructureProperties.atom;
         const pdbId: Set<string> = new Set();
         const residueIds: ResidueSelection[] = [];
         const exchanges: Exchange[] = [];
+        const coordinates: { coords: Vec3, residueId: ResidueSelection }[] = [];
+
+        /**
+         * This sets the 'location' to the backbone atom (CA or C4').
+         * @param structure context
+         * @param element wraps atom indices of this residue
+         */
+        const determineBackboneAtom = (structure: Structure, element: { unit: Unit; indices: OrderedSet<UnitIndex> }) => {
+            const { indices } = element;
+            for (let i = 0, il = OrderedSet.size(indices); i < il; i++) {
+                StructureElement.Location.set(location, structure, element.unit, element.unit.elements[OrderedSet.getAt(indices, i)]);
+                const atomLabelId = label_atom_id(location);
+                if ('CA' === atomLabelId || `C4'` === atomLabelId) {
+                    return true;
+                }
+            }
+            return false;
+        };
 
         const loci = this.plugin.managers.structure.selection.additionsHistory;
-        let structure;
         for (let i = 0; i < Math.min(MAX_MOTIF_SIZE, loci.length); i++) {
             const l = loci[i];
-            structure = l.loci.structure;
+            const { structure, elements } = l.loci;
             pdbId.add(structure.model.entry);
             // only first element and only first index will be considered (ignoring multiple residues)
-            const e = l.loci.elements[0];
-            StructureElement.Location.set(location, structure, e.unit, e.unit.elements[OrderedSet.getAt(e.indices, 0)]);
+            if (!determineBackboneAtom(structure, elements[0])) {
+                const struct_oper_list_ids = StructureProperties.unit.pdbx_struct_oper_list_ids(location);
+                const struct_oper_id = struct_oper_list_ids?.length ? struct_oper_list_ids.join('x') : '1';
+                alert(`No CA or C4' atom for ${StructureProperties.residue.label_seq_id(location)} | ${StructureProperties.chain.label_asym_id(location)} | ${struct_oper_id}`);
+                return;
+            }
 
             // handle pure residue-info
             const struct_oper_list_ids = StructureProperties.unit.pdbx_struct_oper_list_ids(location);
+            // TODO honor NCS operators: StructureProperties.unit.struct_ncs_oper_id(location);
             const residueId = {
                 label_asym_id: StructureProperties.chain.label_asym_id(location),
                 // can be empty array if model is selected
@@ -113,11 +141,15 @@ class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, residu
             };
             residueIds.push(residueId);
 
+            // retrieve CA/C4', used to compute residue distance
+            const coords = [x(location), y(location), z(location)] as Vec3;
+            coordinates.push({coords, residueId});
+
             // handle potential exchanges - can be empty if deselected by users
             const residueMapEntry = this.state.residueMap.get(l)!;
             if (residueMapEntry.exchanges?.size > 0) {
                 if (residueMapEntry.exchanges.size > MAX_EXCHANGES) {
-                    alert(`Maximum number of exchanges per position is ${MAX_EXCHANGES} - please remove some exchanges from residue ${residueId.label_asym_id}_${residueId.struct_oper_id}-${residueId.label_seq_id}`);
+                    alert(`Maximum number of exchanges per position is ${MAX_EXCHANGES} - Please remove some exchanges from residue ${residueId.label_seq_id} | ${residueId.label_asym_id} | ${residueId.struct_oper_id}.`);
                     return;
                 }
                 exchanges.push({ residue_id: residueId, allowed: Array.from(residueMapEntry.exchanges.values()) });
@@ -136,7 +168,28 @@ class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, residu
             alert('Selections may only contain polymeric entities!');
             return;
         }
-        // TODO warn if >15 A for better UX
+        // warn if >15 A
+        const a = Vec3();
+        const b = Vec3();
+        // this is not efficient but is good enough for up to 10 residues
+        for (let i = 0, il = coordinates.length; i < il; i++) {
+            Vec3.set(a, coordinates[i].coords[0], coordinates[i].coords[1], coordinates[i].coords[2]);
+            let contact = false;
+            for (let j = 0, jl = coordinates.length; j < jl; j++) {
+                if (i === j) continue;
+                Vec3.set(b, coordinates[j].coords[0], coordinates[j].coords[1], coordinates[j].coords[2]);
+                const d = Vec3.squaredDistance(a, b);
+                if (d < MAX_MOTIF_EXTENT_SQUARED) {
+                    contact = true;
+                }
+            }
+
+            if (!contact) {
+                const { residueId } = coordinates[i];
+                alert(`Residue ${residueId.label_seq_id} | ${residueId.label_asym_id} | ${residueId.struct_oper_id} needs to be less than 15 \u212B from another residue - Consider adding more residues to connect far-apart residues.`);
+                return;
+            }
+        }
 
         const query = {
             type: 'terminal',

+ 5 - 2
tsconfig.json

@@ -1,5 +1,6 @@
 {
     "compilerOptions": {
+        "declaration": true,
         "target": "es5",
         "alwaysStrict": true,
         "noImplicitAny": true,
@@ -9,13 +10,15 @@
         "strictNullChecks": true,
         "strictFunctionTypes": true,
         // "downlevelIteration": true,
+        "module": "CommonJS",
+        "esModuleInterop": true,
+        "moduleResolution": "node",
         "importHelpers": true,
         "noEmitHelpers": true,
-        "jsx": "react",
+        "jsx": "react-jsx",
         "lib": [ "es6", "dom", "esnext.asynciterable", "es2016" ],
         "outDir": "build/src",
         "rootDir": "src",
-        "declaration": true
     },
     "include": [ "src/**/*" ]
 }