diff --git a/.storybook/main.js b/.storybook/main.js index f8846ce2ff8175d246f37bf6bac393b24c78e3f7..73edfadba4b38c9b10e73936bf007591cae57c22 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -4,6 +4,7 @@ module.exports = { "../src/**/*.stories.@(js|jsx|ts|tsx)" ], "addons": [ + "storybook-dark-mode", "@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions" diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index c581803c8eef27cd057d5ce191fd038689c34c0b..9a0d33156c75c65c03712d3cfd3d6427dea93fef 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -3,3 +3,10 @@ </script> <script src="https://unpkg.com/kg-dataset-previewer@1.2.0/dist/kg-dataset-previewer/kg-dataset-previewer.js" defer></script> <script src="main.bundle.js" defer></script> +<style> + body + { + overflow: scroll!important; + } +</style> +<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous"> diff --git a/.storybook/preview.js b/.storybook/preview.js index 7150cb81e0930bf987ad45d4cdad184d9492b78e..ff58e7880dafb6302fe435ed81319e7194ecaa55 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -13,4 +13,8 @@ export const parameters = { }, }, docs: { inlineStories: true }, + darkMode: { + // Set the initial theme + current: 'light' + } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 07b33aec85a2dbe10b7793cb03f7d8f9f7c5cbf3..d084a7ade12dd7f2cee8237026855934edf41f8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2921,6 +2921,12 @@ "minimist": "^1.2.0" } }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true + }, "@compodoc/compodoc": { "version": "1.1.19", "resolved": "https://registry.npmjs.org/@compodoc/compodoc/-/compodoc-1.1.19.tgz", @@ -5167,6 +5173,12 @@ "jsonc-parser": "3.0.0" } }, + "@socket.io/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", + "dev": true + }, "@stencil/core": { "version": "1.17.4", "resolved": "https://registry.npmjs.org/@stencil/core/-/core-1.17.4.tgz", @@ -13261,12 +13273,6 @@ } } }, - "base64-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.1.tgz", - "integrity": "sha512-vFIUq7FdLtjZMhATwDul5RZWv2jpXQ09Pd6jcVEOvIsqCWTRFD/ONHNfyOS8dA/Ippi5dsIgpyKWKZaAKZltbA==", - "dev": true - }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -14228,6 +14234,18 @@ "string-width": "^3.1.0", "strip-ansi": "^5.2.0", "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } } }, "clone": { @@ -15419,9 +15437,9 @@ } }, "date-format": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", - "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.4.tgz", + "integrity": "sha512-/jyf4rhB17ge328HJuJjAcmRtCsGd+NDeAtahRBTaK6vSPR6MO5HlrAit3Nn7dVjaa6sowW0WXt8yQtLyZQFRg==", "dev": true }, "debug": { @@ -16043,9 +16061,9 @@ } }, "engine.io": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.0.tgz", - "integrity": "sha512-ErhZOVu2xweCjEfYcTdkCnEYUiZgkAcBBAhW4jbIvNG8SLU3orAqoJCiytZjYF7eTpVmmCrLDjLIEaPlUAs1uw==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", + "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", "dev": true, "requires": { "@types/cookie": "^0.4.1", @@ -16056,14 +16074,14 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", + "engine.io-parser": "~5.0.3", "ws": "~8.2.3" }, "dependencies": { "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "dev": true }, "ws": { @@ -16075,12 +16093,12 @@ } }, "engine.io-parser": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.2.tgz", - "integrity": "sha512-wuiO7qO/OEkPJSFueuATIXtrxF7/6GTbAO9QLv7nnbjwZ5tYhLm9zxvLwxstRs0dcT0KUlWTjtIOs1T86jt12g==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", + "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", "dev": true, "requires": { - "base64-arraybuffer": "~1.0.1" + "@socket.io/base64-arraybuffer": "~1.0.2" } }, "enhanced-resolve": { @@ -17463,9 +17481,9 @@ } }, "follow-redirects": { - "version": "1.14.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", - "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", "dev": true }, "for-in": { @@ -17665,14 +17683,14 @@ } }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", + "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", "dev": true, "requires": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" } }, "fs-minipass": { @@ -19943,12 +19961,13 @@ "dev": true }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" } }, "jsonparse": { @@ -19987,15 +20006,15 @@ "dev": true }, "karma": { - "version": "6.3.9", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.9.tgz", - "integrity": "sha512-E/MqdLM9uVIhfuyVnrhlGBu4miafBdXEAEqCmwdEMh3n17C7UWC/8Kvm3AYKr91gc7scutekZ0xv6rxRaUCtnw==", + "version": "6.3.17", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.17.tgz", + "integrity": "sha512-2TfjHwrRExC8yHoWlPBULyaLwAFmXmxQrcuFImt/JsAsSZu1uOWTZ1ZsWjqQtWpHLiatJOHL5jFjXSJIgCd01g==", "dev": true, "requires": { + "@colors/colors": "1.5.0", "body-parser": "^1.19.0", "braces": "^3.0.2", "chokidar": "^3.5.1", - "colors": "^1.4.0", "connect": "^3.7.0", "di": "^0.0.1", "dom-serialize": "^2.2.1", @@ -20004,9 +20023,10 @@ "http-proxy": "^1.18.1", "isbinaryfile": "^4.0.8", "lodash": "^4.17.21", - "log4js": "^6.3.0", + "log4js": "^6.4.1", "mime": "^2.5.2", "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", "qjobs": "^1.2.0", "range-parser": "^1.2.1", "rimraf": "^3.0.2", @@ -20058,41 +20078,27 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -20467,22 +20473,22 @@ } }, "log4js": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", - "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.2.tgz", + "integrity": "sha512-k80cggS2sZQLBwllpT1p06GtfvzMmSdUCkW96f0Hj83rKGJDAu2vZjt9B9ag2vx8Zz1IXzxoLgqvRJCdMKybGg==", "dev": true, "requires": { - "date-format": "^3.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.1", - "rfdc": "^1.1.4", - "streamroller": "^2.2.4" + "date-format": "^4.0.4", + "debug": "^4.3.3", + "flatted": "^3.2.5", + "rfdc": "^1.3.0", + "streamroller": "^3.0.4" }, "dependencies": { "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "dev": true } } @@ -21149,9 +21155,9 @@ "dev": true }, "nanoid": { - "version": "3.1.30", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", - "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==" + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==" }, "nanomatch": { "version": "1.2.13", @@ -25864,9 +25870,9 @@ } }, "socket.io": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.0.tgz", - "integrity": "sha512-bnpJxswR9ov0Bw6ilhCvO38/1WPtE3eA2dtxi2Iq4/sFebiDJQzgKNYA7AuVVdGW09nrESXd90NbZqtDd9dzRQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", + "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", "dev": true, "requires": { "accepts": "~1.3.4", @@ -26269,6 +26275,16 @@ "integrity": "sha512-iJtHSGmNgAUx0b/MCS6ASGxb//hGrHHRgzvN+K5bvkBTN7A9RTpPSf1WSp+nPGvWCJ1jRnvY7MKnuqfoi3OEqg==", "dev": true }, + "storybook-dark-mode": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/storybook-dark-mode/-/storybook-dark-mode-1.0.9.tgz", + "integrity": "sha512-ITPXM2OSaga1zM5blpZy5HxMWAhrAqYi9aJtLgRtSdgoRrxVNAInDRD14TjmObdgLHNWxINoNbnEB+sKETa+iw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.0.0", + "memoizerific": "^1.11.3" + } + }, "stream-browserify": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", @@ -26319,32 +26335,54 @@ "dev": true }, "streamroller": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", - "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.4.tgz", + "integrity": "sha512-GI9NzeD+D88UFuIlJkKNDH/IsuR+qIN7Qh8EsmhoRZr9bQoehTraRgwtLUkZbpcAw+hLPfHOypmppz8YyGK68w==", "dev": true, "requires": { - "date-format": "^2.1.0", - "debug": "^4.1.1", - "fs-extra": "^8.1.0" - }, - "dependencies": { - "date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", - "dev": true - } + "date-format": "^4.0.4", + "debug": "^4.3.3", + "fs-extra": "^10.0.1" } }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "string.prototype.matchall": { @@ -27452,9 +27490,9 @@ } }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, "unix-crypt-td-js": { @@ -27559,9 +27597,9 @@ } }, "url-parse": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", - "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, "requires": { "querystringify": "^2.1.1", @@ -28407,7 +28445,6 @@ "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^3.0.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^13.1.2" @@ -28796,6 +28833,18 @@ "ansi-styles": "^3.2.0", "string-width": "^3.0.0", "strip-ansi": "^5.0.0" + }, + "dependencies": { + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } } }, "wrappy": { @@ -28873,6 +28922,18 @@ "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^15.0.1" + }, + "dependencies": { + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } } }, "yargs-parser": { diff --git a/package.json b/package.json index 90a038c908a6032fd78f65263aab7c3d753c4676..d4b47cfb3001a96f0e61c254a7c174ee31cb965a 100644 --- a/package.json +++ b/package.json @@ -46,11 +46,12 @@ "eslint-plugin-storybook": "^0.5.7", "jasmine-core": "~3.8.0", "jasmine-marbles": "^0.8.3", - "karma": "~6.3.0", + "karma": "^6.3.17", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.0.3", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "~1.7.0", + "storybook-dark-mode": "^1.0.9", "typescript": "~4.3.5" }, "dependencies": { diff --git a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.ts b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.ts index f6456eba6b90135ae35ea96b110834093b46a4f8..b587c100ff00445ac0489c5bb0a8ee1561a28bf5 100644 --- a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.ts +++ b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.component.ts @@ -1,10 +1,11 @@ -import { Component, OnDestroy } from "@angular/core"; +import { Component, EventEmitter, OnDestroy, Output } from "@angular/core"; import { Store } from "@ngrx/store"; import { merge, Observable, Subject, Subscription } from "rxjs"; import { RegionBase } from '../region.base' import { CONST, ARIA_LABELS } from 'common/constants' import { ComponentStore } from "src/viewerModule/componentStore"; import { distinctUntilChanged, mapTo } from "rxjs/operators"; +import { SapiRegionalFeatureModel } from "src/atlasComponents/sapi"; @Component({ selector: 'region-menu', @@ -14,6 +15,9 @@ import { distinctUntilChanged, mapTo } from "rxjs/operators"; }) export class RegionMenuComponent extends RegionBase implements OnDestroy { + @Output('region-menu-feat-clicked') + featureClicked = new EventEmitter<SapiRegionalFeatureModel>() + public CONST = CONST public ARIA_LABELS = ARIA_LABELS private subscriptions: Subscription[] = [] @@ -84,4 +88,8 @@ export class RegionMenuComponent extends RegionBase implements OnDestroy { } this.busyFlag = false } + + handleRegionalFeatureClicked(feat: SapiRegionalFeatureModel){ + this.featureClicked.emit(feat) + } } diff --git a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html index 04e8b19b707d269b65ce61ee7831138543a55bba..8e542df8c9dac0d874da0c8ec6d6bffff8b733fa 100644 --- a/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html +++ b/src/atlasComponents/parcellationRegion/regionMenu/regionMenu.template.html @@ -82,7 +82,9 @@ [regional-features-list-region]="region$ | async" [regional-features-list-atlas]="atlas" [regional-features-list-template]="template" - [regional-features-list-parcellation]="parcellation"> + [regional-features-list-parcellation]="parcellation" + (regional-features-list-feat-clicked)="handleRegionalFeatureClicked($event)" + > </regional-features-list> </ng-template> diff --git a/src/atlasComponents/parcellationRegion/regionalFeatures/regionalFeaturesList/regionalFeaturesList.component.ts b/src/atlasComponents/parcellationRegion/regionalFeatures/regionalFeaturesList/regionalFeaturesList.component.ts index 78490a73f88bc2d8b711ee6fb8b2ac9a8091a993..fbeba8f3d258e03cdc56d0f75c2f0a1c53001106 100644 --- a/src/atlasComponents/parcellationRegion/regionalFeatures/regionalFeaturesList/regionalFeaturesList.component.ts +++ b/src/atlasComponents/parcellationRegion/regionalFeatures/regionalFeaturesList/regionalFeaturesList.component.ts @@ -55,6 +55,5 @@ export class RegionalFeaturesList implements OnChanges{ showFeature(feat: SapiRegionalFeatureModel){ this.featureClicked.emit(feat) - console.log('emitting bla') } } diff --git a/src/atlasComponents/sapi/index.ts b/src/atlasComponents/sapi/index.ts index 17d2f7cb23cdee8f3c1036282fdc757e499532f6..07d0d433db71ed44d77b91ca90ba29a22d33613f 100644 --- a/src/atlasComponents/sapi/index.ts +++ b/src/atlasComponents/sapi/index.ts @@ -9,12 +9,10 @@ export { SapiVolumeModel, SapiDatasetModel, SapiRegionalFeatureModel, - SapiSpatialFeatureModel + SapiSpatialFeatureModel, + SapiFeatureModel, } from "./type" -import { SapiRegionalFeatureModel, SapiSpatialFeatureModel } from "./type" -export type SapiFeatureModel = SapiRegionalFeatureModel | SapiSpatialFeatureModel - export { SAPI } from "./sapi.service" export { SAPIAtlas, diff --git a/src/atlasComponents/sapi/stories.base.ts b/src/atlasComponents/sapi/stories.base.ts index 56f0d88dfdb7dc75566843e480ec41fdae3f26e9..1ef57fbe75af6f199dfb15c40b32f5e76e51547a 100644 --- a/src/atlasComponents/sapi/stories.base.ts +++ b/src/atlasComponents/sapi/stories.base.ts @@ -1,44 +1,42 @@ -import { forkJoin } from "rxjs" -import { map, switchMap, withLatestFrom } from "rxjs/operators" import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionalFeatureModel, SapiRegionModel, SapiSpaceModel } from "." import { SapiParcellationFeatureModel } from "./type" - -/** - * base class used to generate wrapper class for storybook - */ -export class HumanHoc1StoryBase { - - public humanAtlas$ = this.sapi.atlases$.pipe( - map(atlases => atlases.find(atlas => /human/i.test(atlas.name))) - ) - public mni152$ = this.humanAtlas$.pipe( - switchMap(atlas => - forkJoin( - atlas.spaces.map(spc => this.sapi.getSpaceDetail(atlas['@id'], spc['@id'])) - ).pipe( - map(spaces => spaces.find(spc => /152/.test(spc.fullName))) - ) - ) - ) - public jba29$ = this.humanAtlas$.pipe( - switchMap(atlas => - forkJoin( - atlas.parcellations.map(parc => this.sapi.getParcDetail(atlas['@id'], parc['@id'])) - ).pipe( - map(parc => parc.find(p => /2\.9/.test(p.name))) - ) - ) - ) - public hoc1Left$ = this.jba29$.pipe( - withLatestFrom(this.humanAtlas$, this.mni152$), - switchMap(([ parc, atlas, space ]) => this.sapi.getParcRegions(atlas['@id'], parc['@id'], space['@id'])), - map(regions => regions.find(r => /hoc1/i.test(r.name) && /left/i.test(r.name) )) - ) - - constructor(protected sapi: SAPI){ - } +import addons from '@storybook/addons'; +import { DARKTHEME } from "src/util/injectionTokens"; +import { APP_INITIALIZER, NgZone } from "@angular/core"; +import { DOCUMENT } from "@angular/common"; +import { BehaviorSubject } from "rxjs"; + +export function addAddonEventListener(eventName: string, callback: (...args: any[]) => void){ + const channel = addons.getChannel() + channel.on(eventName, callback) + return () => channel.off(eventName, callback) } +export const provideDarkTheme = [{ + provide: DARKTHEME, + useFactory: (zone: NgZone, document: Document) => { + const useDarkTheme = document.body.getAttribute('darktheme') === "true" + const sub = new BehaviorSubject(useDarkTheme) + addAddonEventListener("DARK_MODE", flag => { + zone.run(() => { + sub.next(flag) + }) + }) + return sub + }, + deps: [ NgZone, DOCUMENT ] +}, { + provide: APP_INITIALIZER, + multi: true, + useFactory: (document: Document) => { + addAddonEventListener("DARK_MODE", flag => { + document.body.setAttribute('darktheme', flag.toString()) + }) + return () => Promise.resolve() + }, + deps: [ DOCUMENT ] +}] + const atlasId = { human: 'juelich/iav/atlas/v1.0.0/1' } @@ -91,6 +89,10 @@ export async function getHoc1Features(): Promise<SapiRegionalFeatureModel[]> { return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20left/features`)).json() } +export async function getHoc1FeatureDetail(featId: string): Promise<SapiRegionalFeatureModel>{ + return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/regions/hoc1%20left/features/${encodeURIComponent(featId)}`)).json() +} + export async function getJba29Features(): Promise<SapiParcellationFeatureModel[]> { return await (await fetch(`${SAPI.bsEndpoint}/atlases/${atlasId.human}/parcellations/${parcId.human.jba29}/features`)).json() } diff --git a/src/atlasComponents/sapi/type.ts b/src/atlasComponents/sapi/type.ts index 87fddd2c6ee9a2afa81b843a94864a3472061b6b..231d82deab2bd18587e443464a1d5ced7ee11e8d 100644 --- a/src/atlasComponents/sapi/type.ts +++ b/src/atlasComponents/sapi/type.ts @@ -53,6 +53,8 @@ export type SapiRegionalFeatureModel = components["schemas"]["BaseDatasetJsonMod export type SapiParcellationFeatureMatrixModel = components["schemas"]["ConnectivityMatrixDataModel"] export type SapiParcellationFeatureModel = SapiParcellationFeatureMatrixModel | SapiSerializationErrorModel +export type SapiFeatureModel = (SapiRegionalFeatureModel | SapiSpatialFeatureModel | SapiParcellationFeatureModel) & { type: string } + export function guardPipe< InputType, GuardType extends InputType diff --git a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..08bdb9d55bd85a616914cae076797142f3ff1026 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.component.ts @@ -0,0 +1,15 @@ +import { Component, Input } from "@angular/core"; +import { SapiDatasetModel } from "src/atlasComponents/sapi"; + +@Component({ + selector: `sxplr-sapiviews-core-datasets-dataset`, + templateUrl: './dataset.template.html', + styleUrls: [ + `./dataset.style.css` + ] +}) + +export class DatasetView { + @Input('sxplr-sapiviews-core-datasets-dataset-input') + dataset: SapiDatasetModel +} diff --git a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.stories.ts b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a288e0f9493f24efc9accbbff79e5622848dba6 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.stories.ts @@ -0,0 +1,57 @@ +import { CommonModule } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI } from "src/atlasComponents/sapi" +import { getHoc1FeatureDetail, getHoc1Features, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { SapiViewsCoreDatasetModule } from ".." +import { DatasetView } from "./dataset.component" + +export default { + component: DatasetView, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + SapiViewsCoreDatasetModule, + ], + providers: [ + SAPI, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<DatasetView> = (args: DatasetView, { loaded }) => { + const { feature } = loaded + return ({ + props: { + ...args, + dataset: feature, + }, + }) +} + +const loadFeat = async () => { + const features = await getHoc1Features() + const receptorfeat = features.find(f => f.type === "siibra/receptor") + const feature = await getHoc1FeatureDetail(receptorfeat["@id"]) + return { + feature + } +} + +export const ReceptorDataset = Template.bind({}) +ReceptorDataset.args = { + +} +ReceptorDataset.loaders = [ + async () => { + const { feature } = await loadFeat() + return { + feature + } + } +] \ No newline at end of file diff --git a/src/components/markdown/markdown.style.css b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.style.css similarity index 100% rename from src/components/markdown/markdown.style.css rename to src/atlasComponents/sapiViews/core/datasets/dataset/dataset.style.css diff --git a/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html new file mode 100644 index 0000000000000000000000000000000000000000..e5fba069a65e277955b1ac34bb736005b6aa9c96 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/dataset/dataset.template.html @@ -0,0 +1,29 @@ +<mat-card *ngIf="!dataset"> + Dataset not specified. +</mat-card> + +<mat-card *ngIf="dataset" + class="mat-elevation-z4 sxplr-z-index-4"> + <mat-card-title> + {{ dataset.metadata.fullName }} + </mat-card-title> + + <mat-card-subtitle class="sxplr-d-inline-flex sxplr-align-items-stretch"> + <mat-icon class="sxplr-m-a" fontSet="fas" fontIcon="fa-database"></mat-icon> + <span class="sxplr-m-a"> + EBRAINS dataset + </span> + <mat-divider class="sxplr-pl-1" [vertical]="true"></mat-divider> + + <a mat-icon-button *ngFor="let url of dataset.urls" [href]="url.doi | parseDoi" target="_blank"> + <i class="fas fa-external-link-alt"></i> + </a> + </mat-card-subtitle> +</mat-card> + +<mat-card *ngIf="dataset" class="sxplr-z-index-0"> + <mat-card-content> + <markdown-dom class="sxplr-muted" [markdown]="dataset?.metadata?.description"> + </markdown-dom> + </mat-card-content> +</mat-card> diff --git a/src/atlasComponents/sapiViews/core/datasets/index.ts b/src/atlasComponents/sapiViews/core/datasets/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..d265981874bb6564d2dac8f9738d7069436154e9 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/index.ts @@ -0,0 +1,7 @@ +export { + SapiViewsCoreDatasetModule +} from "./module" + +export { + DatasetView +} from "./dataset/dataset.component" \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/datasets/module.ts b/src/atlasComponents/sapiViews/core/datasets/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..c25b3a58eedf49fb938febdd1f95b4bed430d798 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/module.ts @@ -0,0 +1,23 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { MarkdownModule } from "src/components/markdown"; +import { AngularMaterialModule } from "src/sharedModules"; +import { DatasetView } from "./dataset/dataset.component"; +import { ParseDoiPipe } from "./parseDoi.pipe"; + +@NgModule({ + imports: [ + CommonModule, + AngularMaterialModule, + MarkdownModule, + ], + declarations: [ + DatasetView, + ParseDoiPipe, + ], + exports: [ + DatasetView + ] +}) + +export class SapiViewsCoreDatasetModule{} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/datasets/parseDoi.pipe.spec.ts b/src/atlasComponents/sapiViews/core/datasets/parseDoi.pipe.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..866f3c40cad50af75fa45abfe849f8af9a2e5712 --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/parseDoi.pipe.spec.ts @@ -0,0 +1,17 @@ +import {} from 'jasmine' +import { ParseDoiPipe } from './parseDoi.pipe' + +describe('doiPipe.pipe.ts', () => { + const pipe = new ParseDoiPipe() + describe('DoiParsePIpe' , () => { + it('should parse string without prefix by appending doi prefix', () => { + const result = pipe.transform('123.456') + expect(result).toBe(`https://doi.org/123.456`) + }) + + it('should not append doi prefix if the first argument leads by http or https', () => { + expect(pipe.transform('http://google.com')).toBe('http://google.com') + expect(pipe.transform('https://google.com')).toBe('https://google.com') + }) + }) +}) diff --git a/src/atlasComponents/sapiViews/core/datasets/parseDoi.pipe.ts b/src/atlasComponents/sapiViews/core/datasets/parseDoi.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a4d87cbc9bb37216e379e5e80a4360eacfb055a --- /dev/null +++ b/src/atlasComponents/sapiViews/core/datasets/parseDoi.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from "@angular/core"; + +@Pipe({ + name: 'parseDoi', +}) + +export class ParseDoiPipe implements PipeTransform { + public transform(s: string, prefix: string = 'https://doi.org/') { + const hasProtocol = /^https?:\/\//.test(s) + return `${hasProtocol ? '' : prefix}${s}` + } +} diff --git a/src/atlasComponents/sapiViews/core/index.ts b/src/atlasComponents/sapiViews/core/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..6db4628176e571f0a50adca091dc02c5d51801de --- /dev/null +++ b/src/atlasComponents/sapiViews/core/index.ts @@ -0,0 +1,3 @@ +export { + SapiViewsCoreModule +} from "./module" \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/core/module.ts b/src/atlasComponents/sapiViews/core/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c377585d84bfe7f00b5cb2371a0b4fc8313a24e --- /dev/null +++ b/src/atlasComponents/sapiViews/core/module.ts @@ -0,0 +1,15 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SapiViewsCoreDatasetModule } from "./datasets"; + +@NgModule({ + imports: [ + CommonModule, + SapiViewsCoreDatasetModule, + ], + exports: [ + SapiViewsCoreDatasetModule + ] +}) + +export class SapiViewsCoreModule{} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/entry/entry.component.ts b/src/atlasComponents/sapiViews/features/entry/entry.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc5f8a396f61d4d2b63b8ad9c19afa197d15c332 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/entry/entry.component.ts @@ -0,0 +1,36 @@ +import { Component, Input } from "@angular/core"; +import { SapiFeatureModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; + +@Component({ + selector: 'sxplr-sapiviews-features-entry', + templateUrl: './entry.template.html', + styleUrls: [ + './entry.style.css' + ] +}) + +export class FeatureEntryCmp{ + + /** + * in future, hopefully feature detail can be queried with just id, + * and atlas/space/parcellation/region are no longer necessary + */ + @Input('sxplr-sapiviews-features-entry-atlas') + atlas: SapiFeatureModel + + @Input('sxplr-sapiviews-features-entry-space') + space: SapiSpaceModel + + @Input('sxplr-sapiviews-features-entry-parcellation') + parcellation: SapiParcellationModel + + @Input('sxplr-sapiviews-features-entry-region') + region: SapiRegionModel + + @Input('sxplr-sapiviews-features-entry-feature') + feature: SapiFeatureModel + + featureType = { + receptor: "siibra/receptor" + } +} diff --git a/src/atlasComponents/sapiViews/features/entry/entry.style.css b/src/atlasComponents/sapiViews/features/entry/entry.style.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/atlasComponents/sapiViews/features/entry/entry.template.html b/src/atlasComponents/sapiViews/features/entry/entry.template.html new file mode 100644 index 0000000000000000000000000000000000000000..0affeaa6282725139eb7fd7af2258061ff7d1b44 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/entry/entry.template.html @@ -0,0 +1,9 @@ +<div [ngSwitch]="feature?.type"> + <sxplr-sapiviews-features-receptor-entry *ngSwitchCase="featureType.receptor" + [sxplr-sapiviews-features-receptor-atlas]="atlas" + [sxplr-sapiviews-features-receptor-parcellation]="parcellation" + [sxplr-sapiviews-features-receptor-template]="space" + [sxplr-sapiviews-features-receptor-region]="region" + [sxplr-sapiviews-features-receptor-featureid]="feature['@id']"> + </sxplr-sapiviews-features-receptor-entry> +</div> diff --git a/src/atlasComponents/sapiViews/features/module.ts b/src/atlasComponents/sapiViews/features/module.ts index a4971d01512db6b58fe60575a5239b50f09029e2..61dfd3091bf1bb7d4ae815b6173b3194d0830164 100644 --- a/src/atlasComponents/sapiViews/features/module.ts +++ b/src/atlasComponents/sapiViews/features/module.ts @@ -1,6 +1,7 @@ import { CommonModule } from "@angular/common" import { NgModule } from "@angular/core" import { AngularMaterialModule } from "src/sharedModules" +import { FeatureEntryCmp } from "./entry/entry.component" import * as ieeg from "./ieeg" import * as receptor from "./receptors" @@ -19,9 +20,11 @@ const { ], declarations: [ IEEGSessionCmp, + FeatureEntryCmp, ], exports: [ IEEGSessionCmp, + FeatureEntryCmp, ] }) export class SapiViewsFeaturesModule{} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts index c8c33226cfa700d332307be2de1827017ac0de93..281d26289b60e65652c52926a8b184a5b1289891 100644 --- a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts +++ b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiograph.stories.ts @@ -5,7 +5,8 @@ import { FormsModule } from "@angular/forms" import { BrowserAnimationsModule } from "@angular/platform-browser/animations" import { Meta, moduleMetadata, Story } from "@storybook/angular" import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" -import { getHoc1Features, getHoc1Left, getHumanAtlas, getJba29, getMni152, HumanHoc1StoryBase } from "src/atlasComponents/sapi/stories.base" +import { getHoc1FeatureDetail, getHoc1Features, getHoc1Left, getHumanAtlas, getJba29, getMni152 } from "src/atlasComponents/sapi/stories.base" +import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type" import { AngularMaterialModule } from "src/sharedModules" import { Autoradiography } from "./autoradiography.component" @@ -31,6 +32,7 @@ import { Autoradiography } from "./autoradiography.component" [sxplr-sapiviews-features-receptor-template]="template" [sxplr-sapiviews-features-receptor-region]="region" [sxplr-sapiviews-features-receptor-featureid]="featureId" + [sxplr-sapiviews-features-receptor-data]="feature" [sxplr-sapiviews-features-receptor-autoradiograph-selected-symbol]="selectedSymbol" > </sxplr-sapiviews-features-receptor-autoradiograph> @@ -51,6 +53,8 @@ class AutoRadiographWrapperCls { parcellation: SapiParcellationModel template: SapiSpaceModel region: SapiRegionModel + + feature: SapiRegionalFeatureReceptorModel featureId: string @ViewChild(Autoradiography) @@ -59,7 +63,7 @@ class AutoRadiographWrapperCls { selectedSymbol: string get options(){ - return Object.keys(this.ar?.receptorData?.data?.autoradiographs || {}) + return Object.keys(this.feature?.data?.autoradiographs || this.ar?.receptorData?.data?.autoradiographs || {}) } } @@ -85,7 +89,7 @@ export default { } as Meta const Template: Story<AutoRadiographWrapperCls> = (args: AutoRadiographWrapperCls, { loaded }) => { - const { atlas, parc, space, region, receptorfeat } = loaded + const { atlas, parc, space, region, featureId, feature } = loaded return ({ props: { ...args, @@ -93,29 +97,50 @@ const Template: Story<AutoRadiographWrapperCls> = (args: AutoRadiographWrapperCl parcellation: parc, template: space, region: region, - featureId: receptorfeat["@id"] + feature, + featureId, }, }) } -Template.loaders = [ - async () => { - const atlas = await getHumanAtlas() - const parc = await getJba29() - const region = await getHoc1Left() - const space = await getMni152() - const features = await getHoc1Features() - const receptorfeat = features.find(f => f.type === "siibra/receptor") - return { - atlas, parc, space, region, receptorfeat - } +const loadFeat = async () => { + const atlas = await getHumanAtlas() + const parc = await getJba29() + const region = await getHoc1Left() + const space = await getMni152() + const features = await getHoc1Features() + const receptorfeat = features.find(f => f.type === "siibra/receptor") + const feature = await getHoc1FeatureDetail(receptorfeat["@id"]) + return { + atlas, + parc, + space, + region, + featureId: receptorfeat["@id"], + feature } -] +} export const Default = Template.bind({}) Default.args = { } Default.loaders = [ - ...Template.loaders + async () => { + const { atlas, parc, space, region, featureId } = await loadFeat() + return { + atlas, parc, space, region, featureId + } + } +] + +export const LoadViaDirectlyInjectData = Template.bind({}) +LoadViaDirectlyInjectData.loaders = [ + async () => { + + const { feature } = await loadFeat() + return { + feature + } + } ] \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts index 8e6c522938bc6f4a81985615448b0727620186f6..95d301d3e4addf1bdd3406be35167d0b5b47d017 100644 --- a/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts +++ b/src/atlasComponents/sapiViews/features/receptors/autoradiography/autoradiography.component.ts @@ -9,7 +9,7 @@ import { BaseReceptor } from "../base"; styleUrls: [ './autoradiography.style.css' ], - exportAs: 'sxplrSapiviewsFeaturesReceptorAR' + exportAs: 'sxplrSapiViewsFeaturesReceptorAR' }) export class Autoradiography extends BaseReceptor implements OnChanges, AfterViewInit{ @@ -24,11 +24,10 @@ export class Autoradiography extends BaseReceptor implements OnChanges, AfterVie renderBuffer: Uint8ClampedArray async ngOnChanges(simpleChanges: SimpleChanges) { - - if (this.baseInputChanged(simpleChanges)) { - await this.fetchReceptorData() + await super.ngOnChanges(simpleChanges) + if (!this.receptorData) { + return } - if (this.selectedSymbol) { const fp = this.receptorData.data.autoradiographs[this.selectedSymbol] if (!fp) { @@ -42,7 +41,7 @@ export class Autoradiography extends BaseReceptor implements OnChanges, AfterVie const { result } = await this.sapi.processNpArrayData<PARSE_TYPEDARRAY.CANVAS_FORTRAN_RGBA>(fp, PARSE_TYPEDARRAY.CANVAS_FORTRAN_RGBA) this.renderBuffer = result - this.renderCanvas() + this.rerender() } } constructor(sapi: SAPI, private el: ElementRef){ @@ -50,11 +49,11 @@ export class Autoradiography extends BaseReceptor implements OnChanges, AfterVie } ngAfterViewInit(): void { - if (this.pleaseRender) this.renderCanvas() + if (this.pleaseRender) this.rerender() } - private renderCanvas(){ - if (!this.el) { + rerender(){ + if (!this.el || !this.renderBuffer) { this.pleaseRender = true return } diff --git a/src/atlasComponents/sapiViews/features/receptors/base.ts b/src/atlasComponents/sapiViews/features/receptors/base.ts index 2d0a047b2d4aab36a0f643533d760edd9951b2ff..bfb7b941311989ee4fdc242a322620b005d15bda 100644 --- a/src/atlasComponents/sapiViews/features/receptors/base.ts +++ b/src/atlasComponents/sapiViews/features/receptors/base.ts @@ -1,8 +1,9 @@ -import { Input, SimpleChanges } from "@angular/core"; +import { Directive, Input, SimpleChanges } from "@angular/core"; import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi"; import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type"; -export class BaseReceptor{ +@Directive() +export abstract class BaseReceptor{ @Input('sxplr-sapiviews-features-receptor-atlas') atlas: SapiAtlasModel @@ -19,23 +20,40 @@ export class BaseReceptor{ @Input('sxplr-sapiviews-features-receptor-featureid') featureId: string + @Input('sxplr-sapiviews-features-receptor-data') receptorData: SapiRegionalFeatureReceptorModel error: string - protected baseInputChanged(simpleChanges: SimpleChanges) { - const { - atlas, - parcellation, - template, - region, - featureId, - } = simpleChanges - return atlas - || parcellation - || template - || region - || featureId + async ngOnChanges(simpleChanges: SimpleChanges) { + if (simpleChanges.receptorData?.currentValue) { + this.rerender() + return + } + if (this.canFetch) { + this.receptorData = await this.fetchReceptorData() + this.rerender() + } + } + + private get canFetch() { + if (!this.atlas) { + this.error = `atlas needs to be defined, but is not` + return false + } + if (!this.parcellation) { + this.error = `parcellation needs to be defined, but is not` + return false + } + if (!this.region) { + this.error = `region needs to be defined, but is not` + return false + } + if (!this.featureId) { + this.error = `featureId needs to be defined, but is not` + return false + } + return true } protected async fetchReceptorData() { @@ -60,9 +78,11 @@ export class BaseReceptor{ if (result.type !== "siibra/receptor") { throw new Error(`BaseReceptor Error. Expected .type to be "siibra/receptor", but was "${result.type}"`) } - this.receptorData = result + return result } + abstract rerender(): void + constructor( protected sapi: SAPI ){ diff --git a/src/atlasComponents/sapiViews/features/receptors/entry/entry.component.ts b/src/atlasComponents/sapiViews/features/receptors/entry/entry.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..25ee164f8e3f99cd9e2ee4944276224097e8dbc1 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/entry/entry.component.ts @@ -0,0 +1,40 @@ +import { ChangeDetectorRef, Component, Input, OnChanges, SimpleChanges } from "@angular/core"; +import { SAPI } from "src/atlasComponents/sapi"; +import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type"; +import { BaseReceptor } from "../base"; + +@Component({ + selector: 'sxplr-sapiviews-features-receptor-entry', + templateUrl: `./entry.template.html`, + styleUrls: [ + `./entry.style.css` + ] +}) + +export class Entry extends BaseReceptor implements OnChanges { + selectedSymbol: string + symbolsOptions: string[] = [] + + async ngOnChanges(simpleChanges: SimpleChanges): Promise<void> { + await super.ngOnChanges(simpleChanges) + } + + loading = true + rerender(): void { + if (this.receptorData.data.receptor_symbols) { + this.loading = false + this.symbolsOptions = Object.keys(this.receptorData.data.receptor_symbols) + } + this.cdr.detectChanges() + } + constructor( + sapi: SAPI, + private cdr: ChangeDetectorRef, + ){ + super(sapi) + } + + setSelectedSymbol(select: string){ + this.selectedSymbol = select + } +} diff --git a/src/atlasComponents/sapiViews/features/receptors/entry/entry.stories.ts b/src/atlasComponents/sapiViews/features/receptors/entry/entry.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..3adb01e1daa675b984acba42b22bf03c1c6f3a66 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/entry/entry.stories.ts @@ -0,0 +1,129 @@ +import { CommonModule, DOCUMENT } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { FormsModule } from "@angular/forms" +import { BrowserAnimationsModule } from "@angular/platform-browser/animations" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" +import { getHoc1FeatureDetail, getHoc1Features, getHoc1Left, getHumanAtlas, getJba29, getMni152, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { AngularMaterialModule } from "src/sharedModules" +import { Entry } from "./entry.component" +import { ReceptorViewModule } from ".." +import { appendScriptFactory, APPEND_SCRIPT_TOKEN } from "src/util/constants" +import { Component, Output } from "@angular/core" +import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type" + +@Component({ + selector: 'entry-wrapper-cmp', + template: ` + <sxplr-sapiviews-features-receptor-entry + [sxplr-sapiviews-features-receptor-atlas]="atlas" + [sxplr-sapiviews-features-receptor-parcellation]="parcellation" + [sxplr-sapiviews-features-receptor-template]="template" + [sxplr-sapiviews-features-receptor-region]="region" + [sxplr-sapiviews-features-receptor-featureid]="featureId" + [sxplr-sapiviews-features-receptor-data]="feature" + > + </sxplr-sapiviews-features-receptor-entry> + `, + styles: [ + ` + :host + { + display: block; + width: 20rem; + } + ` + ] +}) +class EntryWrappercls { + atlas: SapiAtlasModel + parcellation: SapiParcellationModel + template: SapiSpaceModel + region: SapiRegionModel + feature: SapiRegionalFeatureReceptorModel + featureId: string + +} + +export default { + component: EntryWrappercls, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + HttpClientModule, + ReceptorViewModule, + AngularMaterialModule, + ], + providers: [ + SAPI, + { + provide: APPEND_SCRIPT_TOKEN, + useFactory: appendScriptFactory, + deps: [ DOCUMENT ] + }, + ...provideDarkTheme, + ], + declarations: [ + EntryWrappercls + ] + }) + ], +} as Meta + +const Template: Story<Entry> = (args: Entry, { loaded }) => { + const { atlas, parc, space, region, featureId, feature } = loaded + return ({ + props: { + ...args, + atlas: atlas, + parcellation: parc, + template: space, + region: region, + feature, + featureId, + }, + }) +} + +const loadFeat = async () => { + const atlas = await getHumanAtlas() + const parc = await getJba29() + const region = await getHoc1Left() + const space = await getMni152() + const features = await getHoc1Features() + const receptorfeat = features.find(f => f.type === "siibra/receptor") + const feature = await getHoc1FeatureDetail(receptorfeat["@id"]) + return { + atlas, + parc, + space, + region, + featureId: receptorfeat["@id"], + feature + } +} + +export const Default = Template.bind({}) +Default.args = { + +} +Default.loaders = [ + async () => { + const { atlas, parc, space, region, featureId } = await loadFeat() + return { + atlas, parc, space, region, featureId + } + } +] + +export const LoadViaDirectlyInjectData = Template.bind({}) +LoadViaDirectlyInjectData.loaders = [ + async () => { + + const { feature } = await loadFeat() + return { + feature + } + } +] \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/receptors/entry/entry.style.css b/src/atlasComponents/sapiViews/features/receptors/entry/entry.style.css new file mode 100644 index 0000000000000000000000000000000000000000..0645dc01a568740a2c3d2f51c486adcb7f111a02 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/entry/entry.style.css @@ -0,0 +1,4 @@ +:host +{ + display: block; +} \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/receptors/entry/entry.template.html b/src/atlasComponents/sapiViews/features/receptors/entry/entry.template.html new file mode 100644 index 0000000000000000000000000000000000000000..33636fa42ac00bee0b0e9e29a9caa54d200f8453 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/entry/entry.template.html @@ -0,0 +1,37 @@ +<mat-card> + <spinner-cmp *ngIf="loading"></spinner-cmp> + + <sxplr-sapiviews-features-receptor-fingerprint + [sxplr-sapiviews-features-receptor-data]="receptorData" + (sxplr-sapiviews-features-receptor-fingerprint-receptor-selected)="setSelectedSymbol($event)" + > + </sxplr-sapiviews-features-receptor-fingerprint> + + <mat-form-field appearance="fill" class="w-100"> + <mat-select [(ngModel)]="selectedSymbol"> + <mat-option value="null" disabled> + --select-- + </mat-option> + + <mat-option [value]="option" + *ngFor="let option of symbolsOptions"> + {{ option }} + </mat-option> + </mat-select> + </mat-form-field> + + <ng-template [ngIf]="selectedSymbol"> + + <sxplr-sapiviews-features-receptor-profile + [sxplr-sapiviews-features-receptor-data]="receptorData" + [sxplr-sapiviews-features-receptor-profile-selected-symbol]="selectedSymbol"> + </sxplr-sapiviews-features-receptor-profile> + + <sxplr-sapiviews-features-receptor-autoradiograph + [sxplr-sapiviews-features-receptor-data]="receptorData" + [sxplr-sapiviews-features-receptor-autoradiograph-selected-symbol]="selectedSymbol" + > + </sxplr-sapiviews-features-receptor-autoradiograph> + </ng-template> + +</mat-card> diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts index 3fd3f1760f2a1c4ab484cbb6c31b1abf22f405e5..1a9bd2b0142aecbacb4cff82818e5df17faf5471 100644 --- a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts +++ b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.component.ts @@ -1,8 +1,9 @@ -import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, OnChanges, OnDestroy, Output, SimpleChanges, ViewChild } from "@angular/core"; -import { fromEvent, Subscription } from "rxjs"; +import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, Inject, OnChanges, OnDestroy, Output, SimpleChanges, ViewChild } from "@angular/core"; +import { fromEvent, Observable, Subscription } from "rxjs"; import { distinctUntilChanged, map } from "rxjs/operators"; import { SAPI } from "src/atlasComponents/sapi"; import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type"; +import { DARKTHEME } from "src/util/injectionTokens"; import { BaseReceptor } from "../base"; /** @@ -54,20 +55,16 @@ export class Fingerprint extends BaseReceptor implements OnChanges, AfterViewIni @HostListener('click') onClick(){ - if (this.mouseOverReceptor) { + if (this.mouseOverReceptor) { this.selectReceptor.emit(this.mouseOverReceptor) } } async ngOnChanges(simpleChanges: SimpleChanges) { - if (this.baseInputChanged(simpleChanges)) { - await this.fetchReceptorData() - } - if (this.receptorData) { - this.setDumbRadar() - } + await super.ngOnChanges(simpleChanges) } - constructor(sapi: SAPI, private el: ElementRef){ + + constructor(sapi: SAPI, private el: ElementRef, @Inject(DARKTHEME) public darktheme$: Observable<boolean>){ super(sapi) } @@ -85,7 +82,7 @@ export class Fingerprint extends BaseReceptor implements OnChanges, AfterViewIni ngAfterViewInit(){ if (this.setDumbRadarPlease) { - this.setDumbRadar() + this.rerender() } this.sub.push( @@ -97,8 +94,9 @@ export class Fingerprint extends BaseReceptor implements OnChanges, AfterViewIni }) ) } - - setDumbRadar(){ + + rerender(): void { + if (!this.dumbRadarCmp) { this.setDumbRadarPlease = true return @@ -107,6 +105,6 @@ export class Fingerprint extends BaseReceptor implements OnChanges, AfterViewIni this.dumbRadarCmp.metaBs = this.receptorData.data.receptor_symbols this.dumbRadarCmp.radar= transformRadar(this.receptorData.data.fingerprints) - this.setDumbRadarPlease = false + this.setDumbRadarPlease = false } } diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts index 014a062467786bf42e0d053dd930ecb052a2748f..ae8a161cf62ec7eaf5284d61ef5f9ebb0604ab58 100644 --- a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts +++ b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.stories.ts @@ -3,7 +3,8 @@ import { HttpClientModule } from "@angular/common/http" import { Component, EventEmitter, Output } from "@angular/core" import { Meta, moduleMetadata, Story } from "@storybook/angular" import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" -import { getHoc1Features, getHoc1Left, getHumanAtlas, getJba29, getMni152, HumanHoc1StoryBase } from "src/atlasComponents/sapi/stories.base" +import { getHoc1FeatureDetail, getHoc1Features, getHoc1Left, getHumanAtlas, getJba29, getMni152, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type" import { AngularMaterialModule } from "src/sharedModules" import { appendScriptFactory, APPEND_SCRIPT_TOKEN } from "src/util/constants" import { ReceptorViewModule } from ".." @@ -17,6 +18,7 @@ import { ReceptorViewModule } from ".." [sxplr-sapiviews-features-receptor-template]="template" [sxplr-sapiviews-features-receptor-region]="region" [sxplr-sapiviews-features-receptor-featureid]="featureId" + [sxplr-sapiviews-features-receptor-data]="feature" (sxplr-sapiviews-features-receptor-fingerprint-receptor-selected)="selectReceptor.emit($event)" > </sxplr-sapiviews-features-receptor-fingerprint> @@ -37,6 +39,7 @@ class FingerprintWrapperCls { parcellation: SapiParcellationModel template: SapiSpaceModel region: SapiRegionModel + feature: SapiRegionalFeatureReceptorModel featureId: string @Output() @@ -59,7 +62,8 @@ export default { provide: APPEND_SCRIPT_TOKEN, useFactory: appendScriptFactory, deps: [ DOCUMENT ] - } + }, + ...provideDarkTheme, ], declarations: [] }) @@ -67,7 +71,7 @@ export default { } as Meta const Template: Story<FingerprintWrapperCls> = (args: FingerprintWrapperCls, { loaded }) => { - const { atlas, parc, space, region, receptorfeat } = loaded + const { atlas, parc, space, region, featureId, feature } = loaded return ({ props: { ...args, @@ -75,29 +79,50 @@ const Template: Story<FingerprintWrapperCls> = (args: FingerprintWrapperCls, { l parcellation: parc, template: space, region: region, - featureId: receptorfeat["@id"] + feature, + featureId, }, }) } -Template.loaders = [ - async () => { - const atlas = await getHumanAtlas() - const parc = await getJba29() - const region = await getHoc1Left() - const space = await getMni152() - const features = await getHoc1Features() - const receptorfeat = features.find(f => f.type === "siibra/receptor") - return { - atlas, parc, space, region, receptorfeat - } +const loadFeat = async () => { + const atlas = await getHumanAtlas() + const parc = await getJba29() + const region = await getHoc1Left() + const space = await getMni152() + const features = await getHoc1Features() + const receptorfeat = features.find(f => f.type === "siibra/receptor") + const feature = await getHoc1FeatureDetail(receptorfeat["@id"]) + return { + atlas, + parc, + space, + region, + featureId: receptorfeat["@id"], + feature } -] +} export const Default = Template.bind({}) Default.args = { } Default.loaders = [ - ...Template.loaders -] \ No newline at end of file + async () => { + const { atlas, parc, space, region, featureId } = await loadFeat() + return { + atlas, parc, space, region, featureId + } + } +] + +export const LoadViaDirectlyInjectData = Template.bind({}) +LoadViaDirectlyInjectData.loaders = [ + async () => { + + const { feature } = await loadFeat() + return { + feature + } + } +] diff --git a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.template.html b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.template.html index 1588598e3ece35f1f7353139cf2b61a9d662f395..c315de7303ead0ebb5e0c2032fd4a647d381dd39 100644 --- a/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.template.html +++ b/src/atlasComponents/sapiViews/features/receptors/fingerprint/fingerprint.template.html @@ -1,6 +1,3 @@ -<div *ngIf="error"> - {{ error }} -</div> - -<kg-dataset-dumb-radar> +<kg-dataset-dumb-radar + [attr.kg-ds-prv-darkmode]="darktheme$ | async"> </kg-dataset-dumb-radar> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/features/receptors/module.ts b/src/atlasComponents/sapiViews/features/receptors/module.ts index a1229981ae85fd58e141c4ecb59a7add5f61d872..ca559de014f96f1e54dd38a6313ec1f6bcf3b69c 100644 --- a/src/atlasComponents/sapiViews/features/receptors/module.ts +++ b/src/atlasComponents/sapiViews/features/receptors/module.ts @@ -1,23 +1,34 @@ import { CommonModule } from "@angular/common"; -import { APP_INITIALIZER, NgModule } from "@angular/core"; +import { APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { SpinnerModule } from "src/components/spinner"; +import { AngularMaterialModule } from "src/sharedModules"; import { APPEND_SCRIPT_TOKEN } from "src/util/constants"; import { Autoradiography } from "./autoradiography/autoradiography.component"; +import { Entry } from "./entry/entry.component"; import { Fingerprint } from "./fingerprint/fingerprint.component" import { Profile } from "./profile/profile.component" @NgModule({ imports: [ CommonModule, + AngularMaterialModule, + FormsModule, + BrowserAnimationsModule, + SpinnerModule, ], declarations: [ Autoradiography, Fingerprint, Profile, + Entry, ], exports: [ Autoradiography, Fingerprint, Profile, + Entry, ], providers: [{ provide: APP_INITIALIZER, @@ -33,7 +44,10 @@ import { Profile } from "./profile/profile.component" deps: [ APPEND_SCRIPT_TOKEN ] - }] + }], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA, + ] }) export class ReceptorViewModule{} diff --git a/src/atlasComponents/sapiViews/features/receptors/profile/profile.component.ts b/src/atlasComponents/sapiViews/features/receptors/profile/profile.component.ts index 2a62d72eb74b28b6c413d0e59c7de2bce0828148..a50b2a5be99f761546ae90d22c9d55285496b44c 100644 --- a/src/atlasComponents/sapiViews/features/receptors/profile/profile.component.ts +++ b/src/atlasComponents/sapiViews/features/receptors/profile/profile.component.ts @@ -1,11 +1,73 @@ -import { Component } from "@angular/core"; +import { AfterViewInit, Component, ElementRef, Inject, Input, OnChanges, SimpleChanges } from "@angular/core"; +import { Observable } from "rxjs"; +import { SAPI } from "src/atlasComponents/sapi"; +import { PARSE_TYPEDARRAY } from "src/atlasComponents/sapi/sapi.service"; +import { DARKTHEME } from "src/util/injectionTokens"; import { BaseReceptor } from "../base"; @Component({ + selector: `sxplr-sapiviews-features-receptor-profile`, templateUrl: './profile.template.html', styleUrls: [ './profile.style.css' ] }) -export class Profile extends BaseReceptor{} \ No newline at end of file +export class Profile extends BaseReceptor implements AfterViewInit, OnChanges{ + + @Input('sxplr-sapiviews-features-receptor-profile-selected-symbol') + selectedSymbol: string + + private pleaseRender = false + dumbLineData: Record<number, number> + + constructor(sapi: SAPI, private el: ElementRef, @Inject(DARKTHEME) public darktheme$: Observable<boolean> ){ + super(sapi) + } + + async ngOnChanges(simpleChanges: SimpleChanges) { + await super.ngOnChanges(simpleChanges) + if (!this.receptorData) { + return + } + if (this.selectedSymbol) { + this.rerender() + } + } + + ngAfterViewInit(): void { + if (this.pleaseRender) { + this.rerender() + } + } + + get dumbLineCmp(){ + return this.el?.nativeElement?.querySelector('kg-dataset-dumb-line') + } + + async rerender() { + if (!this.dumbLineCmp) { + this.pleaseRender = true + return + } + this.pleaseRender = false + this.dumbLineData = null + + if (this.receptorData?.data?.profiles?.[this.selectedSymbol]) { + const { rawArray } = await this.sapi.processNpArrayData<PARSE_TYPEDARRAY.RAW_ARRAY>( + this.receptorData.data.profiles[this.selectedSymbol].density, + PARSE_TYPEDARRAY.RAW_ARRAY + ) + if (rawArray.length !== 1) { + this.error = `expected rawArray.length to be 1, but is ${rawArray.length} instead` + return + } + const prof = rawArray[0] + this.dumbLineData = {} + for (const idx in prof) { + this.dumbLineData[idx] = prof[idx] + } + this.dumbLineCmp.profileBs = this.dumbLineData + } + } +} diff --git a/src/atlasComponents/sapiViews/features/receptors/profile/profile.stories.ts b/src/atlasComponents/sapiViews/features/receptors/profile/profile.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..39b199ef32a96b6ea3b31c6518b6c8e010767b62 --- /dev/null +++ b/src/atlasComponents/sapiViews/features/receptors/profile/profile.stories.ts @@ -0,0 +1,155 @@ +import { CommonModule, DOCUMENT } from "@angular/common" +import { HttpClientModule } from "@angular/common/http" +import { Component, NgZone, ViewChild } from "@angular/core" +import { FormsModule } from "@angular/forms" +import { BrowserAnimationsModule } from "@angular/platform-browser/animations" +import { Meta, moduleMetadata, Story } from "@storybook/angular" +import { BehaviorSubject, Subject } from "rxjs" +import { SAPI, SapiAtlasModel, SapiParcellationModel, SapiRegionModel, SapiSpaceModel } from "src/atlasComponents/sapi" +import { addAddonEventListener, getHoc1FeatureDetail, getHoc1Features, getHoc1Left, getHumanAtlas, getJba29, getMni152, provideDarkTheme } from "src/atlasComponents/sapi/stories.base" +import { SapiRegionalFeatureReceptorModel } from "src/atlasComponents/sapi/type" +import { AngularMaterialModule } from "src/sharedModules" +import { appendScriptFactory, APPEND_SCRIPT_TOKEN } from "src/util/constants" +import { DARKTHEME } from "src/util/injectionTokens" +import { ReceptorViewModule } from ".." +import { Profile } from "./profile.component" + +@Component({ + selector: 'autoradiograph-wrapper-cmp', + template: ` + <mat-form-field appearance="fill"> + <mat-select [(ngModel)]="selectedSymbol"> + <mat-option value="" disabled> + --select-- + </mat-option> + + <mat-option [value]="option" + *ngFor="let option of options"> + {{ option }} + </mat-option> + </mat-select> + </mat-form-field> + <sxplr-sapiviews-features-receptor-profile + class="d-inline-block w-100 h-100" + [sxplr-sapiviews-features-receptor-atlas]="atlas" + [sxplr-sapiviews-features-receptor-parcellation]="parcellation" + [sxplr-sapiviews-features-receptor-template]="template" + [sxplr-sapiviews-features-receptor-region]="region" + [sxplr-sapiviews-features-receptor-featureid]="featureId" + [sxplr-sapiviews-features-receptor-data]="feature" + [sxplr-sapiviews-features-receptor-profile-selected-symbol]="selectedSymbol" + > + </sxplr-sapiviews-features-receptor-profile> + `, + styles: [ + ` + :host + { + display: block; + max-width: 24rem; + max-height: 24rem; + } + ` + ] +}) +class ProfileWrapperCls { + atlas: SapiAtlasModel + parcellation: SapiParcellationModel + template: SapiSpaceModel + region: SapiRegionModel + + feature: SapiRegionalFeatureReceptorModel + featureId: string + + @ViewChild(Profile) + profile: Profile + + selectedSymbol: string + + get options(){ + return Object.keys(this.feature?.data?.profiles || this.profile?.receptorData?.data?.profiles || {}) + } +} + +export default { + component: ProfileWrapperCls, + decorators: [ + moduleMetadata({ + imports: [ + CommonModule, + AngularMaterialModule, + HttpClientModule, + BrowserAnimationsModule, + FormsModule, + ReceptorViewModule, + ], + providers: [ + SAPI, + { + provide: APPEND_SCRIPT_TOKEN, + useFactory: appendScriptFactory, + deps: [ DOCUMENT ] + }, + ...provideDarkTheme, + ], + declarations: [] + }) + ], +} as Meta + +const Template: Story<ProfileWrapperCls> = (args: ProfileWrapperCls, { loaded }) => { + const { atlas, parc, space, region, featureId, feature } = loaded + return ({ + props: { + ...args, + atlas: atlas, + parcellation: parc, + template: space, + region: region, + feature, + featureId, + }, + }) +} + +const loadFeat = async () => { + const atlas = await getHumanAtlas() + const parc = await getJba29() + const region = await getHoc1Left() + const space = await getMni152() + const features = await getHoc1Features() + const receptorfeat = features.find(f => f.type === "siibra/receptor") + const feature = await getHoc1FeatureDetail(receptorfeat["@id"]) + return { + atlas, + parc, + space, + region, + featureId: receptorfeat["@id"], + feature + } +} + +export const Default = Template.bind({}) +Default.args = { + +} +Default.loaders = [ + async () => { + const { atlas, parc, space, region, featureId } = await loadFeat() + return { + atlas, parc, space, region, featureId + } + } +] + +export const LoadViaDirectlyInjectData = Template.bind({}) +LoadViaDirectlyInjectData.loaders = [ + async () => { + + const { feature } = await loadFeat() + return { + feature + } + } +] diff --git a/src/atlasComponents/sapiViews/features/receptors/profile/profile.template.html b/src/atlasComponents/sapiViews/features/receptors/profile/profile.template.html index a143d8a0e325953339a1771a3a6b5036d826148e..182db6578a311b5661b8a571c927ab11d6c149ae 100644 --- a/src/atlasComponents/sapiViews/features/receptors/profile/profile.template.html +++ b/src/atlasComponents/sapiViews/features/receptors/profile/profile.template.html @@ -1,4 +1,3 @@ - <kg-dataset-dumb-line - [attr.kg-ds-prv-darkmode]="true"> + [attr.kg-ds-prv-darkmode]="darktheme$ | async"> </kg-dataset-dumb-line> \ No newline at end of file diff --git a/src/atlasComponents/sapiViews/module.ts b/src/atlasComponents/sapiViews/module.ts index 6da0504892874497880c399b7df6e8426ef8baa9..ff029f0362be7205bec3a45957debcc25f87197c 100644 --- a/src/atlasComponents/sapiViews/module.ts +++ b/src/atlasComponents/sapiViews/module.ts @@ -1,9 +1,15 @@ import { NgModule } from "@angular/core"; +import { SapiViewsCoreModule } from "./core"; import { SapiViewsFeaturesModule } from "./features"; @NgModule({ imports: [ - SapiViewsFeaturesModule + SapiViewsFeaturesModule, + SapiViewsCoreModule, ], + exports: [ + SapiViewsFeaturesModule, + SapiViewsCoreModule, + ] }) export class SapiViewsModule{} \ No newline at end of file diff --git a/src/components/components.module.ts b/src/components/components.module.ts index 1ed9c40fe619fa0a10e851d89206e4b4e93d0b09..e0b6f4eeb9b671c6f2d5dc9ddc8abee5f0965af1 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -3,7 +3,7 @@ import { NgModule } from '@angular/core' import { FormsModule } from '@angular/forms' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' -import { MarkdownDom } from './markdown/markdown.component'; +import { MarkdownModule } from './markdown'; import { CommonModule } from '@angular/common'; import { AngularMaterialModule } from 'src/sharedModules'; @@ -21,7 +21,7 @@ import { HighlightPipe } from './flatTree/highlight.pipe'; import { RenderPipe } from './flatTree/render.pipe'; import { IAVVerticalButton } from './vButton/vButton.component'; import { DynamicMaterialBtn } from './dynamicMaterialBtn/dynamicMaterialBtn.component'; -import { SpinnerCmp } from './spinner/spinner.component'; +import { SpinnerModule } from "./spinner" import { ReadmoreModule } from './readmore'; import { TileCmp } from './tile/tile.component'; @@ -34,16 +34,16 @@ import { TileCmp } from './tile/tile.component'; AngularMaterialModule, UtilModule, ReadmoreModule, + SpinnerModule, + MarkdownModule, ], declarations : [ /* components */ - MarkdownDom, FlatTreeComponent, DialogComponent, ConfirmDialogComponent, IAVVerticalButton, DynamicMaterialBtn, - SpinnerCmp, TileCmp, /* pipes */ @@ -59,14 +59,14 @@ import { TileCmp } from './tile/tile.component'; exports : [ BrowserAnimationsModule, ReadmoreModule, - - MarkdownDom, + SpinnerModule, + MarkdownModule, + FlatTreeComponent, DialogComponent, ConfirmDialogComponent, IAVVerticalButton, DynamicMaterialBtn, - SpinnerCmp, TileCmp, TreeSearchPipe, diff --git a/src/components/dynamicMaterialBtn/dynamicMaterialBtn.stories.ts b/src/components/dynamicMaterialBtn/dynamicMaterialBtn.stories.ts index e31cc1af247f25397ab7313ba8ee3dc76fee039d..3c44ad2d5ebf4dfc28212b83c650a4b3283acaba 100644 --- a/src/components/dynamicMaterialBtn/dynamicMaterialBtn.stories.ts +++ b/src/components/dynamicMaterialBtn/dynamicMaterialBtn.stories.ts @@ -11,7 +11,7 @@ export default { moduleMetadata({ imports: [ CommonModule, - AngularMaterialModule + AngularMaterialModule, ], }) ], diff --git a/src/components/markdown/index.ts b/src/components/markdown/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..8751618833a80782c23e4f6d8f8fda2401e2c455 --- /dev/null +++ b/src/components/markdown/index.ts @@ -0,0 +1,2 @@ +export { MarkdownModule } from "./module" +export { MarkdownDom } from "./markdownCmp/markdown.component" \ No newline at end of file diff --git a/src/components/markdown/markdown.component.ts b/src/components/markdown/markdownCmp/markdown.component.ts similarity index 73% rename from src/components/markdown/markdown.component.ts rename to src/components/markdown/markdownCmp/markdown.component.ts index bd5ea00320923c15e4517232021822b91fd8e630..7084626a5ed37fb4162d610edfe232fcf3852381 100644 --- a/src/components/markdown/markdown.component.ts +++ b/src/components/markdown/markdownCmp/markdown.component.ts @@ -1,4 +1,5 @@ -import { ChangeDetectionStrategy, Component, ElementRef, Input, ViewChild, ChangeDetectorRef, AfterViewChecked, OnChanges } from '@angular/core' +import { ChangeDetectionStrategy, Component, ElementRef, Input, ViewChild, ChangeDetectorRef, AfterViewChecked, OnChanges, SecurityContext } from '@angular/core' +import { DomSanitizer } from '@angular/platform-browser' import * as showdown from 'showdown' @Component({ @@ -19,7 +20,8 @@ export class MarkdownDom implements OnChanges { }) constructor( - private cdr: ChangeDetectorRef + private cdr: ChangeDetectorRef, + private ds: DomSanitizer, ) { this.cdr.detach() this.converter.setFlavor('github') @@ -32,9 +34,13 @@ export class MarkdownDom implements OnChanges { } ngOnChanges(){ - this.innerHtml = this.converter.makeHtml( + const innerHtml = this.converter.makeHtml( this.getMarkdown() ) + this.innerHtml = this.ds.sanitize( + SecurityContext.HTML, + innerHtml + ) this.cdr.detectChanges() } diff --git a/src/components/markdown/markdownCmp/markdown.style.css b/src/components/markdown/markdownCmp/markdown.style.css new file mode 100644 index 0000000000000000000000000000000000000000..0645dc01a568740a2c3d2f51c486adcb7f111a02 --- /dev/null +++ b/src/components/markdown/markdownCmp/markdown.style.css @@ -0,0 +1,4 @@ +:host +{ + display: block; +} \ No newline at end of file diff --git a/src/components/markdown/markdown.template.html b/src/components/markdown/markdownCmp/markdown.template.html similarity index 67% rename from src/components/markdown/markdown.template.html rename to src/components/markdown/markdownCmp/markdown.template.html index f4a643338901034ec807c46393c68e4c7b8b29df..ae217e43cef6c30161ce1d6ff048bd8272ab69b9 100644 --- a/src/components/markdown/markdown.template.html +++ b/src/components/markdown/markdownCmp/markdown.template.html @@ -1,4 +1,4 @@ -<div [innerHTML]="innerHtml | safeHtml"> +<div [innerHTML]="innerHtml"> </div> <div class="d-none" #ngContentWrapper> <ng-content> diff --git a/src/components/markdown/module.ts b/src/components/markdown/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..7dd8368aea81ebc68f7091175d83971796a00ffb --- /dev/null +++ b/src/components/markdown/module.ts @@ -0,0 +1,17 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { MarkdownDom } from "./markdownCmp/markdown.component"; + +@NgModule({ + imports: [ + CommonModule, + ], + declarations: [ + MarkdownDom + ], + exports: [ + MarkdownDom + ] +}) + +export class MarkdownModule{} diff --git a/src/components/spinner/index.ts b/src/components/spinner/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b6eb0631154fb743ba8e6f4e4c0ef3b1afadd4f0 --- /dev/null +++ b/src/components/spinner/index.ts @@ -0,0 +1,6 @@ +export { + SpinnerModule +} from "./module" +export { + SpinnerCmp +} from "./spinnerCmp/spinner.component" \ No newline at end of file diff --git a/src/components/spinner/module.ts b/src/components/spinner/module.ts new file mode 100644 index 0000000000000000000000000000000000000000..676496b3d78b0ba6e07924d5ff702af34b4e1b58 --- /dev/null +++ b/src/components/spinner/module.ts @@ -0,0 +1,17 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SpinnerCmp } from "./spinnerCmp/spinner.component"; + +@NgModule({ + imports: [ + CommonModule + ], + declarations: [ + SpinnerCmp + ], + exports: [ + SpinnerCmp + ] +}) + +export class SpinnerModule{} \ No newline at end of file diff --git a/src/components/spinner/spinner.component.ts b/src/components/spinner/spinnerCmp/spinner.component.ts similarity index 100% rename from src/components/spinner/spinner.component.ts rename to src/components/spinner/spinnerCmp/spinner.component.ts diff --git a/src/components/spinner/spinner.template.html b/src/components/spinner/spinnerCmp/spinner.template.html similarity index 100% rename from src/components/spinner/spinner.template.html rename to src/components/spinner/spinnerCmp/spinner.template.html diff --git a/src/extra_styles.css b/src/extra_styles.css index 9bba07ff426360e0b19f2713daa44ff9e6c516e4..8b18c626e51601f4fc3062a9bf95ca100f78b972 100644 --- a/src/extra_styles.css +++ b/src/extra_styles.css @@ -856,3 +856,22 @@ mat-list.sm mat-list-item { text-overflow: ellipsis; } + +iav-cmp-viewer-container .mat-chip-list-wrapper +{ + flex-wrap: nowrap; +} + +iav-cmp-viewer-container poly-update-cmp .mat-chip-list-wrapper +{ + flex-wrap: wrap; +} + +quick-tour-unit svg +{ + pointer-events: none; + stroke-width: 3px; + stroke: rgb(255, 255, 255); + stroke-linecap: round; + stroke-linejoin: round; +} \ No newline at end of file diff --git a/src/main.module.ts b/src/main.module.ts index 5ed5774ef07f50825dc22e721129e76e524f0c94..99766310f8831ca9162c8b89874aa9a24211ff6a 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -2,7 +2,7 @@ import { DragDropModule } from '@angular/cdk/drag-drop' import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; -import { StoreModule, ActionReducer } from "@ngrx/store"; +import { StoreModule, ActionReducer, Store, select } from "@ngrx/store"; import { AngularMaterialModule } from 'src/sharedModules' import { AtlasViewer } from "./atlasViewer/atlasViewer.component"; import { ComponentsModule } from "./components/components.module"; @@ -59,6 +59,8 @@ import { userInteraction, plugins, } from "./state" +import { DARKTHEME } from './util/injectionTokens'; +import { map } from 'rxjs/operators'; export function debug(reducer: ActionReducer<any>): ActionReducer<any> { return function(state, action) { @@ -215,6 +217,14 @@ export function debug(reducer: ActionReducer<any>): ActionReducer<any> { provide: BS_ENDPOINT, useValue: (environment.BS_REST_URL || `https://siibra-api-stable.apps.hbp.eu/v1_0`).replace(/\/$/, '') }, + { + provide: DARKTHEME, + useFactory: (store: Store) => store.pipe( + select(atlasSelection.selectors.selectedTemplate), + map(tmpl => !!(tmpl && tmpl["@id"] !== 'minds/core/referencespace/v1.0.0/a1655b99-82f1-420f-a3c2-fe80fd4c8588')), + ), + deps: [ Store ] + } ], bootstrap : [ AtlasViewer, diff --git a/src/overwrite.scss b/src/overwrite.scss index 8c67b46f60c54b8bc3b3fd11e0fe5ac387e665dd..9ffe638d491a2fe839ae59831c4392cdea489f7e 100644 --- a/src/overwrite.scss +++ b/src/overwrite.scss @@ -2,42 +2,6 @@ $nsp: "sxplr"; -iav-cmp-viewer-container -{ - - .mat-chip-list-wrapper - { - flex-wrap: nowrap; - } - - poly-update-cmp - { - .mat-chip-list-wrapper - { - flex-wrap: wrap; - } - } -} - -quick-tour-unit -{ - svg - { - pointer-events: none; - stroke-width: 3px; - stroke: rgb(255, 255, 255); - stroke-linecap: round; - stroke-linejoin: round; - } -} - -kg-ds-prv-regional-feature-view -{ - div - { - min-height: 20em; - } -} // no prefix @for $i from 1 through 12 { @@ -124,7 +88,20 @@ $transform-origin-maps: ( } } -@for $unit from 0 through 4 { +@for $unit from 5 through 10 { + $width: $unit * 10; + .w-#{$width} { + width: $width * 1%; + } +} + +@for $zlvl from 0 through 10 { + .#{$nsp}-z-index-#{$zlvl} { + z-index: $zlvl; + } +} + +@for $unit from 0 through 8 { .#{$nsp}-pt-#{$unit} { padding-top: $unit * 0.5rem; } @@ -140,4 +117,51 @@ $transform-origin-maps: ( .#{$nsp}-p-#{$unit} { padding: $unit * 0.5rem; } + + .#{$nsp}-mt-#{$unit} { + margin-top: $unit * 0.5rem; + } + .#{$nsp}-mb-#{$unit} { + margin-bottom: $unit * 0.5rem; + } + .#{$nsp}-ml-#{$unit} { + margin-left: $unit * 0.5rem; + } + .#{$nsp}-mr-#{$unit} { + margin-right: $unit * 0.5rem; + } + .#{$nsp}-m-#{$unit} { + margin: $unit * 0.5rem; + } +} + +$display-vars: block, inline-block, flex, inline-flex; +@each $display-var in $display-vars { + .d-#{$display-var} + { + display: $display-var!important; + } + .#{$nsp}-d-#{$display-var} + { + display: $display-var!important; + } +} + +$align-items-vars: center, stretch; +@each $align-items-var in $align-items-vars { + .align-items-#{$align-items-var} { + align-items: $align-items-var; + } + + .#{$nsp}-align-items-#{$align-items-var} { + align-items: $align-items-var; + } +} + +.#{$nsp}-m-a { + margin: auto; +} + +.#{$nsp}-muted { + opacity: 0.75; } diff --git a/src/state/atlasSelection/effects.ts b/src/state/atlasSelection/effects.ts index f8be02508d3355868350ea07cd4991983f78f7c5..1dbaf44a16567047161aa0687fb0bb08a93813e8 100644 --- a/src/state/atlasSelection/effects.ts +++ b/src/state/atlasSelection/effects.ts @@ -14,14 +14,15 @@ export class Effect { onAtlasSelectionSelectTmplParc = createEffect(() => this.action.pipe( ofType(actions.selectAtlas), filter(action => !!action.atlas), - switchMap(action => - this.sapiSvc.getParcDetail(action.atlas["@id"], action.atlas.parcellations[0]["@id"], 100).then( + switchMap(action => { + const selectedParc = action.atlas.parcellations.find(p => /290/.test(p["@id"])) || action.atlas.parcellations[0] + return this.sapiSvc.getParcDetail(action.atlas["@id"], selectedParc["@id"], 100).then( parcellation => ({ parcellation, atlas: action.atlas }) ) - ), + }), switchMap(({ atlas, parcellation }) => { const spacdIds = parcellation.brainAtlasVersions.map(bas => bas.coordinateSpace) as { "@id": string }[] return forkJoin( @@ -32,16 +33,7 @@ export class Effect { ) ).pipe( switchMap(spaces => { - const selectSpaceId = spaces[2]["@id"] - const selectedSpace = spaces.find(s => s["@id"] === selectSpaceId) - if (!selectedSpace) { - return of( - generalAction.generalActionError({ - message: `space with id ${selectSpaceId} not found!` - }) - ) - } - + const selectedSpace = spaces.find(s => /152/.test(s.fullName)) || spaces[0] return of( actions.selectTemplate({ template: selectedSpace diff --git a/src/state/userInteraction/actions.ts b/src/state/userInteraction/actions.ts index fd21b4fd0068f01e69c04468e18fe1c75c314908..2030f734431beb8f36e86dd21e673c522173636a 100644 --- a/src/state/userInteraction/actions.ts +++ b/src/state/userInteraction/actions.ts @@ -3,6 +3,7 @@ import { nameSpace } from "./const" import * as atlasSelection from "../atlasSelection" import { SapiRegionModel, SapiSpatialFeatureModel, SapiVolumeModel } from "src/atlasComponents/sapi" import * as userInterface from "../userInterface" +import { SapiFeatureModel } from "src/atlasComponents/sapi/type" export const { clearSelectedRegions, @@ -35,7 +36,7 @@ export const mouseoverRegions = createAction( export const showFeature = createAction( `${nameSpace} showFeature`, props<{ - feature: SapiSpatialFeatureModel + feature: SapiFeatureModel }>() ) diff --git a/src/theme.scss b/src/theme.scss index 202359464d230d9a1db012512c591898a11a886a..78311e0ea558a28f634e905f279fe1b243022540 100644 --- a/src/theme.scss +++ b/src/theme.scss @@ -162,8 +162,3 @@ $iv-dark-theme: mat.define-dark-theme(( flex: 0 0 16.67%; } } - -.sxplr-test -{ - color: red; -} diff --git a/src/util/injectionTokens.ts b/src/util/injectionTokens.ts index f8720f5d6ce4ca2e19c473799afb695bee18be05..feb547176fb2e37b7fe5222e716d1cf8c257a5ce 100644 --- a/src/util/injectionTokens.ts +++ b/src/util/injectionTokens.ts @@ -1,4 +1,5 @@ import { InjectionToken } from "@angular/core"; +import { Observable } from "rxjs"; export const CLICK_INTERCEPTOR_INJECTOR = new InjectionToken<ClickInterceptor>('CLICK_INTERCEPTOR_INJECTOR') @@ -17,3 +18,5 @@ export type TContextMenu<T> = { register: (fn: T) => void deregister: (fn: (fn: T) => void) => void } + +export const DARKTHEME = new InjectionToken<Observable<boolean>>('DARKTHEME') diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts index 2f3d89f79a5999a2059a454b289c35da97e5d68d..1656efc6e608f235822cba5d0012aad8fd5a78cb 100644 --- a/src/viewerModule/module.ts +++ b/src/viewerModule/module.ts @@ -28,6 +28,7 @@ import { ViewerInternalStateSvc } from "./viewerInternalState.service"; import { LayerBrowserModule } from "src/ui/layerbrowser"; import { SAPIModule } from 'src/atlasComponents/sapi'; import { NehubaVCtxToBbox } from "./pipes/nehubaVCtxToBbox.pipe"; +import { SapiViewsModule } from "src/atlasComponents/sapiViews"; @NgModule({ imports: [ @@ -50,6 +51,7 @@ import { NehubaVCtxToBbox } from "./pipes/nehubaVCtxToBbox.pipe"; KeyFrameModule, LayerBrowserModule, SAPIModule, + SapiViewsModule, ], declarations: [ ViewerCmp, diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts index b19dec0a8cbc277c528390d83df762cebfc3285b..39a38881fe0e2629f58a2218f995bc24e1730331 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.component.ts +++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts @@ -12,8 +12,8 @@ import { ComponentStore } from "../componentStore"; import { DialogService } from "src/services/dialogService.service"; import { SAPI, SapiRegionModel } from "src/atlasComponents/sapi"; import { actions } from "src/state/atlasSelection"; -import { atlasSelection, userInterface, userInteraction } from "src/state"; -import { SapiSpatialFeatureModel } from "src/atlasComponents/sapi/type"; +import { atlasSelection, userInteraction } from "src/state"; +import { SapiSpatialFeatureModel, SapiRegionalFeatureModel, SapiFeatureModel } from "src/atlasComponents/sapi/type"; type TCStoreViewerCmp = { overlaySideNav: any @@ -165,7 +165,7 @@ export class ViewerCmp implements OnDestroy { public viewerCtx$ = this.ctxMenuSvc.context$ - public selectedFeature$ = this.store$.pipe( + public selectedFeature$: Observable<SapiFeatureModel> = this.store$.pipe( select(userInteraction.selectors.selectedFeature) ) @@ -193,7 +193,6 @@ export class ViewerCmp implements OnDestroy { public context: TContextArg<TSupportedViewers> private templateSelected: any - private getRegionFromlabelIndexId: (arg: {labelIndexId: string}) => any constructor( private store$: Store<any>, @@ -214,11 +213,6 @@ export class ViewerCmp implements OnDestroy { this.templateSelected$.subscribe( t => this.templateSelected = t ), - this.parcellationSelected$.subscribe( - p => { - this.getRegionFromlabelIndexId = null - } - ), combineLatest([ this.templateSelected$, this.parcellationSelected$, @@ -363,20 +357,23 @@ export class ViewerCmp implements OnDestroy { this.ctxMenuSvc.dismissCtxMenu() } - showSpatialDataset(feature: SapiSpatialFeatureModel) { - this.store$.dispatch( - actions.navigateTo({ - navigation: { - orientation: [0, 0, 0, 1], - position: feature.location.center.coordinates.map(v => (v.unit as number) * 1e6) - }, - animation: true - }) - ) - + showDataset(feat: SapiFeatureModel) { + if ((feat as SapiSpatialFeatureModel).location) { + const feature = feat as SapiSpatialFeatureModel + this.store$.dispatch( + actions.navigateTo({ + navigation: { + orientation: [0, 0, 0, 1], + position: feature.location.center.coordinates.map(v => (v.unit as number) * 1e6) + }, + animation: true + }) + ) + } + this.store$.dispatch( userInteraction.actions.showFeature({ - feature + feature: feat }) ) } diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html index 55e174ea82ef0dbf3a9a76607578a46a15119207..2acce6eae5cbe909596794f7db01afeb81b55a30 100644 --- a/src/viewerModule/viewerCmp/viewerCmp.template.html +++ b/src/viewerModule/viewerCmp/viewerCmp.template.html @@ -596,6 +596,7 @@ [region-base-template]="templateSelected$ | async" [region-base-parcellation]="parcellationSelected$ | async" [region-base-region]="region" + (region-menu-feat-clicked)="showDataset($event)" class="flex-grow-1 bs-border-box mat-elevation-z4"> </region-menu> </ng-template> @@ -642,17 +643,6 @@ </mat-expansion-panel> </ng-template> -<!-- misc dataset (e.g. PLI) --> -<!-- <ng-template #sidenavDsPreviewTmpl let-file="file" let-filename="filename" let-datasetId="datasetId"> - <div class="w-100 flex-grow-1 d-flex flex-column"> - - Previewing misc dataset - - <ng-container *ngTemplateOutlet="collapseBtn"> - </ng-container> - </div> -</ng-template> --> - <!-- select region error... for whatever reason --> <ng-template #selectRegionErrorTmpl> SELECT REGION ERROR @@ -843,11 +833,7 @@ <!-- feature tmpls --> <ng-template #sapiBaseFeatureTmpl let-backCb="backCb" - let-title="title" - let-subtitle="subtitle" - let-urls="urls" - let-description="description" - let-contentTmpl="contentTmpl"> + let-feature="feature"> <!-- back btn --> <button mat-button @@ -861,45 +847,17 @@ </span> </button> - <mat-card class="sapi-container"> - - <!-- title must exist --> - <mat-card-title> - {{ title }} - </mat-card-title> - - <mat-card-subtitle class="d-inline-flex align-items-center flex-wrap"> - <mat-icon fontSet="fas" fontIcon="fa-database"></mat-icon> - <span *ngIf="subtitle"> - {{ subtitle }} - </span> - - - <ng-template [ngIf]="urls"> - <mat-divider vertical="true" class="ml-2 h-2rem"></mat-divider> - <a [href]="url.doi | doiParserPipe" - *ngFor="let url of urls" - mat-icon-button - matTooltip="Explore in EBRAINS Knowledge Graph" - target="_blank"> - <i class="fas fa-external-link-alt"></i> - </a> - </ng-template> - - </mat-card-subtitle> - - <small *ngIf="description" - class="d-block text-muted iv-custom-comp darker-bg"> - {{ description }} - </small> - - <mat-card-content> - <ng-container *ngTemplateOutlet="contentTmpl"> - - </ng-container> - </mat-card-content> - </mat-card> - + <sxplr-sapiviews-core-datasets-dataset class="sxplr-mt-8 sxplr-z-index-2 mat-elevation-z2" + [sxplr-sapiviews-core-datasets-dataset-input]="feature"> + </sxplr-sapiviews-core-datasets-dataset> + + <sxplr-sapiviews-features-entry + [sxplr-sapiviews-features-entry-atlas]="selectedAtlas$ | async" + [sxplr-sapiviews-features-entry-space]="templateSelected$ | async" + [sxplr-sapiviews-features-entry-parcellation]="parcellationSelected$ | async" + [sxplr-sapiviews-features-entry-region]="(selectedRegions$ | async)[0]" + [sxplr-sapiviews-features-entry-feature]="feature"> + </sxplr-sapiviews-features-entry> </ng-template> <!-- general feature tmpl --> @@ -910,11 +868,7 @@ <!-- spatial feature tmpl --> <ng-container *ngTemplateOutlet="sapiBaseFeatureTmpl; context: { backCb: clearSelectedFeature.bind(this), - title: feature.name, - subtitle: 'Spatial feature', - description: feature.description, - urls: feature.url, - contentTmpl: sapiVOITmpl + feature: feature }"> </ng-container> @@ -953,7 +907,7 @@ <div *ngFor="let feature of spatialFeatureBbox.features$ | async" mat-ripple - (click)="showSpatialDataset(feature)" + (click)="showDataset(feature)" class="iv-custom-comp hoverable w-100 overflow-hidden text-overflow-ellipses"> {{ feature.name }} </div> diff --git a/worker/worker-typedarray.js b/worker/worker-typedarray.js index c6d5933ced25281ac91bf51a90e6f0b5a380904c..6157de059e71cb5f1e78c44199f02f8cc3c1dba3 100644 --- a/worker/worker-typedarray.js +++ b/worker/worker-typedarray.js @@ -34,9 +34,6 @@ const cm = CM_CONST[colormap] function check(nv, current) { - if (!cm[current + 1] || !cm[current]) { - debugger - } const lower = cm[current].index <= nv const higher = nv <= cm[current + 1].index let returnVal = null @@ -56,7 +53,6 @@ while (true) { iter ++ if (iter > 100) { - debugger throw new Error(`iter > 1000, something we`) } const [val, { lower, higher }] = check(nv, current) @@ -193,7 +189,6 @@ min, max, } = unpackToArray(inputArray, width, height, channel, dtype) - console.log({ outputArray }) return { outputArray, min,