diff --git a/webapp/.eslintrc.json b/webapp/.eslintrc.json index 248d5ca8..9ee4c7b2 100644 --- a/webapp/.eslintrc.json +++ b/webapp/.eslintrc.json @@ -16,6 +16,8 @@ "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/ban-ts-comment": "off", "no-tabs": "off", + "@typescript-eslint/strict-boolean-expressions": "warn", + "no-prototype-builtins": "warn", "@typescript-eslint/no-unused-vars": [ "warn", // or "error" { diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 6f81cd98..08c984c5 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -8,6 +8,7 @@ "name": "privateer", "version": "0.0.0", "dependencies": { + "@tanstack/react-table": "^8.13.2", "@types/jest": "^29.5.6", "@types/node": "^20.8.9", "jest": "^29.7.0", @@ -19,6 +20,7 @@ "react": "^18.2.0", "react-collapsed": "^4.1.2", "react-dom": "^18.2.0", + "react-ga4": "^2.1.0", "react-grid-layout": "^1.4.4", "react-icons": "^4.11.0", "react-inlinesvg": "^3.0.2", @@ -32,6 +34,7 @@ "serve": "^14.2.1", "styled-components": "^6.0.6", "vite-plugin-top-level-await": "^1.3.1", + "web-vitals": "^3.5.2", "zlib": "^1.0.5" }, "devDependencies": { @@ -3377,6 +3380,37 @@ "node": ">=10" } }, + "node_modules/@tanstack/react-table": { + "version": "8.13.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.13.2.tgz", + "integrity": "sha512-b6mR3mYkjRtJ443QZh9sc7CvGTce81J35F/XMr0OoWbx0KIM7TTTdyNP2XKObvkLpYnLpCrYDwI3CZnLezWvpg==", + "dependencies": { + "@tanstack/table-core": "8.13.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.13.2", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.13.2.tgz", + "integrity": "sha512-/2saD1lWBUV6/uNAwrsg2tw58uvMJ07bO2F1IWMxjFRkJiXKQRuc3Oq2aufeobD3873+4oIM/DRySIw7+QsPPw==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -5993,13 +6027,14 @@ } }, "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "hasInstallScript": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" }, "engines": { @@ -6597,6 +6632,25 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esniff/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -6666,6 +6720,15 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -7971,9 +8034,9 @@ } }, "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==" }, "node_modules/is-array-buffer": { "version": "3.0.2", @@ -10888,6 +10951,11 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-ga4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", + "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==" + }, "node_modules/react-grid-layout": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.4.4.tgz", @@ -11795,9 +11863,9 @@ } }, "node_modules/socks/node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" }, "node_modules/source-map": { "version": "0.6.1", @@ -12964,6 +13032,11 @@ "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.8.tgz", "integrity": "sha512-lNR9aAefbGPpHO7AEnY0hCFjz1eTkWCXYvkTRrTHs9qv8zJp+SkVYpzfLIFXQQiG3tVvbNFQgVg2bQS8YGgxyw==" }, + "node_modules/web-vitals": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-3.5.2.tgz", + "integrity": "sha512-c0rhqNcHXRkY/ogGDJQxZ9Im9D19hDihbzSQJrsioex+KnFgmMzBiy57Z1EjkhX/+OjyBpclDCzz2ITtjokFmg==" + }, "node_modules/webgl-context": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/webgl-context/-/webgl-context-2.2.0.tgz", diff --git a/webapp/package.json b/webapp/package.json index e5ca028f..448f904b 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -13,6 +13,7 @@ "testserver": "vite --port 5173" }, "dependencies": { + "@tanstack/react-table": "^8.13.2", "@types/jest": "^29.5.6", "@types/node": "^20.8.9", "jest": "^29.7.0", @@ -24,6 +25,7 @@ "react": "^18.2.0", "react-collapsed": "^4.1.2", "react-dom": "^18.2.0", + "react-ga4": "^2.1.0", "react-grid-layout": "^1.4.4", "react-icons": "^4.11.0", "react-inlinesvg": "^3.0.2", @@ -37,6 +39,7 @@ "serve": "^14.2.1", "styled-components": "^6.0.6", "vite-plugin-top-level-await": "^1.3.1", + "web-vitals": "^3.5.2", "zlib": "^1.0.5" }, "devDependencies": { diff --git a/webapp/src/data/Constants.tsx b/webapp/src/data/Constants.tsx index de1d69ab..7282c216 100644 --- a/webapp/src/data/Constants.tsx +++ b/webapp/src/data/Constants.tsx @@ -139,46 +139,105 @@ export const DatabaseColumns = [ }, ]; +function getColour(diagnostic) { + return diagnostic !== 'yes' + ? 'text-red-600' + : diagnostic === 'check' + ? 'text-yellow-600' + : ''; +} + +function extracted(props, accessor) { + const typeValue = props.row.original[accessor]; + const diagnostic = props.row.original.diagnostic; + + const colour = getColour(diagnostic); + return {typeValue}; +} + export const SugarListColumns = [ { Header: 'Sugar ID', accessor: 'sugarId', + Cell: (props) => extracted(props, 'sugarId'), + }, + { + Header: 'Conformation', + accessor: 'conformation', + Cell: (props: { + row: { original: { conformation: any; diagnostic: any } }; + }) => { + const typeValue = props.row.original.conformation; + const diagnostic = props.row.original.diagnostic; + const colour = getColour(diagnostic); + + const regex = /([a-zA-Z]*?\d*)([a-zA-Z])(\d*)/; + const formattedString = typeValue.replace( + regex, + (_, before, letter, after) => { + let string = ''; + if (before) { + string += '' + before + ''; + } + string += letter; + if (after) { + string += '' + after + ''; + } + return string; + } + ); + return ( +
+ ); + }, }, { Header: 'Q', accessor: 'q', + Cell: (props) => extracted(props, 'q'), }, { Header: 'Phi', accessor: 'phi', + Cell: (props) => extracted(props, 'phi'), }, { Header: 'Theta', accessor: 'theta', + Cell: (props) => extracted(props, 'theta'), }, + { Header: 'RSCC', accessor: 'rscc', + Cell: (props) => extracted(props, 'rscc'), }, { Header: 'B Factor', accessor: 'bFactor', + Cell: (props) => extracted(props, 'bFactor'), }, { Header: 'Detected Type', accessor: 'detectedType', + Cell: (props) => extracted(props, 'detectedType'), }, - { - Header: 'mFo', - accessor: 'mFo', - }, + // { + // Header: 'mFo', + // accessor: 'mFo', + // }, { Header: 'Type', accessor: 'type', + Cell: (props) => extracted(props, 'type'), }, { Header: 'Diagnostic', accessor: 'diagnostic', + Cell: (props) => extracted(props, 'diagnostic'), }, ]; @@ -223,3 +282,438 @@ export const binDB = { 'MAN-1,3-MAN': { start: -180, end: 180, size: 4 }, 'MAN-1,2-MAN': { start: -180, end: 180, size: 4 }, }; + +export const sugarLinkageMap: Record = { + 'N-glycans': [ + 'Any', + 'NAG-1,2-ASN', + 'NAG-1,4-NAG', + 'BMA-1,4-NAG', + 'MAN-1,3-BMA', + 'MAN-1,6-BMA', + 'FUC-1,6-NAG', + 'MAN-1,3-MAN', + 'MAN-1,6-MAN', + 'MAN-1,2-MAN', + 'MAN-1,4-NAG', + 'NAG-1,2-MAN', + 'FUC-1,3-NAG', + 'BMA-1,3-BMA', + 'GAL-1,4-NAG', + 'FUL-1,6-NAG', + 'BMA-1,6-BMA', + 'NDG-1,4-NAG', + 'NAG-1,1-ASN', + 'XYP-1,2-BMA', + 'NAG-1,3-NAG', + 'MAN-1,4-BMA', + 'BMA-1,6-MAN', + 'MAN-1,4-MAN', + 'BMA-1,3-NAG', + 'FUL-1,3-NAG', + 'BMA-1,4-NDG', + 'BMA-1,4-MAN', + 'BMA-1,3-MAN', + 'NAG-1,6-NAG', + 'BMA-1,4-BMA', + 'NAG-1,4-MAN', + 'MAN-1,3-NAG', + 'MAN-1,4-NDG', + 'SIA-2,6-GAL', + 'MAN-1,6-NAG', + 'BMA-1,2-MAN', + 'NDG-1,2-ASN', + 'NAG-1,2-BMA', + 'MAN-1,2-BMA', + 'NAG-1,4-BMA', + 'FCA-1,3-NAG', + 'NAG-1,2-ARG', + 'GLC-1,3-MAN', + 'NAG-1,2-LYS', + 'NAG-1,4-NDG', + 'NDG-1,2-MAN', + 'NAG-1,6-MAN', + 'MAN-2,6-BMA', + 'NAG-3,4-NAG', + 'GAL-1,4-NDG', + 'FUC-1,4-NAG', + 'FCA-1,6-NAG', + 'NAG-5,4-NAG', + 'BMA-1,6-NAG', + 'BMA-1,2-BMA', + 'MAN-1,2-ASN', + 'BGC-1,2-ASN', + 'XYS-1,2-BMA', + 'SIA-2,3-GAL', + 'NAG-1,3-MAN', + 'NAG-4,4-NAG', + 'GLA-1,4-NAG', + 'GAL-1,4-MAN', + 'NDG-1,4-NDG', + 'NDG-1,2-BMA', + 'HSQ-1,2-ASN', + 'BMA-1,2-ASN', + 'MAN-2,3-BMA', + 'NAG-1,G-ASN', + 'MAN-1,3-XXR', + 'KDO-2,1-ASN', + 'RM4-1,4-XYP', + 'GLC-1,4-GLC', + 'FUL-1,6-NDG', + 'NAG-1,3-BMA', + 'M6D-1,2-MAN', + 'MAN-3,4-NAG', + 'NDG-1,1-ASN', + 'GLA-1,2-FUC', + 'MAN-3,3-MAN', + 'NAG-8,6-NAG', + 'B6D-1,2-ASN', + 'TGY-2,6-NAG', + 'NAG-1,B-ASN', + 'GAL-1,4-FUC', + 'XYP-1,4-FUC', + 'GLC-1,4-NAG', + 'XYP-1,4-BGC', + 'FUC-5,6-NAG', + 'NAG-1,1-MAN', + 'XXR-1,3-FUC', + 'FUC-1,3-BGC', + 'XYS-1,2-MAN', + 'NAG-1,Z-LYS', + 'NGK-1,4-NAG', + 'NAG-7,8-NAG', + 'FUC-1,2-GAL', + 'FRU-2,2-LYS', + 'NAG-4,1-NAG', + 'BMA-4,4-NAG', + '7CV-1,2-RM4', + 'XYL-1,2-BMA', + 'BGC-1,4-NAG', + 'NAG-1,4-ARG', + 'BMA-1,3-SHD', + 'GMH-1,5-KDO', + 'XYP-1,2-MAN', + 'GL0-1,4-NGA', + 'MAN-3,3-BMA', + 'NGZ-1,3-B6D', + 'NAG-A,A-ASN', + 'GLA-1,6-BMA', + 'MAN-1,3-XYS', + 'GUP-1,4-NAG', + 'MAN-1,3-GLC', + 'NAG-1,4-GAL', + 'BMA-1,6-SHD', + 'NAA-1,3-NAG', + 'MAN-1,1-ASN', + 'BMA-5,4-NAG', + 'G6D-1,6-BGC', + 'FUC-1,6-BMA', + 'NDG-1,4-MAN', + 'Z9N-1,6-NAG', + 'GLC-1,2-ASN', + 'NAA-1,2-ASN', + 'MAN-1,1-ARG', + 'GUP-1,3-BMA', + 'BMA-1,6-GLC', + 'BMA-1,3-NDG', + 'MAN-3,6-BMA', + 'MAN-1,3-ARG', + 'GCS-1,1-ARG', + 'MAN-1,4-ARG', + 'LXZ-1,2-ASN', + 'MAN-2,3-NAG', + 'JHM-1,4-IDS', + 'NGZ-1,4-LXB', + 'NAG-6,6-NAG', + 'SHD-1,4-NAG', + 'GLC-1,3-GLC', + 'BGC-1,3-A2G', + 'GXL-1,6-LXB', + 'RIB-1,1-ARG', + 'NAG-2,4-NAG', + 'NDG-1,6-BMA', + 'BGC-1,6-BGC', + 'BMA-5,6-MAN', + 'NAG-3,3-NAG', + 'MAN-1,3-GAL', + 'MAN-1,6-GUP', + 'SGN-1,4-IDS', + 'GLC-1,6-GL0', + 'JHM-1,1-LYS', + 'BGC-1,2-BGC', + 'FUC-1,6-NDG', + 'GLC-1,4-NDG', + 'XYS-1,6-BMA', + 'NAG-1,1-ARG', + 'BGC-1,3-LXB', + 'IDS-1,4-JHM', + 'IDS-4,1-SGN', + 'XYZ-1,6-MAN', + 'GAL-1,3-GAL', + 'RIP-1,6-NAG', + 'BGC-1,3-LXZ', + 'MAN-1,3-LGU', + 'BGC-1,3-BGC', + 'A2G-1,4-A2G', + 'XYS-1,3-BMA', + 'NGA-1,4-LXZ', + 'NAG-1,2-GUP', + 'GLC-4,1-GLC', + 'XYP-1,2-ASN', + 'LGU-1,4-NAG', + 'SIA-2,6-GLA', + 'MAN-4,6-MAN', + 'IDS-1,4-SGN', + 'MAN-1,4-BGC', + 'BGC-1,1-LYS', + 'NAG-2,3-NAG', + 'MAN-1,3-GUP', + 'BDF-2,2-LYS', + 'A2G-1,3-B6D', + 'GAL-1,2-ASN', + 'GAL-1,3-MAN', + 'KDO-2,6-GCS', + 'LGU-1,6-LGU', + 'GUP-1,2-BMA', + 'NAG-4,3-MAN', + 'MAN-4,2-MAN', + 'GAL-1,6-NAG', + 'NAG-7,7-NAG', + 'FUC-4,3-NAG', + 'GLA-1,3-GAL', + 'SGN-4,1-IDS', + 'GLC-1,4-ASN', + 'MAN-1,Z-LYS', + 'GLC-1,6-LXZ', + 'NAG-1,4-HSQ', + 'BMA-6,3-BMA', + 'BMA-2,4-NAG', + 'SGN-1,4-ARG', + 'MAN-1,3-NDG', + 'MAN-1,3-ASN', + 'GL0-1,4-NGZ', + 'BGC-1,2-BMA', + 'BGC-1,3-GL0', + 'IDS-1,4-ARG', + 'BGS-1,1-LYS', + 'NDG-8,3-NDG', + 'LXB-1,2-ASN', + 'BGC-1,4-BGC', + 'GUP-1,2-MAN', + 'BGC-1,3-BMA', + 'KDO-2,4-KDO', + 'BMA-1,6-ARG', + ], + 'C-glycans': [ + 'Any', + 'MAN-1,1-TRP', + 'BMA-1,1-TRP', + 'GAL-1,1-TRP', + 'NAG-1,4-TRP', + 'BGC-1,1-TRP', + 'NAG-1,1-TRP', + 'NAG-1,2-TRP', + 'NAG-4,1-NAG', + ], + 'O-glycans': [ + 'Any', + 'MAN-1,G-SER', + 'MAN-1,1-THR', + 'A2G-1,1-THR', + 'BGC-1,G-SER', + 'FUC-1,1-THR', + 'NAG-1,G-SER', + 'FUC-1,G-SER', + 'NAG-1,1-THR', + 'A2G-1,G-SER', + 'GAL-1,3-A2G', + 'BGC-1,3-FUC', + 'NGA-1,1-THR', + 'GLC-1,G-SER', + 'RAM-1,4-MAN', + 'G2F-1,2-GLU', + 'NAG-1,2-GLU', + 'BGC-1,4-BGC', + 'MAN-1,2-MAN', + 'G2F-1,1-GLU', + 'GCU-1,2-MAN', + 'NAG-1,2-ASP', + 'XYS-1,3-BGC', + 'NGA-1,G-SER', + 'XYP-1,4-GCU', + 'BGC-1,4-G2F', + 'BGC-1,2-ASP', + 'NDG-1,1-THR', + 'RIP-1,G-SER', + 'BMA-1,4-NAG', + 'GLC-1,4-GLC', + 'NAG-1,2-THR', + 'SIA-2,6-A2G', + 'B9D-1,1-ASP', + 'NAG-1,2-SER', + 'XYS-1,3-XYS', + 'MXY-1,4-XYP', + 'NAG-1,2-TYR', + 'BMA-1,G-SER', + 'GAL-1,3-NGA', + 'NAG-1,4-NAG', + 'SIA-2,3-GAL', + 'MAN-A,A-SER', + 'DFX-1,2-GLU', + 'BMA-1,1-THR', + '2DG-1,2-GLU', + 'BGC-1,3-BGC', + 'XYP-1,4-DFX', + 'BGC-1,H-TYR', + 'GLC-1,2-GLU', + 'XYP-1,4-X2F', + 'BGC-1,4-GLC', + 'GLC-1,1-GLU', + 'GLC-1,1-ASP', + 'GLC-1,4-BGC', + 'SIA-2,6-NDG', + 'X2F-1,2-GLU', + 'MAN-A,A-THR', + 'AC1-1,4-GLC', + 'NBG-1,1-GLU', + 'XYS-1,6-BGC', + 'GLA-1,3-DT6', + 'MAN-A,G-SER', + 'FUL-1,G-SER', + 'GLA-1,3-MXZ', + 'MXZ-1,4-XYP', + 'FUL-1,1-THR', + 'AC1-1,4-ASO', + 'NAG-1,4-ASP', + 'BGC-1,4-MXZ', + 'MAN-1,A-SER', + 'NAG-1,6-A2G', + '7JZ-1,2-ASP', + 'NAG-1,3-FUC', + '2FG-1,2-GLU', + 'XYS-1,G-SER', + 'ASO-1,2-ASP', + 'BGC-1,4-MXY', + 'GLC-1,2-ASP', + 'BGC-1,2-B9D', + 'MAN-A,1-THR', + 'GLC-4,1-GLC', + 'RIP-1,1-THR', + 'GAL-1,3-NDG', + 'EPG-1,1-GLU', + 'G4D-1,3-MXY', + 'GLA-1,3-NAG', + 'X2F-1,1-GLU', + 'XYS-1,1-THR', + 'BGC-1,4-NBG', + 'BGC-1,3-G2F', + 'DT6-1,G-SER', + 'G2F-1,1-ASP', + 'GLC-1,H-TYR', + 'XYS-1,2-GLU', + 'XYS-1,4-GCU', + 'NAG-1,H-TYR', + 'XYF-1,5-ASP', + 'NGA-1,3-NGA', + 'MFU-1,4-XYP', + 'GAL-1,1-THR', + 'XYP-1,3-BXF', + 'NAG-1,4-G2F', + 'GLC-1,4-THR', + 'XYP-1,3-XYP', + 'GAL-1,H-TYR', + 'GAF-1,2-GLU', + 'BDP-1,1-SER', + 'G2F-1,2-ASP', + 'GAL-1,4-GLC', + 'MAN-1,6-BMA', + 'XYP-2,2-XYS', + 'GLC-1,6-BGC', + 'GAL-1,3-NAG', + 'GLC-1,4-ASP', + '8B9-1,1-THR', + 'EBG-1,1-GLU', + 'GLC-1,4-SHG', + 'NGA-1,H-TYR', + 'NAG-1,2-MAN', + 'SHG-4,1-BGC', + 'FUC-1,3-NAG', + 'SHG-1,1-ASP', + 'BMA-1,4-GLU', + 'A2G-1,1-GLU', + 'MAN-1,1-GLU', + 'BGC-1,1-SER', + 'BMA-2,1-MAN', + 'BXF-1,1-GLU', + 'XYS-1,1-GLU', + 'RAM-1,1-THR', + 'RIB-1,2-GLU', + 'BGC-1,3-NBG', + 'XYS-1,4-XYS', + '3HD-1,1-THR', + 'NAG-1,4-MUB', + 'GLA-1,3-B6D', + 'XYP-2,1-GCV', + 'GLA-1,G-SER', + 'XYP-1,3-BGC', + 'AHR-1,1-GLU', + 'GCV-1,2-SER', + '289-1,G-SER', + 'MAN-1,4-MAN', + 'AMP-1,9-ASP', + 'B8D-1,4-BGC', + 'SIA-2,6-8B9', + 'NAG-1,4-GLU', + 'BGC-4,1-BGC', + 'NAG-1,6-NGA', + 'GCU-1,1-SER', + 'GXL-1,4-NDG', + 'C3X-1,1-GLU', + 'SIA-2,1-THR', + 'XYL-4,1-XYP', + 'BGC-1,3-GLC', + 'GAL-1,4-NAG', + 'GXL-1,2-GAL', + 'MAN-1,3-GLU', + 'MAN-1,2-ASP', + 'XYP-1,4-XYS', + 'C5X-1,1-GLU', + 'ARB-1,2-MAN', + 'NGA-1,1-SER', + 'SHG-1,G-SER', + 'G4D-1,4-GLC', + 'BGC-1,4-GLU', + 'NDG-1,6-A2G', + 'ARA-1,1-THR', + 'GLC-1,2-FRU', + 'BGC-1,1-THR', + 'NAG-1,3-A2G', + 'GAL-1,4-BGC', + 'DFX-1,1-GLU', + 'B6D-1,G-SER', + 'NDG-1,H-TYR', + 'FRU-2,2-GLU', + 'XYP-4,1-XYP', + 'GLC-1,1-THR', + 'GAL-1,1-GLU', + 'MAN-1,2-BMA', + 'MUB-1,2-GLU', + ], + 'S-glycans': [ + 'Any', + 'NAG-1,G-CYS', + 'KDO-2,G-CYS', + 'BGC-1,G-CYS', + 'MAN-1,G-CYS', + 'A2G-1,G-CYS', + 'MAN-1,2-MAN', + ], + Ligands: [ + 'Any', + 'NAG-1,2-ASN', + 'NAG-1,4-NAG', + 'NAG-1,4-BMA', + 'NAG-1,4-NAG', + 'NAG-1,4-NAG', + ], +}; diff --git a/webapp/src/database/DatabaseComponents/BFactorVsRSCC.tsx b/webapp/src/database/DatabaseComponents/BFactorVsRSCC.tsx index 4e4da786..1cf9b4d9 100644 --- a/webapp/src/database/DatabaseComponents/BFactorVsRSCC.tsx +++ b/webapp/src/database/DatabaseComponents/BFactorVsRSCC.tsx @@ -2,48 +2,64 @@ import React, { useEffect, useState, lazy } from 'react'; const Plot = lazy(async () => await import('react-plotly.js')); -function calculatePoints(data): [number[], number[], string[]] { +function calculatePoints( + data +): [number[], number[], string[], number[], number[], string[]] { const glycans = data.data.glycans; const xAxis: number[] = []; const yAxis: number[] = []; const text: string[] = []; + const errorXAxis: number[] = []; + const errorYAxis: number[] = []; + const errorText: string[] = []; + for (const key in glycans) { const glycanType = glycans[key]; for (let i = 0; i < glycanType.length; i++) { const sugars = glycanType[i].sugars; for (let j = 0; j < sugars.length; j++) { - xAxis.push(sugars[j].bFactor as number); - yAxis.push(sugars[j].rscc as number); - text.push(sugars[j]['Sugar ID'] as string); + if (sugars[j].diagnostic !== 'yes') { + errorXAxis.push(sugars[j].bFactor as number); + errorYAxis.push(sugars[j].rscc as number); + errorText.push(sugars[j].sugarId as string); + } else { + xAxis.push(sugars[j].bFactor as number); + yAxis.push(sugars[j].rscc as number); + text.push(sugars[j].sugarId as string); + } } } } - return [xAxis, yAxis, text]; + return [xAxis, yAxis, text, errorXAxis, errorYAxis, errorText]; } export default function BFactorVsRSCC(props) { const [trace, setTrace] = useState({}); const [corrTrace, setCorrTrace] = useState({}); + const [badTrace, setBadTrace] = useState({}); useEffect(() => { - const [xAxis, yAxis, text] = calculatePoints(props); - - const maxX = Math.max(...xAxis) + 5; + const [xAxis, yAxis, text, errorXAxis, errorYAxis, errorText] = + calculatePoints(props); + const maxX = Math.max(Math.max(...xAxis), Math.max(...errorXAxis)) + 5; setCorrTrace({ x: [0, maxX], y: [0.7, 0.7], + text, + hoverinfo: 'text', fill: 'tozeroy', fillcolor: 'rgba(173,181,189,0.3)', fillopacity: 0.1, marker: { size: 1, color: 'rgba(173,181,189,0.5)', - symbol: ['o'], + symbol: 'x', }, + showlegend: false, }); setTrace({ @@ -55,9 +71,25 @@ export default function BFactorVsRSCC(props) { type: 'scatter', marker: { size: 8, - color: 'green', - symbol: ['o'], + color: 'blue', + symbol: 'o', }, + name: 'No Issues', + }); + + setBadTrace({ + x: errorXAxis, + y: errorYAxis, + text: errorText, + hoverinfo: 'text', + mode: 'markers', + type: 'scatter', + marker: { + size: 8, + color: 'red', + symbol: 'x', + }, + name: 'Issues', }); }, [props]); @@ -65,12 +97,20 @@ export default function BFactorVsRSCC(props) {
BFactor vs RSCC diff --git a/webapp/src/database/DatabaseComponents/SugarList.tsx b/webapp/src/database/DatabaseComponents/SugarList.tsx index 515aa074..9344662e 100644 --- a/webapp/src/database/DatabaseComponents/SugarList.tsx +++ b/webapp/src/database/DatabaseComponents/SugarList.tsx @@ -1,7 +1,8 @@ -import React, { useMemo, useEffect, useState } from 'react'; +import React, { useMemo, useEffect, useState, useRef } from 'react'; import { useTable } from 'react-table'; import { SugarListColumns } from '../../data/Constants.tsx'; import styled from 'styled-components'; +import { Tooltip, type TooltipRefProps } from 'react-tooltip'; function customSort( a: Record, @@ -135,12 +136,37 @@ export default function SugarList(props) { setData(results); }, [props]); + const tooltipRef = useRef(null); + + const tooltipContent: string = `A “spherical” coordinate system is used to describe six-membered rings, where the total puckering amplitude of the ring, Q, is defined, as well as
+ the distortion-type which is specified by two angles, phi and theta:

+ Radius, Q: describes the overall distortion or shape of the puckered ring,
+ measuring the deviation from a perfectly flat six-membered ring (Q = 0). This is calculated using out-of-plane deviations of puckered rings using the
+ z-coordinates of the ring atoms relative to a mean plane cutting through the ring.

+ Azimuthal angle, theta: describes the orientation of the puckering plane around the rings circumference. This parameter indicates the direction
+ in which the puckering occurs along the ring, providing information about the spatial distribution of the distortion.

+ Meridian angle, phi: describes the orientation of the out-of-plane puckering with respect to the rings mean plane. This provides information about
+ the directionality of the puckering, indicating whether the distortion of the ring is primarily upward or downward with respect to the mean plane.`; + return (
- - Detailed monosaccharide validation data - - + + +
+ + Detailed monosaccharide validation data + + + + +
@@ -166,7 +192,6 @@ export default function SugarList(props) { return ( diff --git a/webapp/src/database/DatabaseComponents/TorsionGraph.tsx b/webapp/src/database/DatabaseComponents/TorsionGraph.tsx new file mode 100644 index 00000000..7ca84613 --- /dev/null +++ b/webapp/src/database/DatabaseComponents/TorsionGraph.tsx @@ -0,0 +1,126 @@ +import React, { useEffect, useState } from 'react'; +import TorsionMultiPlot from '../../shared/TorsionPlot/TorsionMultiPlot.tsx'; + +function TorsionGraphTabs(props): React.JSX.Element[] { + return props.keys.map((item, index) => { + return ( +
  • + +
  • + ); + }); +} + +export default function TorsionGraph(data) { + const [torsions, setTorsions] = useState | undefined>(); + const [torsionTab, setTorsionTab] = useState(0); + const [glycanTab, setGlycanTab] = useState(0); + + useEffect(() => { + const glycans = data.data.glycans; + const torsionList: Record>> = {}; + + for (const key in glycans) { + const glycanType = glycans[key]; + for (let i = 0; i < glycanType.length; i++) { + const chainID = glycanType[i].rootSugarChainId; + const linkages = glycanType[i].linkages; + + for (const linkage in linkages) { + for (let j = 0; j < linkages[linkage].length; j++) { + const data = { + sugar_1: linkages[linkage][j].first_residue, + sugar_2: linkages[linkage][j].second_residue, + atom_number_1: + linkages[linkage][j].donor_atom.slice(-1), + atom_number_2: + linkages[linkage][j].acceptor_atom.slice(-1), + phi: linkages[linkage][j].phi, + psi: linkages[linkage][j].psi, + }; + if (chainID in torsionList) { + torsionList[chainID].push(data); + } else { + torsionList[chainID] = [data]; + } + } + } + } + } + // torsionList.sort; + + const sortedTorsionList = {}; + + Object.keys(torsionList) + .sort() + .forEach(function (k, _) { + sortedTorsionList[k] = torsionList[k]; + }); + + if (Object.keys(sortedTorsionList).length === 0) { + setTorsions(undefined); + } else { + setTorsions(sortedTorsionList); + } + }, [data]); + + useEffect(() => { + setTorsionTab(0); + }, [glycanTab]); + + return ( +
    + {torsions !== undefined ? ( + <> + Linkage Torsion Analysis +
    + + Chain: + +
      + +
    +
    + + + + ) : ( + <> + Linkage Torsion Analysis +
    + + There are no linkages detected in this model. + +
    + + )} +
    + ); +} diff --git a/webapp/src/database/DatabaseInput/DatabaseInput.tsx b/webapp/src/database/DatabaseInput/DatabaseInput.tsx new file mode 100644 index 00000000..d8b0f3dd --- /dev/null +++ b/webapp/src/database/DatabaseInput/DatabaseInput.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import DatabaseFetch from '../DatabaseFetch/DatabaseFetch'; +import UploadButton from '../../shared/Upload/UploadButton.tsx'; +import Submit from '../../shared/Submit/Submit.tsx'; +import PDBFetch from '../../shared/PDBFetch/PDBFetch.tsx'; +import DatabaseSearch from '../DatabaseSearch/DatabaseSearch.tsx'; + +export default function DatabaseInput(props: { + PDBCode: string; + setPDBCode: any; + submitPressed: boolean; +}) { + const [showSearchBegin, setSearchBegin] = useState(false); + + return ( + <> +
    + { + + } + + {!showSearchBegin ? ( +
    + OR +
    + ) : ( + <> + )} + {!showSearchBegin ? ( + + ) : ( + <> + )} +
    + + ); +} diff --git a/webapp/src/database/DatabaseResult/DatabaseResult.tsx b/webapp/src/database/DatabaseResult/DatabaseResult.tsx index 47bb03f4..0575824c 100644 --- a/webapp/src/database/DatabaseResult/DatabaseResult.tsx +++ b/webapp/src/database/DatabaseResult/DatabaseResult.tsx @@ -4,6 +4,8 @@ import CremerPopleGraph from '../DatabaseComponents/CremerPopleGraph.tsx'; import BFactorVsRSCC from '../DatabaseComponents/BFactorVsRSCC.tsx'; import SNFGList from '../DatabaseComponents/SNFGList.tsx'; import SugarList from '../DatabaseComponents/SugarList.tsx'; +import TorsionGraph from '../DatabaseComponents/TorsionGraph.tsx'; +import BorderElement from '../../layouts/BorderElement.tsx'; export default function DatabaseResult(props: DatabaseResultProps) { const [pdbShown, setPDBShown] = useState(true); @@ -18,7 +20,6 @@ export default function DatabaseResult(props: DatabaseResultProps) { if (props.pdbResults === null) return; if (props.pdbRedoResults === null) return; - console.log('running use effect'); setSelectedData(props.pdbResults); }, []); @@ -35,7 +36,7 @@ export default function DatabaseResult(props: DatabaseResultProps) { return ( <> {selectedData !== undefined ? ( -
    +

    Validation Report - {props.PDBCode} {props.pdbRedoResults !== '' ? toggleSwitch() : <>} @@ -43,6 +44,21 @@ export default function DatabaseResult(props: DatabaseResultProps) {
    + + + +
    + +
    +
    diff --git a/webapp/src/database/DatabaseSearch/DatabaseSearch.tsx b/webapp/src/database/DatabaseSearch/DatabaseSearch.tsx new file mode 100644 index 00000000..fdad5211 --- /dev/null +++ b/webapp/src/database/DatabaseSearch/DatabaseSearch.tsx @@ -0,0 +1,315 @@ +import React, { + type Dispatch, + type SetStateAction, + useEffect, + useState, +} from 'react'; +import DatabaseSearchTable from '../DatabaseSearchTable/DatabaseSearchTable.tsx'; +import Loading from '../../shared/Loading/Loading.tsx'; +import { sugarLinkageMap } from '../../data/Constants.tsx'; +function ViewAllEntriesButton(props: { + text: string; + label: any; + onClickMethod: any; + secondLabel: string | undefined; + onSecondClick: any; +}) { + return ( +
    + + + +

    + {props.text} +

    +
    + + {props.secondLabel !== undefined ? ( + + ) : ( + <> + )} +
    +
    + ); +} + +function TypeFilterBox(props: { selected: string; onClickMethod: any }) { + const sugars = ['N-glycans', 'O-glycans', 'S-glycans', 'C-glycans']; + return ( +
    + + + + +

    + Filter Sugars +

    + +
    + {sugars.map((item, index) => { + return ( + + ); + })} +
    +
    + ); +} + +function LinkageFilterBox(props: { + selected: string; + sugars: string[]; + onClickMethod: Dispatch>; +}) { + return ( +
    + + + + +

    + Filter Linkages +

    + +
    + {props.sugars.map((item, index) => { + return ( + + ); + })} +
    +
    + ); +} + +function FilterZone(props: { setSearchBegin: any }) { + const [search, setSearch] = useState(false); + const [linkage, setLinkage] = useState('Any'); + const [type, setType] = useState('N-glycans'); + const [text, setText] = useState(''); + + useEffect(() => { + setLinkage(sugarLinkageMap[type][0]); + }, [type]); + + useEffect(() => { + let textString = 'Find'; + + if (type === 'Any') { + textString += ' any carbohydrate'; + } else { + textString += ' ' + type; + } + textString += ' with '; + if (linkage === 'Any') { + textString += ' any linkage type'; + } else { + textString += ' ' + linkage + ' linkages'; + } + + setText(textString); + }, [linkage, type]); + + useEffect(() => { + if (!search) { + return; + } + let formattedType = ''; + let url = + 'https://raw.githubusercontent.com/Dialpuri/PrivateerDatabase/master/linkages/'; + if (type === 'N-glycans') { + url += 'n-glycan/'; + formattedType = 'n-glycan'; + } + if (type === 'O-glycans') { + url += 'o-glycan/'; + formattedType = 'o-glycan'; + } + if (type === 'S-glycans') { + url += 's-glycan/'; + formattedType = 's-glycan'; + } + if (type === 'C-glycans') { + url += 'c-glycan/'; + formattedType = 'c-glycan'; + } + if (type === 'Ligands') { + url += 'ligand/'; + formattedType = 'ligand'; + } + if (linkage === 'Any') { + url += 'any'; + } else { + url += linkage.replace(',', '%2C'); + } + url += '.json'; + + void fetch(url) + .then(async (response) => await response.json()) + .then((json: Record) => { + let formattedData: Record; + if (linkage === 'Any') { + formattedData = Object.keys(json).flatMap((item) => { + return json[item].map((element) => { + return { + pdb: element.pdb, + count: element.count, + resolution: element.resolution, + linkage: item, + type: formattedType, + link: + 'https://privateer.york.ac.uk/database?pdb=' + + element.pdb, + }; + }); + }); + } else { + formattedData = json.map((item) => { + return { + pdb: item.pdb, + count: item.count, + resolution: item.resolution, + linkage, + type: formattedType, + link: + 'https://privateer.york.ac.uk/database?pdb=' + + item.pdb, + }; + }); + } + setData(formattedData); + }) + .catch(() => {}); + }, [search]); + + const [data, setData] = useState | null>( + null + ); + + return !search ? ( + <> + {ViewAllEntriesButton({ + label: 'Back', + text, + onClickMethod: props.setSearchBegin, + secondLabel: 'Search', + onSecondClick: setSearch, + })} + {TypeFilterBox({ onClickMethod: setType, selected: type })} + {LinkageFilterBox({ + onClickMethod: setLinkage, + sugars: sugarLinkageMap[type], + selected: linkage, + })} + + ) : data === null ? ( + + ) : ( +
    + Query: {text} + + +
    + ); +} + +export default function DatabaseSearch(props: { + setSearchBegin: any; + searchBegin: boolean; +}) { + return !props.searchBegin + ? ViewAllEntriesButton({ + label: 'View', + text: ( + <> + View all entries in the Privateer Database + + ), + onClickMethod: props.setSearchBegin, + }) + : FilterZone({ setSearchBegin: props.setSearchBegin }); +} diff --git a/webapp/src/database/DatabaseSearchTable/DatabaseSearchTable.tsx b/webapp/src/database/DatabaseSearchTable/DatabaseSearchTable.tsx new file mode 100644 index 00000000..d8d21f72 --- /dev/null +++ b/webapp/src/database/DatabaseSearchTable/DatabaseSearchTable.tsx @@ -0,0 +1,348 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { + type PaginationState, + useReactTable, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + flexRender, + getSortedRowModel, + type Column, + type SortingState, +} from '@tanstack/react-table'; +import styled from 'styled-components'; + +const Styles = styled.div` + table { + border-collapse: collapse; + border-spacing: 0; + width: 100%; + // border: 0px solid #ddd; + } + + table th { + text-align: left; + padding: 16px; + // border: 1px solid #ddd; + } + + table td { + text-align: center; + padding: 16px; + border: 1px solid #ddd; + border-style: solid none; + } + + table td { + border-style: none none; + } + + table tr:nth-child(even) { + background-color: #f6f6f6; + } + + table tr:nth-child(even) { + background-color: #f4f9ff; + // color: #000000 + } + + table th { + padding-top: 12px; + padding-bottom: 12px; + text-align: center; + background-color: #f4f9ff; + color: black; + } + + table th:first-of-type { + border-top-left-radius: 30px; + } + + table th:last-of-type { + border-top-right-radius: 30px; + } + + table tr:last-of-type td:first-of-type { + border-bottom-left-radius: 30px; + } + + table tr:last-of-type td:last-of-type { + border-bottom-right-radius: 30px; + } + + #row:hover { + scale: 101%; + cursor: grab; + } +`; + +function Filter({ column, table }: { column: Column; table: any }) { + const firstValue = table + .getPreFilteredRowModel() + .flatRows[0]?.getValue(column.id); + + const columnFilterValue = column.getFilterValue(); + + return typeof firstValue === 'number' ? ( +
    + { + column.setFilterValue((old: [number, number]) => [ + e.target.value, + old?.[1], + ]); + }} + placeholder={' Min'} + className="w-24 border shadow rounded" + /> + { + column.setFilterValue((old: [number, number]) => [ + old?.[0], + e.target.value, + ]); + }} + placeholder={' Max'} + className="w-24 border shadow rounded" + /> +
    + ) : ( + { + column.setFilterValue(e.target.value); + }} + placeholder={'Search...'} + className="w-36 border shadow rounded" + /> + ); +} +function Table({ data }: { data: any }) { + const COLUMNS = [ + { + header: 'Type', + accessorKey: 'type', + }, + { + header: 'PDB', + accessorKey: 'pdb', + }, + { + header: 'Linkage', + accessorKey: 'linkage', + }, + { + header: 'Count', + accessorKey: 'count', + }, + { + header: 'Resolution', + accessorKey: 'resolution', + }, + { + header: 'Link', + accessorKey: 'link', + enableColumnFilter: false, + cell: (props: { getValue: () => string }) => { + return ( + + + + + + ); + }, + }, + ]; + + const columns = useMemo(() => COLUMNS, []); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + const [sorting, setSorting] = useState([ + { + id: 'count', // Must be equal to the accessorKey of the coulmn you want sorted by default + desc: true, + }, + ]); + const table = useReactTable({ + columns, + data, + debugTable: false, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onPaginationChange: setPagination, + state: { + pagination, + // sorting + }, + }); + + return ( + <> + +

    + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ); + })} + + ))} + + + {table.getRowModel().rows.map((row) => { + return ( + + {row.getVisibleCells().map((cell) => { + return ( + + ); + })} + + ); + })} + +
    +
    + {flexRender( + header.column.columnDef + .header, + header.getContext() + )} + {{ + asc: ' ↑', + desc: ' ↓', + }[ + header.column.getIsSorted() as string + ] ?? null} + {header.column.getCanFilter() ? ( +
    + +
    + ) : null} +
    +
    + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} +
    +
    +
    + + + + + +
    Page
    + + {table.getState().pagination.pageIndex + 1} of{' '} + {table.getPageCount().toLocaleString()} + +
    + + | Go to page: + { + const page = e.target.value + ? Number(e.target.value) - 1 + : 0; + table.setPageIndex(page); + }} + className="border p-1 rounded w-16" + /> + + +
    +
    + Showing {table.getRowModel().rows.length.toLocaleString()}{' '} + of {table.getRowCount().toLocaleString()} Rows +
    + + + ); +} + +export default function DatabaseSearchTable(props: { data: any }) { + return ; +} diff --git a/webapp/src/database/Header/DatabaseHeader.tsx b/webapp/src/database/Header/DatabaseHeader.tsx index 6e33a376..8045fb0e 100644 --- a/webapp/src/database/Header/DatabaseHeader.tsx +++ b/webapp/src/database/Header/DatabaseHeader.tsx @@ -7,8 +7,8 @@ const NavBar = lazy(async () => await import('../../layouts/NavBar.tsx')); const NoGlycans = lazy( async () => await import('../../shared/NoGlycans/NoGlycans.tsx') ); -const DatabaseFetch = lazy( - async () => await import('../DatabaseFetch/DatabaseFetch.jsx') +const DatabaseInput = lazy( + async () => await import('../DatabaseInput/DatabaseInput.jsx') ); const DatabaseResult = lazy( async () => await import('../DatabaseResult/DatabaseResult.tsx') @@ -18,7 +18,7 @@ export function DatabaseHeader(props: DatabaseHeaderProps): ReactElement { return (
    -
    +
    {!props.fallback ? ( {props.pdbResults === '' ? ( - @@ -12,3 +15,17 @@ ReactDOM.createRoot(document.getElementById('root')).render( // , ); +const SendAnalytics = () => { + ReactGA.send({ + hitType: 'pageview', + page: window.location.pathname, + }); +}; + +try { + onCLS(SendAnalytics); + onLCP(SendAnalytics); + onFID(SendAnalytics); +} catch (err) { + console.error(err); +} diff --git a/webapp/src/main/GlycanDetail/GlycanDetailTorsionPlot.tsx b/webapp/src/main/GlycanDetail/GlycanDetailTorsionPlot.tsx index 1e2b25db..e2f96813 100644 --- a/webapp/src/main/GlycanDetail/GlycanDetailTorsionPlot.tsx +++ b/webapp/src/main/GlycanDetail/GlycanDetailTorsionPlot.tsx @@ -1,12 +1,12 @@ -import { type TableDataEntry } from '../../interfaces/types.ts'; +import { type ResultsEntry } from '../../interfaces/types.ts'; import React, { lazy, useEffect, useState } from 'react'; const TorsionMultiPlot = lazy( - async () => await import('./TorsionPlot/TorsionMultiPlot.tsx') + async () => await import('../../shared/TorsionPlot/TorsionMultiPlot.tsx') ); export function GlycanDetailTorsionPlot(props: { key: string; - tableDataEntries: TableDataEntry[]; + tableDataEntries: ResultsEntry[]; rowID: number; tab: number; tab1: (value: ((prevState: number) => number) | number) => void; @@ -33,6 +33,7 @@ export function GlycanDetailTorsionPlot(props: { tab={props.tab} setTab={props.tab1} size={dimension} + background={'#D6D9E5'} />
    ); diff --git a/webapp/src/routes/Database/Database.tsx b/webapp/src/routes/Database/Database.tsx index ee6f2401..36da7f37 100644 --- a/webapp/src/routes/Database/Database.tsx +++ b/webapp/src/routes/Database/Database.tsx @@ -20,8 +20,8 @@ export default function Database(props: { const [resetApp, setResetApp] = useState(false); const [fallback, setFallBack] = useState(false); const [failureText, setFailureText] = useState(''); - const [pdbResults, setPDBResults] = useState(''); - const [pdbRedoResults, setPDBRedoResults] = useState(''); + const [pdbResults, setPDBResults] = useState(''); + const [pdbRedoResults, setPDBRedoResults] = useState(''); // const [failure, setFailure] = useState(false); @@ -33,9 +33,12 @@ export default function Database(props: { try { const response = await fetch(pdbUrl); - const data: string = await response.json(); - setPDBResults(data); - } catch { + const text = await response.text(); + const replacedText = text.replace(/\bNaN\b/g, 'null'); + const result = JSON.parse(replacedText); + // const data: string = await response.json(); + setPDBResults(result); + } catch (e) { setFallBack(true); setFailureText('This PDB is not in the database'); } @@ -44,9 +47,12 @@ export default function Database(props: { try { const response = await fetch(pdbRedoUrl); - const redoData: string = await response.json(); - setPDBRedoResults(redoData); + const text = await response.text(); + const replacedText = text.replace(/\bNaN\b/g, 'null'); + const result = JSON.parse(replacedText); + setPDBRedoResults(result); } catch { + console.log('PDB REDO Failed'); // setFallBack(true); // setFailureText('This PDB is not in the database'); } @@ -73,7 +79,7 @@ export default function Database(props: { setPDBCode(''); setPDBResults(''); setPDBRedoResults(''); - props.setSearchParams({}); + // props.setSearchParams({}); }, [resetApp]); useEffect(() => { diff --git a/webapp/src/main/GlycanDetail/TorsionPlot/TorsionMultiPlot.tsx b/webapp/src/shared/TorsionPlot/TorsionMultiPlot.tsx similarity index 77% rename from webapp/src/main/GlycanDetail/TorsionPlot/TorsionMultiPlot.tsx rename to webapp/src/shared/TorsionPlot/TorsionMultiPlot.tsx index 6f8184d4..8b0ff530 100644 --- a/webapp/src/main/GlycanDetail/TorsionPlot/TorsionMultiPlot.tsx +++ b/webapp/src/shared/TorsionPlot/TorsionMultiPlot.tsx @@ -1,10 +1,9 @@ -import React, { useEffect, lazy, type ReactElement } from 'react'; +import React, { useEffect, lazy, type ReactElement, useState } from 'react'; const TorsionPlot = lazy(async () => await import('./TorsionPlot.tsx')); function sortTorsions(torsions): [string[], any] { const linkageSet = new Set(); - torsions.forEach( (torsion: { sugar_1: string; @@ -84,14 +83,22 @@ function sortTorsions(torsions): [string[], any] { return [linkageArray, sortedLinkageMap]; } -function TorsionMultiPlotTabs({ torsions, setTab }): React.JSX.Element[] { +function TorsionMultiPlotTabs({ + torsions, + currentTab, + setTab, +}): React.JSX.Element[] { const [linkageArray, _] = sortTorsions(torsions); return linkageArray.map((item, index) => { return (