diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 99a6b1a..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.202.3/containers/typescript-node/.devcontainer/base.Dockerfile - -# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster -ARG VARIANT="16-bullseye" -FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT} - -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends - -# [Optional] Uncomment if you want to install an additional version of node using nvm -# ARG EXTRA_NODE_VERSION=10 -# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" - -# [Optional] Uncomment if you want to install more global node packages -# RUN su node -c "npm install -g " diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index f5fcba2..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,33 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.202.3/containers/typescript-node -{ - "name": "Node.js & TypeScript", - "runArgs": ["--init"], - "build": { - "dockerfile": "Dockerfile", - // Update 'VARIANT' to pick a Node version: 16, 14, 12. - // Append -bullseye or -buster to pin to an OS version. - // Use -bullseye variants on local on arm64/Apple Silicon. - "args": { - "VARIANT": "16-bullseye" - } - }, - - // Set *default* container specific settings.json values on container create. - "settings": {}, - - - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "dbaeumer.vscode-eslint" - ], - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [3000], - - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "npm install", - - // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "node" -} \ No newline at end of file diff --git a/.env.development b/.env.development index ff3b30f..11a13ed 100644 --- a/.env.development +++ b/.env.development @@ -1 +1,2 @@ +PORT = 3001 REACT_APP_BACKEND = "http://localhost:3000/api/v0/" \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index 9b21b2b..e529941 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,7 @@ { "extends": ["airbnb-typescript", "prettier", "plugin:import/errors", "plugin:import/warnings", "plugin:import/typescript"], "plugins": ["prettier", "@typescript-eslint", "import", "react"], + "ignorePatterns": ["**/ldavis/*.js"], "rules": { "prettier/prettier": ["error"], "no-unused-expressions": "off", diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b6a71a3..f8bb896 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: - name: ✅ Setup node uses: actions/setup-node@v2 with: - node-version: '17' + node-version: '18' - name: 📦 Install dependencies run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bcbc328..2892a58 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: - name: ✅ Setup node uses: actions/setup-node@v2 with: - node-version: '17' + node-version: '18' - name: 🏷 Bump version and push tag uses: anothrNick/github-tag-action@1.36.0 diff --git a/package-lock.json b/package-lock.json index 40051f3..fdeb544 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,10 @@ "@mui/icons-material": "^5.5.1", "@mui/material": "^5.5.1", "@mui/x-data-grid": "^5.10.0", - "apexcharts": "^3.35.3", + "apexcharts": "^3.35.4", "lodash": "^4.17.21", "react": "^17.0.2", - "react-apexcharts": "^1.3.9", + "react-apexcharts": "^1.4.0", "react-dom": "^17.0.2", "react-git-info": "^2.0.1", "react-query": "^3.38.1", @@ -4151,6 +4151,259 @@ "@types/node": "*" } }, + "node_modules/@types/d3": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", + "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", + "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==", + "dev": true + }, + "node_modules/@types/d3-axis": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.1.tgz", + "integrity": "sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz", + "integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==", + "dev": true + }, + "node_modules/@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", + "dev": true + }, + "node_modules/@types/d3-contour": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz", + "integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "dev": true + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==", + "dev": true + }, + "node_modules/@types/d3-drag": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz", + "integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz", + "integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==", + "dev": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", + "dev": true + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==", + "dev": true, + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz", + "integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==", + "dev": true + }, + "node_modules/@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "dev": true + }, + "node_modules/@types/d3-geo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz", + "integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz", + "integrity": "sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==", + "dev": true + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dev": true, + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", + "dev": true + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", + "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", + "dev": true + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", + "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", + "dev": true + }, + "node_modules/@types/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", + "dev": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", + "dev": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", + "dev": true + }, + "node_modules/@types/d3-selection": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz", + "integrity": "sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA==", + "dev": true + }, + "node_modules/@types/d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==", + "dev": true, + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "dev": true + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", + "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", + "dev": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", + "dev": true + }, + "node_modules/@types/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-Sv4qEI9uq3bnZwlOANvYK853zvpdKEm1yz9rcc8ZTsxvRklcs9Fx4YFuGA3gXoQN/c/1T6QkVNjhaRO/cWj94g==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz", + "integrity": "sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==", + "dev": true, + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/eslint": { "version": "7.29.0", "license": "MIT", @@ -4191,6 +4444,12 @@ "@types/range-parser": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "dev": true + }, "node_modules/@types/graceful-fs": { "version": "4.1.5", "license": "MIT", @@ -5031,8 +5290,9 @@ } }, "node_modules/apexcharts": { - "version": "3.35.3", - "license": "MIT", + "version": "3.35.4", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.35.4.tgz", + "integrity": "sha512-dsXjETHF2OmKtxNv66wBeFGU2qtZQnr6kp/vcNY05GWs4vcBepg54qNgOJ2Gp/gXskiGw/frrmIKGi8lJ/UDnQ==", "dependencies": { "svg.draggable.js": "^2.2.2", "svg.easing.js": "^2.0.0", @@ -6528,6 +6788,11 @@ "version": "3.1.0", "license": "MIT" }, + "node_modules/d3": { + "version": "3.5.17", + "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", + "integrity": "sha512-yFk/2idb8OHPKkbAL8QaOaqENNoMhIaSHZerk3oQsECwkObkCpJyjYwCe+OHiq6UEdhe1m8ZGARRRO3ljFjlKg==" + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "license": "BSD-2-Clause" @@ -18853,6 +19118,258 @@ "@types/node": "*" } }, + "@types/d3": { + "version": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", + "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "dev": true, + "requires": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "@types/d3-array": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", + "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==", + "dev": true + }, + "@types/d3-axis": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.1.tgz", + "integrity": "sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-brush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz", + "integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==", + "dev": true + }, + "@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", + "dev": true + }, + "@types/d3-contour": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz", + "integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==", + "dev": true, + "requires": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "dev": true + }, + "@types/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==", + "dev": true + }, + "@types/d3-drag": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz", + "integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-dsv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz", + "integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==", + "dev": true + }, + "@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", + "dev": true + }, + "@types/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==", + "dev": true, + "requires": { + "@types/d3-dsv": "*" + } + }, + "@types/d3-force": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz", + "integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==", + "dev": true + }, + "@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "dev": true + }, + "@types/d3-geo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz", + "integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, + "@types/d3-hierarchy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz", + "integrity": "sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==", + "dev": true + }, + "@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dev": true, + "requires": { + "@types/d3-color": "*" + } + }, + "@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", + "dev": true + }, + "@types/d3-polygon": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", + "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", + "dev": true + }, + "@types/d3-quadtree": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", + "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", + "dev": true + }, + "@types/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", + "dev": true + }, + "@types/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", + "dev": true, + "requires": { + "@types/d3-time": "*" + } + }, + "@types/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", + "dev": true + }, + "@types/d3-selection": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz", + "integrity": "sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA==", + "dev": true + }, + "@types/d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==", + "dev": true, + "requires": { + "@types/d3-path": "*" + } + }, + "@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "dev": true + }, + "@types/d3-time-format": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", + "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", + "dev": true + }, + "@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", + "dev": true + }, + "@types/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-Sv4qEI9uq3bnZwlOANvYK853zvpdKEm1yz9rcc8ZTsxvRklcs9Fx4YFuGA3gXoQN/c/1T6QkVNjhaRO/cWj94g==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-zoom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz", + "integrity": "sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==", + "dev": true, + "requires": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "@types/eslint": { "version": "7.29.0", "requires": { @@ -18889,6 +19406,12 @@ "@types/range-parser": "*" } }, + "@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "dev": true + }, "@types/graceful-fs": { "version": "4.1.5", "requires": { @@ -19463,7 +19986,9 @@ } }, "apexcharts": { - "version": "3.35.3", + "version": "3.35.4", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.35.4.tgz", + "integrity": "sha512-dsXjETHF2OmKtxNv66wBeFGU2qtZQnr6kp/vcNY05GWs4vcBepg54qNgOJ2Gp/gXskiGw/frrmIKGi8lJ/UDnQ==", "requires": { "svg.draggable.js": "^2.2.2", "svg.easing.js": "^2.0.0", @@ -20371,6 +20896,10 @@ "csstype": { "version": "3.1.0" }, + "d3": { + "version": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", + "integrity": "sha512-yFk/2idb8OHPKkbAL8QaOaqENNoMhIaSHZerk3oQsECwkObkCpJyjYwCe+OHiq6UEdhe1m8ZGARRRO3ljFjlKg==" + }, "damerau-levenshtein": { "version": "1.0.8" }, diff --git a/package.json b/package.json index 4fd6202..7e27448 100644 --- a/package.json +++ b/package.json @@ -49,10 +49,10 @@ "eslint-import-resolver-typescript": "^3.2.5", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.6.0", + "eslint-plugin-markdown": "^2.2.1", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-markdown": "^2.2.1", "prettier": "^2.6.2", "react-error-overlay": "^6.0.9" }, @@ -62,10 +62,10 @@ "@mui/icons-material": "^5.5.1", "@mui/material": "^5.5.1", "@mui/x-data-grid": "^5.10.0", - "apexcharts": "^3.35.3", + "apexcharts": "^3.35.4", "lodash": "^4.17.21", "react": "^17.0.2", - "react-apexcharts": "^1.3.9", + "react-apexcharts": "^1.4.0", "react-dom": "^17.0.2", "react-git-info": "^2.0.1", "react-query": "^3.38.1", diff --git a/src/App.css b/src/App.css index a484873..33ad88f 100644 --- a/src/App.css +++ b/src/App.css @@ -9,7 +9,7 @@ margin-top: 48px; margin-bottom: 48px; pointer-events: none; - color: #61DBFB; + color: #61dbfb; font-size: calc(50px + 10vmin); font-weight: bold; } @@ -52,9 +52,9 @@ grid-template-columns: auto 1fr; grid-template-rows: auto auto 1fr; grid-template-areas: - "header header " - "side tools " - "side content" + 'header header ' + 'side tools ' + 'side content'; } .stack { @@ -98,9 +98,9 @@ grid-template-columns: 1fr auto; grid-template-rows: auto auto auto; grid-template-areas: - "bar box " - "grid box " - "tree tree " + 'bar box ' + 'grid box ' + 'tree tree '; } .tools { @@ -134,22 +134,23 @@ /*background: blue;*/ } +/* TODO remove when boxplot log scale is fixed */ +.boxplot .apexcharts-menu-item.exportCSV { + display: none; +} + .treemap { /*background: orange;*/ grid-area: tree; } -.treemapTextField input{ - height: 10px +.treemapTextField input { + height: 10px; } -/*.treemap {*/ -/* overflow: scroll;*/ -/* overflow-wrap: break-word;*/ -/*}*/ - -/*.treemap .apexcharts-datalabel {*/ -/* overflow: scroll;*/ -/* overflow-wrap: break-word;*/ -/* font-size: 14px !important;*/ -/*}*/ +.title { + color: #263238; + font-size: 14px; + font-weight: bold; + font-family: Helvetica, Arial, sans-serif; +} diff --git a/src/App.tsx b/src/App.tsx index 535a59b..36e0786 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { BrowserRouter, Route, Routes } from 'react-router-dom'; import { SnackbarProvider } from './context/SnackbarContext'; import Login from './routes/login'; @@ -16,16 +15,15 @@ import { ROUTE_FIELDS_OF_STUDY, ROUTE_HOME, ROUTE_LOGIN, - ROUTE_PAPER_TYPES, ROUTE_PAPERS, ROUTE_PASSWORD, ROUTE_PUBLISHERS, ROUTE_REGISTER, ROUTE_TOPICS, + ROUTE_TYPES_OF_PAPER, ROUTE_VENUES, } from './consts'; import Account from './routes/account'; - import { ErrorBoundaryWrapper } from './context/ErrorBoundary'; import { QueryClient, QueryClientProvider } from 'react-query'; import ForgotPassword from './routes/forgotPassword'; @@ -34,8 +32,9 @@ import FieldsOfStudy from './routes/dashboards/fieldsOfStudy'; import Citations from './routes/dashboards/citations'; import Topics from './routes/dashboards/topics'; import Publishers from './routes/dashboards/publishers'; +import { RefreshProvider } from './context/RefreshContext'; -export const queryClient = new QueryClient(); +const queryClient = new QueryClient(); export default function App() { return ( @@ -44,23 +43,25 @@ export default function App() { - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + diff --git a/src/components/LoadingCircle.tsx b/src/components/ChartLoadingIcon.tsx similarity index 71% rename from src/components/LoadingCircle.tsx rename to src/components/ChartLoadingIcon.tsx index 0108185..10ad235 100644 --- a/src/components/LoadingCircle.tsx +++ b/src/components/ChartLoadingIcon.tsx @@ -1,11 +1,10 @@ -import React from 'react'; -import Box from '@mui/material/Box'; -import { CircularProgress } from '@mui/material'; +import { Box, CircularProgress } from '@mui/material'; +import { ReactElement } from 'react'; -export default function LoadingCircle(props: { +export default function ChartLoadingIcon(props: { isFetching: boolean; className?: string; - children: React.ReactElement | React.ReactElement[]; + children: ReactElement | ReactElement[]; }) { return ( diff --git a/src/components/FilterAutocomplete.tsx b/src/components/FilterAutocomplete.tsx index 863082f..cd0a508 100644 --- a/src/components/FilterAutocomplete.tsx +++ b/src/components/FilterAutocomplete.tsx @@ -1,7 +1,9 @@ -import React, { Fragment, useCallback, useEffect } from 'react'; +import { Fragment, ReactElement, useCallback, useEffect, useState } from 'react'; import '../App.css'; import { Autocomplete, + AutocompleteRenderInputParams, + Box, capitalize, Chip, CircularProgress, @@ -17,7 +19,6 @@ import { FIELDS_OF_STUDY, TYPES_OF_PAPER, } from '../consts'; -import Box from '@mui/material/Box'; import FilterLabel from './FilterLabel'; function useDebounce( @@ -27,10 +28,10 @@ function useDebounce( inputValue: string, setInputValue: (value: string) => void ): { - handleInputChange: (newInputValue: string, event: React.SyntheticEvent, reason: string) => void; + handleInputChange: (newInputValue: string) => void; isFetching: boolean; } { - const [pattern, setPattern] = React.useState(''); + const [pattern, setPattern] = useState(''); const { refetch, isFetching } = useNetworkGet( `fe/${route}/list`, @@ -56,15 +57,9 @@ function useDebounce( [] ); - function handleInputChange(newInputValue: string, event: React.SyntheticEvent, reason: string) { - if (event && event.type !== 'blur' && reason === 'reset') { - setInputValue(''); - setOptions([]); - setPattern(''); - } else if (reason !== 'reset') { - setInputValue(newInputValue); - handleInputChangeDebounce(newInputValue); - } + function handleInputChange(newInputValue: string) { + setInputValue(newInputValue); + handleInputChangeDebounce(newInputValue); } return { handleInputChange, isFetching }; @@ -76,7 +71,7 @@ function TagsAndTooltip void; - children: React.ReactElement; + children: ReactElement; }) { function chipLabel(chipValue: { _id: string; [key: string]: string } | string) { const str = typeof chipValue === 'string' ? chipValue : chipValue[props.labelName]; @@ -116,11 +111,25 @@ function TagsAndTooltip + {props.isFetching ? ( + + ) : null} + {props.params.InputProps.endAdornment} + + ); +} + export function FilterMultipleObjectFetch( props: FilterAutocompleteProps ) { - const [options, setOptions] = React.useState([]); - const [inputValue, setInputValue] = React.useState(''); + const [options, setOptions] = useState([]); + const [inputValue, setInputValue] = useState(''); const { handleInputChange, isFetching } = useDebounce( props.route, @@ -146,9 +155,7 @@ export function FilterMultipleObjectFetch props.setValue(value)} inputValue={inputValue} - onInputChange={(event, newInputValue, reason) => - handleInputChange(newInputValue, event, reason) - } + onInputChange={(event, newInputValue) => handleInputChange(newInputValue)} isOptionEqualToValue={(option: T, value: T) => option._id === value._id} getOptionLabel={(option: T) => option[props.labelName]} options={options} @@ -166,14 +173,7 @@ export function FilterMultipleObjectFetch - {isFetching ? ( - - ) : null} - {params.InputProps.endAdornment} - - ), + endAdornment: , }} /> )} @@ -183,8 +183,8 @@ export function FilterMultipleObjectFetch) { - const [options, setOptions] = React.useState([]); - const [inputValue, setInputValue] = React.useState(''); + const [options, setOptions] = useState([]); + const [inputValue, setInputValue] = useState(''); const { handleInputChange, isFetching } = useDebounce( props.route, @@ -210,9 +210,7 @@ export function FilterMultipleStringFetch(props: FilterAutocompleteProps props.setValue(value)} inputValue={inputValue} - onInputChange={(event, newInputValue, reason) => - handleInputChange(newInputValue, event, reason) - } + onInputChange={(event, newInputValue) => handleInputChange(newInputValue)} options={options} loading={isFetching} filterOptions={(x) => x} @@ -223,14 +221,7 @@ export function FilterMultipleStringFetch(props: FilterAutocompleteProps - {isFetching ? ( - - ) : null} - {params.InputProps.endAdornment} - - ), + endAdornment: , }} /> )} diff --git a/src/components/FilterLabel.tsx b/src/components/FilterLabel.tsx index baa17bd..97735a2 100644 --- a/src/components/FilterLabel.tsx +++ b/src/components/FilterLabel.tsx @@ -1,6 +1,5 @@ import { Stack, Tooltip } from '@mui/material'; import { HelpOutline } from '@mui/icons-material'; -import React from 'react'; export default function FilterLabel(props: { label: string; helpTooltip: string }) { return ( diff --git a/src/components/FilterRange.tsx b/src/components/FilterRange.tsx index d1d3c17..314e1bf 100644 --- a/src/components/FilterRange.tsx +++ b/src/components/FilterRange.tsx @@ -1,13 +1,21 @@ +import { Fragment, useCallback, useEffect, useState } from 'react'; import { FilterRangeProps, FilterTextFieldProps } from '../types'; import FilterLabel from './FilterLabel'; import { debounce, Stack, TextField } from '@mui/material'; -import React, { useCallback, useState } from 'react'; import { DEBOUNCE_DELAY_TEXTFIELD } from '../consts'; function FilterTextField(props: FilterTextFieldProps) { const [inputValue, setInputValue] = useState(props.value); + // Clicking "Clear filters" will only set the filter value (props.value), not the inputValue + // We have to check for this case manually + useEffect(() => { + if (props.value === '' && inputValue !== '') { + setInputValue(''); + } + }, [props.value]); + // 2 functions, so debounce reference does not get lost const handleInputChangeDebounce = useCallback( debounce(async (newInputValue: string) => { @@ -40,7 +48,7 @@ function FilterTextField(props: FilterTextFieldProps) { export default function FilterRange(props: FilterRangeProps) { return ( - + - + ); } diff --git a/src/components/Frame.tsx b/src/components/Frame.tsx index fa7e401..6b7aa49 100644 --- a/src/components/Frame.tsx +++ b/src/components/Frame.tsx @@ -1,37 +1,34 @@ import ResponsiveAppBar from './ResponsiveAppBar'; import Sidebar from './Sidebar'; -import React, { useEffect } from 'react'; +import { ReactElement, useEffect } from 'react'; import { Navigate } from 'react-router-dom'; import { ROUTE_LOGIN } from '../consts'; -import { RefreshProvider } from '../context/RefreshContext'; import { useAuth } from '../context/AuthContext'; import { useSnack } from '../context/SnackbarContext'; import Tools from './Tools'; -export default function Frame(props: { - route: string; - children: React.ReactElement | React.ReactElement[]; -}) { +export default function Frame(props: { route: string; children: ReactElement | ReactElement[] }) { const auth = useAuth(); const setSnack = useSnack(); + useEffect(() => { + if (!auth.token) { + setSnack('You need to login to access this page.'); + } + }, [auth.token]); + if (auth.token) { return ( - -
- - {/* To counteract the height of the AppBar*/} -
- - - {props.children} -
- +
+ + {/* To counteract the height of the AppBar*/} +
+ + + {props.children} +
); } else { - useEffect(() => { - setSnack('You need to login to access this page.'); - }); return ; } } diff --git a/src/components/FrameWithGraphs.tsx b/src/components/FrameWithGraphs.tsx index 7b66f68..db11e84 100644 --- a/src/components/FrameWithGraphs.tsx +++ b/src/components/FrameWithGraphs.tsx @@ -1,22 +1,30 @@ import Frame from './Frame'; -import BarChart from './charts/BarChart'; +import BarChart from './visualizations/BarChart'; import { GraphsProps } from '../types'; -import BoxPlot from './charts/BoxPlot'; -import React from 'react'; +import BoxPlot from './visualizations/BoxPlot'; import { useFilter } from '../context/FilterContext'; -import TreeMap from './charts/TreeMap'; -import Grid from './charts/Grid'; -import { mapMetric } from '../tools'; +import TreeMap from './visualizations/TreeMap'; +import Grid from './visualizations/Grid'; +import { mapMetric, metrics } from '../tools'; +import { ROUTE_PAPERS } from '../consts'; export default function FrameWithGraphs(props: GraphsProps) { const filter = useFilter(); - const metric = mapMetric(filter.filter.metric); + let metric: string; + + // This distinction is necessary, so it won't show #Citations on the papers dashboard. + // The endpoints for papers in the backend ignore the metric for queries, so it does not matter what is set. + if (props.route === ROUTE_PAPERS.slice(1)) { + metric = metrics[1].label; + } else { + metric = mapMetric(filter.filter.metric); + } return (
- + - +
diff --git a/src/components/IconLabel.tsx b/src/components/IconLabel.tsx index 62e034c..32cbb27 100644 --- a/src/components/IconLabel.tsx +++ b/src/components/IconLabel.tsx @@ -1,5 +1,4 @@ import { Stack } from '@mui/material'; -import React from 'react'; import { IconLabelProps } from '../types'; export default function IconLabel(props: IconLabelProps) { diff --git a/src/components/ModelIdAutocomplete.tsx b/src/components/ModelIdAutocomplete.tsx new file mode 100644 index 0000000..0d48f3c --- /dev/null +++ b/src/components/ModelIdAutocomplete.tsx @@ -0,0 +1,52 @@ +import { useEffect, useState } from 'react'; +import { Autocomplete, TextField } from '@mui/material'; +import { useNetworkGet } from '../network'; +import { useModelId } from '../context/ModelIdContext'; +import { AutocompleteLoadingIcon } from './FilterAutocomplete'; + +export function ModelIdAutocomplete() { + const [options, setOptions] = useState([]); + const [open, setOpen] = useState(false); + const modelId = useModelId(); + + const { refetch, isFetching } = useNetworkGet(`fe/topics/models`, 'ldaModelId', (data) => { + setOptions(data.models); + }); + + useEffect(() => { + if (open && options.length === 0) { + refetch(); + } + }, [open]); + + return ( + { + setOpen(true); + }} + onClose={() => { + setOpen(false); + }} + value={modelId.modelId} + onChange={(event, newValue) => { + modelId.setModelId(newValue); + }} + options={options} + loading={isFetching} + renderInput={(params) => ( + , + }} + /> + )} + /> + ); +} diff --git a/src/components/ResponsiveAppBar.tsx b/src/components/ResponsiveAppBar.tsx index d0b8d45..f296f0e 100644 --- a/src/components/ResponsiveAppBar.tsx +++ b/src/components/ResponsiveAppBar.tsx @@ -1,13 +1,15 @@ -import * as React from 'react'; -import AppBar from '@mui/material/AppBar'; -import Box from '@mui/material/Box'; -import Toolbar from '@mui/material/Toolbar'; -import IconButton from '@mui/material/IconButton'; -import Typography from '@mui/material/Typography'; -import Menu from '@mui/material/Menu'; -import Button from '@mui/material/Button'; -import Tooltip from '@mui/material/Tooltip'; -import MenuItem from '@mui/material/MenuItem'; +import { MouseEvent as ReactMouseEvent, useState } from 'react'; +import { + AppBar, + Box, + Button, + IconButton, + Menu, + MenuItem, + Toolbar, + Tooltip, + Typography, +} from '@mui/material'; import { useAuth } from '../context/AuthContext'; import { ROUTE_ACCOUNT, @@ -15,11 +17,11 @@ import { ROUTE_CITATIONS, ROUTE_FIELDS_OF_STUDY, ROUTE_HOME, - ROUTE_PAPER_TYPES, ROUTE_PAPERS, ROUTE_PUBLISHERS, + ROUTE_TOPICS, + ROUTE_TYPES_OF_PAPER, ROUTE_VENUES, - STORAGE_TOKEN, } from '../consts'; import { Link, useLocation, useNavigate } from 'react-router-dom'; import { @@ -27,6 +29,7 @@ import { Article, Book, Bookmarks, + BubbleChart, Group, LibraryBooks, Logout, @@ -39,11 +42,11 @@ const pages = [ { label: 'Papers', route: ROUTE_PAPERS, icon: Article }, { label: 'Authors', route: ROUTE_AUTHORS, icon: Group }, { label: 'Venues', route: ROUTE_VENUES, icon: Place }, - { label: 'Types of Paper', route: ROUTE_PAPER_TYPES, icon: LibraryBooks }, + { label: 'Types of Paper', route: ROUTE_TYPES_OF_PAPER, icon: LibraryBooks }, { label: 'Fields of Study', route: ROUTE_FIELDS_OF_STUDY, icon: Science }, { label: 'Publishers', route: ROUTE_PUBLISHERS, icon: Book }, { label: 'Citations', route: ROUTE_CITATIONS, icon: Bookmarks }, - // { label: 'Topics', route: ROUTE_TOPICS, icon: QuestionMark }, + { label: 'LDA Topics', route: ROUTE_TOPICS, icon: BubbleChart }, ]; const settings = [ { label: 'Account', id: 'account', icon: AccountCircle }, @@ -51,12 +54,12 @@ const settings = [ ]; const ResponsiveAppBar = () => { - const [anchorElUser, setAnchorElUser] = React.useState(null); + const [anchorElUser, setAnchorElUser] = useState(null); const auth = useAuth(); const navigate = useNavigate(); const location = useLocation(); - function handleOpenUserMenu(event: React.MouseEvent) { + function handleOpenUserMenu(event: ReactMouseEvent) { setAnchorElUser(event.currentTarget); } @@ -64,8 +67,7 @@ const ResponsiveAppBar = () => { setAnchorElUser(null); switch (id) { case 'logout': - localStorage.removeItem(STORAGE_TOKEN); - auth.setToken(''); + auth.logout(); break; case 'account': navigate(ROUTE_ACCOUNT); @@ -77,19 +79,24 @@ const ResponsiveAppBar = () => { // position is not 'absolute', so AppBar has full width when there is horizontal scrolling - - + + + + A: Dashboards {pages.map((page) => ( diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 505dd2d..20582f2 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import { useEffect, useRef } from 'react'; import '../App.css'; import { FilterMultipleObjectFetch, @@ -42,7 +42,7 @@ export default function Sidebar() { return ( - + diff --git a/src/components/Tools.tsx b/src/components/Tools.tsx index 1dbc0c0..56f538d 100644 --- a/src/components/Tools.tsx +++ b/src/components/Tools.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import '../App.css'; import { Button, @@ -15,8 +14,16 @@ import { useRefresh } from '../context/RefreshContext'; import { Construction } from '@mui/icons-material'; import { useFilter } from '../context/FilterContext'; import _ from 'lodash'; -import { ROUTE_CITATIONS, ROUTE_PAPERS, ROUTE_TOPICS } from '../consts'; +import { + ROUTE_AUTHORS, + ROUTE_FIELDS_OF_STUDY, + ROUTE_PUBLISHERS, + ROUTE_TOPICS, + ROUTE_TYPES_OF_PAPER, + ROUTE_VENUES, +} from '../consts'; import { metrics } from '../tools'; +import { ModelIdAutocomplete } from './ModelIdAutocomplete'; export default function Tools(props: { route: string }) { const refresh = useRefresh(); @@ -26,13 +33,17 @@ export default function Tools(props: { route: string }) { filter.setFilter({ ...filter.filter, metric: event.target.value }); }; - const routeExceptions = [ROUTE_PAPERS, ROUTE_CITATIONS, ROUTE_TOPICS].map((route) => - route.slice(1) - ); + const metricSwitchRoutes = [ + ROUTE_AUTHORS, + ROUTE_VENUES, + ROUTE_TYPES_OF_PAPER, + ROUTE_FIELDS_OF_STUDY, + ROUTE_PUBLISHERS, + ].map((route) => route.slice(1)); return ( - + ) : null} +
+ C2: Grid +
); } diff --git a/src/components/visualizations/LdaTopicVis.tsx b/src/components/visualizations/LdaTopicVis.tsx new file mode 100644 index 0000000..7739116 --- /dev/null +++ b/src/components/visualizations/LdaTopicVis.tsx @@ -0,0 +1,132 @@ +import { useEffect, useState } from 'react'; +import { LdaVisData } from '../../types'; +import { useRefresh } from '../../context/RefreshContext'; +import { useNetworkGet } from '../../network'; +import ChartLoadingIcon from '../ChartLoadingIcon'; +import { useModelId } from '../../context/ModelIdContext'; +import { Button } from '@mui/material'; +import { Download } from '@mui/icons-material'; +import { useLdaExport } from '../../tools'; +import { LDAvis } from '../ldavis'; + +export default function LdaTopicVis(props: { route: string }) { + const [ldaVisData, setLdaVisData] = useState(undefined); + const refresh = useRefresh(); + const { modelId } = useModelId(); + const queryKey = props.route + 'Lda'; + + const queryParameters = modelId ? { modelId: modelId } : {}; + + const { refetch, isFetching } = useNetworkGet( + `fe/${props.route}/lda`, + queryKey, + (data) => { + setLdaVisData(data.outputData); + }, + queryParameters + ); + + useEffect(() => { + refresh.addRefetch(queryKey, refetch); + return () => { + refresh.removeRefetch(queryKey); + }; + }, []); + + const filename = useLdaExport(queryParameters); + + function handleClick() { + if (ldaVisData) { + // This was taken directly from one of the .html exports of pyLDAvis. We just added our own data. + const html = + '\n' + + '\n' + + '\n' + + '
\n' + + ''; + const blob = new Blob([html], { type: 'text/html' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.setAttribute('href', url); + a.setAttribute('download', filename); + a.click(); + } + } + + return ( +
+
+ C5: Topic modelling +
+
+ + {ldaVisData ? ( + + ) : ( +
+ No data +
+ )} +
+ {ldaVisData ? ( + + ) : null} +
+
+ ); +} diff --git a/src/components/charts/TreeMap.tsx b/src/components/visualizations/TreeMap.tsx similarity index 55% rename from src/components/charts/TreeMap.tsx rename to src/components/visualizations/TreeMap.tsx index 9156025..8124050 100644 --- a/src/components/charts/TreeMap.tsx +++ b/src/components/visualizations/TreeMap.tsx @@ -1,30 +1,54 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import ReactApexChart from 'react-apexcharts'; import { ApexOptions } from 'apexcharts'; -import { TreeMapData, TreeMapProps } from '../../types'; +import { TreeMapData, TreeMapDataBackend, TreeMapProps } from '../../types'; import { useRefresh } from '../../context/RefreshContext'; import { useNetworkGet } from '../../network'; -import LoadingCircle from '../LoadingCircle'; +import ChartLoadingIcon from '../ChartLoadingIcon'; import { debounce, TextField } from '@mui/material'; import { DEBOUNCE_DELAY_K } from '../../consts'; -import { useExport } from '../../tools'; +import { useApexChartExport } from '../../tools'; + +const processLineBreak = (text: string, maxNumChars = 15) => { + const splitWords = text.split(/\s+/); + const words = []; + let currentLine = ''; + for (const word of splitWords) { + if (currentLine.length + word.length > maxNumChars) { + words.push(currentLine); + currentLine = word; + } else { + currentLine += ` ${word}`; + } + } + words.push(currentLine); + return words; +}; export default function (props: TreeMapProps) { const [chartData, setChartData] = useState([]); const [k, setK] = useState(20); const refresh = useRefresh(); + const queryKey = props.route + 'Treemap'; const { refetch, isFetching } = useNetworkGet( `fe/${props.route}/topk`, - 'treemapData' + props.route, - (data: TreeMapData) => { - setChartData(data); + queryKey, + (data: TreeMapDataBackend) => { + setChartData( + data.map((el) => { + return { y: el.y, x: processLineBreak(el.x) }; + }) + ); }, { k: k } ); useEffect(() => { - refresh.addRefetch(refetch); + refresh.addRefetch(queryKey, refetch); + return () => { + refresh.removeRefetch(queryKey); + }; }, []); // outside useEffect(), so debounce reference does not get lost @@ -49,20 +73,20 @@ export default function (props: TreeMapProps) { const options: ApexOptions = { chart: { parentHeightOffset: 0, - toolbar: useExport('treemap', props.route, { k: k }), + toolbar: useApexChartExport('treemap', props.route, { k: k }), }, title: { - text: `Top k by ${props.yDimension}`, + text: `C4: Top k by ${props.yDimension}`, offsetY: 8, }, }; return ( - -
+ +
(event.target.value ? setK(parseInt(event.target.value)) : setK(0))} />
- +
); } diff --git a/src/consts.ts b/src/consts.ts index 556aec7..ba606b8 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -6,7 +6,7 @@ export const ROUTE_PASSWORD = '/forgotpassword'; export const ROUTE_PAPERS = '/papers'; export const ROUTE_AUTHORS = '/authors'; export const ROUTE_VENUES = '/venues'; -export const ROUTE_PAPER_TYPES = '/types'; +export const ROUTE_TYPES_OF_PAPER = '/types'; export const ROUTE_FIELDS_OF_STUDY = '/fields'; export const ROUTE_PUBLISHERS = '/publishers'; export const ROUTE_CITATIONS = '/citations'; diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index 657ed06..0509e89 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -1,28 +1,35 @@ -import React, { createContext, useState } from 'react'; +import { createContext, ReactElement, useContext, useState } from 'react'; import { STORAGE_TOKEN } from '../consts'; +import { AuthContextType } from '../types'; -const AuthContext = createContext<{ token: string; setToken: (token: string) => void } | undefined>( - undefined -); +const AuthContext = createContext(undefined); export function useAuth() { - const context = React.useContext(AuthContext); + const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth must be used within an AuthProvider'); } return context; } -export function AuthProvider({ - children, -}: { - children: React.ReactElement | React.ReactElement[]; -}) { +export function AuthProvider(props: { children: ReactElement | ReactElement[] }) { const [token, setToken] = useState(localStorage.getItem(STORAGE_TOKEN) || ''); + function login(newToken: string, remember: boolean) { + setToken(newToken); + if (remember) { + localStorage.setItem(STORAGE_TOKEN, newToken); + } + } + + function logout() { + localStorage.removeItem(STORAGE_TOKEN); + setToken(''); + } + return ( - - {children} + + {props.children} ); } diff --git a/src/context/ErrorBoundary.tsx b/src/context/ErrorBoundary.tsx index cc22717..698dfa4 100644 --- a/src/context/ErrorBoundary.tsx +++ b/src/context/ErrorBoundary.tsx @@ -1,4 +1,4 @@ -import React, { ErrorInfo, ReactElement, useEffect, useState } from 'react'; +import { Component, ErrorInfo, ReactElement, useEffect, useState } from 'react'; import { useSnack } from './SnackbarContext'; type State = { @@ -9,7 +9,7 @@ type ErrorBoundaryProps = { setError(error: State): void; }; -class ErrorBoundary extends React.Component { +class ErrorBoundary extends Component { constructor(props: ErrorBoundaryProps) { super(props); this.state = { error: null }; @@ -56,7 +56,7 @@ class ErrorBoundary extends React.Component { } } -export function ErrorBoundaryWrapper({ children }: { children: ReactElement | ReactElement[] }) { +export function ErrorBoundaryWrapper(props: { children: ReactElement | ReactElement[] }) { const setSnack = useSnack(); const [error, setError] = useState({ error: null }); @@ -65,5 +65,5 @@ export function ErrorBoundaryWrapper({ children }: { children: ReactElement | Re setSnack('' + error.error); } }, [error]); - return {children}; + return {props.children}; } diff --git a/src/context/FilterContext.tsx b/src/context/FilterContext.tsx index 2cb61fb..44350e1 100644 --- a/src/context/FilterContext.tsx +++ b/src/context/FilterContext.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useState } from 'react'; +import { createContext, ReactElement, useContext, useState } from 'react'; import { Filter } from '../types'; import { metrics } from '../tools'; @@ -13,14 +13,14 @@ const FilterContext = createContext< >(undefined); export function useFilter() { - const context = React.useContext(FilterContext); + const context = useContext(FilterContext); if (context === undefined) { throw new Error('useFilter must be used within a FilterProvider'); } return context; } -export function FilterProvider({ children }: { children: React.ReactElement }) { +export function FilterProvider(props: { children: ReactElement | ReactElement[] }) { const [filter, setFilter] = useState({ yearStart: '1960', yearEnd: '', @@ -46,7 +46,7 @@ export function FilterProvider({ children }: { children: React.ReactElement }) { setOldFilter: setOldFilter, }} > - {children} + {props.children} ); } diff --git a/src/context/ModelIdContext.tsx b/src/context/ModelIdContext.tsx new file mode 100644 index 0000000..c8751eb --- /dev/null +++ b/src/context/ModelIdContext.tsx @@ -0,0 +1,23 @@ +import { createContext, ReactElement, useContext, useState } from 'react'; + +const ModelIdContext = createContext< + { modelId: string | null; setModelId: (modelId: string | null) => void } | undefined +>(undefined); + +export function useModelId() { + const context = useContext(ModelIdContext); + if (context === undefined) { + throw new Error('useModelId must be used within an ModelIdProvider'); + } + return context; +} + +export function ModelIdProvider(props: { children: ReactElement | ReactElement[] }) { + const [modelId, setModelId] = useState(null); + + return ( + + {props.children} + + ); +} diff --git a/src/context/RefreshContext.tsx b/src/context/RefreshContext.tsx index eea829f..41fe34c 100644 --- a/src/context/RefreshContext.tsx +++ b/src/context/RefreshContext.tsx @@ -1,42 +1,49 @@ -import React, { createContext, useState } from 'react'; +import { createContext, ReactElement, useContext, useState } from 'react'; const RefreshContext = createContext< | { refresh: () => void; - addRefetch: (refetch: () => Promise) => void; + addRefetch: (key: string, refetch: () => Promise) => void; + removeRefetch: (key: string) => void; } | undefined >(undefined); export function useRefresh() { - const context = React.useContext(RefreshContext); + const context = useContext(RefreshContext); if (context === undefined) { throw new Error('useRefresh must be used within an RefreshProvider'); } return context; } -export function RefreshProvider({ - children, -}: { - children: React.ReactElement | React.ReactElement[]; -}) { - const [refetchFunctions, setRefetchFunctions] = useState Promise>>([]); +export type State = { + key: string; + refetch: () => Promise; +}; - function addRefetch(refetch: () => Promise) { - setRefetchFunctions((oldArray) => [...oldArray, refetch]); +export function RefreshProvider(props: { children: ReactElement | ReactElement[] }) { + const [refetchFunctions, setRefetchFunctions] = useState>([]); + + function addRefetch(key: string, refetch: () => Promise) { + setRefetchFunctions((oldArray) => [...oldArray, { key: key, refetch: refetch }]); + } + + function removeRefetch(key: string) { + setRefetchFunctions((oldArray) => oldArray.filter((func) => func.key != key)); } function refresh() { - // queryClient.cancelQueries(); for (const refetch of refetchFunctions) { - refetch(); + refetch.refetch(); } } return ( - - {children} + + {props.children} ); } diff --git a/src/context/SnackbarContext.tsx b/src/context/SnackbarContext.tsx index d4b0657..c10212c 100644 --- a/src/context/SnackbarContext.tsx +++ b/src/context/SnackbarContext.tsx @@ -1,17 +1,17 @@ -import React, { createContext, useState } from 'react'; +import { createContext, ReactElement, useContext, useState } from 'react'; import { Alert, Snackbar } from '@mui/material'; const SnackbarContext = createContext<((message: string) => void) | undefined>(undefined); export function useSnack() { - const context = React.useContext(SnackbarContext); + const context = useContext(SnackbarContext); if (context === undefined) { throw new Error('useSnack must be used within a SnackbarProvider'); } return context; } -export function SnackbarProvider({ children }: { children: React.ReactElement }) { +export function SnackbarProvider({ children }: { children: ReactElement | ReactElement[] }) { const [snack, setSnack] = useState(''); const handleClose = () => { @@ -20,7 +20,7 @@ export function SnackbarProvider({ children }: { children: React.ReactElement }) return ( - + {snack} diff --git a/src/network.tsx b/src/network.tsx index 4f87340..0c4e676 100644 --- a/src/network.tsx +++ b/src/network.tsx @@ -1,4 +1,9 @@ -import { StringArrayParameters, NonFilterParameters, QueryParameters } from './types'; +import { + AuthContextType, + NonFilterParameters, + QueryParameters, + StringArrayParameters, +} from './types'; import { useMutation, useQuery } from 'react-query'; import { useEffect } from 'react'; import { useAuth } from './context/AuthContext'; @@ -8,11 +13,12 @@ import { ACCESS_TYPE_OPEN, ACCESS_TYPE_OTHER } from './consts'; async function sendRequest( route: string, - token: string, + auth: AuthContextType, setSnack: (message: string) => void, data: Object | null = null ) { const url = process.env.REACT_APP_BACKEND + route; + // This is needed for debugging console.debug(url); let init: RequestInit; if (data) { @@ -21,7 +27,7 @@ async function sendRequest( method: 'POST', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, + Authorization: `Bearer ${auth.token}`, }, body: JSON.stringify(data), }; @@ -30,14 +36,24 @@ async function sendRequest( init = { method: 'GET', headers: { - Authorization: `Bearer ${token}`, + Authorization: `Bearer ${auth.token}`, }, }; } const response = await fetch(url, init); - if (!response.ok || response.status >= 400) { - setSnack(`${response.status} ${response.statusText}: ${(await response.json()).message}`); + if (response.status === 401 && auth.token) { + auth.logout(); + setSnack('Your stored token was invalid. Please log in again.'); + } else { + const contentType = response.headers.get('content-type'); + const snackMessage = `${response.status} ${response.statusText}`; + if (contentType && contentType.indexOf('application/json') !== -1) { + setSnack(`${snackMessage}: ${(await response.json()).message}`); + } else { + setSnack(snackMessage); + } + } return new Promise((resolve) => resolve('')); } else { return response.json(); @@ -98,7 +114,7 @@ export function useNetworkGet( const { data, dataUpdatedAt, refetch, isFetching } = useQuery( [queryKey, queryParameters, filter.filter], () => { - return sendRequest(route, auth.token, setSnack); + return sendRequest(route, auth, setSnack); }, { refetchOnWindowFocus: false, @@ -124,7 +140,7 @@ export function useNetworkPost( return useMutation( (data: Object) => { - return sendRequest(route, auth.token, setSnack, data); + return sendRequest(route, auth, setSnack, data); }, { onSuccess: (data) => { diff --git a/src/routes/dashboards/authors.tsx b/src/routes/dashboards/authors.tsx index da8f37f..7114050 100644 --- a/src/routes/dashboards/authors.tsx +++ b/src/routes/dashboards/authors.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { GridCellParams } from '@mui/x-data-grid'; import { Link } from '@mui/material'; import FrameWithGraphs from '../../components/FrameWithGraphs'; diff --git a/src/routes/dashboards/citations.tsx b/src/routes/dashboards/citations.tsx index c97ec99..95ba32b 100644 --- a/src/routes/dashboards/citations.tsx +++ b/src/routes/dashboards/citations.tsx @@ -1,6 +1,5 @@ -import React from 'react'; import Frame from '../../components/Frame'; -import BarChart from '../../components/charts/BarChart'; +import BarChart from '../../components/visualizations/BarChart'; import { ROUTE_CITATIONS } from '../../consts'; export default function Citations() { diff --git a/src/routes/dashboards/fieldsOfStudy.tsx b/src/routes/dashboards/fieldsOfStudy.tsx index a701887..6e4e8ea 100644 --- a/src/routes/dashboards/fieldsOfStudy.tsx +++ b/src/routes/dashboards/fieldsOfStudy.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import FrameWithGraphs from '../../components/FrameWithGraphs'; import { GridCellParams } from '@mui/x-data-grid'; import { GRID_DECIMAL_PLACES } from '../../consts'; diff --git a/src/routes/dashboards/papers.tsx b/src/routes/dashboards/papers.tsx index 884a7df..cbf1152 100644 --- a/src/routes/dashboards/papers.tsx +++ b/src/routes/dashboards/papers.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { GridCellParams } from '@mui/x-data-grid'; import { Link } from '@mui/material'; import FrameWithGraphs from '../../components/FrameWithGraphs'; diff --git a/src/routes/dashboards/publishers.tsx b/src/routes/dashboards/publishers.tsx index 8113351..e258c2d 100644 --- a/src/routes/dashboards/publishers.tsx +++ b/src/routes/dashboards/publishers.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import FrameWithGraphs from '../../components/FrameWithGraphs'; import { GridCellParams } from '@mui/x-data-grid'; import { GRID_DECIMAL_PLACES } from '../../consts'; diff --git a/src/routes/dashboards/topics.tsx b/src/routes/dashboards/topics.tsx index 592460b..3adbeac 100644 --- a/src/routes/dashboards/topics.tsx +++ b/src/routes/dashboards/topics.tsx @@ -1,9 +1,13 @@ import Frame from '../../components/Frame'; +import { ModelIdProvider } from '../../context/ModelIdContext'; +import LdaTopicVis from '../../components/visualizations/LdaTopicVis'; export default function Topics() { return ( - -

Topics (WIP)

- + + + + + ); } diff --git a/src/routes/dashboards/typesOfPaper.tsx b/src/routes/dashboards/typesOfPaper.tsx index f52895a..690a07c 100644 --- a/src/routes/dashboards/typesOfPaper.tsx +++ b/src/routes/dashboards/typesOfPaper.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import FrameWithGraphs from '../../components/FrameWithGraphs'; import { GridCellParams } from '@mui/x-data-grid'; import { capitalize } from '@mui/material'; diff --git a/src/routes/dashboards/venues.tsx b/src/routes/dashboards/venues.tsx index f0379a7..80f1a4c 100644 --- a/src/routes/dashboards/venues.tsx +++ b/src/routes/dashboards/venues.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { GridCellParams } from '@mui/x-data-grid'; import { Link } from '@mui/material'; import FrameWithGraphs from '../../components/FrameWithGraphs'; diff --git a/src/routes/login.tsx b/src/routes/login.tsx index 89c56f5..6e60eaa 100644 --- a/src/routes/login.tsx +++ b/src/routes/login.tsx @@ -1,20 +1,22 @@ -import * as React from 'react'; -import Avatar from '@mui/material/Avatar'; -import Button from '@mui/material/Button'; -import CssBaseline from '@mui/material/CssBaseline'; -import TextField from '@mui/material/TextField'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import Checkbox from '@mui/material/Checkbox'; -import { default as MuiLink } from '@mui/material/Link'; -import Grid from '@mui/material/Grid'; -import Box from '@mui/material/Box'; -import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; -import Typography from '@mui/material/Typography'; -import Container from '@mui/material/Container'; import { useNetworkPost } from '../network'; -import { useAuth } from '../context/AuthContext'; import { Link, useNavigate } from 'react-router-dom'; -import { ROUTE_PAPERS, ROUTE_PASSWORD, ROUTE_REGISTER, STORAGE_TOKEN } from '../consts'; +import { FormEvent } from 'react'; +import { + Avatar, + Box, + Button, + Checkbox, + Container, + CssBaseline, + FormControlLabel, + Grid, + Link as MuiLink, + TextField, + Typography, +} from '@mui/material'; +import { LockOutlined } from '@mui/icons-material'; +import { useAuth } from '../context/AuthContext'; +import { ROUTE_PAPERS, ROUTE_PASSWORD, ROUTE_REGISTER } from '../consts'; function Copyright(props: any) { return ( @@ -34,7 +36,7 @@ export default function Login() { const navigate = useNavigate(); const mutation = useNetworkPost('login'); - const handleSubmit = (event: React.FormEvent) => { + const handleSubmit = (event: FormEvent) => { event.preventDefault(); const input = new FormData(event.currentTarget); const login = { @@ -44,10 +46,7 @@ export default function Login() { mutation.mutate(login, { onSuccess: (data: any) => { if (data) { - auth.setToken(data.token); - if (input.get('remember')) { - localStorage.setItem(STORAGE_TOKEN, data.token); - } + auth.login(data.token, !!input.get('remember')); navigate(ROUTE_PAPERS); } }, @@ -66,7 +65,7 @@ export default function Login() { }} > - + Sign in @@ -93,7 +92,9 @@ export default function Login() { autoComplete="current-password" /> } + control={ + + } label="Remember me" />