diff --git a/frontend/package.json b/frontend/package.json index 0447485..360b75d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -58,6 +58,7 @@ "d3": "^7.9.0", "d3-cloud": "^1.2.7", "iconify-icon": "^2.3.0", + "molstar": "=4.8.0", "pdbe-molstar": "^3.3.2" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 3ee7480..e1188e1 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: iconify-icon: specifier: ^2.3.0 version: 2.3.0 + molstar: + specifier: ^4.8.0 + version: 4.11.0(@types/react@18.3.9)(fp-ts@2.16.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) pdbe-molstar: specifier: ^3.3.2 version: 3.3.2(@types/react@18.3.9)(fp-ts@2.16.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1200,9 +1203,15 @@ packages: '@types/express-serve-static-core@4.19.6': resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + '@types/express-serve-static-core@5.0.6': + resolution: {integrity: sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==} + '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + '@types/express@5.0.0': + resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==} + '@types/geojson@7946.0.16': resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} @@ -1275,6 +1284,10 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-walk@8.3.4: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} @@ -1328,6 +1341,9 @@ packages: array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + array-flatten@3.0.0: + resolution: {integrity: sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==} + array.prototype.reduce@1.0.7: resolution: {integrity: sha512-mzmiUCVwtiD4lgxYP8g7IYy8El8p2CSMePvIbTS7gchKir/L1fgJrk0yDKmAX6mnRQFKNADYIk8nNlTris5H1Q==} engines: {node: '>= 0.4'} @@ -1393,6 +1409,10 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.0.2: + resolution: {integrity: sha512-SNMk0OONlQ01uk8EPeiBvTW7W4ovpL5b1O3t1sjpPgfxOQ6BqQJ6XjxinDPR79Z6HdcD5zBBwr5ssiTlgdNztQ==} + engines: {node: '>=18'} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -1535,6 +1555,10 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -1545,6 +1569,10 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -1752,6 +1780,23 @@ packages: supports-color: optional: true + debug@3.1.0: + resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -1944,6 +1989,10 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} + express@5.0.1: + resolution: {integrity: sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==} + engines: {node: '>= 18'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -1973,6 +2022,10 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} + finalhandler@2.0.0: + resolution: {integrity: sha512-MX6Zo2adDViYh+GcxxB1dpO43eypOGUOL12rLCOTMQv/DfIbpSJUy4oQIIZhVZkH9e+bZWKMon0XHFEju16tkQ==} + engines: {node: '>= 0.8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -2012,6 +2065,10 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -2140,6 +2197,10 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + iconv-lite@0.5.2: + resolution: {integrity: sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==} + engines: {node: '>=0.10.0'} + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -2153,6 +2214,9 @@ packages: immutable@4.3.7: resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} + immutable@5.0.3: + resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==} + import-meta-resolve@4.1.0: resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} @@ -2266,6 +2330,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} @@ -2437,9 +2504,17 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2531,6 +2606,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.0: + resolution: {integrity: sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==} + engines: {node: '>= 0.6'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -2572,6 +2651,29 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + molstar@4.11.0: + resolution: {integrity: sha512-HGP18+bAQxClUpVVHtz+3U8Ubon9rSqi8WtdmEKBWsg8PTOS+85ayDOPpxNz2LseMxspZpwAzL7bRpf4rZirAg==} + hasBin: true + peerDependencies: + '@google-cloud/storage': ^7.14.0 + canvas: ^2.11.2 + gl: ^6.0.2 + jpeg-js: ^0.4.4 + pngjs: ^6.0.0 + react: '>=16.14.0' + react-dom: '>=16.14.0' + peerDependenciesMeta: + '@google-cloud/storage': + optional: true + canvas: + optional: true + gl: + optional: true + jpeg-js: + optional: true + pngjs: + optional: true + molstar@4.8.0: resolution: {integrity: sha512-RX2zqSJWN4tyyW0r95VcFzKqU3i+muwFxpfxq/tuqOh3zbSgh1vXokJefmyKo7lsaie1odXwQrgR4Wo8bEu+Gw==} hasBin: true @@ -2606,6 +2708,9 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2629,6 +2734,10 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + node-fetch@2.6.13: resolution: {integrity: sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==} engines: {node: 4.x || >=6.0.0} @@ -2697,6 +2806,9 @@ packages: resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} engines: {node: '>= 0.8'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -2753,6 +2865,10 @@ packages: path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -2967,6 +3083,10 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + react-dom@18.3.1: resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -3052,6 +3172,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.0.0: + resolution: {integrity: sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==} + engines: {node: '>= 0.10'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -3104,10 +3228,18 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} + send@1.1.0: + resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} + engines: {node: '>= 18'} + serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} + serve-static@2.1.0: + resolution: {integrity: sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==} + engines: {node: '>= 18'} + set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} @@ -3308,6 +3440,10 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + type-is@2.0.0: + resolution: {integrity: sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==} + engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -3514,6 +3650,9 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -4317,7 +4456,7 @@ snapshots: '@types/compression@1.7.5': dependencies: - '@types/express': 4.17.21 + '@types/express': 5.0.0 '@types/connect@3.4.38': dependencies: @@ -4465,6 +4604,13 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 0.17.4 + '@types/express-serve-static-core@5.0.6': + dependencies: + '@types/node': 22.13.1 + '@types/qs': 6.9.18 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + '@types/express@4.17.21': dependencies: '@types/body-parser': 1.19.5 @@ -4472,6 +4618,13 @@ snapshots: '@types/qs': 6.9.18 '@types/serve-static': 1.15.7 + '@types/express@5.0.0': + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 5.0.6 + '@types/qs': 6.9.18 + '@types/serve-static': 1.15.7 + '@types/geojson@7946.0.16': {} '@types/hast@3.0.4': @@ -4546,6 +4699,11 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + accepts@2.0.0: + dependencies: + mime-types: 3.0.0 + negotiator: 1.0.0 + acorn-walk@8.3.4: dependencies: acorn: 8.14.0 @@ -4588,6 +4746,8 @@ snapshots: array-flatten@1.1.1: {} + array-flatten@3.0.0: {} + array.prototype.reduce@1.0.7: dependencies: call-bind: 1.0.8 @@ -4677,6 +4837,21 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@2.0.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 3.1.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.5.2 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 3.0.0 + type-is: 1.6.18 + transitivePeerDependencies: + - supports-color + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 @@ -4858,12 +5033,18 @@ snapshots: dependencies: safe-buffer: 5.2.1 + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + content-type@1.0.5: {} convert-source-map@2.0.0: {} cookie-signature@1.0.6: {} + cookie-signature@1.2.2: {} + cookie@0.6.0: {} cookie@0.7.1: {} @@ -5113,6 +5294,14 @@ snapshots: dependencies: ms: 2.0.0 + debug@3.1.0: + dependencies: + ms: 2.0.0 + + debug@4.3.6: + dependencies: + ms: 2.1.2 + debug@4.4.0: dependencies: ms: 2.1.3 @@ -5429,6 +5618,43 @@ snapshots: transitivePeerDependencies: - supports-color + express@5.0.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.0.2 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.2.2 + debug: 4.3.6 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.0.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + methods: 1.1.2 + mime-types: 3.0.0 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + router: 2.0.0 + safe-buffer: 5.2.1 + send: 1.1.0 + serve-static: 2.1.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 2.0.0 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extend@3.0.2: {} fast-glob@3.3.3: @@ -5467,6 +5693,18 @@ snapshots: transitivePeerDependencies: - supports-color + finalhandler@2.0.0: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -5497,6 +5735,8 @@ snapshots: fresh@0.5.2: {} + fresh@2.0.0: {} + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -5668,6 +5908,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.5.2: + dependencies: + safer-buffer: 2.1.2 + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -5678,6 +5922,8 @@ snapshots: immutable@4.3.7: {} + immutable@5.0.3: {} + import-meta-resolve@4.1.0: {} inherits@2.0.4: {} @@ -5788,6 +6034,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-promise@4.0.0: {} + is-reference@3.0.3: dependencies: '@types/estree': 1.0.6 @@ -6018,8 +6266,12 @@ snapshots: media-typer@0.3.0: {} + media-typer@1.1.0: {} + merge-descriptors@1.0.3: {} + merge-descriptors@2.0.0: {} + merge2@1.4.1: {} methods@1.1.2: {} @@ -6175,6 +6427,10 @@ snapshots: dependencies: mime-db: 1.52.0 + mime-types@3.0.0: + dependencies: + mime-db: 1.53.0 + mime@1.6.0: {} mime@3.0.0: {} @@ -6216,6 +6472,38 @@ snapshots: minipass@7.1.2: {} + molstar@4.11.0(@types/react@18.3.9)(fp-ts@2.16.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@types/argparse': 2.0.17 + '@types/benchmark': 2.1.5 + '@types/compression': 1.7.5 + '@types/express': 5.0.0 + '@types/node': 18.19.75 + '@types/node-fetch': 2.6.12 + '@types/swagger-ui-dist': 3.30.5 + argparse: 2.0.1 + compression: 1.7.5 + cors: 2.8.5 + express: 5.0.1 + h264-mp4-encoder: 1.0.12 + immer: 10.1.1 + immutable: 5.0.3 + io-ts: 2.2.22(fp-ts@2.16.9) + node-fetch: 2.7.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-markdown: 9.0.3(@types/react@18.3.9)(react@18.3.1) + rxjs: 7.8.1 + swagger-ui-dist: 5.18.3 + tslib: 2.8.1 + util.promisify: 1.1.3 + xhr2: 0.2.1 + transitivePeerDependencies: + - '@types/react' + - encoding + - fp-ts + - supports-color + molstar@4.8.0(@types/react@18.3.9)(fp-ts@2.16.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@types/argparse': 2.0.17 @@ -6257,6 +6545,8 @@ snapshots: ms@2.0.0: {} + ms@2.1.2: {} + ms@2.1.3: {} mustache@4.2.0: {} @@ -6273,6 +6563,8 @@ snapshots: negotiator@0.6.4: {} + negotiator@1.0.0: {} + node-fetch@2.6.13: dependencies: whatwg-url: 5.0.0 @@ -6324,6 +6616,10 @@ snapshots: on-headers@1.0.2: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + own-keys@1.0.1: dependencies: get-intrinsic: 1.2.7 @@ -6387,6 +6683,8 @@ snapshots: path-to-regexp@6.3.0: {} + path-to-regexp@8.2.0: {} + pathe@1.1.2: {} pbkdf2@3.1.2: @@ -6572,6 +6870,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -6720,6 +7025,16 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.34.4 fsevents: 2.3.3 + router@2.0.0: + dependencies: + array-flatten: 3.0.0 + is-promise: 4.0.0 + methods: 1.1.2 + parseurl: 1.3.3 + path-to-regexp: 8.2.0 + setprototypeof: 1.2.0 + utils-merge: 1.0.1 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -6790,6 +7105,23 @@ snapshots: transitivePeerDependencies: - supports-color + send@1.1.0: + dependencies: + debug: 4.4.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime-types: 2.1.35 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -6799,6 +7131,15 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@2.1.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.1.0 + transitivePeerDependencies: + - supports-color + set-cookie-parser@2.7.1: {} set-function-length@1.2.2: @@ -7068,6 +7409,12 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + type-is@2.0.0: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.0 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.3 @@ -7343,6 +7690,8 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + wrappy@1.0.2: {} + ws@8.18.0: {} xhr2@0.2.1: {} diff --git a/frontend/src/lib/components/StructureViewer/MolstarWrapper.ts b/frontend/src/lib/components/StructureViewer/MolstarWrapper.ts new file mode 100644 index 0000000..9730d8b --- /dev/null +++ b/frontend/src/lib/components/StructureViewer/MolstarWrapper.ts @@ -0,0 +1,373 @@ +// Copyright 2025 Tobias Olenyi. +// SPDX-License-Identifier: Apache-2.0 +import type { + HighlightState, + SelectionState, + StructureSelectionQuery, +} from "$lib/stores/StructureMarksStore"; + +import type { RGB } from "$lib/utils"; + +import { isEmptyLoci, Loci } from "molstar/lib/mol-model/loci"; +import { StructureElement } from "molstar/lib/mol-model/structure"; +import { Structure } from "molstar/lib/mol-model/structure/structure"; +import type { StructureComponentRef } from "molstar/lib/mol-plugin-state/manager/structure/hierarchy-state"; +import type { PluginStateObject } from "molstar/lib/mol-plugin-state/objects"; +import { StateTransforms } from "molstar/lib/mol-plugin-state/transforms"; +import { PluginContext } from "molstar/lib/mol-plugin/context"; +import { + StateBuilder, + StateObjectCell, + StateSelection, + StateTransform, +} from "molstar/lib/mol-state"; +import { Transparency } from "molstar/lib/mol-theme/transparency"; + +type TransparencyEachReprCallback = ( + update: StateBuilder.Root, + repr: StateObjectCell< + PluginStateObject.Molecule.Structure.Representation3D, + StateTransform< + typeof StateTransforms.Representation.StructureRepresentation3D + > + >, + transparency?: StateObjectCell< + any, + StateTransform< + typeof StateTransforms.Representation.TransparencyStructureRepresentation3DFromBundle + > + >, +) => Promise; +const TransparencyManagerTag = "transparency-controls"; + +export class MolstarWrapper { + private viewer: any; + private ready: boolean = false; + private readyCallbacks: (() => void)[] = []; + + constructor(viewerElement: any) { + this.viewer = viewerElement; + this.initializeWhenReady(); + } + + private setReady() { + this.ready = true; + this.readyCallbacks.forEach((cb) => cb()); + this.readyCallbacks = []; + } + + private initializeWhenReady() { + const setupLoadCompleteListener = () => { + this.viewer.viewerInstance.events.loadComplete.subscribe( + (success: boolean) => { + if (success) { + this.setReady(); + } + }, + ); + }; + + if (this.viewer?.viewerInstance) { + setupLoadCompleteListener(); + } else { + // Create MutationObserver to watch for viewerInstance + const observer = new MutationObserver(() => { + if (this.viewer?.viewerInstance) { + setupLoadCompleteListener(); + observer.disconnect(); + } + }); + + // Observe the viewer element for changes + observer.observe(this.viewer, { + subtree: true, + childList: true, + attributes: true, + }); + } + } + + async whenReady(): Promise { + if (this.ready) return Promise.resolve(); + return new Promise((resolve) => this.readyCallbacks.push(resolve)); + } + + get plugin() { + return this.viewer?.viewerInstance?.plugin; + } + + get canvas() { + return this.viewer?.viewerInstance?.canvas; + } + + getBackgroundColor(): RGB { + const parentElement = this.viewer?.parentElement; + let bgColor = "rgb(255, 255, 255)"; + + if (parentElement) { + const computedStyle = getComputedStyle(parentElement); + bgColor = computedStyle.backgroundColor; + + if ( + !bgColor || + bgColor === "transparent" || + bgColor === "rgba(0, 0, 0, 0)" + ) { + bgColor = getComputedStyle(document.body).backgroundColor; + } + } + + const rgbValues = bgColor.match(/\d+/g)?.map(Number) ?? [255, 255, 255]; + return { + r: rgbValues[0], + g: rgbValues[1], + b: rgbValues[2], + }; + } + + async updateBackground() { + await this.whenReady(); + if (this.canvas) { + this.canvas.setBgColor(this.getBackgroundColor()); + } + } + + async select( + residues: StructureSelectionQuery[], + color?: RGB | null, + nonSelectedColor?: RGB | null, + structureId?: string, + structureNumber?: number, + keepColors?: boolean, + keepRepresentations?: boolean, + ) { + await this.whenReady(); + await this.viewer.viewerInstance.visual.select({ + data: residues, + color, + nonSelectedColor, + structureId, + structureNumber, + keepColors, + keepRepresentations, + }); + await this.viewer.viewerInstance.visual.tooltips({ data: residues }); + } + + async highlight( + residues: StructureSelectionQuery[], + color?: RGB | null, + focus?: boolean, + structureId?: string, + structureNumber?: number, + ) { + await this.whenReady(); + return await this.viewer.viewerInstance.visual.highlight({ + data: residues, + color, + focus, + structureId, + structureNumber, + }); + } + + async clearHighlight() { + await this.whenReady(); + return await this.viewer.viewerInstance.visual.clearHighlight(); + } + + async clearSelection( + keepColors: boolean = false, + keepRepresentations: boolean = true, + ) { + await this.whenReady(); + await this.viewer.viewerInstance.visual.clearSelection({ + keepColors, + keepRepresentations, + }); + await this.viewer.viewerInstance.visual.clearTooltips(); + } + + async updateSelectionState(state: SelectionState | null) { + await this.whenReady(); + if (!state) { + await this.clearSelection(); + return; + } + const { + residues, + color, + nonSelectedColor, + structureId, + structureNumber, + keepColors, + keepRepresentations, + } = state; + + await this.select( + residues, + color, + nonSelectedColor, + structureId, + structureNumber, + keepColors, + keepRepresentations, + ); + } + + async updateHighlightState(state: HighlightState | null) { + await this.whenReady(); + if (!state) { + await this.clearHighlight(); + return; + } + + const { residues, color, focus, structureId, structureNumber } = state; + await this.highlight(residues, color, focus, structureId, structureNumber); + } + + async applyConfidenceVisualization() { + await this.whenReady(); + + const plugin = this.plugin; + if (!plugin) { + console.warn("No transparency plugin found"); + return; + } + + const pLDDT = 70; + const transparency = 1; + + const assemblyRef = + this.plugin.managers.structure.hierarchy.current.structures[0].cell + .transform.ref; + const structure = + plugin.managers.structure.hierarchy.current?.refs.get(assemblyRef); + if (!structure) { + console.warn("No structure found"); + return; + } + + return plugin.dataTransaction( + async (ctx: any) => { + const loci = this.viewer.viewerInstance.getLociByPLDDT(pLDDT); + await this.setStructureTransparency( + plugin, + structure.components, + transparency, + loci, + ); + }, + { canUndo: "Apply Transparency" }, + ); + } + + private getFilteredBundle( + layers: Transparency.BundleLayer[], + structureRef: Structure, + ) { + const transparency = Transparency.ofBundle(layers, structureRef.root); + const merged = Transparency.merge(transparency); + return Transparency.filter( + merged, + structureRef, + ) as Transparency; + } + + private async updateRepresentations( + plugin: PluginContext, + components: StructureComponentRef[], + callback: TransparencyEachReprCallback, + ) { + const state = plugin.state.data; + const update = state.build(); + + for (const c of components) { + for (const r of c.representations) { + const transparency = state.select( + StateSelection.Generators.ofTransformer( + StateTransforms.Representation + .TransparencyStructureRepresentation3DFromBundle, + r.cell.transform.ref, + ).withTag(TransparencyManagerTag), + ); + + await callback(update, r.cell, transparency[0]); + } + } + + update.commit(); + } + + private async setStructureTransparency( + plugin: PluginContext, + components: StructureComponentRef[], + value: number, + loci: StructureElement.Loci, + ) { + await this.updateRepresentations( + plugin, + components, + async (update, repr, transparencyCell) => { + const structure = repr.obj!.data.sourceData; + if (Loci.isEmpty(loci) || isEmptyLoci(loci)) { + return; + } + + const layer = { + bundle: StructureElement.Bundle.fromLoci(loci), + value, + }; + + if (transparencyCell) { + const bundleLayers = [ + ...transparencyCell.params!.values.layers, + layer, + ]; + const filtered = this.getFilteredBundle(bundleLayers, structure); + update.to(transparencyCell).update(Transparency.toBundle(filtered)); + } else { + const parentRef = repr.transform.ref; + const parentNode = plugin.state.data.tree.transforms.get(parentRef); + if (!parentNode) { + throw new Error(`Parent node ${parentRef} not found in state tree`); + } + + // Create the transparency transform params + const filtered = this.getFilteredBundle([layer], structure); + const transparencyRef = `transparency-${parentRef}`; + const params = { + ...Transparency.toBundle(filtered), + parent: parentRef, + }; + update + .to(parentRef) + .apply( + StateTransforms.Representation + .TransparencyStructureRepresentation3DFromBundle, + params, + { + tags: TransparencyManagerTag, + ref: transparencyRef, + }, + ); + } + }, + ); + } + + private async clearTransparency( + plugin: PluginContext, + components: StructureComponentRef[], + ) { + await this.updateRepresentations( + plugin, + components, + async (update, repr, transparencyCell) => { + if (transparencyCell) { + update.delete(transparencyCell.transform.ref); + } + }, + ); + } +} diff --git a/frontend/src/lib/components/StructureViewer/StructureViewer.svelte b/frontend/src/lib/components/StructureViewer/StructureViewer.svelte index e342e4d..1b6d4e1 100644 --- a/frontend/src/lib/components/StructureViewer/StructureViewer.svelte +++ b/frontend/src/lib/components/StructureViewer/StructureViewer.svelte @@ -1,12 +1,10 @@